相关资源

【课程官网】
【在线教材】

在教材的导航栏点击【MXNet】或【PyTorch】下载对应离线版本,点击【Jupter记事本】下载源码

笔记一:深度学习基础 - 图1
【PyTorch中文文档】

深度学习基础

第一节:数据操作

笔记一:深度学习基础 - 图2
笔记一:深度学习基础 - 图3
三维数组解释:把RGB通道的三层分别视为3列,叠加起来

参考:【关于深度学习中三维矩阵的理解(RGB图像的输入)】
笔记一:深度学习基础 - 图4

四维数组解释:读入图片不是一张一张读,而是一批一批读,叫一个批量

笔记一:深度学习基础 - 图5
子区域[1:3,1:]解释:前面1:3表示从第一行到第三行,左闭右开,所以就是取了第1行和第2行,后面1:表示从第一列拿到最后一列,在本例中就是第1列,第2列和第3列。

子区域[::3,::2]解释:跳着访问,::3表示每3行一跳,在本例中就是取了第0行和第3行,::2表示每2列一跳,在本例中就是取了第0列和第2列。

张量表示由一个数值组成的数组,这个数组可能有多个维度。

创建数组需要:

  • 形状:例如笔记一:深度学习基础 - 图6矩阵
  • 每个元素的数据类型,例如32位浮点数
  • 每个元素的值,例如全是0,或者随机数

导入torch

  1. import torch

torch.arange()函数

  1. torch.arange(start, end, step=1, out=None)

返回一个1维张量,长度为笔记一:深度学习基础 - 图7%2Fstep)#card=math&code=floor%28%28end-start%29%2Fstep%29&id=dKC4X)包含从到,以为步长的一组序列值(默认start为0,步长为1)

参数:

  • start (float) – 序列的起始点
  • end (float) – 序列的终止点
  • step (float) – 相邻点的间隔大小
  • out (Tensor, optional) – 结果张量

指定:

  1. x = torch.arange(12)
  2. print(x)
  3. print(x.dtype)
  1. tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
  2. torch.int64

指定:和:

x = torch.arange(1, 12)
print(x)
tensor([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

指定、和:

x = torch.arange(1, 12, 3)
print(x)
tensor([ 1,  4,  7, 10])

指定和:

x = torch.arange(12,  dtype=torch.float32)
print(x)
tensor([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11.])
torch.float32

pytorch里一般使用torch.arange(),而不使用torch.range(),关于两者的区别:【pytorch.range() 和 pytorch.arange() 的区别】

访问张量的形状和元素总数

x = torch.arange(12)
print(x)

# 访问张量形状
print(x.shape)
print(x.size())

# 访问张量长度
print(len(x))

# 访问张量中元素总数
print(x.numel())
tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
torch.Size([12])
torch.Size([12])
12
12

torch.reshape()函数

torch.reshape()接受的参数依次为最高维,次高维,…,第三维(通道),第二维(行),第一维(列)

x = torch.arange(12).reshape(3, 4)
print(x)

# 访问张量形状
print(x.shape)
print(x.size())

# 访问张量长度
print(len(x))

# 访问张量中元素总数
print(x.numel())
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])

torch.Size([3, 4])
torch.Size([3, 4])
3
12

tensor([[[ 0,  1],
         [ 2,  3]],

        [[ 4,  5],
         [ 6,  7]],

        [[ 8,  9],
         [10, 11]]])

使用全0,全1,其他常量或从特定分布中随机采样的数字

x = torch.zeros(2, 3, 4)
print(x)
tensor([[[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]],

        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]]])
x = torch.ones(2, 3, 4)
print(x)
tensor([[[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.]],

        [[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.]]])

通过提供包含数值的列表(或嵌套列表)来为所需张量中的每个元素赋予确定值。在这⾥,最外层的列表对应于轴0,内层的列表对应于轴1:

x = torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
print(x)
print(x.sum())
tensor([[2, 1, 4, 3],
        [1, 2, 3, 4],
        [4, 3, 2, 1]])
tensor(30)

torch.randn()方法从均值为0、标准差为1的标准⾼斯(正态)分布中随机采样得到元素:

x = torch.randn(3, 4)
print(x)
tensor([[-1.0596, -1.1175, -0.0242,  1.0464],
        [-1.3995, -1.0616, -0.7274, -0.4666],
        [-0.5376,  1.4548, -0.3597,  0.0560]])

运算

按元素运算:标准算术运算符(+、-、、/和*)已被重载过,被升级为按(对应)元素运算

⼴播机制:对不同形状的张量,将其复制元素形成形状相同的张量,再按元素运算

a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
print(a, '\n', b)
tensor([[0],
        [1],
        [2]]) 
 tensor([[0, 1]])

执行a+b运算时

print(a+b)

把a从笔记一:深度学习基础 - 图8的列向量向右复制一列,变成成笔记一:深度学习基础 - 图9的矩阵

tensor([[0, 0],
        [1, 1],
        [2, 2]])

把b从笔记一:深度学习基础 - 图10的行向量复制向下两行,变成成笔记一:深度学习基础 - 图11的矩阵

tensor([[0, 1],
        [0, 1],
        [0, 1]])

再按元素相加:

tensor([[0, 1],
        [1, 2],
        [2, 3]])

torch.cat()函数把两个张量连接起来:

X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
print(X, '\n\n', Y)
tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11.]]) 

 tensor([[2., 1., 4., 3.],
        [1., 2., 3., 4.],
        [4., 3., 2., 1.]])

指定dim = 0是在轴0(按列)方向拼接两个张量(竖着拼接到一起),指定dim = 1是在轴1(按行)方向拼接两个张量(横着拼接到一起):

print(torch.cat((X, Y), dim=0))
print()
print(torch.cat((X, Y), dim=1))
tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11.],
        [ 2.,  1.,  4.,  3.],
        [ 1.,  2.,  3.,  4.],
        [ 4.,  3.,  2.,  1.]])

tensor([[ 0.,  1.,  2.,  3.,  2.,  1.,  4.,  3.],
        [ 4.,  5.,  6.,  7.,  1.,  2.,  3.,  4.],
        [ 8.,  9., 10., 11.,  4.,  3.,  2.,  1.]])

索引和切片

笔记一:深度学习基础 - 图12
如图,创建对应的笔记一:深度学习基础 - 图13矩阵:

x = torch.arange(1, 17).reshape(4, 4)
print(x)
tensor([[ 1,  2,  3,  4],
        [ 5,  6,  7,  8],
        [ 9, 10, 11, 12],
        [13, 14, 15, 16]])

一个元素:[1, 2]

print(x[1, 2])
tensor(7)

一行:[1, :] [-1]

print(x[1, :])
# 和print(x[1])一样,无逗号默认为行操作
print(x[-1])
tensor([5, 6, 7, 8])
tensor([13, 14, 15, 16])

一列:[:, 1]

print(x[:, 1])
tensor([ 2,  6, 10, 14])

子区域:[1:3, 1:]

print(x[1:3, 1:])
tensor([[ 6,  7,  8],
        [10, 11, 12]])

子区域:[::3, ::2]

print(x[::3, ::2])
tensor([[ 1,  3],
        [13, 15]])

节省内存和原地操作

在Python里,变量名的本质是指针(或可看作Java、C#的引用数据类型),当执行右边是常量的赋值表达式时,实际上是先开辟一块内存空间存储式右侧的值,再令式左侧的变量名指向存储值的这块内存空间。

a = 5
print("id(a)_first:", id(a))
before = id(a)
a = 6
print("id(a)_second:", id(a))
print("是否相等:", id(a) == before)
id(a)_first: 140710131607456
id(a)_second: 140710131607488
是否相等: False

解释:id() 函数返回对象的唯一标识符(对象的内存地址),标识符是一个整数。

令a = 5后,id(a)的地址是140710131607456,令a = 6,此时先分配一块地址是140710131607488的内存空间来存储6这个值,然后再令a指向新内存空间,故赋值操作前后a的地址不再相等。

我们再看另一个操作:

a = 5
print("id(a):", id(a))
b = a
print("id(b):", id(b))
print("是否相等:", id(b) == id(a))
id(a): 140708293781408
id(b): 140708293781408
是否相等: True

解释:b = a是把b作为指针指向了a所指向的那块内存空间,所以b和a(所指向)的地址完全相同。

另一个例子:

a = [1, 0.4, 'string']
print("id(a):", id(a))
b = a
print("id(b):", id(b))
print("是否相等:", id(b) == id(a))
b[2] = "char[]"
print(a)
id(a): 2687673048192
id(b): 2687673048192
是否相等: True
[1, 0.4, 'char[]']

对列表执行原地操作的办法:b[:] =

对b原地操作之后输出a,a就是改变后的列表,说明对b的操作即是对a的操作。

张量亦是如此:

X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
before = id(Y)
Y = Y + X
print(id(Y) == before)
False

对张量执行原地操作的办法:Z[:] =

# 创建⼀个新的矩阵Z,其形状与Y相同
# 使⽤zeros_like来分配⼀个全0的块

Z = torch.zeros_like(Y)
print(Z, '\n')

print('id(Z):', id(Z))
Z[:] = X + Y
print('id(Z):', id(Z))
tensor([[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]]) 

id(Z): 1977010202240
id(Z): 1977010202240

特别的,+= 重载后执行原地操作,即X[:] = X + Y和X += Y等价:

label1 = id(X)
X[:] = X + Y
label2 = id(X)
X += Y
label3 = id(X)
print("label1:", label1)
print("label2:", label2)
print("label2:", label2)
label1: 1405191724288
label2: 1405191724288
label2: 1405191724288

转换为其他Python对象

对tensor调用numpy()方法将其转变成numpy张量,对numpy张量调用torch.tensor转变成torch张量

X = torch.arange(12, dtype=torch.float32).reshape((3,4))
A = X.numpy() # A被转换为numpy张量
print('\n')
print(A)
print('\n')
B = torch.tensor(A) # B被转换为torch张量
print(B)

print('\nA:', type(A), '\nB:', type(B))
[[ 0.  1.  2.  3.]
 [ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]]


tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11.]])

A: <class 'numpy.ndarray'> 
B: <class 'torch.Tensor'>

转换成标量:调⽤item函数或Python的内置函数

a = torch.tensor([3.5])
print(a, a.item(), float(a), int(a))
print('\n', type(a), type(a.item()), type(float(a)), type(int(a)))
tensor([3.5000]) 3.5 3.5 3

<class 'torch.Tensor'> <class 'float'> <class 'float'> <class 'int'>

第二节:数据预处理

创建人工数据集

首先导入os

import os
os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv')

这里os.path.join()函数的作用是把多个字符串连接成一个路径,.是当前路径,..是上一级路径,os.path.join('..', 'data')..data连接成路径'..\data',os.makedirs('..\data', exist_ok=True)exist_ok=True的意思是如果不存在此路径则创建之,如果存在则不变,也不抛出异常(如果为False就会抛出异常)

然后再通过os.path.join()方法创建保存文件路径的变量data_file = ..\data\house_tiny.csv

写入文件内容

with open(data_file, 'w') as f:
    f.write('NumRooms,Alley,Price\n')
    f.write('NA,Pave,127500\n')
    f.write('2,NA,106000\n')
    f.write('4,NA,178100\n')
    f.write('NA,NA,140000\n')

'r'是只读模式,'w'是写入模式,with open(data_file, 'w') as f:意为以写入模式打开文件(如果文件不存在就创建它)并绑定到f对象上,然后执行对f对象的操作.

打开创建的csv文件,里面已经被写入内容了:
笔记一:深度学习基础 - 图14

处理csv文件

一般使用pandas库处理csv文件.

导入pandas包:

import pandas as pd

调用read_csv()函数

data = pd.read_csv(data_file)
print(data)

print('\n')
print(type(data))
   NumRooms Alley   Price
0       NaN  Pave  127500
1       2.0   NaN  106000
2       4.0   NaN  178100
3       NaN   NaN  140000

<class 'pandas.core.frame.DataFrame'>

该数据集有四⾏三列,其中每⾏描述了房间数量(“NumRooms”)、巷⼦类型(“Alley”)和房屋价格(“Price”),“NaN”项代表缺失值。

处理缺失值有删除和插值两种方法,删除就是直接把有缺失值那一行删除,这种方法简单粗暴,适合有较少确实值的大数据集,但是对于小数据集并不适合.

插值则从当前列中完好的数据中采样得到一个值,并把这个值填入缺失处

通过位置索引iloc从原始数据集data中读取内容:

inputs = data.iloc[:, 0:2]
print(inputs)
   NumRooms Alley
0       NaN  Pave
1       2.0   NaN
2       4.0   NaN
3       NaN   NaN

使用fillna()方法,将平均值填补到NaN处:

inputs = inputs.fillna(inputs.mean())
print(inputs)
   NumRooms Alley
0       3.0  Pave
1       2.0   NaN
2       4.0   NaN
3       3.0   NaN

但是fillna()方法只能填补浮点型和整型数的缺失值,不能填补字符型的缺失值,对于Alley这一列,采用pd.get_dummies()方法,把这一列有缺失值和没有缺失值的类型分开,并且用1代表是否是某类:

   NumRooms  Alley_Pave  Alley_nan
0       3.0           1          0
1       2.0           0          1
2       4.0           0          1
3       3.0           0          1

将outputs分离出来:

outputs = data.iloc[:, 2]
print(outputs)
0    127500
1    106000
2    178100
3    140000
Name: Price, dtype: int64

现在inputs和outputs中的所有条⽬都是数值类型,它们可以转换为张量格式,然后可以进行进一步操作。

a, b = torch.tensor(inputs.values), torch.tensor(outputs.values)
print(a)
print('\n')
print(b)
print(type(a))
tensor([[3., 1., 0.],
        [2., 0., 1.],
        [4., 0., 1.],
        [3., 0., 1.]], dtype=torch.float64)

tensor([127500, 106000, 178100, 140000])
<class 'torch.Tensor'>

第三节:线性代数

按元素操作、求和与降维

访问矩阵的转置:

A = torch.arange(20).reshape(5, 4)
print(A)
print()
print(A.T)
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11],
        [12, 13, 14, 15],
        [16, 17, 18, 19]])

tensor([[ 0,  4,  8, 12, 16],
        [ 1,  5,  9, 13, 17],
        [ 2,  6, 10, 14, 18],
        [ 3,  7, 11, 15, 19]])

默认情况下,调⽤求和函数会沿所有的轴降低张量的维度,使它变为⼀个标量。我们还可以指定张量沿哪⼀个轴来通过求和降低维度。我们可以在调⽤函数时指定axis=x。由于输⼊矩阵沿x轴降维以⽣成输出向量,因此输⼊的轴x的维数在输出形状中丢失。

对张量A:
    shape:[ 5,4]
    axis :  (0, 1)
沿axis=0求和: [   4]
沿axis=1求和: [5   ]

A = torch.arange(20).reshape(5, 4)
print(A.sum())
print(A.sum(axis=0))      # 求和所有行的元素来降维(轴0)
print(A.sum(axis=1))      # 求和所有行的元素来降维(轴0)
print(A.sum(axis=[0, 1])) # 沿着行和列对矩阵求和,等价于对矩阵的所有元素进行求和
print(A.sum(axis=1, keepdims=True)) # 指定轴数不变,求和的轴的参数变为1
tensor(190)

tensor([40, 45, 50, 55])

tensor([ 6, 22, 38, 54, 70])

tensor(190)

tensor([[ 6],
        [22],
        [38],
        [54],
        [70]])

以一个三维张量为例,按维数求和并指定轴数不变:

a = torch.ones(2, 5, 4)
print(a.shape)
print(a.sum(axis=1, keepdims=True).shape)
print(a.sum(axis=[0, 2], keepdims=True).shape)
torch.Size([2, 5, 4])
torch.Size([2, 1, 4])
torch.Size([1, 5, 1])

如果我们想沿某个轴计算A元素的累积总和,⽐如axis=0(按⾏计算),我们可以调⽤cumsum函数。此函数不会沿任何轴降低输⼊张量的维度

print(A.cumsum(axis=0))
tensor([[ 0,  1,  2,  3],
        [ 4,  6,  8, 10],
        [12, 15, 18, 21],
        [24, 28, 32, 36],
        [40, 45, 50, 55]])

向量、矩阵的乘法

两个向量的点积是一个标量:

x = torch.tensor([5, 3, 9, 2])
y = torch.tensor([8, 12, 7, 4])
print(torch.dot(x, y))
tensor(147)

矩阵笔记一:深度学习基础 - 图15和(列)向量笔记一:深度学习基础 - 图16是(列)向量笔记一:深度学习基础 - 图17:

A = torch.arange(20).reshape(5, 4)
x = torch.tensor([5, 3, 9, 2])
# print(A)
# print(x)
print(torch.mv(A, x))
tensor([ 27, 103, 179, 255, 331])

在torch里调用mm()函数实现两个矩阵的乘法(注意:不是按元素相乘)

A = torch.arange(5, 25).reshape(5, 4)
B = torch.arange(8, 20).reshape(4, 3)
# print(A)
# print(B)
print(torch.mm(A, B))
tensor([[ 340,  366,  392],
        [ 540,  582,  624],
        [ 740,  798,  856],
        [ 940, 1014, 1088],
        [1140, 1230, 1320]])

范数

范数是将向量映射到标量的函数笔记一:深度学习基础 - 图18,可以看作是一种对距离的度量,常用的范数有:
笔记一:深度学习基础 - 图19范数:即欧几里得距离,是向量元素平⽅和的平⽅根,也即向量的模(长度)

笔记一:深度学习基础 - 图20

通过torch.norm()函数计算向量的范数,但是向量不可以是torch.int64类型的

x = torch.tensor([2.0, 6.4, 8.0])
print(torch.norm(x))
tensor(10.4384)

笔记一:深度学习基础 - 图21范数:向量元素的绝对值之和

笔记一:深度学习基础 - 图22

print(torch.abs(x).sum())
tensor(16.4000)

torch.norm()函数是重载的,当接受一个矩阵的时候,它计算该矩阵笔记一:深度学习基础 - 图23的弗罗贝尼乌斯范数:

笔记一:深度学习基础 - 图24

X = torch.arange(20, dtype=float).reshape(5, 4)
print(torch.norm(X))

(矩阵同样不可以是torch.int64类型的)

tensor(49.6991, dtype=torch.float64)

弗罗⻉尼乌斯范数满⾜向量范数的所有性质,它就像是矩阵形向量的笔记一:深度学习基础 - 图25范数

第四节:自动求导

对张量的导数

标量对标量的导数大家都很熟悉,现在把导数拓展到向量/矩阵领域

笔记一:深度学习基础 - 图26

  • 标量对标量的导数是标量
  • 向量对标量的导数是向量
  • 标量对向量的导数是向量
  • 向量对向量的导数是矩阵
  • 矩阵对标量的导数是矩阵
  • 标量对矩阵的导数是矩阵
  • 矩阵对向量的导数是三维张量
  • 向量对矩阵的导数是三维张量
  • 矩阵对矩阵的导数是四维张量

标量对向量的导数是向量(标量笔记一:深度学习基础 - 图27,向量笔记一:深度学习基础 - 图28):
笔记一:深度学习基础 - 图29
笔记一:深度学习基础 - 图30
向量对标量的导数是向量(向量笔记一:深度学习基础 - 图31,标量笔记一:深度学习基础 - 图32):
笔记一:深度学习基础 - 图33
向量对向量的导数是矩阵(向量笔记一:深度学习基础 - 图34,向量笔记一:深度学习基础 - 图35):

可以看作向量中的每一个元素(标量)先对向量求导,这些导数再组成向量
笔记一:深度学习基础 - 图36
笔记一:深度学习基础 - 图37
向量的链式法则
笔记一:深度学习基础 - 图38

反向传播

笔记一:深度学习基础 - 图39笔记一:深度学习基础 - 图40
笔记一:深度学习基础 - 图41
笔记一:深度学习基础 - 图42
笔记一:深度学习基础 - 图43
笔记一:深度学习基础 - 图44
笔记一:深度学习基础 - 图45

Pytorch的自动求导机制

笔记一:深度学习基础 - 图46关于列向量笔记一:深度学习基础 - 图47求导为例:

x = torch.arange(4.0, requires_grad=True).reshape(2, 2)
# 如果创建tensor时未指定requires_grad=True
# 也可以显式的调用方法x.requires_grad_(True) 
print(x.grad)
None

创建函数笔记一:深度学习基础 - 图48

y = 2 * torch.dot(x, x)
print(y)

调用反向传播函数计算梯度:

tensor(28., grad_fn=<MulBackward0>)
y.backward()
print(x.grad)
tensor([ 0.,  4.,  8., 12.])

验证:笔记一:深度学习基础 - 图49

print(x.grad == 4 * x)
tensor([True, True, True, True])

在计算另一个函数对x的梯度前需要先把原本存的梯度清零

# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_() # 梯度清零
y = x.sum()
y.backward()
print(x.grad)
tensor([1., 1., 1., 1.])