本应在jupytor里实现,现在还在跑模型,因此先在此记录一下
# 多层感知机
import torch
from torch import nn
from torch.nn import functional as F
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10)) # Sequential定义了一种特殊的`Module`
X = torch.rand(2, 20) # 2是批量大小,20是输入维度
net(X)
# 任何一个层或者说神经网络都应该是module的一个子类
class MLP(nn.Module): # 自定义一个MLP实现多层感知机,是nn.Module的一个子类,有很多好用的函数
# 每个module有两个最重要的函数,init和forward
def __init__(self): # 定义了需要的类和参数,定义了网络所可能需要的全部的层
super().__init__() # 先调用父类的init函数,设置好内部需要的参数
# 下面定义两个全连接层
self.hidden = nn.Linear(20, 256)
self.out = nn.Linear(256, 10)
def forward(self, X): # 给定输入进行输出
return self.out(F.relu(self.hidden(X)))
# 使用方法:
net = MLP()
net(X)
当Sequential不能满足需求时,可以在init自定义大量计算
# 实现上面的Sequential的功能
class MySequential(nn.Module):
def __init__(self, *args): # *args:收集参数,相当于把若干个参数打包成一个来传入
super().__init__()
for block in args:
self._modules[block] = block # 相当于顺序表(排好序的),存放需要的层
def forward(self, X):
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)
class FixedHiddenMLP(nn.Module):
def __init__(self):
super().__init__()
self.rand_weight = torch.rand((20, 20), requires_grad=False)
self.linear = nn.Linear(20, 20) # 可以直接生成参数不参与梯度计算
def forward(self, X): # forward中定义很多自己的东西
X = self.linear(X)
X = F.relu(torch.mm(X, self.rand_weight)+1)
X = self.linear(X)
while X.abs().sum() > 1:
X /= 2
return X.sum() # 返回标量
# 反向计算就是对forward的自动求导
net = FixedHiddenMLP()
net(X)
torch中的已有的东西、我们自己定义的东西,都是Module的子类可以嵌套使用:
# 嵌套使用例子:
class NestMLP(nn.Module):
def __init__(self): # 定义了需要的类和参数,定义了网络所可能需要的全部的层
super().__init__()
self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
nn.Linear(64, 32), nn.ReLU())
self.linear = nn.Linear(32, 16)
def forward(self, X):
return self.linear(self.net(X))
# 对于Sequential来说,输入可以是任何module的子类,也可以是我们自己定义的子类
chimera = nn.Sequential(NestMLP(),nn.Linear(16,20),FixedHiddenMLP())
chimera(X)
继承nn.Module类就可以进行比较灵活的构造
其中需要在init中定义好使用的层(torch在定义层时会自动初始化)
需要确定前向计算如何计算
假设我们已经定义好我们的类了,我们的参数如何访问。
我们首先关注具有单隐藏层的多层感知机
import torch
form torch import nn
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8,1)) # 单隐藏层的MLP
X = torch.rand(size=(2,4))
net(X)
需要将每一层中的权重拿出来,net就是一个Sequential,相当于一个list
print(net[2].state_dict()) # 2访问的就是第三层(relu算一层);state是状态,权重可以认为是一个状态;_dict可以认为是_modules,就是ordereddict是排好序的全部参数
可以直接访问某一个具体的参数
print(type(net[2].bias)) # 类型,这里是parameter类型的,定义的是一个可以优化的参数
print(net[2].bias) # 全部打印,包括值和梯度
print(net[2].bias.data) # 只打印值
net[2].weight.grad == None # 只访问梯度,这里还没有反向计算,因此为None
print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])
net.state_dict()['2.bias'].data # 有名字之后还可以通过名字获取我们需要的参数
若网络中存在嵌套
def block1():
return nn.Sequential(nn.Linear(4,8), nn.ReLU(), nn.Linear(8,4), nn.ReLU())
def block2(): # 每次插入一个block1
net = nn.Sequential()
for i in range(4):
net.add_module(f'block {i}', block()) # 相当于sequantial,但是add_module的好处就是可以给块命名
return net
rgnet = nn.Sequential(block2(), nn.Linear(4, 1)) # 相当于sequential嵌套block2的sequential嵌套block1的sequential
rgnet(X)
# 想要查看网络结构,可以打印输出一下
print(rgnet) # 可以看到整体的嵌套结构,但是由于网络很复杂时难以理解,因此最好能在构建网络时就对网络进行分块,更好访问和打印
上面讲了如何访问参数,下面将讲如何修改默认的初始函数
# 首先定义一个函数,初始化为正态分布
def init_normal(m): # 这里m就是一个module
if type(m) == nn.Linear: # 如果是线性类,初始化,不是则不用管
nn.init.normal_(m.weight, mean=0, std=0.01) # 下划线在后面,说明是替换函数,原地操作,而非返回一个值
nn.init.zeros_(m.bias) # 这些用于初始化的函数都在init这个module里
net.apply(init_normal) # 对网络里所有的module(有嵌套的会进行嵌套)进行初始化,这个apply可以进行很多操作,本质上就是对网络进行一个遍历,遍历每一层
net[0].weight.data[0], net[0].bias.data[0]
# 定义一个函数
def init_constant(m):
if type(m) == nn.Linear:
nn.init.constant_(m.weight, 1) # 初始化为constant为1的参数,当然不应该这么做(初始化为常数)!这样没法继续训练了
nn.init.zeros_(m.bias)
net.apply(init_normal)
net[0].weight.data[0], net[0].bias.data[0]
def xavier(m): # 初始化方法之一
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
def init_42(m):
if type(m) == nn.Linear:
nn.init.constant_(m.weight, 42)
net[0].apply(xavier) # 可以对不同的层使用apply函数进行遍历
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)
因为初始化函数是自定义的,因此可以自定义一些东西,例如帮助debug,或者进行一些判断等等。
有两个数据流,希望一些层可以共享权重,也就是这些层的权重是一样的
# 首先构造需要share的层
shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), shared, nn.ReLU(), shared, nn.ReLU(), nn.Linear(8, 1))
net(X)
print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] =100
print(net[2].weight.data[0] == net[4].weight.data[0]) # 这两层是一样的,要变一起变
和自定义网络没有区别,因为层也是nn.module的子类
import torch
import torch.nn.functional as F
from torch import nn
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
layer = CenteredLayer()
layer(torch.FloatTensor([1, 2, 3, 4, 5]))
这个层可以作为组件合并到构建更复杂的模型中
net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())
Y = net(torch.rand(4, 8))
Y.mean()
class MyLayer(nn.Module):
def __init__(self, in_units, units): # 线性层有输入维度和输出维度
super().__init__()
self.weight = nn.Parameter(torch.randn(in_units, units)) # 层的参数都是parameter类的实例,因此调用parameter设置参数,这里也进行初始化了,放入nn.parameter里后会自动帮忙加入梯度、给一个合适的命名
self.bias = nn.Parameter(torch.randn(units,)) # 有逗号创建的是向量
def forward(self, X):
linear = torch.matmul(X,self.weight.data)+self.bias.data # 需要注意是.data才能访问参数值
return F.relu(linear)
# 访问参数
dense = MyLinear(5,3)
dense.weight
# 构建网络
net = nn.Sequential(MyLinear(64, 8), MyLayer(8, 1))
net(torch.rand(2, 64))
自定义层和自定义网络没区别,都要继承nn.module,但是要是有参数需要放入nn.parameter里
训练好的东西如何存下来
import torch
from torch import nn
from torch.nn import function as F
# 保存矩阵
x = torch.arange(4)
torch.save(x, 'x-file') # 保存下的内容及保存到的文件名
x2 = torch.load("x-file") # 读取
x2
# 保存张量列表,list
y = torch.zeros(4)
torch.save([x, y], 'x-files')
x2, y2 = torch.load('x-files') # 两个
# 保存张量字典
mydict = {'x':x,'y':y}
torch.save(mydic, 'mydict')
mydict2 = torch.load('mydiact')
mdict2 # 字典
# 定义
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)))
# 测试
net = MLP()
X = torch.randn(size(2, 20))
Y = net(X)
# 保存
torch.save(net.state_dict(), 'mlp.params') # 访问全部参数
# 读取
clone = MLP() # 需要读取保存参数的文件&读取并生成整个网络模型!这里的模型参数已经被随机初始化了,但是没关系
clone.load_state_dict(torch.load('mlp.params')) # 1.从文件中读取出参数信息;2.放入刚刚定义好的模型
clone.eval() # 进入测试模式
Y_clone = clone(X)
Y_clone == Y # 可以发现,二者相等
train和eval两种模式,除非存在droupout或batchnorm,两者没有区别
将类别变量转换(独热编码)成伪变量时炸内存:
mlp每层单元数可以靠交叉验证寻参
实例调用的net(x)
是调用了父类实现的net.__call__()
函数,因为用的是nn.module,将—call函数等价于forward函数了,两者调用效果相同(官方不建议调用forward函数)
forward函数是对着paper里的公式得到的,你这块儿的网络怎么实现就怎么进行forward
还有很多初始化方法:例如kaiming初始化,初始化只是在模型训练最开始时不要直接炸掉
torch默认会进行一个初始化
自定义的激活函数可能非处处可导,但是一般都能导,数值计算遇到不可导点概率较低,遇到随便给个值就像,因此不用特别关心
nvidia-smi
,可以看到CUDA版本,有几块,每块多大,用了多少,占用百分比(百分比低于50%说明模型定义的不好)import torch
from torch import nn
# 查看有几块GPU
torch.cuda.device_count()
# 下面分别表示CPU、第0个GPU、第1个GPU
torch.device('cpu'), torch.cuda.device('cuda'), torch.cuda.device('cuda:1')
需要定义函数,当请求的GPU不存在时,运行如下代码:
def try_gpu(i=0):
# 如果存在,则返回gpu(i),否则返回cpu()
if torch.cuda.device_count() >= i+1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')
def try_all_gpus():
# 返回所有可用的GPU,如果没有GPU则返回[cpu(),]
devices = [torch.device(f'cuda:{i}') for i in range(torch.cuda.device_count())]
return device if devices else [torch.device('cpu')]
try_gpu(), try_gpu(10), try_all_gpus()
x = torch.tensor([1, 2, 3]) # 默认创建的tensor
x.device # 由此可以看到张量在哪里,默认是在CPU上
# 储存在GPU上
X = torch.ones(2, 3, device=try_gpu()) # 确定张量放在哪里
X
# 在第二个GPU上创建一个随机张量
Y = torch.rand(2, 3, device=try_gpu(1))
Y
当两个张量都在CPU或者都在同一块GPU上时,直接计算,不在时需要移动:GPU上移动数据到CPU、GPU间移数据是很慢的,因此不如直接报错,不在同一块就不运算
# 上面定义的X、Y,需要做运算时,要确定在哪里执行这个操作
Z = X.cuda(1) # 将X放到第1个GPU上
print(X)
print(Z)
# 在同一块GPU上,可以开始计算,结果也会写在这个GPU上
Y+Z
# 判断Z是否已经在需要的GPU上,先判断可以避免重复运算
Z.cuda(1) is Z
net = nn.Sequential(nn.Linear(3, 1)) # 正常创建
net = net.to(device=try_gpu()) # 将网络移动到需要的GPU上去,将所有的参数在0号GPU上拷贝一份
net(X) # X也在0号GPU上,因此该网络会在GPU上做运算
# 确认模型参数存储在同一个GPU上
net[0].weight.data.device # 访问存储的GPU位置
重点:
建议:
batch_size
调小,但是这样性能会变小==》可以把模型变小to(device)
调整到需要的GPU上第18节,还没看
例如图片分类模型,如果还是使用MLP,图片的输入(像素个数)太多了,需要的参数太多了,这就需要卷积层。
对于图像判定,需要在任何位置都能找到这个”像素“,需要有两个原则:
从全连接层出发,应用上述原则,得到卷积,因此卷积就是一个特殊的全连接层
h i , j = ∑ k , l w i , j , k , l x k , l = ∑ a , b v i , j , a , b x i + a , j + b h_{i,j}=\sum_{k,l}w_{i,j,k,l}x_{k,l}=\sum_{a,b}v_{i,j,a,b}x_{i+a,j+b} hi,j=k,l∑wi,j,k,lxk,l=a,b∑vi,j,a,bxi+a,j+b
h i , j = ∑ a , b v a , b x i + a , j + b h_{i,j}=\sum_{a,b}v_{a,b}x_{i+a,j+b} hi,j=a,b∑va,bxi+a,j+b
也就是说前两个维度是不变的,是常数(也就是说,对一张图像使用同一个卷积核进行扫描,这样不管目标出现在哪里,都是使用的同一个“探测器”,对应位置的输出不变),这就是卷积核
相当于输出,就是对应位置的输入,在周围进行(a,b)大小的移动(访问周围的像素),和对应的参数相乘做累积。
这里(a,b)就是一个二维卷积核
二维卷积可以理解为全连接,但是由于平移不变性,使得一些参数是重复的,不是每一个元素都可以自由变换,这样取值范围受限,模型复杂度降低,需要存储的参数数量降低。
也就是说想要(i,j)输出,需要看(i,j)输入的(a,b)范围,实际上不应该看的太远,输出的(i,j)只应该考虑输入(i,j)附近的像素,不应该考虑较远的参数
解决方案:当|a|,|b|>△时,使得 v a , b = 0 v_{a,b}=0 va,b=0
h i , j = ∑ a = − △ △ ∑ b = − △ △ v a , b x i + a , j + b h_{i,j}=\sum_{a=-△}^△\sum_{b=-△}^△v_{a,b}x_{i+a,j+b} hi,j=a=−△∑△b=−△∑△va,bxi+a,j+b
卷积核大小变小只考虑△大小的范围内的像素
含义:
输入被核扫描计算出输出,输出维度会发生变化(输入-核+1)
下图中的星星就是上面定义的二位交叉相关计算
不同卷积核值,可以带来不同的效果:
神经网络可以通过核来检测到想要的东西。
y i , j = ∑ a = 1 h ∑ b = 1 w w a , b x i + a , j + b y_{i,j}=\sum_{a=1}^h\sum_{b=1}^ww_{a,b}x_{i+a,j+b} yi,j=a=1∑hb=1∑wwa,bxi+a,j+b
y i , j = ∑ a = 1 h ∑ b = 1 w w − a , − b x i + a , j + b y_{i,j}=\sum_{a=1}^h\sum_{b=1}^w w_{-a,-b}x_{i+a,j+b} yi,j=a=1∑hb=1∑ww−a,−bxi+a,j+b
可以是文本、语言、时序序列
y i = ∑ a = 1 h w a x i + a y_i=\sum_{a=1}^hw_ax_{i+a} yi=a=1∑hwaxi+a
w就是向量了
可以是视频、医学图像、气象地图
y i , j , k = ∑ a = 1 h ∑ b = 1 w ∑ c = 1 d w a , b , c x i + a , j + b , k + c y_{i,j,k}=\sum_{a=1}^h\sum_{b=1}^w\sum_{c=1}^dw _{a,b,c}x_{i+a,j+b,k+c} yi,j,k=a=1∑hb=1∑wc=1∑dwa,b,cxi+a,j+b,k+c
这两个操作可以控制卷积层的输出大小
给定(32*32)输入图像,应用5*5卷积核,每次卷积减小4*4,而更大的卷积核可以更快地减小输出大小,这有可能和我们期望的大小不一样,而且很小的结果不能得到更深的网络。
因此一个解决方法就是填充:
在输入周围添加额外的行/列
填充 p h 行 , p w 列 p_h行,p_w列 ph行,pw列,输出形状: ( n h − k h + p h + 1 ) ∗ ( n w − k W + p W + 1 ) (n_h-k_h+p_h+1)*(n_w-k_W+p_W+1) (nh−kh+ph+1)∗(nw−kW+pW+1)
通常取 p h = k h − 1 , p w = k w − 1 p_h=k_h-1,p_w=k_w-1 ph=kh−1,pw=kw−1(这样卷积后输出大小=输入大小)
填充减小的输出大小与层数线性相关,步幅变为指数相关
对于一个较大的输出,若使用较小的卷积核,想要得到的较小的输出需要计算很多层,计算量太大。当然可以使用较大的卷积核,但是我们使用的卷积核一般为5*5或3*3很少使用很大的卷积核。
步幅是指行/列的滑动步长
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ItZDVtLO-1652441310715)(https://gitee.com/tianyiduan/figurebed/raw/master/img/image-20220506153331691.png)]
当步幅为 s h 行 和 s w 列 s_h行和s_w列 sh行和sw列,输出的形状:
⌊ ( n h − k h + p h + s h ) s h ⌋ ∗ ⌊ ( n w − k w + p w + s w ) s w ⌋ \lfloor\frac{(n_h-k_h+p_h+s_h)}{s_h}\rfloor*\lfloor\frac{(n_w-k_w+p_w+s_w)}{s_w}\rfloor ⌊sh(nh−kh+ph+sh)⌋∗⌊sw(nw−kw+pw+sw)⌋
如果 p h = k h − 1 , p w = k w − 1 p_h=k_h-1,p_w=k_w-1 ph=kh−1,pw=kw−1,输出结果为:
⌊ ( n h + s h − 1 ) s h ⌋ ∗ ⌊ ( n w + s w − 1 ) s w ⌋ \lfloor\frac{(n_h+s_h-1)}{s_h}\rfloor*\lfloor\frac{(n_w+s_w-1)}{s_w}\rfloor ⌊sh(nh+sh−1)⌋∗⌊sw(nw+sw−1)⌋
如果输入高度和宽度可以被步幅整除(很容易遇到,一般步幅取2;又因为向下取整,会将 s h s_h sh计算出的那个1约掉):
( n h s h ) ∗ ( n w s w ) (\frac{n_h}{s_h})*(\frac{n_w}{s_w}) (shnh)∗(swnw)
在调用pytorch的函数时,加两个参数即可。
一个重要的超参数:通道数
彩色图像可能有RGB三个通道,转换为灰度会丢失信息
当存在多个输入通道时:对每个通道都有一个卷积核,结果是所有通道卷积结果的和
Y i , : , : = X ★ W i , : , : , : f o r i = 1 , . . . , c o Y_{i,:,:}=X★W_{i,:,:,:}\quad for\quad i=1,...,c_o Yi,:,:=X★Wi,:,:,:fori=1,...,co
为什么要这样的原因:
k h = k w = 1 k_h=k_w=1 kh=kw=1是一个受欢迎的选择。它不识别空间模式,只是融合通道(特征维度)。相当于输入形状为 n h n w ∗ c i n_hn_w*c_i nhnw∗ci,权重为 c o ∗ c i c_o*c_i co∗ci的连接层。
这就是二维卷积层的全部参数:
模型存储比较小,但是计算量较大
当成单通道计算得到的值与当成矩阵计算出的值相等,单通道就相当于全连接,而矩阵相当于卷积,因此二者相等
调包实现:
允许输入有一点点小的偏移,有一点模糊化的效果
最早用于手写数字识别
附带了著名的数据集:MNIST数据集
1*32*32–卷积–>6*28*28–pooling–>6*14*14–卷积–>16*10*10–pooling–>16*5*5–flatten–>拉成一个向量–全连接层–>120向量–全连接层–>84向量–高斯层–>10向量(多分类,类似于多类分类)
可以看到模型没有过拟合,这可能表示模型不够大,因为卷积层,参数受限,模型不够大~
机器学习2000年时最主流的方法:首先特征提取;选择核函数来计算相似性(核函数可以把空间拉成我们想要的样子);就变成凸优化问题(可求极值);漂亮的定理
SVM不需要调参,对调参不敏感~
卷积一般用在图片上面,就是计算机视觉就是几何学:抽取特征;描述几何(例如多相机);(非)凸优化;漂亮定理;如果假设满足了,效果会很好
十年前,在深度学习领域如何抽取特征非常重要(对于SVM直接放入图片效果很差,需要先提取特征);特征描述子:SIFT,SURF;视觉词袋;最后
90年代用神经网络,较为简单,2000年核方法,现在又回到神经网络,计算量上去了,可以构造更深的神经网络。
ImageNet:一个兴起的数据集(2010)。也是因为神经网络更深了可以抽取更复杂的信息。
赢得了2012年ImageNet竞赛,计算机视觉方法论的改变(从人工特征提取到SVM;变成了通过(端到端)CNN学习特征到Softmax回归(一起训练,cnn提取到的特征可能就是softmax需要的))
主要改进:
因为图片边打,可以看到的窗口也应该变大,
池化层窗口变大,图片允许"移一点点"的范围变大,使用最大池化
层数变多,提取的特征变多
用了两个隐藏层,因为有1000类,因此隐藏层大小更大
相对于图片,卷积层参数增加不多(卷积省参数)。但是计算量增加很多。
alexnet长得不规则,感觉像是随变调的。想要更深更大,需要更好的思想,将框架搞好。
其实就是alexnet的一个拓展,将其中的部分提出来
变成下面这个样子:
越往下越慢,需要的内存高,准确率高
视觉领域现在都不再进行人工特征研究啦
有时间可以学习一下特征值、特征向量、奇异值分解
用的不多,但是思想很重要
之后的神经网络一般都有一个自己的块
卷积层需要较少的参数,数量: c i ∗ c o ∗ k 2 c_i*c_o*k^2 ci∗co∗k2相当于输入的通道数*输出的通道数*窗口的高*窗口的宽
但是卷积层后的第一个全连接层的参数很多(会占用很多内存,占用很多计算带宽,容易过拟合),数量:输入通道*输入的高*输入的宽*输出的通道数*输出的高*输出的宽
为了解决这一问题cnn完全不要全连接层
这样相比于全连接层,参数数量变小很多。对于全部的像素,使用的参数都是一样的。但是对通道进行了融合。
NIN块中没有全连接层了
需要交替使用NiN块和步幅为2的最大池化层
最后使用全局平均池化层得到输出(池化层窗口的高宽等于输入的高宽,相当于对每个通道,将最大的值拿出来;将输入变小,没有可学习的参数,计算变简单,模型复杂度贬变低,泛化性;缺点是收敛变慢),最后也没有用到全连接层
softmax放到loss里面(和交叉熵捆绑在一起了,不需要单独写),可以看到卷积神经网络中都没有softmax
一般框架中使用交叉熵就默认加上了softmax,相当于隐式调用了。
之前的lenet、alexnet、nin、vgg块用了不同的结构,但是那个好呢?不知道选哪个,那么googlenet全部都要,四个路径从不同层面抽取信息,四条路均没有改变高宽,只需要在输出通道维合并。高宽不变,通道变多
从左到右:(输入192通道,输出64+128+32+32=256;这些数据不知道怎么来的,但是可以将我们觉得重要的路径的通道数变多)
优点:
和单3*3或5*5卷积层相比,inception块有更少的参数个数和计算复杂度,而且具有多样性。
使用了两个inception block,使用后高宽不变,但是通道数变多(每条道路上通道数分配并不均匀,重要的分的多)
有点复杂~
V3虽然仍然计算量上较慢,内存需求也挺多的,但是准确率比vgg高很多
比alexnet计算量大很多,性能还不错
对于很深的网络,批量归一化很重要
为什么梯度不一样?是因为不同层的分布不一样,如果分布都一样,那么每一层学习就一样了
批量归一化思想就是:尝试固定小批量里面不同层的不同的地方的输出的均值和方差,然后再做额外的调整(可学习的参数)
(公式太复杂不好敲,其中 x i x_i xi是给批量归一化层的输入, x i + 1 x_{i+1} xi+1是批量归一化层的输出,其中均值和方差是算出来的,其他两个$\beta和\gamma $是可以学习的参数)
x i + 1 = γ x i − μ B σ B + β x_{i+1}=\gamma\frac{x_i-\mu_B}{\sigma_B}+\beta xi+1=γσBxi−μB+β
作用:假设变成均值为0方差为1的分布不太合适,可以去学习一个新的分布来获得对深度神经网络更好的数据,但是对$\beta和\gamma $的变化要有一个限制,不能变化的太猛烈
可学习的两个参数 $\beta和\gamma $
可以作用在:
对全连接层,作用在特征维(相当于列,一行是一个样本,一列是一个特征;对每一个全连接进行处理,而不像数据预处理只作用于数据)
对卷积层,作用在通道维(将通道层当作特征,一个通道是一个特征,一个通道的全部像素为一个样本)
加更多的层总是能改进精度吗?不一定,可能学偏了,最优解效果还不如小模型(模型偏差)。需要是更深的层严格包含小模型,这样起码不会变差。
下面是两种实现:(右边加上1*1卷积可以变换通道)
核心:加了一个加法~
相 当 于 对 于 底 层 : y = f ( x ) , 此 时 求 导 w = w − γ ∂ y ∂ w 对 于 普 通 的 深 层 网 络 : y ′ = g ( f ( x ) ) , 此 时 求 导 ∂ y ′ ∂ w = ∂ g ( y ) ∂ y ∂ y ∂ w , 前 者 若 是 很 小 , 连 乘 会 更 小 对 于 r e s n e t 网 络 : y ′ ′ = f ( x ) + g ( f ( x ) ) , 此 时 求 导 ∂ y ′ ′ ∂ w = ∂ y ∂ w + ∂ y ′ ∂ w , 将 乘 法 变 成 了 加 法 , 一 般 来 说 对 于 底 层 参 数 , 因 为 有 了 高 速 通 道 , 不 会 比 只 有 底 层 的 效 果 小 , 加 快 收 敛 , 可 以 更 好 地 训 练 相当于对于底层:y=f(x),此时求导w=w-\gamma \frac{\partial y}{\partial w}\\ 对于普通的深层网络:y'=g(f(x)),此时求导\frac{\partial y'}{\partial w}=\frac{\partial g(y)}{\partial y}\frac{\partial y}{\partial w},前者若是很小,连乘会更小\\ 对于resnet网络:y''=f(x)+g(f(x)),此时求导\frac{\partial y''}{\partial w}=\frac{\partial y}{\partial w}+\frac{\partial y'}{\partial w},将乘法变成了加法,一般来说对于底层参数,因为有了高速通道,不会比只有底层的效果小,加快收敛,可以更好地训练 相当于对于底层:y=f(x),此时求导w=w−γ∂w∂y对于普通的深层网络:y′=g(f(x)),此时求导∂w∂y′=∂y∂g(y)∂w∂y,前者若是很小,连乘会更小对于resnet网络:y′′=f(x)+g(f(x)),此时求导∂w∂y′′=∂w∂y+∂w∂y′,将乘法变成了加法,一般来说对于底层参数,因为有了高速通道,不会比只有底层的效果小,加快收敛,可以更好地训练
第30节暂略