CV领域已经成熟了,最近都在搞产品,没搞CNN的设计
数据
数据预处理
模型
参数,初始化参数
超参数
损失函数,先计算损失,清空梯度(防止有累积的梯度),再对损失后向传播计算损失关于参数的梯度
优化算法,使用优化算法更新参数
训练求参数
# 开始训练
num_epochs=3
for epoch in range(num_epochs):
# 获取小批量样本
for X,y in data_iter:
# net中带有w和b,传入x即可
l=loss(net(X),y)
# 梯度请0
trainer.zero_grad()
# 后向传播,内部torch帮助求sum()了
l.backward()
# 走一步 更新1次w和b
trainer.step()
l=loss(net(features),labels)
# {1:f} 把l用浮点数方式表示
print(f'epoch {epoch+1}, loss {l:f}')
##引入包##
import torch
from torch import nn
from d2l import torch as d2l
##设置超参数##
# 超参数
num_epochs = 10
batch_size = 256
##获取数据##
# 数据
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
##定义模型##
# pytorch不会调整input的形状
# 定义展平层(flatten)保留第0维,展开其他维度为1个向量,保留样本数,展开28*28=784,先展平再输入
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中
net.apply(init_weights)
##定义损失函数##
# 损失函数
loss = nn.CrossEntropyLoss()
##定义优化算法##
# 优化算法 以一定的学习率去学习 更新参数
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
##开始训练##
# todo(训练的结果曲线没有 train loss)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
# pytorch不会调整input的形状
# 定义展平层(flatten)保留第0维,展开其他维度为1个向量,保留样本数,展开28*28=784,先展平再输入
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
MLP简介实现过程
##引入包##
import torch
from torch import nn
from d2l import torch as d2l
##定义超参数##
batch_size,lr,num_epochs=256,0.1,10
##获取数据##
# 数据
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)
##定义模型##
# 模型 超参数 激活函数(使用那种激活函数也可以认为是超参数)
net=nn.Sequential(nn.Flatten(),nn.Linear(784,256),nn.ReLU(),nn.Linear(256,10))
##初始化参数##
# 参数 W和b
def init_weights(m):
'''
设置线性层的权重
:param 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=lr)
##开始训练##
# 训练
d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,trainer)
print(all_features.shape)
# 离散值处理,one-hot编码,将离散特征的值转为类别,列为离散类别,,行为1或0,行只有1个为1其余全0,行只有1个类别被激活(为1)其余都为0
# 参考网址 https://zhuanlan.zhihu.com/p/139144355
# 问题:会对number类型其作用吗?会对number其作用,为了统一处理
all_features=pd.get_dummies(all_features,dummy_na=True)
print(all_features.shape)
似然函数
似然函数,最大似然估计?
答:最小化损失=最大化似然函数(最相似)
答:数据和模型给定的情况下,调整模型参数,使得模型与数据最贴合,最相似
# 随机梯度下降
updater=torch.optim.SGD(params,lr=lr)
# 优化算法,SGD,Adam
# adam对学习率没有SGD那么敏感
# adam优化算法,结合了AdaGrad和RMSProp两种优化算法的优点
# AdaGrad:学习率自适应,梯度自适应,Adaptive Gradient,自适应梯度
# RMSProp:学习率自适应,梯度自适应,AdaGrad的改进,克服AdaGrad梯度急剧减小的问题
optimizer=torch.optim.Adam(net.parameters(),lr=learning_rate,weight_decay=weight_decay)
数据集类型不平衡。例如,二分类问题A类样本1个,B类样本9个
答:验证集的不同类别的样本数量做到相等,A类样本数:B类样本数=1:1,避免训练倾向没有通过验证集发现
答:先明确是不是真实世界就是这样的类别分布,还是采样每采样好。如果是采样没有采样好,则采用1方,使用权重平衡A类样本和B类型样本(loss中小的类别给更大的权重;简单点就直接把少样本类别复制到数量跟多样本类型差不多) 2方,验证集1:1
使用卷积的目的
答:MLP的参数量与输入层单元数量即输入数据量强相关。如此,计算量也跟输入数据量强相关
卷积原理
答:平移不变性 和 局部性原理
使用小的卷积核,视野小
答:卷积核虽然小,但是每次看一局部信息,进而综合了局部信息,当神经网络很深时,往上抽象看到了全局信息
权重衰退
实际中权重衰退效果模型很复杂的情况下,权重衰退不会带来好的效果
实际中权重衰减取值
答:wd=lambda=1e-3,1e-2,1e-4
# 从0开始实现,权重衰退正则项在loss函数中。简洁实现,权重衰退正则项在优化函数中
# wd=权重衰退=lambda
trainer=torch.optim.SGD([
{'params':net[0].weight,'weight_decay':wd},
{'params':net[0].bias}],
lr=lr)
# 权重衰退写在优化函数中
optimizer=torch.optim.Adam(net.parameters(),lr=learning_rate,weight_decay=weight_decay)
丢弃法
x i ′ = { 0 , p r o b a b l i t y p x i 1 − p , o t h e r w i s e x'_i= \begin{cases} 0,\ probablity\ p \\ \frac{x_i}{1-p},\ otherwise \end{cases} xi′={0, probablity p1−pxi, otherwise
# nn.Dropout(dropout1)
dropout1,dropout2=0.2,0.5
net=nn.Sequential(nn.Flatten(),
nn.Linear(784,256),nn.ReLU(),nn.Dropout(dropout1),
nn.Linear(256,256),nn.ReLU(),nn.Dropout(dropout2),
nn.Linear(256,10))
k折交叉验证过程:
权重初始化作用:
xavier权重初始化方法
使用靠近y=x的激活函数作用:
归一化作用:
使用:
训练集,验证集,测试集的划分标准
答:深度学习,训练集一般大,不用k折交叉验证。传统机器学习一般会使用k折交叉验证
答:训练:测试=7:3,训练集上做5折交叉验证。数据集大的情况下,训练:测试=5:5
答:举例,imageNet,1000个类别,每个类别5000个样本,从每个类别中拿出50张图片样本作为测试集,共1000类别*50张图片样本/类=5万张图片做测试集样本
恰当拟合:训练误差和泛化误差(验证损失)曲线,都很小和紧密贴合(gap小)
欠拟合:训练误差和泛化误差曲线,都很大
欠拟合:训练精度和测试(验证)精度都很低
过拟合:训练误差和泛化误差曲线,训练误差一直减小,泛化误差是凹曲线。某个epoch之前贴合(gap小),之后分开(gap大)
过拟合:测试(验证)精度先上升再下降是过拟合
# 1个epoch包含多个batch_size
# 迭代次数,1个epoch会把数据都使用完
num_epochs=10
# 批量大小,随机梯度下降
# 执行1次优化算法的样本数量
batch_size=256
# 控制参数更新步长
lr=0.01
# 正则项/权重衰退:用的少了
# 权重衰退可以放到loss函数或者优化函数中
# 常用值:wd=lambda=1e-3,1e-2,1e-4
# 正则项/丢弃法:用批量归一化层后可以不用丢弃层
# 控制线性层输出丢弃概率
# 常用值0.1,0.5,0.9
dropout1,dropout2=0.2,0.5
# 网络深度
# 计算量相比宽度小
# 学习就是不停的基于前面成果的向上抽象
# 过于具体,泛化能力就差
# 卷积
# 原理 平移不变形 局部性
# 卷积核内容是训练求得的
# 卷积层的卷积核越大计算量越大
# 为什么使用卷积:参数量少。MLP的参数量与输入层单元数量即输入数据量强相关。如此,计算量也跟输入数据量强相关
# 卷积对位置信息很敏感,使用池化层让卷积对位置信息不那么敏感
# 1x1卷积核,相当于权重(c_o,c_i),输入(c_i,hw)的全连接层,y=wx+b
# 1x1卷积层作用,1x1的卷积不看空间信息,只看通道信息(看不到空间信息,只能看到通道信息),只抽取通道信息
# 1x1卷积层作用,放到3x3卷积层之前的目的是3x3卷积计算慢,减少输出通道,降低计算量。在ResNet中用于统一输出通道数量
# 通道数
# 每个通道至少1个卷积核,卷积结果feature map再对应元素相加
# 输入通道是上层的输出通道
# 输出通道可以变多,多卷积几次即可
# 输出要减半的情况下,将通道数量加1倍,多1倍的卷积核,提取更多信息。空间信息减半,把更多的信息存储到更多的输出通道里
# 通道数量增加对计算量的影响不大
# 学习到了不同特征或模式,温故而知新
# 100万张图片,1000类别,最多1024个通道就够了。10万类别输出,可能最多2048个通道
# 卷积核(特征提取器)的大小
# 填充padding
# 防止输出shape太小
# 输出shape的行=(输入行数+padding*2-kernel行数+stride)/stride
# 步幅stride
# 减少输出shape
# 池化层:用的少了
# 分类:平均池化,最大池化
# 作用,卷积对位置信息不敏感
# 作用,stride>2减少输出的维数
# 池化层一般放到卷积层的后面
# 池化层对每个通道单独做池化,不改变通道数量
# 一般stride=池化层窗口大小,现在不=了,没有太多区别,池化层用的很少了
# 针对第1个作用对数据就做很多处理(平移,旋转,畸变)
# 针对第2个作用stride可以放到卷积层
# 全局平均池化层作用:对前一层输出的整张图片做平均池化
pool2d = nn.MaxPool2d((2,3), padding=(1,1),stride=(2, 3))
# AdaptiveAvgPool2d 全局平均池化层,1x1的宽高图片
nn.AdaptiveAvgPool2d((1,1)),
# mini_batch_normal 批量归一化:用批量归一化层后可以不用丢弃层
#
激活函数作用
softmax
均方误差
M S E = 1 N ( y ^ − y ) 2 MSE=\frac{1}{N}(\hat{y}-y)^2 MSE=N1(y^−y)2
# 损失函数 MSE 均方误差
loss=nn.MSELoss()
交叉熵
l ( y , y ^ ) = − ∑ i y i l o g y i ^ = − l o g y ^ y l(\boldsymbol{\rm{y}},\boldsymbol{\rm{\hat{y}}})=-\sum_i y_ilog\hat{y_i}=-log\hat{y}_y l(y,y^)=−∑iyilogyi^=−logy^y
log RMSE(对数均方根误差)
loss=$\hat{y}-y$
train loss=预测值-真实值
train accuracy=预测正确的样本数量/总样本数量
test accuracy=预测正确的样本数量/总样本数量
平均精度=总精度/输出元素数量
# !1个输入,1个通道,28*28的图片
# !4个括号4个维度,shape看括号里面的元素数量
X=torch.rand(size=(1,1,28,28),dtype=torch.float32)
for layer in net:
X=layer(X)
print(layer.__class__.__name__,'output shape:\t',X.shape)
class Animator:
'''
动画类
动画展示训练过程
'''
def __init__(self,
xlabel=None,ylabel=None,
legend=None,
xlim=None,ylim=None,
xscale='linear',yscale='linear',
fmts=('-','m--','g--','r:'),
nrows=1,ncols=1,
figsize=(3.5,2.5)):
'''
:param xlabel x轴标签控制 字体大小类型
:param legend 标记
:param xlim x轴范围
:param xscale x轴缩放类型 这里线性缩放
:param fmts 线条样式
:nrows 坐标轴行数
:figsize 画布大小
'''
if legend is None:
legend=[]
# 使用svg格式展示的更加清晰
d2l.use_svg_display()
# 获取画布和轴
self.fig,self.axes=d2l.plt.subplots(nrows,ncols,figsize=figsize)
if nrows*ncols==1:
self.axes=[self.axes,]
# 配置轴
# lambda函数,匿名函数,sum=lambda 入参: 函数体,例子 sum=lambda x,y : x+y
# 使用lambda函数捕获参数
self.config_axes=lambda: d2l.set_axes(self.axes[0],
xlabel,ylabel,
xlim,ylim,
xscale,yscale,
legend)
self.X,self.Y,self.fmts=None,None,fmts
def add(self,x,y):
'''
向图中增加数据点
todo
'''
if not hasattr(y,'__len__'):
y=[y]
n=len(y)
if not hasattr(x,'__len__'):
x=[x]*n
if not self.X:
self.X=[[] for _ in range(n)]
if not self.Y:
self.Y=[[] for _ in range(n)]
# 1个x对应1个yi
for i,(a,b) in enumerate(zip(x,y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
# cla()清除当前轴
self.axes[0].cla()
for x,y,fmt in zip(self.X,self.Y,self.fmts):
self.axes[0].plot(x,y,fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
# 获取list类型变量
indices=list(range(num_examples))
# 生成器(生成数据、可迭代对象)
# 每次取i到i+batch_size的数据
for i in range(0,num_examples, batch_size):
# min防止越界
batch_indices=torch.tensor(indices[i:min(i+batch_size,num_examples)])
# yield 产出
yield features[batch_indices],labels[batch_indices]
# 资源管理 防止资源泄漏
# __enter__()魔法方法,进入with时调用open()
# __exit__()魔法方法,退出with时调用close()
with open('xcrj.txt', 'a') as f:
f.write("\nxcrj")
import random
# 操作索引好处 后面可以操作features和label
random.shuffle(indices)
torch.arange(12)
torch.tensor([[],[]])
torch.ones((1,3))
torch.zeros((1,3))
X=torch.arange(12,dtype=torch.float32).reshape((3,4))
# 符合normal正态分布的随机值。0均值,1方差的随机数
X=torch.normal(0,1,(num_examples,len(w)))
X.shape
# number of elements
X.numel()
X.reshape((3,4))
# -1这一维由计算得出
y.reshape((-1,1))
X.sum()
X.mean()
type(X)
len(X)
A=torch.arange(24).reshape(2,3,4)
print(A)
# axis代表第i维,n维数组的第i个括号
# 消除第1个括号,把第1个括号内的n个记录中的对应位置元素相加
print(A.sum(axis=0))
print(A.sum(axis=0).shape)
# 消除第2个括号,把第2个括号内的n个记录中的对应位置元素相加
print(A.sum(axis=1))
print(A.sum(axis=1).shape)
# 消除第1个和第2个括号,先把第1个括号内的n个记录中的对应位置元素相加,再把第2个括号内的n个记录中的对应位置元素相加
print(A.sum(axis=[0,1]))
print(A.sum(axis=[0,1]).shape)
# 保留维度
sum_A=A.sum(axis=1,keepdims=True)
# cumulation
A.cumsum(axis=0)
# 所有的运算都是基于元素的, **是幂运算,//是地板除法
x+y, x-y, x*y, x/y, x**y, x//y
# e的x次方
torch.exp(x)
# 不增加新的维度,在第0维拼接
torch.cat((X,Y),dim=0), torch.cat((X,Y),dim=1)
# +1元素值加法,c_o=3,在第0个维度堆叠,增加第0维
K = torch.stack((K, K+1, K+2), 0)
# tensor>numpy
X.numpy()
# 在计算图中需要先detach()出去再转成numpy()
d2l.plt.scatter(features[:,1].detach().numpy(),labels.detach().numpy(),1);
# tensor>基本数据类型
x.item()
# 更倾向于创建一个新的对象
# 指针变化
# id获取指针
before=id(Y)
# 创建了1个新的对象Y
Y=Y+X
id(Y)==before
# 存在优化
# 指针不变
before=id(X)
X+=Y
id(X)==before
# 细节创建方式
A=torch.arange(20,dtype=torch.float32).reshape(5,4)
# 深拷贝
B=A.clone()
A,A+B
# copy不一定是深拷贝
B=A.copy()
A.T
# 乘积:元素按位置相乘
m_h=x*y
# 点乘
# 向量.向量
# 点积:元素按位置相乘再求和,就是数学的矩阵运算
m_d=torch.dot(x,y)
# 点乘
# matrix.vector=矩阵.向量
torch.mv(A,x)
# 点乘
# 二维矩阵相乘
# matrix.matrix=矩阵.矩阵
torch.mm(A,B)
# 点乘
# 多维矩阵相乘
# matrix multiple
y=torch.matmul(X,w)+b
# 点乘
# @矩阵乘法,不是元素相乘
H=relu(X@W1+b1)
# L1范数=向量元素绝对值求和
x=torch.tensor([3.0,4.0])
l1=torch.abs(x).sum()
l1
# L2范数=向量元素平方和开根号
x=torch.tensor([3.0,4.0])
# norm规范
l2=torch.norm(x)
l2
# F范数=矩阵元素平方和开根号
x=torch.arange(6,dtype=torch.float32).reshape(2,3)
torch.norm(x)
# 存储梯度
# 等价于x=torch.arange(4.0,requires_grad=True)
x.requires_grad_(True)
# 访问梯度 默认None
x.grad
# 清空梯度,pytorch模型进行梯度累积
x.grad.zero_()
# 计算梯度,反向传播,y对x的每个分量求梯度
y.backward()
# 不计算梯度
# detach离开,移出梯度计算图
u=y.detach()
z=u*x
# z.sum()对x的各个分量求梯度
z.sum().backward()
# u被移出计算图,u是常数,所以相等
print(x.grad==u)
问题
为什么loss是标量
答:因为标量对矩阵或向量求导的结果shape不会变大
# features[:,1].detach().numpy()
# 要转为numpy类型需要先从计算图中移除detach(隐式构造计算图)
d2l.plt.scatter(features[:,1].detach().numpy(),labels.detach().numpy(),1);
# 优化算法更新参数时不需要计算梯度,需要清空梯度
# 展示n个epoch的训练情况时不需要计算梯度,不需要情况梯度
# 定义优化函数
# 随机梯度下降,随机从样本中选取batch_size的数据,所有样本都会取到。知识对所有样本进行了shuffle
def sgd(params,lr,batch_size):
'''
小批量梯度下降
:param params 初始化参数
:param lr learning rate
:param batch_size
'''
'''
with A:
block
进入block时,执行A的__enter__()
退出block时,执行A的__exit__()
'''
# 进入环境管理器时记录梯度状态和禁止梯度计算, 退出环境管理器时还原
# 为什么使用with语句。因为 更新param时不需要梯度计算
with torch.no_grad():
for param in params:
# batch_size本来放到squared_loss中,线性回归模型放到这里也可以
param-=lr*param.grad/batch_size
# 清空梯度
param.grad.zero_()
# 训练多少轮,每轮有多个小批量
for epoch in range(num_epochs):
for X,y in data_iter(batch_size,features,labels):
# 小批量损失
l=loss(net(X,w,b),y)
# l shape batch_size*1所以要sum()弄成标量求梯度
# 标量对向量求梯度 向量只是转置了。向量对向量求梯度成矩阵了,矩阵对向量求梯度成三维张量了
# 计算关于x,关于b的梯度
l.sum().backward()
# 使用优化函数更新w, b
sgd([w,b],lr,batch_size)
# 展示1个epoch(1轮)的训练过程
with torch.no_grad():
# 1个epoch后,使用被更新的w, b得到的损失
train_1=loss(net(features,w,b),labels)
print(f'epoch {epoch+1}, loss {float(train_1.mean()):f}')
# 优化算法使用梯度更新完参数之后,需要重新计算梯度,先要清空梯度
全连接层
# 全连接层就是线性层。入参(输入神经元个数,输出神经元个数)
# linear层 全连接层 2,1 输入维度,输出维度
# Sequential 神经网络层的容器
net=nn.Sequential(nn.Linear(2,1))
网络参数
# 获取神经网络第1层的权重参数
net[0].weight
# 获取神经网络第1层的偏置参数
net[0].bias
参数初始化方法
# net[0]表示神经网络的第1层
# 初始化模型参数
net[0].weight.data.normal_(0,0.01)
net[0].bias.data.fill_(0)
##引入包##
import torch
from torch import nn
from d2l import torch as d2l
##设置超参数##
# 超参数
num_epochs = 10
batch_size = 256
##获取数据##
# 数据
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
##定义模型##
# pytorch不会调整input的形状
# 定义展平层(flatten)保留第0维,展开其他维度为1个向量,保留样本数,展开28*28=784,先展平再输入
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中
net.apply(init_weights)
##定义损失函数##
# 损失函数
loss = nn.CrossEntropyLoss()
##定义优化算法##
# 优化算法 以一定的学习率去学习 更新参数
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
##开始训练##
# todo(训练的结果曲线没有 train loss)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
# pytorch不会调整input的形状
# 定义展平层(flatten)保留第0维,展开其他维度为1个向量,保留样本数,展开28*28=784,先展平再输入
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
MLP简介实现过程
##引入包##
import torch
from torch import nn
from d2l import torch as d2l
##定义超参数##
batch_size,lr,num_epochs=256,0.1,10
##获取数据##
# 数据
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)
##定义模型##
# 模型 超参数 激活函数(使用那种激活函数也可以认为是超参数)
net=nn.Sequential(nn.Flatten(),nn.Linear(784,256),nn.ReLU(),nn.Linear(256,10))
##初始化参数##
# 参数 W和b
def init_weights(m):
'''
设置线性层的权重
:param 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=lr)
##开始训练##
# 训练
d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,trainer)
总结
总结
自定义网络MLP
class MLP(nn.Module):
'''
神经网络是块(nn.Module)的子类
'''
def __init__(self):
'''
使用层自定义网络
'''
super().__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()
# 魔法方法会自动调用forward()
net(X)
自定义顺序网络
class MySequential(nn.Module):
'''
神经网络是块(nn.Module)的子类
实现nn.Sequential()
'''
def __init__(self,*args):
'''
将层放入self._modules
:param *args 列表入参 神经网络层
'''
super().__init__()
# block 层
for block in args:
# !
self._modules[block]=block
def forward(self,X):
'''
为self._modules的每层赋值
'''
# 遍历层,和输入顺序一致
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):
'''
隐藏层被固定,两次调用同1个隐藏层
'''
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):
'''
在正向传播中执行代码
'''
# !调用第1次
X=self.linear(X)
# 自行计算转换函数
# torch.mm(a,b) 矩阵乘法,数学矩阵乘法
# +1是偏置
X=F.relu(torch.mm(X,self.rand_weight)+1)
# !调用第2次
X=self.linear(X)
while X.abs().sum()>1:
X/=2
return X.sum()
net=FixedHiddenMLP()
net(X)
自定义嵌套网络
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))
# 把MLP net套到了nn.Sequential中
chimera=nn.Sequential(NestMLP(),nn.Linear(16,20),FixedHiddenMLP())
chimera(X)
创建带有参数层
class MyLinear(nn.Module):
def __init__(self,in_units,units):
'''
使用参数自定义层
使用Parameter类自定义线性层
:param in_units 前一层神经元数量
:param units 后一层神经元数量
'''
super().__init__()
# !nn.Parameter
self.weight=nn.Parameter(torch.randn(in_units,units))
self.bias=nn.Parameter(torch.randn(units,))
def forward(self,X):
linear=torch.matmul(X,self.weight.data)+self.bias.data
return F.relu(linear)
dense=MyLinear(5,3)
dense.weight
# 使用自定义层直接执行前向传播计算
dense(torch.rand(2,5))
# 使用自定义层构建网络
net=nn.Sequential(MyLinear(64,8),MyLinear(8,1))
net(torch.rand(2,64))
# 访问指定层所有参数
# net[2]拿到0,1,2。拿到nn.Linear(8,1)
# nn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,1))
# state?权重从自动机角度来看就是状态机
# OrderedDict?有weight和bias
print(net[2].state_dict())
# 网络的所有参数的第3层的偏置的数据
# !神经元+输出算作1层(神经元和突起),激活函数算作1层
net.state_dict()['2.bias'].data
# 访问指定层的指定参数
# torch.nn.parameter.Parameter定义的是可以优化的参数
print(type(net[2].bias))
# 访问参数
print(net[2].bias)
# 访问参数的数据
print(net[2].bias.data)
# 访问参数的梯度
# 还没有做反向传播,所以=None
net[2].weight.grad==None
# 访问神经网络中的所有参数
# named_parameters含有name和parameter
# *[] 展开list
print(*[(name,param.shape) for name,param in net[0].named_parameters()])
print(*[(name,param.shape) for name,param in net.named_parameters()])
# 内置初始化函数
def init_normal(m):
'''
:param m nn.Module类型
'''
if type(m)==nn.Linear:
# 正态分布
# !替换函数,normal_后下划线的写法表示这个函数是替换函数,替换m.weight
# !所有替换函数都在initModule里面
nn.init.normal_(m.weight,mean=0,std=0.01)
nn.init.zeros_(m.bias)
# net中所有的层都应用这个初始化函数
net.apply(init_normal)
net[0].weight.data[0],net[0].bias.data[0]
# 内置初始化函数
def init_constant(m):
'''
:param m nn.Module类型
'''
if type(m)==nn.Linear:
# 正态分布
# !替换函数,normal_后下划线的写法表示这个函数是替换函数,替换m.weight
# !所有替换函数都在initModule里面
nn.init.constant_(m.weight,1)
nn.init.zeros_(m.bias)
# net中所有的层都应用这个初始化函数
net.apply(init_constant)
net[0].weight.data[0],net[0].bias.data[0]
# 1个张量
import torch
from torch import nn
from torch.nn import functional as F
x=torch.arange(4)
torch.save(x,'x-file')
x2=torch.load('x-file')
x2
# 存储权重参数
torch.save(net.state_dict(),'mlp.params')
# 加载权重参数
clone=MLP()
clone.load_state_dict(torch.load('mlp.params'))
# evaluation 评估模式,对评估存在优化
clone.eval()
# GPU使用情况
!nvidia-smi
查询可用GPU数量
torch.cuda.device_count()
# 可以使用cpu
X = torch.ones(2,3,device=torch.device('cuda:0'))
X = torch.ones(2,3,device=torch.device('cpu'))
# 只能使用gpu
X = torch.ones(2,3).cuda(0)
# 查看张量在什么设备上
print(x.device)
# 张量深拷贝
# GPU内存上tensor运算
# X.cuda(1),把张量X深拷贝到第2张GPU
# 将神经网络模型深拷贝一份到gpu0设备上
net=net.to(device=try_gpu())
# 定义函数,允许GPU不存在时,也能运行代码
def try_gpu(i=0):
'''
:return 如果存在gpu(i),则返回gpu(i),否则返回cpu()
'''
if torch.cuda.device_count()>=i+1:
# !不要写成torch.cuda.device(f'cuda:{i}')
return torch.device(f'cuda:{i}')
return torch.device('cpu')
def try_all_gpus():
'''
:return 返回所有可用gpu,否则返回[cpu(),]
'''
devices=[torch.device(f'cuda:{i}') for i in range(torch.cuda.device_count())]
return devices if devices else [torch.device('cpu')]
# 1输入通道数,1输出通道数,核大小3*3。padding是超参数,padding=1,上下左右都1像素
conv2d=nn.Conv2d(1,1,kernel_size=3,padding=1)
conv2d=nn.Conv2d(1,1,kernel_size=(3,5),padding=(0,1),stride=(3,4))
# (8+0+0-3+3)/3=2,(8+1+1-5+4)/4=2
单通道二维池化
# 1输入通道数,1输出通道数
X=torch.arange(16,dtype=torch.float32).reshape((1, 1, 4, 4))
print(X.shape)
# 使用默认值,步幅=池化窗口大小
# (4+0+0-3+4)/4=1,(4+0+0-3+4)/4=1
pool2d=nn.MaxPool2d(3)
print(pool2d(X).shape)
# 手动设定填充和步幅
# (4+1+1-3+2)/2=2, (4+1+1-3+2)/2=2
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
print(pool2d(X).shape)
# (4+1+1-2+2)/2=3, (4+1+1-3+3)/3=2
pool2d = nn.MaxPool2d((2,3), padding=(1,1),stride=(2, 3))
print(pool2d(X).shape)
多通道二维池化
print(X.shape)
# !cat是沿维数直接拼接,stack是沿着维数产生维度再进行拼接
# cat拼接之后的维度=序列长度*要拼接维度原来值
# stack堆叠之后的维度=序列长度
# +1元素加1,序列长度=2,第1维 1*2
X=torch.cat((X,X+1),1)
print(X.shape)
# 会在每个通道单独做池化
pool2d=nn.MaxPool2d(3,padding=1,stride=2)
print(pool2d(X).shape)
# batch_size, 1输入通道
# view和reshape的区别:reshape的功能比view更强大。view视图是原有tensor的视图,不开辟新的存储空间,返回原有存储空间的引用。view只适用于连续性的tensor
# view用来reshape,-1这一维由计算得出,1单通道
return x.view(-1,1,28,28)
# 将模型放到GPU上
net.to(device)
# !数据放到gpu上
# 在计算之前放到gpu上
X, y = X.to(device), y.to(device)
对比LeNet
对比AlexNet
结构:
VGG
类比:
为什么出现:
结构:NiN块
NiN
结构:
GoogLeNet总结
u B = 1 B ∑ i ∈ B x i u_B=\frac{1}{B}\sum\limits_{i \in B}x_i uB=B1i∈B∑xi
σ B 2 = 1 ∣ B ∣ ∑ i ∈ B ( x i − u B ) 2 + ϵ \sigma^2_B=\frac{1}{|B|}\sum\limits_{i\in B}(x_i-u_B)^2+\epsilon σB2=∣B∣1i∈B∑(xi−uB)2+ϵ, ϵ \epsilon ϵ 是1个很小的数,防止方差为0
x i + 1 = γ x i − μ ^ B σ ^ B + β x_{i+1}=\gamma\frac{x_i-\hat{\mu}_B}{\hat{\sigma}_B}+\beta xi+1=γσ^Bxi−μ^B+β, B is mini_batch_data, γ \gamma γ是需要学习的方差, β \beta β是需要学习的期望, μ ^ B \hat{\mu}_B μ^B is mean, σ ^ B \hat{\sigma}_B σ^B is var,
总结数值稳定性:
总结批量归一化
必须要学习的神经网络
可实现千层网络
idea:
实现
为什么叫残差
结构
类比