此文档是一篇有关PyTorch的学习笔记

需要有一定的深度学习基础才能无障碍阅读本篇笔记

你可以通过右下角的齿轮切换夜间模式和阅读模式


Overview

这篇笔记是本人学完《深度学习入门》后,在bilibili看PyTorch入门教程时所做的笔记。

Something (Img, voice, etc) -> Prediction

  1. Prediction: 从信息到抽象,比如看到2知道这个是数值2,看到猫知道这个是概念猫

Informantion -> Infer

  1. Infer: 基于信息做推测,比如饿了根据自己的钱、想吃的东西推测出要买啥吃

线性模型(Linear Model)

现有输入xx,输出yy,其数据集(Dataset)表示为(x,y)(x,y)构成了分布D(x,y)D(x,y)

大数定律:在试验不变的条件下,重复试验多次,随机事件的频率近似于它的概率。偶然中包含着某种必然

  • 很多时候,数据集量并不能满足大数定律,于是(x,y)(x,y)很多时候不能精确地拟合到D(x,y)D(x,y),因此神经网络会有准确率的差异
  • 在使用训练集进行训练时,可能会出现过拟合的现象。原因是因为会学习到训练集中一些非必要的特征

线性模型:y^=xω+b\hat{y}=x * \omega + b

  • 对于损失函数(Loss),是对于单个样本(x,y)(x,y)的。对于整体(Training Set)的Error,即代价函数(Cost Function),需要将每一个样本的Loss求和,再除以样本数NN,得到平均后的数据

  • 均方差误差(Mean Square Error, MSE): $$\mathrm{cost} = \frac{1}{N}\sum^{N}{n=1}(\hat{y{n}}-y_{n})$$

  • 对于全连接层(Affine),实际上每一层为H=ω×X+β\vec{H}=\vec{\omega}\times\vec{X}+\vec{\beta},实际上也是线性模型

在只有一个样本时,我们通常将 xx 视为一个列向量(但在深度学习框架如 PyTorch/TensorFlow 中,默认通常是行向量)。

  • 单样本形式:

    如果 xx 的形状是 (1,n)(1, n)WW 的形状是 (n,m)(n, m),那么:

    y=xW    (1,n)×(n,m)=(1,m)y = x \cdot W \implies (1, n) \times (n, m) = (1, m)

  • 多样本(Batch)形式:

    当你把 xx 的形状变为 (batch_size,n)(batch\_size, n) 时,你实际上是把 batch_sizebatch\_size 个行向量垂直堆叠在了一起。此时,同一个权重矩阵 WW依次作用于每一行

    Y=XW    (batch_size,n)×(n,m)=(batch_size,m)Y = X \cdot W \implies (batch\_size, n) \times (n, m) = (batch\_size, m)

梯度下降法(Gradient Descent)

优化问题:寻找一组合理的权重ω\vec{\omega}使cost  functioncost\;function能达到最小值

更新权重的方法:梯度下降法

ω=ωηCOSTω\omega = \omega - \eta\frac{\partial COST}{\partial \omega}

  • 每次迭代都在寻找最优,这实际上是一种贪心算法
  • 问题1:只能找到局部最优,可能无法找到全局最优
  • 问题2:可能存在鞍点。在鞍点时,其梯度g=0g=0,使梯度无法更新

一种更有效的方法:随机梯度下降法(SGD),不再使用代价函数作为更新的依据,而使用单个样本的损失函数去更新。现在更常用Mini-Batch的损失函数(Loss Function)去更新

反向传播(Back Propagation)

矩阵求导相关知识:Matrix Cookbook

线性层无论中间有多少层,最终都可以统一成

y^=W2(W1X+b1)+b2=W2W1X+(W2b1+b2)=WX+b\begin{aligned} \hat{y} &= W_{2}(W_{1}\cdot X+b_{1}) +b_{2}\\ &= W_{2}\cdot W_{1}\cdot X + (W_{2}b_{1}+b_{2}) \\ &=W\cdot X+b \\ \end{aligned}

为了提高模型的复杂程度,不让这种现象发生,需要在每一层的输出加一个非线性的变换函数,叫做激活函数,这样线性层就不会“塌陷”成一层了

链式法则:Lω=Lzzω\frac{\partial L}{\partial \omega}=\frac{\partial L}{\partial z}\cdot \frac{\partial z}{\partial \omega}

PyTorch中,Tensor是一种重要的数据类型,可以存标量、向量、矩阵等更高阶的数据

  • Tensor 是一种类,包含了data和grad两种重要的成员
  • Tensor 的grad不会自动清零
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import torch



x_data = [1.0,2.0,3.0]

y_data = [2.0,4.0,6.0]



w = torch.tensor([1.0])

w.requires_grad = True



def forward(x):

    return x * w

    #注意w是一个tensor,x被自动转换成一个tensor,进行数乘



def loss(x,y):

    y_pred = forward(x)

    return (y_pred - y) ** 2

    #y_pred 是tensor,因此reutrn回去的loss也是tensor,有backward方法



for epoch in range(100):

    for x, y in zip(x_data, y_data):

        l = loss(x,y)

        l.backward() #backward存到w中,这里直接就把loss对w的导数算出来了

        w.data = w.data - 0.01 * w.grad.data



        w.grad.data.zero_()



    print("progress: ",epoch ,l.item())



print(w.item())

用PyTorch实现线性回归

使用PyTorch提供的工具更方便的完成线性模型搭建,步骤如下:

  1. Prepare dataset
  2. Design moduel using class : inherit from nn.Module
  3. Construct loss function and optimizer by using PyTorch API
  4. Set traning cycle : forwad, backward and update

可执行对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Model:



    def __init__(self):

        pass



    def __call__(self, *args, **kwds):

        print("Hello" + str(args[0]))

        print(kwds)



model = Model()

model(1,2,3,x=1,y=2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import torch



x_data = torch.tensor([[1],[2],[3]])

y_data = torch.tensor([[2],[4],[6]])



class LinearModel(torch.nn.Module):

    def __init__(self):

        super().__init__()

        self.linear = torch.nn.Linear(in_features = 1,out_features = 1)



    def forward(self, x):

        y_pred = self.linear(x)

        return y_pred



model = LinearModel()



criterion = torch.nn.MSELoss()

optimizer = torch.optim.SGD(model.parameters(), lr = 0.01)



for epoch in range(100):

    y_pred = model(x_data)

    loss = criterion(y_pred,y_data)

    print(epoch, loss)



    optimizer.zero_grad()

    loss.backward() #算梯度

    optimizer.step() #用梯度和nn.opti中设置的更新方法进行更新

逻辑回归(Logistic Regression)

逻辑回归就是二分类问题,一般计算y^=1\hat{y}=1的概率

Logistic  function:Logistic\; function:

σ(x)=11+ex\sigma(x)=\frac{1}{1+e^{-x}}

此时损失函数计算的是正确和预测分布的差异而非几何空间上的差异。故不宜再使用MSE等方法

计算分布差异的方法:KL散度、交叉熵(BCE)等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import torch



x_data = torch.tensor([[1.0],[2.0],[3.0]])

y_data = torch.tensor([[0.0],[0.0],[1.0]])



class LogisticRegressionModel(torch.nn.Module):

    def __init__(self):

        super().__init__()

        self.linear = torch.nn.Linear(1,1)

        self.sig = torch.nn.Sigmoid()



    def forward(self,x):

        y_pred = self.sig(self.linear(x))

        return y_pred

model = LogisticRegressionModel()



criterion = torch.nn.BCELoss(reduction='sum')

optimizer = torch.optim.SGD(model.parameters(),lr=0.01)



for epoch in range(100):

    y_pred = model(x_data)

    loss = criterion(y_pred,y_data)

    print(epoch,loss.item()) #.item()方法可以使单元素tensor转换成python标量



    optimizer.zero_grad() #清空梯度

    loss.backward() #从loss开始反向传播并计算梯度

    optimizer.step() #根据梯度等数据更新参数



x_test = torch.tensor([[0.5],[3.5]])

print(model(x_test).tolist()) #.tolist()方法使tensor变换成 python list

多维输入(Multiple Dimension Input)

element-wise : 指按逐个元素计算

Mini-Batch的线性多维输入模型

[z(1)z(N)]=[x1(1)x8(1)x1(N)x8(N)][w1w8]+[bb] \begin{bmatrix} z^{(1)} \\ \vdots \\ z^{(N)} \end{bmatrix} = \begin{bmatrix} x_1^{(1)} & \cdots & x_8^{(1)} \\ \vdots & \ddots & \vdots \\ x_1^{(N)} & \cdots & x_8^{(N)} \end{bmatrix} \begin{bmatrix} w_1 \\ \vdots \\ w_8 \end{bmatrix} + \begin{bmatrix} b \\ \vdots \\ b \end{bmatrix}

可以通过矩阵运算,一下子将z(1)z(N)z^{(1)}\dots z^{(N)}全部算出来,提高运算效率

神经网络模型实际上是希望找到一种非线性函数进行变换

矩阵函数实际上是一种线性变换

yM×1=AM×NxN×1\vec{y_{M\times 1}}=A_{M\times N}\cdot\vec{x}_{N\times 1}

矩阵A将N维向量x\vec{x}变化到M维向量y\vec{y},引入非线性变换函数如sigmoid(x)sigmoid(x)后,整体上就变成非线性变换

层数越深,模型越复杂,学习能力越强。但学习能力太强,会学习到数据集中的噪声,会出现过拟合等现象

加载数据集(Dataset and DataLoader)

Dataset : 构造数据集,要求构造index和len方法
DataLoader : 用来构建Mini-Batch

在每次循环中只用一个样本 -> 得到比较好的随机性(克服鞍点问题) ->使用Batch -> 提升计算速度

Epoch : One forard pass and one backward pass of all the training examples
Batch-Size : The number of training examples in one forward backward pass
Iteration : Number of passes, each pass using [batch size] number of examples

As an example :10000个样本,Batchsize设为1000,那么1个epoch中要跑10次才能遍历完整个样本,所以Iteration就是10

torch.utils.dataDataset 是抽象类,不能被实例化,只能作为父类被继承,在子类中,需要实现__getitem__()__len__()两个方法
Dataloader则需要被实例化,用作处理shufflebatch_size的工具

1
2
3
4
for images, labels in trainLoader:
# 这里的 images 是一个形状为 [batchSize, 1, 28, 28] 的张量
# 这里的 labels 是一个长度为 batchSize 的向量,包含 0-9 的数字
...

如你单独观察其中的一个批次

组成部分 数据类型 (Type) 维度 (Shape) 含义
Images torch.Tensor [batchSize, 1, 28, 28] 一个批次的图片:[数量, 通道, 高, 宽]
Labels torch.Tensor [batchSize] 对应这组图片真实的数字标签

多分类问题(Softmax Classifier)

分布的性质要求Pi>0  and  Pi=1P_{i}>0\; and \; \sum P_{i} = 1

We hope the outputs is competitive and actually we hope the neural network outputs a distribution

使用softmax(x)softmax(x)函数可以使神经网络的输出满足分布的性质要求

使用softmaxsoftmax函数计算y=iy=i的输出时,前面层传来ZiZ_{i},进入softmaxsoftmax层计算结果

P(y=i)=eZij=0K1eZj,i{0,1,,K1}P(y=i)=\frac{e^{Z_{i}}}{\sum^{K-1}_{j=0}e^{Z_{j}}},i\in\{0,1,\dots,K-1\}

通过torchvision.transfromstransfrom.compose的功能,将PIL图像变化为PyTorch的Tensor

PIL  ImageZ28×28,pixel{0,1,,255}PyTorchTensorR1×28×28,pixel[0,1]\begin{aligned} &\rm{PIL\;Image} \\ &\mathbb{Z}^{28 \times 28}, \text{pixel} \in \{0, 1, \dots, 255\} \end{aligned} \Rightarrow \quad \begin{aligned} &\rm{PyTorch Tensor} \\ &\mathbb{R}^{1 \times 28 \times 28}, \text{pixel} \in [0, 1] \end{aligned}

Z28×28\mathbb{Z}^{28 \times 28}表示整数,R1×28×28\mathbb{R}^{1 \times 28 \times 28}表示实数

1
2
3
4
5
6
7
transfrom = transforms.Compose([

    transforms.ToTensor(),#转变为张量,且转变为0-1之间的实数。且改变维度顺序为CHW

    transforms.Normalize(0.1307,0.3081)

])

transforms.ToTensor():格式转换

这一步主要做了三件事:

  • 改变维度顺序:普通的图片存储格式通常是 [高度, 宽度, 通道数] (H, W, C),但 PyTorch 神经网络要求的输入格式是 [通道数, 高度, 宽度] (C, H, W)。它会自动帮你完成这个置换。

  • 归一化数值:它将图片像素值从原本的 0 到 255 压缩到 0.0 到 1.0 之间。

  • 数据类型转换:把图片转换为 torch.FloatTensor(浮点型张量)。

transforms.Normalize(0.1307, 0.3081):标准化

这是基于正态分布的标准化操作,计算公式如下:

output=inputμσ\mathrm{output} = \frac{\mathrm{input} - \mu}{\sigma}

  • 0.1307 是均值 (μ\mu),0.3081 是标准差 (σ\sigma)。

  • 为什么要这样做?

    数据标准化后,输入的分布会变成均值为 0、标准差为 1 左右。这能有效防止梯度消失或梯度爆炸,让模型训练得更快、更稳定。

  • 为什么是这两个数字?

    这两个值是 MNIST 数据集(手写数字)的官方统计结果。如果你处理的是自己的图片,通常需要根据自己的数据集计算出对应的均值和标准差。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import torch

from torchvision import transforms

from torchvision import datasets

from torch.utils.data import DataLoader

import torch.nn.functional as F

import torch.optim as optim



batchSize = 64

transform = transforms.Compose([

    transforms.ToTensor(),#转变为张量,且转变为0-1之间的实数。且改变维度顺序为CHW

    transforms.Normalize(0.1307,0.3081)

])



#Dataset,训练集有60000条,测试集有10000条,60000/64 == 937.5

trainDataset = datasets.MNIST(root='data/MNIST/',

                              download=True,

                              train=True,

                              transform=transform)



testDataset = datasets.MNIST(root='data/MNIST/',

                             download=True,

                             train=False,

                             transform=transform)



#DataLodaer

trainLoader = DataLoader(dataset=trainDataset,

                         shuffle=True,

                         batch_size=batchSize)



testLoader = DataLoader(dataset=testDataset,

                         shuffle=True,

                         batch_size=batchSize)



class newModel(torch.nn.Module):

    def __init__(self):

        super().__init__()

        self.l1 = torch.nn.Linear(784,512)

        self.l2 = torch.nn.Linear(512,256)

        self.l3 = torch.nn.Linear(256,128)

        self.l4 = torch.nn.Linear(128,64)

        self.l5 = torch.nn.Linear(64,10)



    def forward(self, x):

        x = x.reshape((-1,784))

        x = F.relu(self.l1(x))

        x = F.relu(self.l2(x))

        x = F.relu(self.l3(x))

        x = F.relu(self.l4(x))



        return self.l5(x) #注意l5没有接relu

model = newModel()



criterion = torch.nn.CrossEntropyLoss()

optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)



def train(epoch):

    runningLoss = 0.0

    for batchIndex, (xData, yData) in enumerate(trainLoader):

        #forward

        yPred = model(xData)

        loss = criterion(yPred, yData)

        #backward

        optimizer.zero_grad()

        loss.backward()

        #update

        optimizer.step()



        runningLoss += loss.item()

        if batchIndex % 300 == 299:

            print(f'|epoch:{epoch+1} batchIndex:{batchIndex+1}|loss:{runningLoss/300}')

            runningLoss = 0.0



def test():

    correct = 0

    total = 0

    with torch.no_grad():

        for (xData, yData) in testLoader:

            yPred = model(xData)

            _, predicted = torch.max(yPred.data,dim=1)

            total += yData.shape[0]

            correct += (predicted == yData).sum().item()

    print(f'accuracy:{100*correct/total}')



if __name__ == '__main__':



    for epoch in range(15):

        train(epoch=epoch)

        test()



    torch.save(model.state_dict(),'.\weights\mnist_model.pth')

    print("模型权重已保存!")

卷积神经网络(CNN)

基础卷积神经网络(Basic CNN)

在神经网络中,CNN层又被叫做特征提取器,Affine层又被叫做分类器

输出特征图计算公式:

OH=H+2PFHS+1OH=\frac{H+2P-FH}{S}+1

OH:输出特征图的高H:输入特征图的高P:填充FH:卷积核的高S:步幅\begin{aligned} \text{OH} &\text{:} \text{输出特征图的高} \\ \text{H} &\text{:} \text{输入特征图的高} \\ \text{P} &\text{:} \text{填充} \\ \text{FH} &\text{:} \text{卷积核的高} \\ \text{S} &\text{:} \text{步幅} \\ \end{aligned}

OW=W+2PFWS+1OW=\frac{W+2P-FW}{S}+1

OW:输出特征图的宽W:输入特征图的宽P:填充FW:卷积核的宽S:步幅\begin{aligned} \text{OW} &\text{:} \text{输出特征图的宽} \\ \text{W} &\text{:} \text{输入特征图的宽} \\ \text{P} &\text{:} \text{填充} \\ \text{FW} &\text{:} \text{卷积核的宽} \\ \text{S} &\text{:} \text{步幅} \\ \end{aligned}

input.shape == (N,3,H,W),the 3 from (R,G,B)

多通道的卷积运算:

[3465724682167849746237541][123456789]=[211295262259282214251253169]\begin{bmatrix} 3 & 4 & 6 & 5 & 7 \\ 2 & 4 & 6 & 8 & 2 \\ 1 & 6 & 7 & 8 & 4 \\ 9 & 7 & 4 & 6 & 2 \\ 3 & 7 & 5 & 4 & 1 \end{bmatrix} \odot \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} = \begin{bmatrix} 211 & 295 & 262 \\ 259 & 282 & 214 \\ 251 & 253 & 169 \end{bmatrix}

[3465724682167849746237541][987654321]=[179245268201278256239287241]\begin{bmatrix} 3 & 4 & 6 & 5 & 7 \\ 2 & 4 & 6 & 8 & 2 \\ 1 & 6 & 7 & 8 & 4 \\ 9 & 7 & 4 & 6 & 2 \\ 3 & 7 & 5 & 4 & 1 \end{bmatrix} \odot \begin{bmatrix} 9 & 8 & 7 \\ 6 & 5 & 4 \\ 3 & 2 & 1 \end{bmatrix} = \begin{bmatrix} 179 & 245 & 268 \\ 201 & 278 & 256 \\ 239 & 287 & 241 \end{bmatrix}

[3465724682167849746237541][147258369]=[235297248253294204255259169]\begin{bmatrix} 3 & 4 & 6 & 5 & 7 \\ 2 & 4 & 6 & 8 & 2 \\ 1 & 6 & 7 & 8 & 4 \\ 9 & 7 & 4 & 6 & 2 \\ 3 & 7 & 5 & 4 & 1 \end{bmatrix} \odot \begin{bmatrix} 1 & 4 & 7 \\ 2 & 5 & 8 \\ 3 & 6 & 9 \end{bmatrix} = \begin{bmatrix} 235 & 297 & 248 \\ 253 & 294 & 204 \\ 255 & 259 & 169 \end{bmatrix}

注意其中卷积核为(3,3,3)的形状,三个通道上的卷积核的权重都不一样,一共有3×9=273\times9=27个权重。算出来的结果需要按位置相加。故输出的形状为(1,3,3)

[211295262259282214251253169]+[179245268201278256239287241]+[235297248253294204255259169]=[625837778713854674745799579]\begin{bmatrix} 211 & 295 & 262 \\ 259 & 282 & 214 \\ 251 & 253 & 169 \end{bmatrix} + \begin{bmatrix} 179 & 245 & 268 \\ 201 & 278 & 256 \\ 239 & 287 & 241 \end{bmatrix} + \begin{bmatrix} 235 & 297 & 248 \\ 253 & 294 & 204 \\ 255 & 259 & 169 \end{bmatrix} = \begin{bmatrix} 625 & 837 & 778 \\ 713 & 854 & 674 \\ 745 & 799 & 579 \end{bmatrix}

torch.nn.Conv2d必须要给定的参数有in_channels,out_channels,kernel_sizein_channels能定义kernel的通道数,out_channels能定义kernel的个数,kernel_size能定义kernel的形状,kernel_size可以是int(3),也可以是tuple(5,3)

torch.nn.Conv2d要求输入必须是四维,因此不能接受(C,H,W)的输入,必须弄成(N,C,H,W)的形式,如果想测试,可以弄成(1,C,H,W)

torch.nn.Conv2dbias值,其作用在每一个通道上,每个通道不一样

torch.nn.MaxPool2d中的stride默认等于其kernel_size

一般来说,conv->ReLU->pooling

使用显卡加速计算

Move Model to GPU : Conver parameters and buffers of all modules to CUDA Tensor.

1
2
3
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

model.to(device)

Move Input and Output Tensors to GPU : Send the inputs and targets at every step to the GPU.

1
2
3
for batch_idx, (x_data, y_data) in enumerate(train_loader):
x_data,y_data = x_data.to(device),y_data.to(device)
...

train(epoch)test()中都需要将data迁移到cuda上

注意x_data这里的.to()并不是in-place操作,所以在前面需要加上=。当调用 x_data.to(device) 时,系统实际上是在目标设备(如 GPU)上创建了一个全新的副本,而原来的 x_data 依然静静地躺在 CPU 内存里。如果不使用变量去接收这个新副本,它就会被立即丢弃

并注意datamodel要迁移到同一个设备上

Advanced CNN

减少代码冗余:函数/类,把相似的地方封装成函数/类

GoogleNet

Concatenate: 将tensor(tensor.shape == (batch_size,C,H,W))沿通道方向连接,要求每个张量的H,WH,W要相同

1×11\times1的特殊卷积

  1. 输入(1,C,H,W)(1,C,H,W),经过(N,C,1,1)(N,C,1,1)的卷积核(注意每个通道上的1x1卷积核的值可能不一样),变成(1,N,H,W)(1,N,H,W)。若拆开简化来看,输入(C,H,W)(C,H,W),经过(C,1,1)(C,1,1)的卷积核,输出(1,H,W)(1,H,W)的特征图,其中每一个像素都是在通道方向上按位置加权相加,只包含了一个像素在所有通道上的信息,即实现了信息融合的功能

  2. 通过增加一层1x1卷积, 可以降低计算量。通道多的时候降低了卷积核大小,卷积核大的时候降低了通道数

192@28×285x5 Convolution32@28×28\begin{array}{c} \mathbf{192@28\times28} \\ \downarrow \\ \boxed{\text{5x5 Convolution}} \\ \downarrow \\ \mathbf{32@28\times28} \end{array}

Operations=52×282×192×32=120,422,400\text{Operations} = 5^{2} \times 28^{2} \times 192 \times 32 = 120,422,400

192@28×281x1 Convolution16@28×285x5 Convolution32@28×28\begin{array}{c} \mathbf{192@28\times28} \\ \downarrow \\ \boxed{\text{1x1 Convolution}} \\ \downarrow \\ \mathbf{16@28\times28} \\ \downarrow \\ \boxed{\text{5x5 Convolution}} \\ \downarrow \\ \mathbf{32@28\times28} \end{array}

Operations=(12×282×192×16)+(52×282×16×32)=12,443,648\text{Operations} = (1^{2} \times 28^{2} \times 192 \times 16) + (5^{2} \times 28^{2} \times 16 \times 32) = 12,443,648

这里计算量降低了近10倍,大大缩短了计算时间

Inception的构造

Inception的输出通道是88,长宽不变

Input{Branch 1: AvgPool241×1 Conv24Branch 2: 1×1 Conv16Branch 3: 1×1 Conv165×5 Conv24Branch 4: 1×1 Conv163×3 Conv243×3 Conv24Concatenate\text{Input} \longrightarrow \begin{cases} \text{Branch 1: } \text{AvgPool} \xrightarrow{24} 1 \times 1 \text{ Conv} \xrightarrow{24}\\ \text{Branch 2: } 1 \times 1 \text{ Conv} \xrightarrow{16} \\ \text{Branch 3: } 1 \times 1 \text{ Conv} \xrightarrow{16} 5 \times 5 \text{ Conv} \xrightarrow{24} \\ \text{Branch 4: } 1 \times 1 \text{ Conv} \xrightarrow{16} 3 \times 3 \text{ Conv} \xrightarrow{24} 3 \times 3 \text{ Conv} \xrightarrow{24} \end{cases} \longrightarrow \text{Concatenate}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class InceptionA(torch.nn.Module):

    def __init__(self,in_channels):

        super().__init__()



        self.branch_1x1 = torch.nn.Conv2d(in_channels=in_channels,out_channels=16,kernel_size=1)



        self.branch_5x5_1 = torch.nn.Conv2d(in_channels=in_channels,out_channels=16,kernel_size=1)

        self.branch_5x5_2 = torch.nn.Conv2d(in_channels=16,out_channels=24,kernel_size=5,padding=2)

        self.branch_3x3_1 = torch.nn.Conv2d(in_channels=in_channels,out_channels=16,kernel_size=1)

        self.branch_3x3_2 = torch.nn.Conv2d(in_channels=16,out_channels=24,kernel_size=3,padding=1)

        self.branch_3x3_3 = torch.nn.Conv2d(in_channels=24,out_channels=24,kernel_size=3,padding=1)



        self.branch_pool_1 = torch.nn.AvgPool2d(kernel_size=3,stride=1,padding=1)

        self.branch_pool_2 = torch.nn.Conv2d(in_channels=in_channels,out_channels=24,kernel_size=1)




    def forward(self, x):



        branch_1x1 = self.branch_1x1(x)



        branch_5x5 = self.branch_5x5_1(x)

        branch_5x5 = self.branch_5x5_2(branch_5x5)



        branch_3x3 = self.branch_3x3_1(x)

        branch_3x3 = self.branch_3x3_2(branch_3x3)

        branch_3x3 = self.branch_3x3_3(branch_3x3)

        branch_pool = self.branch_pool_1(x)

        branch_pool = self.branch_pool_2(branch_pool)



        outputs = [branch_1x1,branch_5x5,branch_3x3,branch_pool]

        return torch.cat(outputs,dim = 1)




class NewModel(torch.nn.Module):

    def __init__(self):

        super().__init__()

        self.conv1 = torch.nn.Conv2d(1,10,kernel_size=5)

        self.conv2 = torch.nn.Conv2d(88,20,kernel_size=5)



        self.incep1 = InceptionA(in_channels=10)

        self.incep2 = InceptionA(in_channels=20)



        self.max_pooling = torch.nn.MaxPool2d(kernel_size=2)

        self.l1 = torch.nn.Linear(1408,10) #适用于MNIST数据集

ResNet

梯度消失:根据链式法则,越靠近输入层,其梯度相乘项越多,越可能为0。因此靠近输入层的权重可能无法得到充分的训练

ResNet的核心思想

H(x)=F(x)+xH(x)=F(x)+x

注意: 输入xx与输出F(x)F(x)的维度(C,H,W)(C,H,W)的大小必须完全一致

反向传播的梯度

H(x)x=[F(x)+x]x=F(x)x+1\begin{aligned} \frac{\partial H(x)}{\partial x} &= \frac{\partial[F(x)+x]}{\partial x} \\ &=\frac{\partial F(x)}{\partial x}+1 \end{aligned}

因此即使F(x)x=0\frac{\partial F(x)}{\partial x}=0 ,其往前传的梯度仍然为0,很好地解决了梯度消失的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ResidualBlock(torch.nn.Module):

    def __init__(self, channels):

        super().__init__()

        self.channels = channels

        self.conv1 = torch.nn.Conv2d(channels,channels,kernel_size=3,padding=1)

        self.conv2 = torch.nn.Conv2d(channels,channels,kernel_size=3,padding=1)



    def forward(self, x):

        y = F.relu(self.conv1(x))

        y = self.conv2(y)



        return F.relu(x+y)

循环神经网络(RNN)

基础循环神经网络(Basic RNN)

DNN(Deep Neural Networks):一般指全连接神经网络

RNN处理的对象:有序列关系的输入数据,天气、股票、自然语言

xtRInputSize  ht1RHideenSizeWihxt+bihWhhxt+bhhADDtanhhtRHideenSize\begin{array}{c} \boxed{x_{t} \in \mathbb{R}^{\rm{InputSize}}}\; \boxed{h_{t-1}\in\mathbb{R}^{\rm{HideenSize}}}\\ \downarrow \qquad \qquad \qquad \downarrow\\ \boxed{W_{ih}x_{t}+b_{ih}} \qquad \boxed{W_{hh}x_{t}+b_{hh}} \\ \downarrow \qquad \qquad \qquad \downarrow\\ \boxed{\qquad \qquad ADD\qquad \qquad}\\ \downarrow \\ \boxed{tanh} \\ \downarrow \\ \qquad\qquad\quad h_t \in \mathbb{R}^{\mathrm{HideenSize}} \end{array}

Whixi+Whhhh=[WhiWhh][xihh]W_{hi}x_{i} + W_{hh}h_{h} =\begin{bmatrix}W_{hi}&W_{hh}\end{bmatrix}\begin{bmatrix}x_{i} \\ h_{h}\end{bmatrix}

使用torch.nn.RNNCell

1
cell = torch.nn.RNNCell(input_size = input_size, hidden_size = hidden_size)

注意参数是hidden_size而不是output_size,这里的参数是两个输入的维度。并且输出的维度实际上和hidden_size是一样的

1
hidden = cell(input,hidden)

给定了input_sizehidden_size就可以完全定义RNN Cell

输入:x1,h0,返回:h1。x1.shape = (batch,input_size),h0.shape = (batch,hidden_size),h1.shape = (batch,hidden_size)

Suppose we have sequence with below properties:

  • batch_size = 1
  • seq_len = 3
  • input_size = 4
  • hidden_size = 2

So the shape of inputs and outputs of RNN cell are :

  • input.shape = (batch_size, input_size)
  • output.shape = (batch_size, hidden_size)

The squence can be warped in one Tensor with shape:

  • dataset.shape = (seq_len, batch_size, input_size)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import torch



batch_size = 1

seq_len = 3

input_size = 4

hidden_size = 2



cell = torch.nn.RNNCell(input_size,hidden_size)



dataset = torch.randn(seq_len,batch_size,input_size)

hidden = torch.zeros(batch_size,hidden_size)



for index, input in enumerate(dataset):

    print('='*20,index,'='*20)

    print('Input Size: ',input.shape)



    hidden = cell(input,hidden)



    print('Output Size: ',hidden.shape)

    print(hidden)

在这个for循环中:

  1. 第一次:h1 = f(x1) + g(h0)
  2. 第二次:h2 = f(x2) + g(h1)
  3. 第三次:h3 = f(x3) + g(h2)
  4. 退出循环,此时hidden = h3,同时hidden.shape = (batch_size, hidden_size)

注意f(x)f(x)g(x)g(x)都是线性变换层

使用torch.nn.RNN

1
2
3
4
5
6
7
cell = torch.nn.RNN(input_size = input_size,

                    hidden_size = hidden_size,

                    num_layers = num_layers)
                   
out, hidden = cell(inputs, hidden)
  • 调用cell的输入:inputsx1,x2,x3,...,xN\vec{x_1},\vec{x_2},\vec{x_3},...,\vec{x_N}hiddenh0h_0
  • 调用cell的输出:outh1,h2,h3,...,hN\vec{h_1},\vec{h_2},\vec{h_3},...,\vec{h_N}hiddenhN\vec{h_N}

输入的形状:

  • inputs.shape = (seq_size, batch_size, input_size)
  • hidden.shape = (num_layers, batch_size, hidden_size)

输出的形状:

  • out.shape = (seq_size, batch_size, hidden_size)
  • hidden.shape = (num_layers, batch_size, hidden_size)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import torch



batch_size = 1

seq_len = 3

input_size = 4

hidden_size = 2

num_layers = 1



cell = torch.nn.RNN(input_size = input_size,

                    hidden_size = hidden_size,

                    num_layers = num_layers)



inputs = torch.randn(seq_len,batch_size,input_size)

hidden = torch.randn(num_layers,batch_size,hidden_size)



out, hidden = cell(inputs, hidden)



print('Output: ',out)

print('Hideen: ',hidden)

下一步该干什么?

  1. 理论《深度学习》花书
  2. 阅读PyTorch文档(通读一遍)
  3. 复现经典工作:代码下载->run(x),读代码->复现(自己写)(√)
  4. 扩充视野:读论文积累、解决数学上和代码上的盲点,把别人的工作变为自己的知识积累