2.1 PyTorch安装
2.1.1 Anaconda安装
2.1.2 PyTorch安装
2.2 Jupyter Notebook使用
Jupyter Notebook(此前被称为IPython Notebook)是一个交互式笔记本,支持多种编程语言。这个笔记本可以编写代码、实时执行、可视化结果和嵌入资源,常用语数据分析和机器学习,基于网络分享内容也很方便,在需要文本和代码结合的场景下进行交流时,它是不可多得的首选环境。
2.3 Numpy 基础知识
- numpy是最常见的用于科学计算的基础包,该包的核心是ndarray对象
Numpy数组和标准的Python列表之间的几个重要区别:
- 数组大小固定
- 数组中的元素必须具有相同的数据类型
2.3.1 基本概念
Numpy主要对象是同种元素的多维数组。维度(dimensions)又叫做轴(axes),轴的个数叫做秩(rank)。ndarry对象属性有:
- ndarray.ndim:数组的维度
- ndarray.shape:数组的大小,n排m列:(n,m)
- ndarray.size:数组元素的总个数:n*m
- ndarray.dtype:描述数组中元素的类型
- ndarray.itemsize:数组中每个元素的字节大小。float64:8(64/8)
import numpy as np
a = np.arange(15).reshape(3,5)
a
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
a.shape
(3, 5)
a.ndim
2
a.dtype.name
'int64'
a.itemsize
8
a.size
15
type(a)
numpy.ndarray
2.3.2 创建数组
# 常规方法,使用列表和元组,可以显示制定数组类型
b = np.array([(1.5, 2, 3), (4, 5, 6)])
c = np.array([[1, 2], [3, 4]], dtype = complex)
b,c
(array([[1.5, 2. , 3. ],
[4. , 5. , 6. ]]),
array([[1.+0.j, 2.+0.j],
[3.+0.j, 4.+0.j]]))
# 一些特殊函数,默认dtype为float64
np.zeros((3, 4)) #全0
np.ones((2, 3, 4), dtype = np.int16) #全1
np.empty((2, 3)) #占位,未初始化
array([[1.39069238e-309, 1.39069238e-309, 1.39069238e-309],
[1.39069238e-309, 1.39069238e-309, 1.39069238e-309]])
# 创建数列,从10到20,间隔为5
np.arange(10, 20, 5)
np.arange(0, 2, 0.3)
array([0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])
# 创建数列,从0到2的9个数
np.linspace(0, 2, 9)
x = np.linspace(0, 2*np.pi, 100)
np.sin(x)
array([ 0.00000000e+00, 6.34239197e-02, 1.26592454e-01, 1.89251244e-01,
2.51147987e-01, 3.12033446e-01, 3.71662456e-01, 4.29794912e-01,
4.86196736e-01, 5.40640817e-01, 5.92907929e-01, 6.42787610e-01,
6.90079011e-01, 7.34591709e-01, 7.76146464e-01, 8.14575952e-01,
8.49725430e-01, 8.81453363e-01, 9.09631995e-01, 9.34147860e-01,
9.54902241e-01, 9.71811568e-01, 9.84807753e-01, 9.93838464e-01,
9.98867339e-01, 9.99874128e-01, 9.96854776e-01, 9.89821442e-01,
9.78802446e-01, 9.63842159e-01, 9.45000819e-01, 9.22354294e-01,
8.95993774e-01, 8.66025404e-01, 8.32569855e-01, 7.95761841e-01,
7.55749574e-01, 7.12694171e-01, 6.66769001e-01, 6.18158986e-01,
5.67059864e-01, 5.13677392e-01, 4.58226522e-01, 4.00930535e-01,
3.42020143e-01, 2.81732557e-01, 2.20310533e-01, 1.58001396e-01,
9.50560433e-02, 3.17279335e-02, -3.17279335e-02, -9.50560433e-02,
-1.58001396e-01, -2.20310533e-01, -2.81732557e-01, -3.42020143e-01,
-4.00930535e-01, -4.58226522e-01, -5.13677392e-01, -5.67059864e-01,
-6.18158986e-01, -6.66769001e-01, -7.12694171e-01, -7.55749574e-01,
-7.95761841e-01, -8.32569855e-01, -8.66025404e-01, -8.95993774e-01,
-9.22354294e-01, -9.45000819e-01, -9.63842159e-01, -9.78802446e-01,
-9.89821442e-01, -9.96854776e-01, -9.99874128e-01, -9.98867339e-01,
-9.93838464e-01, -9.84807753e-01, -9.71811568e-01, -9.54902241e-01,
-9.34147860e-01, -9.09631995e-01, -8.81453363e-01, -8.49725430e-01,
-8.14575952e-01, -7.76146464e-01, -7.34591709e-01, -6.90079011e-01,
-6.42787610e-01, -5.92907929e-01, -5.40640817e-01, -4.86196736e-01,
-4.29794912e-01, -3.71662456e-01, -3.12033446e-01, -2.51147987e-01,
-1.89251244e-01, -1.26592454e-01, -6.34239197e-02, -2.44929360e-16])
2.3.3 基本运算
NumPy中数组的运算是按元素位置执行的,其中的矩阵乘法运算符* 是按元素位置计算,矩阵乘法可以使用dot函数。
a = np.array([20, 30, 40, 50])
b = np.arange(4)
c = a - b
print("a =", a, "b =", b, "c =", c)
print(b**2)
print(10*np.sin(a))
print(a<35)
A = np.array([[1, 1],
[0, 1]])
B = np.array([[5, 4],
[3, 4]])
print("A*B =\n", A*B)
print("A.dot(B) =\n", A.dot(B))
print("np.dot(A, B) =\n", np.dot(A, B))
a = [20 30 40 50] b = [0 1 2 3] c = [20 29 38 47]
[0 1 4 9]
[ 9.12945251 -9.88031624 7.4511316 -2.62374854]
[ True True False False]
A*B =
[[5 4]
[0 4]]
A.dot(B) =
[[8 8]
[3 4]]
np.dot(A, B) =
[[8 8]
[3 4]]
操作符+=、* =被用来更改已存在数组而不会创建一个新的数组,因此要注意类型的兼容问题。当运算的不是同类型的数组时,结果会以更精确地方式存储。
a = np.ones((2, 3), dtype = int)
b = np.random.random((2, 3))
a *= 3
b += a
print(a)
print(b)
[[3 3 3]
[3 3 3]]
[[3.47683583 3.31039014 3.96454545]
[3.08689366 3.96040117 3.09556949]]
# a += b # b中元素不会自动转为整形,会报错
c = a+b # 正常
c
array([[6.47683583, 6.31039014, 6.96454545],
[6.08689366, 6.96040117, 6.09556949]])
还有一类统计类的运算,如求和、最大值、最小值,这些运算对数组形状不敏感,会把多维数组当成一维数组。当制定了 axis 参数时,可以把运算应用到数组指定的轴上。
a = np.random.random((2, 3))
print(a)
print(a.sum())
print(a.min())
print(a.max())
[[0.94321135 0.26487535 0.29239105]
[0.00761934 0.89637173 0.39615906]]
2.8006278831252454
0.007619343961588032
0.9432113525852343
b = np.arange(12).reshape((3, 4))
print(b)
print(b.sum(axis = 0)) # 列
print(b.sum(axis = 1)) # 行
print(b.min(axis = 1))
print(b.cumsum(axis = 1))
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[12 15 18 21]
[ 6 22 38]
[0 4 8]
[[ 0 1 3 6]
[ 4 9 15 22]
[ 8 17 27 38]]
2.3.4 索引、切片和迭代
Nmupy的一维数组就像列表和其他Python序列一样,可以被索引、切片和迭代。多维数组可以每个轴有一个索引,这些索引有一个逗号分割的元组给出。当少轴数的索引被提供时,确实的索引被认为是整个切片。
a = np.arange(10)**3
a
array([ 0, 1, 8, 27, 64, 125, 216, 343, 512, 729])
a[2]
8
a[2:5]
array([ 8, 27, 64])
a[:6:2] = -1000
# 等价于a[0:6:2] = -1000;从开始位置到索引6的元素止,每隔一个元素将其赋值为-1000
a
array([-1000, 1, -1000, 27, -1000, 125, 216, 343, 512,
729])
a = a[ : :-1]
# 翻转a
a
array([ 729, 512, 343, 216, 125, -1000, 27, -1000, 1,
-1000])
for i in a:
print(i**(1/3.))
8.999999999999998
7.999999999999999
6.999999999999999
5.999999999999999
4.999999999999999
nan
3.0
nan
1.0
nan
<ipython-input-22-a01b03b90f1f>:2: RuntimeWarning: invalid value encountered in power
print(i**(1/3.))
def f(x,y):
return 10*x + y
b = np.fromfunction(f,(5,4),dtype = int)
b
array([[ 0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23],
[30, 31, 32, 33],
[40, 41, 42, 43]])
b[2,3]
23
b[0:5, 1] #每行第二个元素
array([ 1, 11, 21, 31, 41])
b[:, 1] #同上
array([ 1, 11, 21, 31, 41])
b[1:3, :] #第二行到第三行
array([[10, 11, 12, 13],
[20, 21, 22, 23]])
b[-1] #最后一行,等价于b[: -1]
array([40, 41, 42, 43])
在上面的示例代码中,b[i]括号中的表达式被当做i轴和一系列:代表剩下的轴。NumPy也允许使用连续的“点”来表示,如b[i, …]。连续的点代表不显式地产生一个完整的索引元组必要的分号。
c = np.array([
[[0,1,2],[10,12,13]],
[[100,101,102],[110,112,113]]
])
c.shape
(2, 2, 3)
c[1,...] #等价于c[1,:,:]或c[1]
array([[100, 101, 102],
[110, 112, 113]])
c[...,2] #等价于c[:,:,2]
array([[ 2, 13],
[102, 113]])
以上是Numpy相对于Python序列相似的访问方法,接下来认识以下Numpy提供的更多独特的索引功能。除了整数索引和切片,数组还可以被整数数组和布尔数组索引。通过数组进行索引,这个数组索引的值指的是在目标数组(ndarry)的位置。
当被索引数组a是多维的时候,每一个唯一的索引数列指向a的第一维。
a = np.arange(12)**2
i = np.array([1,1,3,8,5])
a[i]
array([ 1, 1, 9, 64, 25])
j = np.array([[3,4],[9,7]])
a[j]
array([[ 9, 16],
[81, 49]])
palette = np.array([
[0,0,0],
[255,0,0],
[0,225,0],
[0,0,225],
[255,255,255]
])
image = np.array([
[0,1,2,0],
[0,3,4,0]
])
palette[image]
array([[[ 0, 0, 0],
[255, 0, 0],
[ 0, 225, 0],
[ 0, 0, 0]],
[[ 0, 0, 0],
[ 0, 0, 225],
[255, 255, 255],
[ 0, 0, 0]]])
当然我们也可以给出不止一维的索引,但是要保证每一维的索引数组必须有相同的形状。我们还可以把数组i和j定义成类似[i,j]放到序列中(比如说列表),然后通过list索引。但要注意,不能把[i,j]放在一个ndarrary数组中,因为这个ndarray数组将被解释成索引的第一维。
a = np.arange(12).reshape(3,4)
a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
i = np.array([
[0,1],
[1,2]
])
j = np.array([
[2,1],
[3,3]
])
a[i,j]
array([[ 2, 5],
[ 7, 11]])
a[i,2]
array([[ 2, 6],
[ 6, 10]])
a[:,j]
array([[[ 2, 1],
[ 3, 3]],
[[ 6, 5],
[ 7, 7]],
[[10, 9],
[11, 11]]])
l =[i,j]
a[l]
<ipython-input-43-84e96e020564>:2: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.
a[l]
array([[ 2, 5],
[ 7, 11]])
s = np.array([i,j])
a[s]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-45-537bf3252a3e> in <module>
1 s = np.array([i,j])
----> 2 a[s]
IndexError: index 3 is out of bounds for axis 0 with size 3
a[tuple(s)]
array([[ 2, 5],
[ 7, 11]])
布尔数组索引是数组索引的一种特例,当使用整数数组索引ndarray时,我们提供一个索引列表去选择。
通过布尔数组索引的方法不同的是,要显式的表示ndarray中想要保留和丢弃的元素。我们能想到的使用布尔数组索引最自然的方式就是使用和原ndarray一样形状的布尔数组。
a = np.arange(12).reshape(3,4)
b = a>4
b
array([[False, False, False, False],
[False, True, True, True],
[ True, True, True, True]])
a[b]
array([ 5, 6, 7, 8, 9, 10, 11])
a[a>4]
array([ 5, 6, 7, 8, 9, 10, 11])
第二种通过布尔来索引的方法更近似于整数索引,对数组的每个维度,我们给一个一维布尔数组来选择想要的切片。
注意,一维数组的长度必须和想要切片的维度或轴的长度一致
a = np.arange(12).reshape(3,4)
b1 = np.array([False,True,True])
b2 = np.array([True,False,True,False])
a[b1,:]
array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
a[:,b2]
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
a[b1,b2]
array([ 4, 10])
如果说索引是对ndarray确定位置的访问,那么对ndarray又是怎样进行迭代呢?
多维ndarray数组的迭代就是对第一个轴而言的,如果相对ndarray数组中的每个元素进行运算,我们可以使用flat属性,该属性是数组元素的一个迭代器。
b = np.arange(12).reshape(3,4)
for row in b:
print(row)
print()
for element in b.flat:
print(element)
[0 1 2 3]
[4 5 6 7]
[ 8 9 10 11]
0
1
2
3
4
5
6
7
8
9
10
11
2.3.5 数组赋值
利用前面大量便捷的索引方法,我们能很方便地找到需要定位的元素进行赋值。当一个索引列表有重复值时,赋值被多次完成,仅保留最后的值。
但如果想用Python中的+=语句,可能结果并非所期望的。如下代码片段示例,即使0在索引列表中出现两次,索引为0的元素仅仅增加以此。这是因为Python要求a+=q和a=a+1等同。
a = np.arange(5)
a
array([0, 1, 2, 3, 4])
a[[1,3,4]] = 0
a
array([0, 0, 2, 0, 0])
a = np.arange(5)
a[[0,0,2]] = [1,2,3]
a
array([2, 1, 3, 3, 4])
a[[0,0,2]]+=1
a
array([3, 1, 4, 3, 4])
另外一种技巧是利用布尔索引进行赋值操作,只要对应的位置进行标识,这对于不便于制定确切索引值的情况非常有用。
a = np.arange(12).reshape(3,4)
a[a>4] = 0
a
array([[0, 1, 2, 3],
[4, 0, 0, 0],
[0, 0, 0, 0]])
2.3.6 更改数组的形状
本节开头提到了ndarray数组的shape属性,表示一个数组的形状,其值由它的每个轴上的元素个数给出。ndarray数组的形状可以被多种函数修改。
- 使用ravel函数能够将ndarray数组展开成扁平化的形式
- reshape函数则会要求指定新的轴大小,将ndarray数组改变成其他形状。
Numpy的ravel函数和reshape函数通常会新建一个符合目标形状的ndarray数组保存数据。
如果在改变形状操作中一个维度被设置为-1,该维度将自动被计算。
a = np.floor(10*np.random.random((3,4)))
a
array([[9., 4., 6., 7.],
[1., 4., 2., 5.],
[9., 3., 5., 9.]])
a.shape
(3, 4)
a.ravel()
array([9., 4., 6., 7., 1., 4., 2., 5., 9., 3., 5., 9.])
a.reshape(6,2)
array([[9., 4.],
[6., 7.],
[1., 4.],
[2., 5.],
[9., 3.],
[5., 9.]])
a
array([[9., 4., 6., 7.],
[1., 4., 2., 5.],
[9., 3., 5., 9.]])
a.T
array([[9., 1., 9.],
[4., 4., 3.],
[6., 2., 5.],
[7., 5., 9.]])
a.T.shape
(4, 3)
a.reshape(3,-1)
array([[9., 4., 6., 7.],
[1., 4., 2., 5.],
[9., 3., 5., 9.]])
除了以上两种函数方法外,resize函数也是和reshape函数相同功能的函数,区别是resize函数仅仅改变数组本身,并不会创建新的ndarray数组。
a.resize((2,6))
a
array([[9., 4., 6., 7., 1., 4.],
[2., 5., 9., 3., 5., 9.]])
2.3.7 组合、拆分数组
当有多个ndarray数组时,NumPy提供了集中方法可以沿不同轴将这些数组拼接在一起。
- vstack函数使多个ndarray数组沿着第一个轴组合
- hstack函数则是沿着第二个轴组合
- column_stack的组合方式会略有不同,它仅支持以序列的顺序将多个一维数组或者一个二维数组按对位组合成新的二维数组。
a = np.floor(10*np.random.random((2,2)))
a
array([[7., 3.],
[3., 3.]])
b = np.floor(10*np.random.random((2,2)))
b
array([[3., 9.],
[6., 5.]])
np.vstack((a,b))
array([[7., 3.],
[3., 3.],
[3., 9.],
[6., 5.]])
np.hstack((a,b))
array([[7., 3., 3., 9.],
[3., 3., 6., 5.]])
np.column_stack((a,b))
array([[7., 3., 3., 9.],
[3., 3., 6., 5.]])
既然可以将ndarray数组按照不同的轴组合,同样Numpy也能方便地拆分数组,vsplit和hsplit就是这样一堆具有拆分功能的函数。
- vsplit函数将ndarray数组沿着纵向的轴分割
- hsplit函数将ndarray数组沿着水平轴分割
- split函数对拆分控制的自由度更高
允许指定返回相同形状数组的个数,或者指定在哪些行列前发生分割:
a = np.floor(10*np.random.random((2,12)))
a
array([[0., 0., 8., 9., 4., 5., 1., 6., 4., 1., 1., 9.],
[8., 8., 9., 5., 9., 5., 7., 2., 9., 4., 9., 6.]])
np.hsplit(a,3)
[array([[0., 0., 8., 9.],
[8., 8., 9., 5.]]),
array([[4., 5., 1., 6.],
[9., 5., 7., 2.]]),
array([[4., 1., 1., 9.],
[9., 4., 9., 6.]])]
np.hsplit(a,(3,4))
[array([[0., 0., 8.],
[8., 8., 9.]]),
array([[9.],
[5.]]),
array([[4., 5., 1., 6., 4., 1., 1., 9.],
[9., 5., 7., 2., 9., 4., 9., 6.]])]
x = np.arange(16.0).reshape(4,4)
x
array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]])
np.vsplit(x,2)
[array([[0., 1., 2., 3.],
[4., 5., 6., 7.]]),
array([[ 8., 9., 10., 11.],
[12., 13., 14., 15.]])]
np.vsplit(x,np.array([3,6]))
[array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]]),
array([[12., 13., 14., 15.]]),
array([], shape=(0, 4), dtype=float64)]
如果觉得这还不够强大,那么推荐使用split函数。
x = np.arange(9.0)
np.split(x,3)
[array([0., 1., 2.]), array([3., 4., 5.]), array([6., 7., 8.])]
x = np.arange(8.0)
np.split(x,[3,5,6,10])
[array([0., 1., 2.]),
array([3., 4.]),
array([5.]),
array([6., 7.]),
array([], dtype=float64)]
2.3.8 广播
广播是Numpy在操作数组的方法中另一大显著性优势,对于不同的shape数组之间的转换具有重要意义。广播的说法是从“Broadcasting”直译来的,有一种推荐的理解是“Broad-casting”,即体现出沿某个方向延伸转换的意图。
广播的原则能使通用函数有意义的处理不具有相同形状的输入。应用广播原则之后,所有数组的大小必须匹配。
- 广播的第一原则是,如果所有的输入数组维度不都相同,数值“1”将被重复的田间在维度较小的数组上,直至所有的数组拥有一样的维度。
- 广播的第二原则是,确定长度为1的数组沿着特殊的方向向最大形状延展。对数组来说,沿着那个维度的数组元素的值理应相同。
x = np.arange(4)
xx = x.reshape(4,1)
y = np.ones(5)
z = np.ones((3,4))
x.shape
(4,)
y.shape
(5,)
x+y
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-115-259706549f3d> in <module>
----> 1 x+y
ValueError: operands could not be broadcast together with shapes (4,) (5,)
xx.shape
(4, 1)
y.shape
(5,)
xx+y
array([[1., 1., 1., 1., 1.],
[2., 2., 2., 2., 2.],
[3., 3., 3., 3., 3.],
[4., 4., 4., 4., 4.]])
x.shape
(4,)
z.shape
(3, 4)
x+z
array([[1., 2., 3., 4.],
[1., 2., 3., 4.],
[1., 2., 3., 4.]])
利用广播的原则,Numpy还允许执行一种交叉运算。下面这段代码示范了两个一维ndarray数组是如何实现交叉相加的。注意,其中的newaxis实际上是向a增加了一个轴,使a变成了4*1的数组。
a = np.array([0.,10.,20.,30.])
b = np.array([1.,2.,3.])
a[:,np.newaxis] + b
array([[ 1., 2., 3.],
[11., 12., 13.],
[21., 22., 23.],
[31., 32., 33.]])
2.4 PyTorch基础知识
PyTorch的许多函数在使用上和Numpy几乎一样,能够平滑的结合使用,前一节介绍的绝大多数操作同样可以结合到本节中使用。PyTorch的特色之一是提供构建动态计算图的框架,这样的网络结构就不再是一成不变的了,甚至可以在运行时修正它们。
在神经网络方面,PyTorch的优点还在于使用了多GPU的强大加速能力、自定义数据加载器和极简的预处理过程。尽管PyTorch与其他框架相比还是新秀,仍然需要完善和改进,但不可否认他一出现就得到了广泛的认同和运用。
2.4.1 Tensor简介
Tensor是PyTorch中的基本对象,意思为张量,表示多维的矩阵,是PyTorch中的基本操作对象之一。与Numpy的ndarray类似,Tensor的生命和获取size可以这样:
import torch
x = torch.Tensor(5,3)
x.size()
torch.Size([5, 3])
Tensor的算术运算和选取操作与Numpy一样,因此Numpy相似的运算操作都可以迁移过来:
x = torch.rand(5,3)
y = torch.rand(5,3)
x+y
tensor([[1.1451, 0.9631, 0.4258],
[1.5533, 0.3438, 0.5367],
[1.0274, 0.1135, 1.3550],
[0.6828, 0.8289, 0.4505],
[1.4933, 1.1646, 0.0931]])
x[:,1]
tensor([0.0319, 0.0043, 0.0411, 0.7052, 0.7834])
Tensor与Numpy的array还可以进行相互转换,有专门的转换函数:
x = torch.rand(5,3)
y = x.numpy()
z = torch.from_numpy(y)
2.4.2 Variable简介
Variable是Pytorch的另一个基本对象,可以把它理解为是对Tensor的这一种封装。Variable用于放入计算图中以进行前向传播、反向传播和自动求导。
在一个Variable中有三个重要属性:data、grad、creator。其中:
- data表示包含的Tensor数据部分
- grad表示传播方向的梯度,这个属性是延时分配的,而且仅允许进行一次
- creator表示创建这个Variable的Function的引用,该引用用于回溯整个创建链路。如果是用户创建的Variable,其creator为None,同时这种Variable称作Leaf Variable,autograd只会给Leaf Variable分配梯度。
from torch.autograd import variable
x = torch.tensor([1.,2.,3.,4.])
x = variable(x, requires_grad = True)
y = x**3
grad_variables = torch.tensor([1, 0.1, 0.01, 0.001])
y.backward(grad_variables)
x.grad
tensor([3.0000, 1.2000, 0.2700, 0.0480])
对于y.backward(grad_variables),grad_variables就是y求导时的梯度参数,由于autograd仅用于标量,因此当y不是标量且在声明时使用了requires_grad=True时,必须指定grad_variables参数,在完成原始的反向传播后得到的梯度会用这个grad_variables进行修正,然后将结果保存至Variable的grad中。
grad_variables的长度与y要一致。在深度学习中求导与梯度有关,因此grad_vaiables一般会定义类似为[1, 0.1, 0.01, 0.001],表示梯度的方向,取较小的值不会对求导效率有影响。
2.4.3 CUDA简介
如果安装了支持CUDA版本的PyTorch,就可以启用显卡运算了。torch.cuda用于设置和运行CUDA操作,它会记录当前选择的GPU,并且分配的所有CUDA装量将默认在上面创建,可以使用torch.cuda.device上下文管理器更改所选设备。
不过,一旦张量被分配,可以直接对其进行操作,而不考虑所选择的设备,结果将始终放在与张量相关的设备上,默认情况下,不支持跨GPU操作,唯一的例外是copy_()。除非启用对等存储器的访问,否则对于分布不同设备上的张量,任何启动操作的尝试都将引发错误。
torch.cuda.is_available()
True
x = x.cuda()
y = y.cuda()
x+y
tensor([ 2., 10., 30., 68.], device='cuda:0', grad_fn=<AddBackward0>)
2.4.4 模型的保存与加载
Python中对于模型数据的保存和加载操作都是引用Python内置的pickle包,使用pickle.dump()和pickle.load()方法。在PyTroch中也有同样功能的方法提供。
torch.save(model, "model.pkl") #保存整个模型
model = torch.load("model.pkl") #加载整个模型
torch.save(alexnet.state_dict(), "params.pkl") #保存网络中的参数
alexnet.load_state_dict(torch.load('params.pkl')) #加载网络中的参数
在torchvision.models模块里,PyTorch提供了一些常用的模型:
- AlexNet
- VGG
- ResNet
- SqueezeNet
- DenseNet
- Inception v3
可以使用torch.util.model_zoo来与加载它们,具体设置通过参数pretrained=True来实现。
import torchvision.models as models
ResNet18 = models.resnet18(pretrained = True)
AlexNet = models.alexnet(pretrained = True)
# 等等
加载这类与预训练模型的过程中,还可以进行微处理。
Pretrained_dict = model_zoo.load_url(model_urls['resnet134'])
model_dict = model.state_dict()
pretrained_dict = {
# 将pretrained_dict里不属于model_dict的键剔除掉
k: v for k, vin pretrained_dict.items() if k in model_dict
}
model_dict.update(pretrained_dict) #更新现有的model_dict
model.load_state_dict(model_dict)
2.4.5 第一个PyTorch程序
下面这段程序是对线性回归模型的简单演练。该示例中先创建了一些随机训练样本,让其符合经典线性函数 Y = W T X + b Y=W^TX+b Y=WTX+b 分布,并加了一点噪声处理使得样本出现一定的偏差。接着使用PyTroch创建了一个线性回归模型。在训练过程中对训练样本进行反向传播,求导后根据指定的损失边界结束训练。最后显示模型学习的结果与真实情况的对比示意图。
完整的代码如下:
#!/usr/bin/env python
from __future__ import print_function
from itertools import count
import numpy as np
import torch
import torch.autograd
import torch.nn.functional as F
from torch.autograd import Variable
import matplotlib.pyplot as plt
random_state = 5000
torch.manual_seed(random_state)
PLOY_DEGREE = 4
W_target = torch.randn(PLOY_DEGREE, 1)*5
b_target = torch.randn(1)*5
def make_features(x):
""" 创建一个特征矩阵结构为[x, x^2, x^3, x^4] """
x = x.unsqueeze(1)
return torch.cat([x**i for i in range(1, PLOY_DEGREE+1)], 1)
def f(x):
""" 近似函数 """
return x.mm(W_target) + b_target[0]
def poly_desc(W, b):
""" 生成多项式描述内容 """
result = "y="
for i,w in enumerate(W):
result += "{:+.2f} x^{}".format(w, len(W)-i)
result += '{:+.2f}'.format(b[0])
return result
def get_batch(batch_size = 32):
""" 创建类似(x, f(x))的批数据 """
random = torch.from_numpy(np.sort(torch.randn(batch_size)))
x = make_features(random)
y = f(x)
return Variable(x), Variable(y)
if __name__ == "__main__":
# 声明模型
fc = torch.nn.Linear(W_target.size(0), 1)
for batch_idx in count(1):
# 获取数据
batch_x, batch_y = get_batch()
# 重置求导
fc.zero_grad()
# 前向传播
output = F.smooth_l1_loss(fc(batch_x), batch_y)
loss = output.data
# 后向传播
output.backward()
# 应用导数
for param in fc.parameters():
param.data.add_(-0.1 * param.grad.data)
# 停止条件
if loss < 1e-3:
plt.cla()
plt.scatter(batch_x.data.numpy()[:, 0], batch_y.data.numpy()[:, 0], label = "real curve", color = 'b')
plt.plot(batch_x.data.numpy()[:, 0], fc(batch_x).data.numpy()[:, 0], label = "fitting curve", color = 'r')
plt.legend()
plt.show()
break
print('Loss: {:.6f} after {} batches'.format(loss, batch_idx))
print("==> Learned function:\t" + poly_desc(fc.weight.data.view(-1), fc.bias.data))
print("==> Actual function:\t" + poly_desc(W_target.view(-1), b_target))
Loss: 0.000518 after 760 batches
==> Learned function: y=+6.07 x^4+3.91 x^3+0.90 x^2+8.19 x^1-1.64
==> Actual function: y=+6.13 x^4+3.89 x^3+0.86 x^2+8.20 x^1-1.62