python --Numpy详解(科学计算)
pip install numpy
NumPy系统是Python的一种开源的数值计算扩展
导包
import numpy as np
查看版本
print(np.__version__) # 获取当前numpy版本号
# 1.21.3
创建ndarray
import numpy as np
t = np.array([1, 2, 3, 4]) # 一维数组
print(t, type(t))
输出:
[1 2 3 4] <class 'numpy.ndarray'>
t = np.array([[1,2,3],[4,5,6]]) # 二维数组
print(t)
输出: [[1 2 3 4]
[4 5 6 7]]
可以使用多种方式来构建多维数组,最常见的是使用列表来构建多维数组。下面的例子便使用一维列表构建了一个一维数组。
import numpy as np
nda1 = np.array([1, 2, 3]) # 使用一维列表来作为输入
nda1
array([1, 2, 3])6 >>> type(nda1)
<class 'numpy.ndarray'>
如果希望构建二维数组,可以使用下面的方法:
input_list = [
... [1, 2, 3],
... [4, 5, 6]
... ]
nda2 = np.array(input_list)
nda2
array([[1, 2, 3], # 查看值
[4, 5, 6]])
type(nda2) # 查看类型
<class 'numpy.ndarray'>
也可以指定一些特征值,让 NumPy 自动产生相关的数组。例如指定维度,让其产生所有元素都为 0 的数组,代码如下:
np.zeros(5) # 5个元素的一维数组
array([0., 0., 0., 0., 0.])
np.zeros((5, 2)) # 二维数组,5行,2列
array([[0., 0.],
[0., 0.],
[0., 0.],
[0., 0.],
[0., 0.]])
也可以指定维度,让其产生所有元素值都为 1 的数组,代码如下:
np.ones((5, 2)) # 二维数组,5行,2列,所有元素都为1
array([[1., 1.],
[1., 1.],
[1., 1.],
[1., 1.],
[1., 1.]])
np.ones(5) # 一维数组,5个元素
array([1., 1., 1., 1., 1.])
还可以让 NumPy 自动产生等差数组,此时需要指定开始值、结束值和步长。代码如下:
np.arange(3,7,2) # 从3开始,直到7,步长为2
array([3, 5])
np.arange(3,7,1) # 从3开始,直到7,步长为1
array([3, 4, 5, 6])
np.arange(7, 3, -1) # 从7开始,直到3,步长为-1
array([7, 6, 5, 4])
np.arange(7, 3, -2) # 从7开始,直到3,步长为-2
array([7, 5])
arange() 函数和 range() 类似,如果仅提供一个值,那么开始值是 0,步长是 1,代码如下:
np.arange(7)
array([0, 1, 2, 3, 4, 5, 6])
如果提供两个参数,那么步长为 1:
np.arange(2, 5) # 从2开始,直到5,步长为1
array([2, 3, 4])
np.arange(2, 6) # 从2开始,直到6,步长为1
array([2, 3, 4, 5])
另外一个等差数列函数是 linspace(),其指定开始位置和结束位置,但不指定步长,而是指定元素个数。例如从 1 开始,到 5 结束,一共有 8 个数,那么生成的数组如下面所示:
np.linspace(1, 5, 8) # 包括1和5,等分8个点
array([1. , 1.57142857, 2.14285714, 2.71428571, 3.28571429,
3.85714286, 4.42857143, 5])
# 可以发现元素个数和指定的一致,开始值和结束值也都被包含,而且它们的确是等差数列。
inspace() 函数比较有用,例如要画正弦函数在 0 到 2π 之间的图形,便可以使用该函数在 0 到 2π 之间产生均匀分布的 100 个点,然后使用 matplotlib 将它们画出来。下面是演示的代码:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2*np.pi, 100)
y = [np.sin(e) for e in x]
plt.plot(x, y)
plt.savefig("sindemo1.png")
还可以使用 logspace() 函数让 NumPy 自动产生等比数列,此时需要指定开始点和结束点,同时指定点的个数。如果没有提供点的数目,默认是生成 50 个点。
np.logspace(2.0, 3.0, num=4) # 4个点,其实位置是102,结束位置是103
array([ 100. , 215.443469, 464.15888336, 1000.])
下面是一个例子,其演示了 logspace() 的用法和参数 endpoint 的用法。endpoint=True 表示结束值被包含在输出数组中,否则表示不包含在输出数组中。下面是完整的代码:
import matplotlib.pyplot as plt
import numpy as np
N = 10 # 一共10个点
x1 = np.logspace(0.1, 1, N, endpoint=True) # 10被算作是最后一个点
x2 = np.logspace(0.1, 1, N, endpoint=False) # 10不被算作是最后一个点
y = np.zeros(N)
plt.plot(x1, y, 'o')
plt.plot(x2, y + 0.5, 'x')
plt.ylim([-0.5, 1]) # y轴的范围是-0.5到1
plt.savefig("logspace1.png") # 保存图片到文件
还可以使用 full() 函数指定维度和一个值,让所有的元素都等于该值。该函数和 ones() 类似,但值是由用户指定的。
np.full((2, 2), np.inf) # 所有元素都是无穷大
array([[inf, inf],
[inf, inf]])
np.full((2, 2), 11) # 所有元素都是11
array([[11, 11],
[11, 11]])
np.full((2, 2), 1.51) # 所有元素都是1.51
array([[1.51, 1.51],
[1.51, 1.51]])
使用 eye() 函数还可以自动生成单位矩阵,就是仅对角线上的值为 1,其他位置上的值都为 0。
np.eye(2) # 2x2的单位矩阵
array([[1., 0.],
[0., 1.]])
np.eye(3) # 3x3的单位矩阵
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
还可以自动产生随机的矩阵,例如可以使用 random.normal() 函数产生一个正态分布的一维矩阵:
mu, sigma = 0, 0.1 # mu是平均值,sigma代表分散程度
s = np.random.normal(mu, sigma, 1000)
s.size # 元素个数为1000
1000
np.mean(s) # 平均值接近0
-0.0011152161285000821
abs(mu - np.mean(s)) < 0.01 # 平均值接近mu=0
True
abs(sigma - np.std(s, ddof=1)) < 0.01 # 分散程度检查
True
可以将生成的数据画出来,使用下面的代码:
import matplotlib.pyplot as plt
import numpy as np
mu, sigma = 0, 0.1
s = np.random.normal(mu, sigma, 1000)
count, bins, ignored = plt.hist(s, 30, density=True)
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) *
np.exp( - (bins - mu)**2 / (2 * sigma**2) ),
linewidth=2, color='r')
plt.savefig("rand1.png")
还可以生成完全随机的矩阵,方法是使用 np.random.rand(外形)函数。例如在下面的例子中,就生成了随机内容组成的指定外形的矩阵。
np.random.rand(3,2) # 3x2的二维矩阵
array([[0.11319256, 0.84668147],
[0.4040353 , 0.70912343],
[0.6511614 , 0.80706271]])
np.random.rand(3,2,2) # 3x2x2的三维矩阵
array([[[0.64851863, 0.3895985 ],
[0.63038544, 0.58402249]],
[[0.39816687, 0.92149102],
[0.07113285, 0.17109903]],
[[0.06713956, 0.39415293],
[0.06125844, 0.71276929]]])
np.random.rand(4) # 一维矩阵
array([0.11918788, 0.91847982, 0.29599804, 0.42242323])
ndarray 是一个类,其包含一些属性。
最基本的便是其维度。可以用属性 ndim 来得到指定矩阵的维度,方法如下:
a = np.array([1, 2, 3])
a.ndim # 维度为1
1
b = np.eye(3)
b.ndim # 维度为2
2
可以用属性 shape 来得到指定数组的外形,方法如下:
a = np.eye(3) # 3x3的单元矩阵
a # 查看a的值
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
a.shape # a的外形
(3, 3)
可以用属性 dtype 来得到指定矩阵每个元素的类型,方法如下:
a = np.eye(3, dtype=int) # 指定类型为整型
a
array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
a.dtype # 查看类型
dtype('int64')
可以用属性 size 来得到指定矩阵的元素个数,方法如下:
a = np.eye(3, dtype=int)
a
array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
a.size # 矩阵元素个数,9个
9
可以用属性 T 来得到指定矩阵的转置矩阵,方法如下:
a = np.array([1, 2, 3]) # 一维矩阵
a.T # 转置矩阵是自己
array([1, 2, 3])
b = np.array([[1, 2, 3], [4, 5, 6]]) # 二维矩阵
b
array([[1, 2, 3],
[4, 5, 6]])
b.T # 转置矩阵
array([[1, 4],
[2, 5],
[3, 6]])
需要注意的是,转置矩阵就是将原矩阵旋转 90 度得到的矩阵,但其仅对二维以及多维矩阵有效。对于一维矩阵来说,其转置矩阵还是自己。
对于多维矩阵,可以进行变形、加减乘除等操作,本节就来介绍常见的相关操作。、
变形reshape(维度列表)
例如原来是 3×4 的矩阵,可以将其变成 6×2 的矩阵。内容不变,但是样子发生了改变。方法如下:
x = np.arange(1, 7) # 一维矩阵
x
array([1, 2, 3, 4, 5, 6])
y = x.reshape(2, 3) # 变成2行3列的二维矩阵
y
array([[1, 2, 3],
[4, 5, 6]])
加减乘除操作
加法操作是指对外形相同的两个矩阵,进行相同位置元素的加法运算,得到一个和输入矩阵相同外形的矩阵。下面的代码便演示了加法操作的使用:
a = numpy.random.rand(3,2) # a是3行2列的随机二维矩阵
b = numpy.random.rand(3,2) # b是3行2列的随机二维矩阵
a # 查看a的值
array([[0.49219148, 0.30470874],
[0.42371119, 0.96857757],
[0.09432051, 0.55935613]])
b # 查看b的值
array([[0.41471195, 0.85316671],
[0.6231908 , 0.98244841],
[0.65246256, 0.73501929]])
a+b # a+b的值
array([[0.90690343, 1.15787545],
[1.04690199, 1.95102597],
[0.74678307, 1.29437542]])
与之类似的还有减法、乘法和除法操作,其运算符号和普通的数值运算符号相同,都是对相同的位置进行操作。下面的例子是减法操作的使用:
a = numpy.random.rand(3,2) # 随机生成3x2的矩阵
b = numpy.random.rand(3,2) # 随机生成3x2的矩阵
a # 查看矩阵a的内容
array([[0.49219148, 0.30470874],
[0.42371119, 0.96857757],
[0.09432051, 0.55935613]])
b # 查看矩阵b的内容
array([[0.41471195, 0.85316671],
[0.6231908 , 0.98244841],
[0.65246256, 0.73501929]])
a-b # 减法操作
array([[ 0.07747953, -0.54845797],
[-0.19947961, -0.01387084],
[-0.55814205, -0.17566316]])
下面的例子是乘法操作的使用:
a = numpy.random.rand(3,2) # 随机生成3x2的矩阵
b = numpy.random.rand(3,2) # 随机生成3x2的矩阵
a # 查看矩阵a的内容
array([[0.49219148, 0.30470874],
[0.42371119, 0.96857757],
[0.09432051, 0.55935613]])
b # 查看矩阵b的内容
array([[0.41471195, 0.85316671],
[0.6231908 , 0.98244841],
[0.65246256, 0.73501929]])
a * b # 乘法操作
array([[0.20411769, 0.25996735],
[0.26405291, 0.95157749],
[0.0615406 , 0.41113754]])
下面是除法操作运算的例子:
a = numpy.random.rand(3,2) # 随机生成3x2的矩阵
b = numpy.random.rand(3,2) # 随机生成3x2的矩阵
a # 查看矩阵a的内容
array([[0.49219148, 0.30470874],
[0.42371119, 0.96857757],
[0.09432051, 0.55935613]])
b # 查看矩阵b的内容
array([[0.41471195, 0.85316671],
[0.6231908 , 0.98244841],
[0.65246256, 0.73501929]])
a / b # 除法操作
array([[1.18682735, 0.3571503 ],
[0.67990604, 0.98588135],
[0.1445608 , 0.76100877]])
逻辑判断
还可以对矩阵进行逻辑判断,例如判断矩阵元素是否大于 0 或者小于 1。其结果是一个矩阵,分别表示各元素是否满足规定的判断。例如下面就是判断矩阵元素是否小于 0.5 的操作:
a = np.random.rand(3,2) # 随机生成3x2的矩阵
a # 查看矩阵a的内容
array([[0.36826283, 0.1993915 ],
[0.3278179 , 0.66236192],
[0.98973706, 0.67244684]])
a < 0.5 # 各个元素是否小于0.5
array([[ True, True],
[ True, False],
[False, False]])
当然也可以和其他矩阵进行比较,例如下面的例子:
a = numpy.random.rand(3,2) # 随机生成3x2的矩阵
b = numpy.random.rand(3,2) # 随机生成3x2的矩阵
a # 查看矩阵a的内容
array([[0.49219148, 0.30470874],
[0.42371119, 0.96857757],
[0.09432051, 0.55935613]])
b # 查看矩阵b的内容
array([[0.41471195, 0.85316671],
[0.6231908 , 0.98244841],
[0.65246256, 0.73501929]])
a < b # a的各个元素是否小于b对应的元素
array([[False, True],
[ True, True],
[ True, True]])
我们知道 NumPy 有强大的数值计算功能,在生活中,经常会用到的一个计算就是贷款计算。
例如如果贷款 200 万,在 15 年内还清,每个月需要还多少钱?其中支付的利息是多少?再例如,如果购买了一份医疗保险,要求每个月存钱 2000 元,一直存 20 年,20 年内每年最多可以报销医疗费用 5000 元,20 年后可以全额返还本金,这样是否划算?
由于一年期固定存/贷款利率是由央行规定的,所以这个利率在一定时间内基本是固定的。在本节中会经常用到这个概念。
首先来看看存款利息的问题,假定一年期存款利率是 5%,那么每个月存入 100 元,10 年后账户余额是多少?可以使用函数 np.fv() 来处理这类问题,该函数定义如下:
np.fv(利率,年限,每月存款,当前存款)
在上面的问题中,可以这么来求解,月利率是 0.05/12;年限是 12*10 个月;每月存款是 -100,负数表示支出;当前存款是 0。计算方法如下:
np.fv(0.05/12, 10*12, -100, 0)
15528.227944566719
其中本金部分是 12000,由此得出利息收入是 3528.22。
下面来看一个贷款的问题。如果我们的还款能力是每个月 13000 元,贷款 300 万,那么应该贷款多长时间呢?可以使用函数 np.nper() 来完成该任务,该函数定义如下:
numpy.nper(利率, 还款能力, 贷款数目, 剩余贷款数目=0)
对于我们的场景,可以使用下面的方法来计算:
np.nper(0.05/12, -13000, 3000000, 0)
array(783.57108846)
结果是需要 783 个月才能还清。这里有一个问题,如果每个月还款的数目小于贷款每月产生的利息,那么就永远也还不清,而且欠款还会随着时间而增加。例如上面的例子,300 万每个月的利息是 12500,如果还款低于这个值就永远也还不完了。
如果希望查看每个月还的利息是多少,可以使用利息的计算函数 np.ipmt(),该函数定义如下:
np.ipmt(利率, 时间段列表, 还款年限, 贷款数目, 未来剩余贷款数目=0)
还是以上面的买房贷款为例,可以看到下面的情况:
per = np.arange(10*12) + 1 # 前10年还的利息
ipmt = np.ipmt(0.05/12, per, 783, 3000000)
ipmt # 每个月支付的利息,负数表示支付
array([-12500. , -12497.91151511, -12495.81432821, -12493.70840303,
-12491.59370315, -12489.47019203, -12487.33783295, -12485.19658903,
… # 省略中间数据
-12202.70446367, -12199.37724738, -12196.0361677 , -12192.68116684,
-12189.31218682, -12185.92916938, -12182.53205603, -12179.12078805])
可以很容易发现每个月还的利息数目都在减少,因为每个月都还了一部分本金,随着本金的减少,利息的基数也在减少,所以利息也在减少。
如果希望得到全部利息的数目,也就是贷款所支付的利息总和,可以对利息列表求和,方法如下:
per = np.arange(783) + 1 # 总计783个月
ipmt = np.ipmt(0.05/12, per, 783, 3000000)
ipmt.sum() # 总计利息为717万
-7179968.0796168335
可以看到利息数目比较大,为了解决这个问题,可以提高每个组的还款金额或者减少还款周期,例如调整为 25 年还完,那么利息总额为:
per = np.arange(12*25) + 1
ipmt = np.ipmt(0.05/12, per, 12*25, 3000000)
ipmt.sum() # 利息总额是226万
-2261310.3735718103
所以说在 5% 的年化利率情况下,等额还款方式,贷款 25 年,利息约是本金的 75%。这是比较常见的情况,如果将年限降低为 15 年,那么可以得到如下的利息总额:
per = np.arange(12*10) + 1
ipmt = np.ipmt(0.05/12, per, 12*10, 3000000)
ipmt.sum() # 利息总额是81万
-818358.548606708
可以看到利息数目大幅下降,但每月的还款金额却上升的很大,每月需要支付的还款金额为:
np.pmt(0.05/12, 12*15, 3000000)
-23723.808802246393 # 每月支付的金额
本金还款计划和利息的计算方法类似,不过需要将 ipmt() 函数改为 ppmt() 函数。下面是贷款 300万,等额还款 783 个月的情况:
per = np.arange(10*12) + 1 # 头10年还的利息
ppmt = np.ppmt(0.05/12, per, 783, 3000000)
ppmt
array([-501.23637244, -503.32485732, -505.42204423, -507.52796941,
-509.64266928, -511.7661804 , -513.89853949, -516.0397834 ,
-518.18994917, -520.34907396, -522.5171951 , -524.69435008,
-526.88057654, -529.07591227, -531.28039524, -533.49406355,
-535.71695548, -537.94910947, -540.19056409, -542.44135811,
-544.70153043, -546.97112014, -549.25016648, -551.53870884,
-553.83678679, -556.14444007, -558.46170857, -560.78863235,
-563.12525165, -565.47160687, -567.82773857, -570.19368748,
-572.56949451, -574.95520073, -577.3508474 , -579.75647593,
-582.17212792, -584.59784512, -587.03366947, -589.4796431 ,
-591.93580827, -594.40220748, -596.87888334, -599.36587869,
-601.86323652, -604.371 , -606.8892125 , -609.41791755,
-611.95715888, -614.50698037, -617.06742612, -619.6385404 ,
-622.22036765, -624.81295252, -627.41633982, -630.03057457,
-632.65570196, -635.29176739, -637.93881642, -640.59689482,
-643.26604855, -645.94632375, -648.63776676, -651.34042413,
-654.05434256, -656.77956899, -659.51615052, -662.26413449,
-665.02356838, -667.79449991, -670.576977 , -673.37104773,
-676.17676043, -678.9941636 , -681.82330595, -684.66423639,
-687.51700404, -690.38165823, -693.25824847, -696.1468245 ,
-699.04743627, -701.96013392, -704.88496782, -707.82198851,
-710.7712468 , -713.73279366, -716.7066803 , -719.69295814,
-722.6916788 , -725.70289412, -728.72665618, -731.76301725,
-734.81202982, -737.87374661, -740.94822056, -744.03550481,
-747.13565275, -750.24871797, -753.37475429, -756.51381577,
-759.66595667, -762.83123149, -766.00969495, -769.20140201,
-772.40640785, -775.62476789, -778.85653775, -782.10177333,
-785.36053072, -788.63286626, -791.91883654, -795.21849836,
-798.53190877, -801.85912505, -805.20020474, -808.55520559,
-811.92418562, -815.30720306, -818.7043164 , -822.11558439])
可以看到最开始时还的本金特别少,而且本金在逐月增加。
总的来说,每个月还给银行的钱分为两部分,一部分是利息,一部分是本金,可以使用的公式表示:
本金+利息=每月还款额
因此,如果本金等于 0,那么就永远也还不完;如果本金小于 0,那么不仅还不完,而且背负的债务还会随时间而增加;只有当本金大于 0 时才是正常的还款情况。
利息=剩余本金*利率
剩余本金=上次剩余本金–最近一次归还本金
归还贷款的最终目的就是将剩余本金降低为 0。当剩余本金降低为 0 时,利息也降低为 0。
傅里叶变换是将时域数据转换成频域数据。例如周期为25的正弦波可以用下面的函数来表示:
y=sin(2π*f*x)=sin(50πx)
如果以 150Hz 的采样频率来采样,也就是在 0 到 1 之间有 150 个采样点,那么其时间列表,即公式中的 x 的列表如下:
[0. 0.00666667 0.01333333 0.02 0.02666667 0.03333333
0.04 0.04666667 0.05333333 0.06 0.06666667 0.07333333
…. 省略中间数据
0.88 0.88666667 0.89333333 0.9 0.90666667 0.91333333
0.92 0.92666667 0.93333333 0.94 0.94666667 0.95333333
0.96 0.96666667 0.97333333 0.98 0.98666667 0.99333333]
得到的输出列表,即 y 的列表如下:
[ 0.00000000e+00 8.66025404e-01 8.66025404e-01 1.22464680e-16
-8.66025404e-01 -8.66025404e-01 -2.44929360e-16 8.66025404e-01
…. 省略中间数据
2.25434048e-14 8.66025404e-01 8.66025404e-01 -1.17627991e-14
-8.66025404e-01 -8.66025404e-01]
将这些数据画成一个图,就如图 1 所示。
如何将它们转换成频域图呢?很简单,只需将 y 输入到 np.fft.fft() 函数即可。下面是实现的代码:
import numpy as np
import matplotlib.pyplot as plt
from numpy.fft import fft,ifft
# x是时域值
# y是频域值
# time_span是采样时长
def show(x, ft, time_span = 5):
n = len(x) # 采样点个数
interval = time_span / n
frequency = np.arange(n / 2) / (n * interval)
nfft = abs(ft[range(int(n / 2))] / n )
plt.plot(frequency, nfft, 'red')
plt.xlabel('Freq (Hz)'), plt.ylabel('Amp. Spectrum')
#plt.show()
plt.savefig("fftv2_1_1.png")
time = np.arange(0, 5, .005)
x = np.sin(2 * np.pi * 10 * time) # 时域信号
y = np.fft.fft(x) # 频域信号
show(x, y, 5)
如果输入信号中带有两个不同幅度的正弦信号,例如输入信号为:
y=sin(2*10πx)+3sin(2*50πx)
那么它们的傅里叶变换代码如下:
import numpy as np
import matplotlib.pyplot as plt
from numpy.fft import fft, ifft
# x是时域值
# y是频域值
# time_span是采样时长
def show(x, ft, time_span = 5):
n = len(x) # 采样点个数
interval = time_span / n
frequency = np.arange(n / 2) / (n * interval)
nfft = abs(ft[range(int(n / 2))] / n )
plt.plot(frequency, nfft, 'red')
plt.xlabel('Freq (Hz)'), plt.ylabel('Amp. Spectrum')
#plt.show()
plt.savefig("fftv2_1_2.png")
time = np.arange(0, 5, .0005)
x0 = np.sin(2 * np.pi * 10 * time) # 10HZ
x1 = 3 * np.sin(2 * np.pi * 50 * time) # 30HZ
x = x0 + x1 # 时域信号
y = np.fft.fft(x) # 频域信号
show(x, y, 5)
反变换也很简单,下面便是一个反变换的例子代码:
import numpy as np
import matplotlib.pyplot as plt
from numpy.fft import fft,ifft
# 在0到2π之间取50个点
x = np.linspace(0, 2*np.pi, 50)
wave = np.cos(x) # 时域的值
transformed=np.fft.fft(wave) # 傅里叶变换
reversed_data = np.fft.ifft(transformed) # 反变换
plt.plot(x, wave, 'r')
# 反变换出来的值+0.2,以免和变换之前的值重合
plt.plot(x, reversed_data.real+0.2, 'b')
plt.savefig("fft4.png")
运行该脚本,可以得到如图 5 所示的时域图,其中上面那条曲线是傅里叶变换后再傅里叶反变换并向上移动 0.2 的曲线;下面那条曲线是 y=cos(x) 的曲线。可以发现傅里叶变换后再次反变换得到的曲线近似于输入的曲线。
在 20 世纪,人们认为任何神经网络都可以等效于某个 3 层的神经网络,这样就没有必要去研究多层的神经网络。直到最近几年人们才意识到多层神经的独特用途,其在图片识别、博弈等方面有着很大的优势。但不论是多少层的神经网络,其基本工作单元都是神经元,通过多个神经的不同连接方式来搭建不同的神经网络,从而解决不同的问题。
本节将介绍神经元和最简单的神经网络,以及基本的神经网络参数训练。
由于神经网络是由很多的神经元组成,神经元又是由多个输入和一个输出组成,而对于前向神经网络,最终输出和输入之间的关系基本可以用一个矩阵来表示,这和 NumPy 的基本数据结构 ndarray 是很一致的,所以很多 NumPy 的方法也可以用在神经网络技术上。
每个神经元有 n 个输入 x,一个输出 y,输出和输入的关系可以用下面的数学公式表示
y=S(m)
m=w1x2+w2x2+...+wnxn
简单来说就是对所有输入求一个加权和,然后通过 S(x) 这个函数输出出来,不同的加权和标识不同的神经网络。我们也可以用图 1 来表示神经元的基本形式。
S(x) 这个函数一般来说是固定的,主要是为了将 y 的输出范围压缩到指定的范围。常用的 S(x) 是 Sigmoid 函数,该函数定义如下:
可以使用下面的代码来画出该函数输入 x 和输出 y 的关系:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(-10,10,0.01)
y = 1.0/(np.exp(x*-1.0)+1.0)
plt.plot(x, y, 'b') # 画图
plt.savefig("sigmod1.png")
我们可以发现其规律如下:曲线是平滑的;输出 y 随输入 x 单调递增;输出 y 的范围是 0~1;当 x 超出 -5~5 这个范围,输出 y 基本等于 0 或者 1,和数字信息一致。由于有这些特性,Sigmoid 函数被广泛应用在各种神经元。
该函数还有一个特别的地方,就是求导特别方便,下面是其导数计算公式:
在训练模型的过程中,如果发现对于输入 x,输出 y 和目标输出 y0 存在差别 ∆y,就需要调整权重 w 来消除这种差别。方法就是找到 ∆w,让新的 w’=w+∆w。
该如何找到这个 ∆w 呢?这时就需要求导了,我们需要知道 y 对 w 的在当前 x 位置的导数 ∂,∆y=∆w∂,由于知道了 ∆y 和 ∂,便可以求出 ∆w。但在实际应用中我们并不会使用这个 ∆w,而是自己定义一个固定的步长 s。∂ 的作用是规定了移动的方向,s 确定了移动的长度,结合 s 和 ∂ 可以让 ∆y 变小。
按照这种方式多次迭代就可以让 ∆y 趋近于 0。这个过程就是模型训练,训练的目的就是调整权重 w 以消除 ∆y。∆y 也叫损失函数,就是真实输出和期待输出之间的差别。这种计算 ∂ 的办法也叫作梯度下降法,就是确定某个位置 x 最快下降方向。
在实际应用中,网络非常复杂,包含的神经元个数庞大,神经元之间的连接方式也是数量庞大。为了演示神经网络的使用,不太可能拿实际使用情况作为例子,这里仅以单个神经元组成的神经网络作为例子。该神经元有 3 个输入,输入基本就是 0 和 1;该神经元有一个输出,输出也只有 0 和 1 两个值,它们可以用图 5 表示。
该神经元的输入和输出的关系如表 6 所示
这里使用单个神经元来达到设计目的。由于仅有一个神经元,所以参数个数也就只有 3 个,分别是 w1、w2 和 w3。我们的任务就是通过给代码输入期望的结果来训练这 3 个参数。
import numpy as np
class NeuralNetwork():
def __init__(self):
# 设置随机数种子
np.random.seed(1)
# 将权重转化为一个3x1的矩阵,其值分布为-1~1,并且均值为0
self.weights = 2 * np.random.random((3, 1)) - 1
def sigmoid(self, x):
# 应用Sigmoid激活函数
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(self, x):
#计算Sigmoid函数的偏导数
return x * (1 - x)
def train(self, training_inputs, training_outputs, iterations_num):
# 训练模型
# 输入是training_inputs,是一个二维矩阵
# 期待值是training_outputs,是一个一维矩阵
# iterations_num是迭代的次数
for iteration in range(iterations_num):
# 得到输出
output = self.think(training_inputs)
# 计算误差
error = training_outputs - output
# 微调权重
adjustments = np.dot(training_inputs.T, error * self.sigmoid_
derivative(output))
self.weights += adjustments
def think(self, inputs):
# 基于当前参数,计算inputs的输出
inputs = inputs.astype(float)
output = self.sigmoid(np.dot(inputs, self.weights))
return output
if __name__ == "__main__":
# 初始化神经类
neural_network = NeuralNetwork()
print(u"随机生成最初的权重值")
print(u"初始权重值为:", neural_network.weights)
#训练数据的输入部分
training_inputs = np.array([[0,0,1],
[1,1,1],
[1,0,1],
[0,1,1]])
# 训练数据的输出
training_outputs = np.array([[0,1,1,0]]).T
# 开始训练,进行150000次训练
neural_network.train(training_inputs, training_outputs, 15000)
print(u"训练后的权重:", neural_network.weights)
# 验证结果
y = neural_network.think(np.array(training_inputs[0]))
assert abs(y - training_outputs[0]) < 1e-2
y = neural_network.think(np.array(training_inputs[1]))
assert abs(y - training_outputs[1]) < 1e-2
y = neural_network.think(np.array(training_inputs[2]))
assert abs(y - training_outputs[2]) < 1e-2
y = neural_network.think(np.array(training_inputs[3]))
assert abs(y - training_outputs[3]) < 1e-2
print(u"验收通过")
$ python ex1.py
随机生成最初的权重值
初始权重值为: [[-0.16595599]
[ 0.44064899]
[-0.99977125]]
训练后的权重: [[10.08740896]
[-0.20695366]
[-4.83757835]]
验收通过
可以发现,其实训练就是调整权重参数的一个过程。虽然不知道最终我们期望的参数是多少,但是可以通过比较实际输出和期望输出的差值来微调这些权重,最终可以达到输出和我们的期望一致。
使用np的routines函数创建
注:函数返回给定形状和数据类型的新数组,其中元素的值设置为1。
t = np.ones([3, 3])
print(t)
输出:
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
t = np.ones([3, 3], dtype=int)
print(t)
输出:
[[1 1 1]
[1 1 1]
[1 1 1]]
numpy.full(shape, fill_value, dtype=None, order=‘C’)[source]
np.full((2,3), 3) # 2行3列,填充值为3
输出:
[[3, 3, 3],
[3, 3, 3]]
numpy.eye(N, M=None, k=0, dtype=<class ‘float’>, order=‘C’)
注: 返回一个二维数组,其对角线元素为1,其余位置元素为0。
np.eye(5,k=1) # 5行5列,从第1个元素为对角填充1
返回值:维度为(N,M)的多维数组 除了第k条对角线上元素为1以外,其余元素均为0的数组
输出:
[[0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0.],
[0., 0., 0., 0., 1.],
[0., 0., 0., 0., 0.]]
np.eye(3,4) # 3行4列
输出:
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.]]
linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
在指定的大间隔内,返回固定间隔的数据。他将返回“num”个等间距的样本,在区间[start, stop]中。其中,区间的结束端点可以被排除在外。
当‘endpoint=False’时,不包含该点。在这种情况下,队列包含除了“num+1"以外的所有等间距的样本。
要注意的是,当‘endpoint=False’时,步长会发生改变。
np.linspace(0,10,5) # 返回0到10的等差数列(默认包含10)
输出:
[ 0. , 2.5, 5. , 7.5, 10. ]
np.linspace(1,11, 6, retstep=True) # 返回步进差值,二元组
输出:
([ 1., 3., 5., 7., 9., 11.]), 2.0)
注意:np.arange([start, ]stop, [step, ]dtype=None) # 返回等差数列,
区别:
arange()类似于内置函数range(),通过指定开始值、终值和步长创建表示等差数列的一维数组,注意
得到的结果数组不包含终值。
linspace()通过指定开始值、终值和元素个数创建表示等差数列的一维数组,可以通过endpoint参数
指定是否包含终值,默认值为True,即包含终值
np.random.randint(low, high=None, size=None, dtype=‘l’)
随机数组
import numpy as np
r = np.random.randint(0, 10, 6)
print(r)
# 输出: [1 0 1 3 6 4]
np.random.randn(d0, d1, …, dn)
np.random.randn(10)
# 每次都不一样
# 输出结果:
array([-1.74976547, 0.3426804 , 1.1530358 , -0.25243604, 0.98132079,
0.51421884, 0.22117967, -1.07004333, -0.18949583, 0.25500144])
//
np.random.seed(100) #随机的种子,有了种子,每次都一样
np.random.randn(10)
# 输出结果:
array([ 0.37332715, -0.2887605 , 0.04985088, -0.93815832, -0.4087037 ,
1.13352254, 0.52713526, -0.76014192, -0.97292788, 0.16290446])
np.random.random(size=None)
np.random.random(100)
# 每次每次都不一样
# 输出结果:
array([ 0.01150584, 0.52951883, 0.07689008, 0.72856545, 0.26700953,
0.38506149, 0.56252666, 0.59974406, 0.38050248, 0.14719008,
0.6360734 , 0.27812695, 0.73241298, 0.10904588, 0.57071762,
0.56808218, 0.33192772, 0.61444518, 0.07289501, 0.86464595,
0.71140253, 0.3221285 , 0.92556313, 0.26511829, 0.8487166 ,
0.38634413, 0.32169243, 0.80473196, 0.92050868, 0.17325157,
0.63503329, 0.89463233, 0.02796505, 0.04396453, 0.20603116,
0.77663591, 0.96595455, 0.77823865, 0.90867045, 0.39274922,
0.89526325, 0.26002297, 0.38606984, 0.69176715, 0.3170825 ,
0.86994578, 0.35648567, 0.19945661, 0.16109699, 0.58245076,
0.20239367, 0.7099113 , 0.41444565, 0.16725785, 0.01170234,
0.79989105, 0.76490449, 0.25418521, 0.55082581, 0.29550998,
0.02919009, 0.32737646, 0.29171893, 0.67664205, 0.24447834,
0.49631976, 0.41136961, 0.82478264, 0.76439988, 0.78829201,
0.24360075, 0.26151563, 0.51388418, 0.19823452, 0.44097815,
0.53198973, 0.50187154, 0.72374522, 0.11090765, 0.63469357,
0.69199977, 0.97093079, 0.35920669, 0.86493051, 0.01984456,
0.32219702, 0.58608421, 0.26591245, 0.51851213, 0.7896492 ,
0.04914308, 0.28711285, 0.36225247, 0.21299697, 0.99046025,
0.11375325, 0.70964612, 0.06599185, 0.47323442, 0.62003386])
///
np.random.random([3,3])
# 输出结果:
array([[ 0.37590691, 0.15563239, 0.7754904 ],
[ 0.40353019, 0.59708594, 0.57000741],
[ 0.33286511, 0.15678606, 0.58814922]])
np.random.normal(loc=0.0, scale=1.0)
np.random.normal(loc=0.0, scale=1.0)
# 输出: 0.9705556599476601
np.random.rand(d0,d1,…dn)
注:使用方法与np.random.randn()函数相同
作用:
通过本函数可以返回一个或一组服从“0~1”均匀分布的随机样本值。随机样本取值范围是[0,1),不包括1。
应用:在深度学习的Dropout正则化方法中,可以用于生成dropout随机向量(dl),例如(keep_prob表
示保留神经元的比例):dl = np.random.rand(al.shape[0],al.shape[1]) < keep_prob
np.random.rand(1,2,3)
# 输出:
[[[0.77655108 0.94720402 0.60342525]
[0.09466063 0.67177335 0.9059549 ]]]
ndim:维度
shape:形状(各维度的长度)
size:总长度
dtype:元素类型
np.random.seed(0)
x = np.random.randint(10,size=(3,4,5))
print(x)
array([[[5, 0, 3, 3, 7],
[9, 3, 5, 2, 4],
[7, 6, 8, 8, 1],
[6, 7, 7, 8, 1]],
[[5, 9, 8, 9, 4],
[3, 0, 3, 5, 0],
[2, 3, 8, 1, 3],
[3, 3, 7, 0, 1]],
[[9, 9, 0, 4, 7],
[3, 2, 7, 2, 0],
[0, 4, 5, 5, 6],
[8, 4, 1, 4, 9]]])
# 维度:x.ndim
# 形状,各维度的长度:x.shape
# 总长度:x.size
元素类型:x.dtype
索引
# 一维与列表完全一致 多维时同理
np.random.seed(1)
x = np.random.randint(10,size=[3,4,5])
print(x[2,0,0])
print(x)
5
[[[5 8 9 5 0]
[0 1 7 6 9]
[2 4 5 2 4]
[2 4 7 7 9]]
[[1 7 0 6 9]
[9 7 6 9 1]
[0 1 8 8 3]
[9 8 7 3 6]]
[[5 1 9 3 4]
[8 1 4 0 3]
[9 2 0 4 9]
[2 7 7 9 8]]]
切片
np.random.seed(0)
x = np.random.randint(100,size = (10,4))
x
# 输出结果:
array([[44, 47, 64, 67],
[67, 9, 83, 21],
[36, 87, 70, 88],
[88, 12, 58, 65],
[39, 87, 46, 88],
[81, 37, 25, 77],
[72, 9, 20, 80],
[69, 79, 47, 64],
[82, 99, 88, 49],
[29, 19, 19, 14]])
# 切片:
x[7:10]
# 切片结果:
array([[69, 79, 47, 64],
[82, 99, 88, 49],
[29, 19, 19, 14]])
变形
# 使用reshape函数,注意参数是一个tuple!
x = np.arange(0,16).reshape(4,4)
x
# 执行结果:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
# 类型是:
type(x.shape)
级联
np.concatenate() 级联需要注意的点:
级联的参数是列表:一定要加中括号
维度必须相同
形状相符
【重点】级联的方向默认是shape这个tuple的第一个值所代表的维度方向
可通过axis参数改变级联的方向
x = np.array([1,2,3])
y = np.array([1,5,6,7,3,20])
x
# 输出 array([1, 2, 3])
# 输出 array([ 1, 5, 6, 7, 3, 20])
z = np.concatenate([x,y])
z
# 输出 array([ 1, 2, 3, 1, 5, 6, 7, 3, 20])
z.shape
# 输出 (9,)
#二维
x = np.array([[1,2,3],[4,5,6]])
x
array([[1, 2, 3],
[4, 5, 6]])
x.shape
(2,3)
p = np.concatenate([x,x]).shape
p
(4, 3)
axis
import numpy as np
x = np.array([[[1,2,3],[2,2,3],[3,3,3]],[[4,4,4],[5,5,5],[6,6,6]]])
print(x)
print(x.shape)
# 输出:
[[[1 2 3]
[2 2 3]
[3 3 3]]
[[4 4 4]
[5 5 5]
[6 6 6]]]
(2, 3, 3)
/
w = np.concatenate([x,x],axis = 0)
print(w.shape)
print(w)
# 输出:
(4, 3, 3)
[[[1 2 3]
[2 2 3]
[3 3 3]]
[[4 4 4]
[5 5 5]
[6 6 6]]
[[1 2 3]
[2 2 3]
[3 3 3]]
[[4 4 4]
[5 5 5]
[6 6 6]]]
/
w = np.concatenate([x,x],axis = 1)
print(w.shape)
print(w)
# 输出:
(2, 6, 3)
[[[1 2 3]
[2 2 3]
[3 3 3]
[1 2 3]
[2 2 3]
[3 3 3]]
[[4 4 4]
[5 5 5]
[6 6 6]
[4 4 4]
[5 5 5]
[6 6 6]]]
//
w = np.concatenate([x,x],axis = 2)
print(w.shape)
print(w)
# 输出:
(2, 3, 6)
[[[1 2 3 1 2 3]
[2 2 3 2 2 3]
[3 3 3 3 3 3]]
[[4 4 4 4 4 4]
[5 5 5 5 5 5]
[6 6 6 6 6 6]]]
np.hstack与np.vstack 水平级联与垂直级联
x = np.array([[1,1],[2,2],[3,3]])
y = np.array([1,2,3])
print(np.hstack(x))
print(np.vstack(y))
# 输出:
[1 1 2 2 3 3]
[[1]
[2]
[3]]
切分
np.split()
x = np.arange(1,10)
x
# 输出:
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
//
x1,x2,x3 = np.split(x,[3,5])
print(x1,x2,x3)
输出: [1 2 3] [4 5] [6 7 8 9]
np.hsplit()
x = np.arange(16).reshape(4,4)
x
# 输出:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
//
print(np.hsplit(x,[2,3]))
# 输出:
[array([[ 0, 1],[ 4, 5], [ 8, 9], [12, 13]]),
array([[ 2], [ 6], [10], [14]]),
array([[ 3], [ 7], [11], [15]])]
np.vsplit()
x = np.arange(16).reshape(4,4)
x
# 输出:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
print(np.vsplit(x,[2,3]))
//
# 输出:
[array([[0, 1, 2, 3],
[4, 5, 6, 7]]), array([[ 8, 9, 10, 11]]), array([[12, 13, 14, 15]])]
所有赋值运算不会为ndarray的任何元素创建副本。对赋值后的对象的操作也对原来的对象生效。
a = np.array([1,2,3])
b=a
print(a,b)
# 输出:
[1 2 3] [1 2 3]
//
b[0]=2
a
# 输出:
array([2, 2, 3])
可使用copy()函数创建副本
a = np.array([1,2,3])
b = a.copy()
b
# 输出:
[1,2,3]
//
b[0]=3
print(a,b)
输出: [1 2 3] [3 2 3]
求和np.sum
import numpy as np
np.random.seed(0)
a = np.random.randint(1000,size = 100)
print(np.sum(a))
//
b = np.random.randint(1000,size = (3,4,5))
print(np.sum(b))
# 输出:
52397
32865
//
# 计算求和时间
%timeit np.sum(a)
# 输出:
2.63 µs ± 34.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
//
l = [1,2,3]
np.sum(l)
最大最小值:np.max/ np.min
# 求平均值
np.mean(heights)
# 求最大值
np.max(heights)
# 求最小值
np.min(heights)
# 计算标准差
heights.std()
ndarray的矩阵操作
算术运算符(加减乘除):
a = np.array([[1,2,3],
[4,5,6]])
a
# 输出:
array([[1, 2, 3],
[4, 5, 6]])
//
a+1
# 输出:
array([[2, 3, 4],
[5, 6, 7]])
//
a*2
# 输出:
array([[ 2, 4, 6],
[ 8, 10, 12]])
//
a+[[1,4,9],[3,3,3]]
# 输出:
array([[ 2, 6, 12],
[ 7, 8, 9]])
//
a*2-2
# 输出:
array([[ 0, 2, 4],
[ 6, 8, 10]])
矩阵积
a = np.array([[1,2,3],
[4,5,6]])
a
# 输出:
array([[1, 2, 3],
[4, 5, 6]])
//
b = np.array([[1,1],
[1,1],
[1,1]])
b
# 输出:
array([[1, 1],
[1, 1],
[1, 1]])
//
np.dot(a,b)
# 输出:
array([[ 6, 6],
[15, 15]])
【重要】ndarray广播机制的两条规则
规则一:为缺失的维度补1
规则二:假定缺失元素用已有值填充
# 例1: m = np.ones((2, 3)) a = np.arange(3) 求m+a
m = np.ones((2,3))
m
# 输出:
array([[ 1., 1., 1.],
[ 1., 1., 1.]])
a = np.arange(3)
a
# 输出:
array([0, 1, 2])
m+a
# 输出:
array([[ 1., 2., 3.],
[ 1., 2., 3.]])
# 例2: a = np.arange(3).reshape((3, 1)) b = np.arange(3) 求a+b
a = np.arange(3).reshape((3,1))
a
# 输出:
array([[0],
[1],
[2]])
b = np.arange(3)
b
# 输出:
array([0, 1, 2])
a+b
# 输出:
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
# 习题 a = np.ones((4, 1)) b = np.arange(4) 求a+b
a = np.ones((4,1))
b = np.arange(4)
a+b
# 输出:
array([[ 1., 2., 3., 4.],
[ 1., 2., 3., 4.],
[ 1., 2., 3., 4.],
[ 1., 2., 3., 4.]])
ndarray的排序
选择排序
import numpy as np
a = np.array([33,2,67,9,88,22,34])
def selectSort(x):
for i in range(len(x)):
swap = np.argmin(x[i:])+i
[x[i],x[swap]] = [x[swap],x[i]]
selectSort(a)
a
# 输出:
array([ 2, 9, 22, 33, 34, 67, 88])
快速排序
# np.sort()与ndarray.sort()都可以,但有区别:
# 1. np.sort()不改变输入
# 2. ndarray.sort()本地处理,不占用空间,但改变输入
# np.sort()不改变输入
np.random.seed(10)
a = np.random.randint(0,100,10)
b = np.random.randint(0,100,10)
print(a,b)
print(np.sort(a),a)
# 输入:
[ 9 15 64 28 89 93 29 8 73 0] [40 36 16 11 54 88 62 33 72 78]
[ 0 8 9 15 28 29 64 73 89 93] [ 9 15 64 28 89 93 29 8 73 0]
# ndarray.sort()本地处理,不占用空间,但改变输入
np.random.seed(20)
a = np.random.randint(0,100,10)
b = np.random.randint(0,100,10)
print(a,b)
print(a.sort(),a)
# 输出:
[99 90 15 95 28 90 9 20 75 22] [71 34 96 40 85 90 26 83 16 62]
None [ 9 15 20 22 28 75 90 90 95 99]