动手学深度学习笔记-基础篇

Posted by BUAADreamer on 2022-03-19
Words 5.2k and Reading Time 23 Minutes
Viewed Times

1.前言·

日常生活中的机器学习·

关键组件·

数据转换数据的模型目标函数,调整模型参数从而优化目标函数的算法

各种机器学习问题·

监督学习(supervised learning)·

  • 回归(regression)
  • 分类(classification)
    • 层次分类
  • 标记问题
    • 二元分类
    • 多元分类 ==> 多标签分类(multi-label classification)
  • 搜索
  • 推荐系统(recommender system)
  • 序列学习
    • 标记与解析
    • 自动语音识别
    • 文本到语音
    • 机器翻译

无监督学习(unsupervised learning)·

  • 聚类(clustering)
  • 主成分分析(principal component analysis)
  • 因果关系(causality)和概率图模型(probabilistic graphical models)
  • 生成对抗性网络(generative adversarial networks)

与环境互动·

前面两者都是离线学习(offline learning),即不需要和环境交互,先训练好再启动

强化学习(reinforcement learning)·

  • 深度Q网络(Q-network)
  • AlphaGo
  • 在强化学习问题中,agent在一系列的时间步骤上与环境交互。 在每个特定时间点,agent从环境接收一些观察(observation),并且必须选择一个动作(action),然后通过某种机制(有时称为执行器)将其传输回环境,最后agent从环境中获得奖励(reward)。 此后新一轮循环开始,agent接收后续观察,并选择后续操作,依此类推。

起源·

深度学习之路·

成功案例·

特点·

2.预备知识·

2.1数据操作·

张量tensor,pytorchtensorflow 中是 Tensor,其实就是多维数组

入门·

1
2
3
4
5
6
7
8
9
10
import torch
x=torch.arange(12)
x.shape
x.numel #将多维数组展平成一维数组的长度,即张量的元素个数
X=x.reshape(3,4) #改变张量的形状但不改元素数量和元素值
X=x.reshape(-1,4) #使用-1自动计算对应位置的大小
X=torch.ones((2,3))
X=torch.zeros(10)
X=torch.randn(3,4) #shape为(3,4)的一个随机数张量,元素分布遵循N(0,1)
X=torch.tensor([1,2,3]) #根据列表建立

运算符·

1
2
3
4
5
6
7
8
9
10
11
12
13
#按元素运算
x=torch.tensor([1,2,3])
y=torch.tensor([2,3,4])
x+y,x-y,x*y,x/y,x**y #张量每个对应位置元素计算得到相同形状的张量
torch.exp(x) #求幂
#连结张量
X=torch.arange(6,dtype=torch.float32).reshape(2,3)
Y=torch.tensor([[1,2,3],[2,3,4]])
torch.cat((X,Y),dim=0),torch.cat((X,Y),dim=1)
#dim=0 shape的第一个元素,即行上进行cat,会影响shape的第一个元素的长度
#dim=1 shape的第二个元素,即列上进行cat,会影响shape的第二个元素的长度
X==Y #产生一个X.shape的布尔数组
X.sum(dim=None, keepdim=False, dtype=None) #默认求所有元素的和 dim设为0和1解释同上

广播机制·

先复制元素扩展,再对同样形状的数组进行按元素操作

1
2
3
4
5
6
a=torch.arange(3).reshape(3,1)
b=torch.arange(2).reshape(1,2)
a+b
#先将a复制扩充成[[0,0],[1,1],[2,2]]
#再将b复制扩充成[[0,1],[0,1],[0,1]]
#再将扩充后的数组相加得到[[0,1],[1,2],[2,3]]

索引和切片·

1
2
3
X[-1],x[1:3] #此处和python索引切片一致
X[1,2]=3
X[0:2,:]=1

节省内存·

1
2
3
4
5
6
before = id(Y)
Y = Y + X
id(Y) == before #False 重新为上面的计算左侧的Y分配了空间
before = id(Y)
Y[:]=Y+X
id(Y) == before #True 原址改动

转换为其他python对象·

1
2
3
4
X=torch.tensor([1,2,3])
A = X.numpy()
B = torch.tensor(A)
B = torch.from_numpy(A)

2.2数据预处理·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pandas基本数据处理
import os
os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', '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')
import pandas as pd
df=pd.read_csv(data_file)
inputs,outputs=data.iloc[:,0:2],data.iloc[:,2]
inputs = inputs.fillna(inputs.mean()) #填补残缺值
inputs = pd.get_dummies(inputs, dummy_na=True) #将离散值转换为数值类型
X, y = torch.tensor(inputs.values), torch.tensor(outputs.values) #转换为张量格式
def drop_col(df):
num = df.isna().sum() #获得缺失值统计信息
num_dict = num.to_dict() #转为字典
max_key =max(num_dict,key=num_dict.get) #取字典中最大值的键
del df[max_key] #删除缺失值最多的列
return df

2.3线性代数·

标量:单个数·

x,y,z

向量:标量组成的列表·

x,y,z

矩阵:标量组成的二维数组·

A,B,C

1
2
A.T
A==A.T #看是否是对称矩阵

张量:标量组成的多维数组·

1
2
3
4
5
6
X=torch.range(24).reshape(3,3,4) #可认为是三颜色通道,尺寸为3*4的图片数据
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = A.clone()
A*B #按元素乘法 Hadamard积(Hadamard product)(数学符号⊙)
A+1 #每个元素都+1
A*2 #每个元素*2

降维·

1
2
3
4
x=torch.arange(6,dtype=torch.float32).reshape(2,3)
x.sum(axis=0) #==>[5,7,9]
x.sum(axis=1) #==>[6,15]
#其他函数比如mean也有类似性质

非降维求和·

1
2
3
4
5
6
x=torch.arange(6,dtype=torch.float32).reshape(2,3)
sum_x = x.sum(axis=1, keepdims=True)
sum_x #==>[[6],[15]]
#这样可以直接用x/sum_x进行标准化
x/sum_x #==>[[0.0000, 0.3333, 0.6667],[0.2500, 0.3333, 0.4167]
x.cumsum(axis=0) #==>累计总和:[[0., 1., 2.],[3., 5., 7.]]

点积·

$x,y\in Rd,xTy=\sum_{i=1}^dx_iy_i$

加权和,计算夹角余弦

1
2
3
4
5
x=torch.range(4)
y=torch.ones(4,dtype=torch.float32)
#两种计算方式
torch.dot(x,y)
torch.sum(x*y)

矩阵-向量积·

$A\in R^{m\times n},x\in R^n$

相乘得到$B^m$ 即长度为m的向量

1
2
3
4
A=torch.arange(6).reshape(2,3)
x=torch.tensor([2,4,5])
#2*3的矩阵与长度为3的向量
torch.mv(A,x) #==>[14,47]

矩阵乘法·

$A\in R^{n\times k},B\in R^{k\times m}$

$AB\in R^{n\times m}$

可以理解为是A的n个行向量与B的m个列向量分别相乘

1
2
3
4
5
6
7
8
9
10
A=torch.range(15).reshape(5,3)
B=torch.range(12).reshape(3,4)
torch.mm(A,B)
'''
[[ 20, 23, 26, 29],
[ 56, 68, 80, 92],
[ 92, 113, 134, 155],
[128, 158, 188, 218],
[164, 203, 242, 281]]
'''

范数 norm·

将向量x映射到标量的函数$f$

满足三个性质

  • $f(\alpha x)=|\alpha|f(x)$
  • $f(x+y)\leq f(x)+f(y)$,三角不等式
  • $f(x)\ge 0$,当且仅当x分量全为0时相等

$L_2$范数:$||x||2=\sqrt{(\sum{i=1}nx_i2)}$ 一般可以简写为$||x||$

1
2
u=torch.tensor([3.0,4.0])
torch.norm(u) #==>[5.0]

$L_1$范数:$||x||1=\sum{i=1}^n|x_i|$

1
2
u=torch.tensor([3.0,4.0])
torch.abs(u).sum() #==>[7.0]

$L_p$范数:$||x||p=(\sum{i=1}n|x_i|p)^{1/p}$

矩阵的$Frobenius$范数(Frobenius norm)是矩阵元素平方和的平方根

1
torch.norm(torch.ones((4, 9))) #==>tensor(6.)

深度学习中的目标常表达为范数

2.4微积分·

2.5自动微分·

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
x = torch.arange(4.0)
x.requires_grad_(True)
y=10*torch.dot(x,x)
y.backward()
x.grad #[0.,4.,8.,12.]
x.grad==20*x

x.grad.zero_() #清除梯度
y = x.sum()
y.backward()
x.grad #[1.,1.,1.,1.]

#非标量变量的反向传播
x.grad.zero_()
y = x * x
y.sum().backward() #等价于y.backward(torch.ones(len(x)))
x.grad #相当于2x 即[0,2,4,6]

#分离计算
x.grad.zero_()
y = x * x
u = y.detach() #将u与x进行分离,即作为常数参与之后的运算
z = u * x
z.sum().backward()
x.grad == u #是一样的

#Python控制流的梯度计算
def f(a):
b = a * 2
while b.norm() < 1000:
b = b * 2
if b.sum() > 0: c = b
else:c = 100 * b
return c
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()

2.6概率·

2.7查阅⽂档·

3.线性神经网络(LNN)·

线性回归·

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
from torch.utils import data
#生成原始数据
def synthetic_data(w, b, num_examples):
"""生成y=Xw+b+噪声
Defined in :numref:`sec_linear_scratch`"""
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, torch.reshape(y, (-1, 1))
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

#定义加载数据方法,获得迭代器
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个PyTorch数据迭代器。"""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)

#定义网络
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))

#定义初始权重
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

#定义损失函数与优化方法
loss = nn.MSELoss() #2范数损失的平均值
trainer = torch.optim.SGD(net.parameters(), lr=0.03) #net.parameters()指定需要优化的参数,同时还需要传入优化算法需要的超参数字典,对于小批量随机梯度下降,这里是lr=0.03

#开始训练
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
#前向计算
l = loss(net(X) ,y)
#清除梯度
trainer.zero_grad()
#反向传播
l.backward()
#优化器根据梯度进行梯度计算更新
trainer.step()
#计算打印损失
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')

softmax回归·

$softmax$ 运算:$y_j=\frac {exp(o_j)} {\sum_{k=1}^nexp(o_k)}$

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
import torchvision,torch
from torch import nn
from torchvision import transforms

#获取输入
def get_dataloader_workers():
"""使用4个进程来读取数据"""
return 4
def load_data_fashion_mnist(batch_size, resize=None):
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize))
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(
root="../data", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(
root="../data", train=False, transform=trans, download=True)
return (data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers()),
data.DataLoader(mnist_test, batch_size, shuffle=False,
num_workers=get_dataloader_workers()))
batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size)

# PyTorch不会隐式地调整输⼊的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整⽹络输⼊的形状
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights)
#损失函数为交叉熵
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
num_epochs = 10
for i in range(num_epochs):
for X,y in train_iter:
#前向计算
l=loss(net(X),y)
#清除梯度
trainer.zero_grad()
#反向传播
l.mean().backward()
#更新权重
trainer.step()
loss_sum = 0
cnt = 0
for X,y in test_iter:
loss_sum+=loss(net(X),y).sum()
cnt+=1
print("epoch%d"%i,":",float(loss_sum/cnt))

4.多层感知机(MLP)·

多层感知机·

实现·

1
2
3
4
5
6
7
#此处代码与第3章中softmax回归的代码除了net定义都是一样的
net = nn.Sequential(
nn.Flatten(),
nn.Linear(784,256),
nn.ReLU(),
nn.Linear(256,10)
)

激活函数·

ReLU函数·

$ReLU(x)=max(x,0)$

sigmoid函数·

$sigmoid(x)=\frac {1} {1+exp(-x)}$

tanh函数·

$tanh(x)=\frac {1-exp(-2x)} {1+exp(-2x)}$

模型选择、欠拟合和过拟合·

权重衰减·

给权重加上惩罚项,也称为$L_2$正则化

最简单的方式:直接使用权重向量的某个范数来度量复杂性,比如二范数

即损失变为:$L(w,b)+\frac \lambda 2 ||\omega||^2$

1
2
3
4
5
#此处代码与第3章中softmax回归的代码除了trainer定义都是一样的
wd=3
trainer=torch.optim.SGD([
{"params":net[0].weight,'weight_decay':wd},
{"params":net[0].bias}],lr=lr)

暂退法·

扰动或者随机抛弃一些隐藏层的输出(即下一层输入)降低影响

1
2
3
4
5
6
7
8
9
10
#此处代码与第3章中softmax回归的代码除了net定义都是一样的
net = nn.Sequential(
nn.Flatten(),
nn.ReLU(),
nn.Dropout(0.2) #抛弃20%的隐藏层神经元
nn.Linear(784,256),
nn.ReLU(),
nn.Dropout(0.2) #抛弃20%的隐藏层神经元
nn.Linear(256,10)
)

前向传播,反向传播与计算图·

数值稳定性和模型初始化·

环境与分布偏移·

5.深度学习计算·

层和块·

自定义块·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from torch.nn.modules.flatten import Flatten
import torchvision,torch
from torch import nn
from torch.nn import functional as F
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden=nn.Linear(20,256)
self.output=nn.Linear(256,10)
def forward(self,X):
return self.output(F.relu(self.hidden(X)))
mlp=MLP()
X=torch.arange(20,dtype=torch.float32)
mlp(X)

顺序块·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MySequential(nn.Module):
def __init__(self, *args):
super().__init__()
for idx, module in enumerate(args):
# 这⾥,module是Module⼦类的⼀个实例。我们把它保存在'Module'类的成员
# 变量_modules中。module的类型是OrderedDict
self._modules[str(idx)] = module
def forward(self, X):
# OrderedDict保证了按照成员添加的顺序遍历它们
for block in self._modules.values():
X = block(X)
return X
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)

前向传播函数中执行代码·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyForwardSequential(nn.Module):
def __init__(self, *args):
super().__init__()
# 不计算梯度的随机权重参数。因此其在训练期间保持不变
self.rand_weight = torch.rand((20, 20), requires_grad=False)
self.linear = nn.Linear(20, 20)

def forward(self, X):
# OrderedDict保证了按照成员添加的顺序遍历它们
X=self.linear(X)
X=F.relu(X)
X=self.linear(X)
#控制流
while X.sum()>1:
X/=2
return X.sum()
net = MyForwardSequential()
net(X)

参数管理·

参数访问·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
net[2].state_dict()
net[2].bias
net[2].bias.data
net[2].bias.grad
net[2].weight
net[2].weight.data
net[2].weight.grad
#新的获取参数的方式
print(*[(name, param.shape) for name, param in net[2].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])
"""
('weight', torch.Size([1, 8])) ('bias', torch.Size([1]))
('0.weight', torch.Size([8, 4])) ('0.bias', torch.Size([8])) ('2.weight', torch.Size([1, 8])) ('2.bias', torch.Size([1]))
"""

参数初始化·

内置初始化·

1
2
3
4
5
6
7
8
9
10
11
12
def init_normal(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=0, std=0.01)
nn.init.zeros_(m.bias)
nn.init.constant_(m.weight,1) #设置为一个常数
nn.init.xavier_uniform_(m.weight) #Xavier初始化⽅法 防止梯度消失或者地图爆炸
net.apply(init_normal)
#不同层使用不同初始化方法
net[0].apply(xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)

延后初始化·

输入维度与前一层输出维度都延后到第一次计算时自动进行推断

自定义层·

不带参数·

1
2
3
4
5
6
7
8
9
10
11
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
net=nn.Sequential(
nn.Linear(20,12),
CenteredLayer(),
nn.Dropout(0.8),
nn.ReLU()
)

带参数·

1
2
3
4
5
6
7
class MyLinear(nn.Module):
def __init__(self,innum,outnum):
super().__init__()
self.weight=nn.Parameter(torch.randn(innum,outnum))
self.bias=nn.Parameter(torch.randn(outnum))
def forward(self,X):
return torch.matmul(X,self.weight.data)+self.bias.data #此处不能用mm!!

读写文件·

加载和保存张量·

1
2
3
4
5
6
7
x=torch.arange(24)
torch.save(x,'x-tensor')
x=torch.load('x-tensor')
torch.save([x,y],'x-tensor')
x,y=torch.load('x-tensor')
torch.save({'x':x,'y':y},'dict')
mydict=torch.load('dict')

加载和保存模型参数·

1
2
3
4
net=MLP()
torch.save(net.state_dict(),'mlp.params')
net.load_state_dict(torch.load('mlp.params'))
net.eval() #不更新梯度,只计算

GPU·

计算设备·

1
2
3
4
5
6
7
8
9
10
import torch
from torch import nn
torch.device('cpu')
torch.device('cuda')
torch.cuda.device_count()
def try_all_gpus():
devices=[torch.device(f'cuda:{i}') for i in range(torch.cuda.device_count())]
return devices if devices else [torch.device('cpu')]
def try_gpu(i=0):
return torch.device('cpu') if torch.cuda.device_count()<i+1 else torch.device(f'cuda:{i}')

张量与GPU·

1
2
3
4
5
6
7
8
#存储在GPU上
x=torch.tensor([1,2,3])
x.device #device(type='cpu')
x=torch.ones(2,3,device=try_gpu())
#复制
x=torch.ones(2,3,device=try_gpu(0))
x=torch.ones(2,3,device=try_gpu(1))
Z=X.cuda(1) #将X的数据复制到第0块GPU上

神经网络与GPU·

1
2
3
net=nn.Sequential(nn.Linear(3,1))
net=net.to(device=(try_gpu()))
#之后net的所有计算都会在GPU上

6.卷积神经网络(CNN)·

卷积基础·

卷积核·

沃尔多检测器—空间不变性!

从二维图像输入,即二维张量$m*n$开始,则如果隐藏层是全连接层,就会需要有$m2n2$个参数,此时的计算公式为:

$H_{i,j}=U_{i,j}+\sum_k\sum_lW_{i,j,k,l}X_{k,l}=U_{i,j}+\sum_{a}\sum_bV_{i,j,a,b}X_{i+a,j+b}$

此时利用图像的不变性原则,即对于同一个物体在不同处检测结果应该一致,所以有$V_{i,j,a,b}=V_{a,b}$

且$U$为常数,设为$u$

所以可以简化为$H_{i,j}=u+\sum_{a}\sum_bV_{a,b}X_{i+a,j+b}$,相当于对其$X_{i,j}$周边的像素进行加权得到$H_{i,j}$

同时,又考虑到局部性,因此最终的$H_{i,j}=u+\sum_{-\Delta}{\Delta}\sum_{-\Delta}{\Delta}V_{a,b}X_{i+a,j+b}$

这里的$V$是卷积核(convolution kernel),或者说是滤波器(filter)

通道·

输入的图像数据一般是三通道的,即红绿蓝,而维数则是3维张量。隐藏层一般也设置成3维张量,但由于需要学习更多的隐藏表示,每个隐藏表示是一系列具有二维张量的通道,比如纹理,边缘等等,有时也称为特征映射,被称为feature maps,为了表示多个隐藏表示,所以需要给卷积核增加一个维度,即

$H_{i,j,d}=\sum_{-\Delta}{\Delta}\sum_{-\Delta}{\Delta}\sum_cV_{a,b,c,d}X_{i+a,j+b,c}$

图像卷积·

互相关运算·

比如以下的卷积计算结果:

1
2
3
4
5
6
7
8
9
10
#input:
[[1,2,3],
[4,5,6],
[7,8,9]]
#卷积核:
[[0,1],
[2,3]]
#卷积结果:
[[25,31],
[43,49]]

输入为$nm$,卷积核为$hw$,则卷积相当于是一个正方块在输入的正方形进行移动计算,并到达自己能够到达的每个位置,最后的卷积结果为$(n-h+1)*(m-w+1)$

卷积层·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 构造⼀个⼆维卷积层,它具有1个输入通道,1个输出通道和形状为(1,2)的卷积核
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)
# 这个⼆维卷积层使⽤四维输⼊和输出格式(批量⼤⼩、通道、⾼度、宽度),
# 其中批量⼤⼩和通道数都为1
X = X.reshape((1, 1, 6, 8))
Y = Y.reshape((1, 1, 6, 7))
lr = 3e-2 # 学习率
for i in range(10):
Y_hat = conv2d(X)
l = (Y_hat - Y) ** 2
conv2d.zero_grad()
l.sum().backward()
# 迭代卷积核
conv2d.weight.data[:] -= lr * conv2d.weight.grad
if (i + 1) % 2 == 0:
print(f'epoch {i+1}, loss {l.sum():.3f}')

特征映射与感受野·

对于某⼀层的任意元素x,其感受野(receptive field)是指在前向传播期间可能影响x计算的所有元素(来⾃所有先前层)

填充与步幅·

填充·

对输入做填充处理从而捕捉边缘信息

假设添加$p_h$行填充和$p_w$列填充,填充的内容都是上下差不多各一半,左右差不多各一半,则输出形状为 $(n_h-k_h+p_h+1)\times (n_w-k_w+p_w+1)$

所以一般设置 $p_h=k_h-1,p_w=k_w-1$ 这样就可以让输入输出形状相同

同时,如果满足:

  • 卷积核大小为奇数
  • 所有边的填充行数与列数相同
  • 输出与输入具有相同高度和宽度

则可以得出:输出$Y[i,j]$是通过输入$X[i,j]$为中心,与卷积核进行互相关计算得到的。

1
2
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=3)

步幅·

按照一定步幅进行卷积,从而输入的宽度高度减少一定比例

1
2
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=(2,3))

变为:$\lfloor(n_k-k_h+p_h+s_h)/s_h\rfloor\times \lfloor(n_w-k_w+p_w+s_w)/s_w\rfloor$

后面的池化也是类似的

多输⼊多输出通道·

多输入通道·

每个通道计算完成之后将每个通道卷积得到的结果相加

多输出通道·

在之前多输入基础上,加上一个维度来产生多通道的输出,相当于每一个通道自己有一组核函数

1
K = torch.stack((K, K + 1, K + 2), 0)

汇聚层·

汇聚层目的:降低卷积层对位置的敏感性,降低对空间降采样表示的敏感性

最大汇聚层与平均汇聚层·

不包含参数,具有确定性,即直接计算汇聚窗口中的所有元素的最大值或平均值

同样,汇聚层也具有填充和步幅

1
2
pool2d = nn.MaxPool2d(3,padding=1, stride=2) #这里的padding相当于是上下各加1行,左右各加一列
pool2d = nn.MaxPool2d(3,padding=(1,2), stride=(2,3))

多通道时,每个输入通道单独运算而不会对结果汇总,因此输出的通道数量与输入通道数相同

卷积神经网络(LeNet)·

Lenet包含两个部分:

  • 卷积编码器:两个卷积块组成
    • 每个卷积块包含1个卷积层,1个sigmoid激活函数,还有平均汇聚层
  • 全连接层密集块:三个全连接层组成

网络结构·

1
2
3
4
5
6
7
8
9
10
#输入为28*28
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(), #==>6*28*28
nn.AvgPool2d(kernel_size=2, stride=2), #==>(28-2+2)/2=14 6*14*14
nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(), #==>16*10*10
nn.AvgPool2d(kernel_size=2, stride=2), #==>16*5*5
nn.Flatten(), #==>400
nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10))

7.现代卷积神经网络·

  • AlexNet。它是第⼀个在⼤规模视觉竞赛中击败传统计算机视觉模型的⼤型神经⽹络;
  • 使⽤重复块的⽹络(VGG)。它利⽤许多重复的神经⽹络块;
  • ⽹络中的⽹络(NiN)。它重复使⽤由卷积层和1 × 1卷积层(⽤来代替全连接层)来构建深层⽹络;
  • 含并⾏连结的⽹络(GoogLeNet)。它使⽤并⾏连结的⽹络,通过不同窗⼝⼤⼩的卷积层和最⼤汇聚层来并⾏抽取信息;
  • 残差⽹络(ResNet)。它通过残差块构建跨层的数据通道,是计算机视觉中最流⾏的体系架构;
  • 稠密连接⽹络(DenseNet)。它的计算成本很⾼,但给我们带来了更好的效果。