李沐动手学深度学习v2/总结3

总结

行情

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的参数量与输入层单元数量即输入数据量强相关。如此,计算量也跟输入数据量强相关

卷积原理
答:平移不变性 和 局部性原理

使用小的卷积核,视野小
答:卷积核虽然小,但是每次看一局部信息,进而综合了局部信息,当神经网络很深时,往上抽象看到了全局信息

trick/正则化(惩罚项)

权重衰退

实际中权重衰退效果模型很复杂的情况下,权重衰退不会带来好的效果
实际中权重衰减取值
答: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 p1pxi, otherwise

  • dropout效果更好,因为更好调参,权重衰退效果不好
  • dropout常用值:0.1,0.5,0.9
  • 权重衰退应用范围广,dropout只能作用于全连接层激活函数之后,mini_batch_normal可作用于卷积层(输出通道上),也可作用于全连接层(特征上)
# 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))

trick/k折交叉验证

k折交叉验证过程:

  • 做k次交叉验证 叫k折交叉验证
  • 数据分成k组,每个子集做1次验证集,剩余k-1组做训练集,得到k个模型
  • 模型性能指标=k个模型最终的验证集的分类准确率的平均数

trick/数值稳定性

  • 答:数值稳定性作用,防止梯度消失和梯度爆炸,保证正向每层输出的稳定性和反向梯度的稳定性
  • 答:权重初始化作用,训练开始时数值稳定性,保证训练开始时输出的稳定性,不能保证训练中输出的稳定性
  • 答:使用靠近y=x的激活函数作用,保证正向每层输出的稳定性和反向梯度的稳定性
  • 答:归一化作用,训练中数值稳定性,训练中网络层输入数据的稳定性

trick/数值稳定性/权重初始化方法

权重初始化作用:

  • 训练开始,输出的稳定性
  • 训练开始时数值稳定性,保证训练开始时输出的稳定性,不能保证训练中输出的稳定性

xavier权重初始化方法

  • Xavier是常用的权重初始化方法,它表示权重和梯度期望=0,权重和梯度的方差由第t层输入和输出的神经元数量决定

trick/数值稳定性/激活函数

使用靠近y=x的激活函数作用:

  • 训练中,输出的稳定性,梯度的稳定性
  • 保证正向每层输出的稳定性和反向梯度的稳定性

trick/数值稳定性/批量归一化

归一化作用:

  • 训练中,输入的稳定性
  • 训练中数值稳定性,训练中网络层输入数据的稳定性
  • 学习输入层的时候,输入层改变时,避免变化输出层

使用:

  • 批量归一化固定小批量中的均值和方差,学习出合适的缩放gamma和偏移mean
  • 批量归一化层作用在全连接层的特征上:全连接层》批量归一化(作用在全连接层的特征上)》激活函数
  • 批量归一化层作用在卷积层的输出通道上:卷积层》批量归一化(作用在卷积层的输出通道上)》激活函数
  • 丢弃层:全连接层》批量归一化》激活函数》不使用丢弃层了

数据集划分

训练集,验证集,测试集的划分标准
答:深度学习,训练集一般大,不用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

  • 每个x的
  • s o f t m a x ( X i j ) = e x p ( X i j ) ∑ k e x p ( X i k ) \rm{softmax}(\boldsymbol{\rm{X}}_{ij})=\frac{\boldsymbol{exp(\rm{X}_{ij}})}{\sum_kexp(\boldsymbol{\rm{X}_{ik}})} softmax(Xij)=kexp(Xik)exp(Xij),这里X是向量,X向量对每1个元素做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(对数均方根误差)

  • 房价适合相对误差 y − y ^ y \frac{y-\hat{y}}{y} yyy^,因为不同房子的价格相差很大,1000万的房子产生的预测误差可能远远大于10万的房子产生的预测误差,本质是误差缩放(类比特征缩放)
  • y − y ^ y , 取对数 , l o g ( y − y ^ ) − l o g y \frac{y-\hat{y}}{y},取对数,log(y-\hat{y})-logy yyy^,取对数,log(yy^)logy,所以使用log_rmse(log均方根误差),RMSE均方根误差
  • l o g R M S E = 1 n ∑ ( l o g ( y p ) − l o g ( y ) ) 2 log RMSE=\sqrt{\frac{1}{n}\sum(log(y_{p})-log(y))^2} logRMSE=n1(log(yp)log(y))2

常用评价指标

loss=$\hat{y}-y$
train loss=预测值-真实值
train accuracy=预测正确的样本数量/总样本数量
test accuracy=预测正确的样本数量/总样本数量
平均精度=总精度/输出元素数量

展示

模型shape

# !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)

python

基础

# 获取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]

with as操作上下文管理器

# 资源管理 防止资源泄漏
# __enter__()魔法方法,进入with时调用open()
# __exit__()魔法方法,退出with时调用close()
with open('xcrj.txt', 'a') as f:
    f.write("\nxcrj")

random

import random
# 操作索引好处 后面可以操作features和label
random.shuffle(indices)

2李沐动手学深度学习v2/ndarray

创建张量

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()

3李沐动手学深度学习v2/线性代数

矩阵操作

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)

4李沐动手学深度学习v2/自动求导

梯度


# 存储梯度
# 等价于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不会变大

5李沐动手学深度学习v2/线性回归-从0开始实现

移出计算图再转ndarray

# 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}')

梯度归0的时机

# 优化算法使用梯度更新完参数之后,需要重新计算梯度,先要清空梯度

6李沐动手学深度学习v2/线性回归的简洁实现

全连接层

# 全连接层就是线性层。入参(输入神经元个数,输出神经元个数)
# 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)

7李沐动手学深度学习v2/图像分类数据集

8李沐动手学深度学习v2/逻辑回归(softmax回归(分类))从0开始实现

9李沐动手学深度学习v2/逻辑回归(softmax回归(分类))简洁实现

逻辑回归

##引入包##
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))

问题

10李沐动手学深度学习v2/MLP (Multilayer Perceptron) 多层感知机从0开始实现

11李沐动手学深度学习v2/MLP (Multilayer Perceptron) 多层感知机简洁实现

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)

12李沐动手学深度学习v2/数据复杂度与模型容量选择不当造成的 过拟合和欠拟合现象

13李沐动手学深度学习v2/权重衰退从0开始实现

总结

  • 正则项(惩罚项): λ \lambda λ越大, w w w选择范围越小,降低模型复杂度,避免过拟合
  • 一般lambda=1e-3=0.001,lambda不会寻到1等等大的值
  • 正则项(惩罚项)效果一般,权重衰退用的已经很少了

14李沐动手学深度学习v2/权重衰退简洁实现

15李沐动手学深度学习v2/丢弃法 (dropout) 从0开始实现

17李沐动手学深度学习v2/实战kaggle比赛,房价预测

18李沐动手学深度学习v2/自定义网络,使用层自定义网络

总结

  • 任何层或者网络都是nn.Module的子类

自定义网络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)

19李沐动手学深度学习v2/自定义层,使用参数自定义层

创建带有参数层

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))

20李沐动手学深度学习v2/参数管理

参数访问

# 访问指定层所有参数
# 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]

21李沐动手学深度学习v2/读写文件,加载和保存张量

加载和保存一个张量

# 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()

22李沐动手学深度学习v2/GPU

注意

  • GPU内存上的张量运算,需要保证张量在同一张GPU上
  • 确定模型和参数存储在同一个gpu上

常用

# 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())

try_gpu() try_all_gpus()

# 定义函数,允许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')]

24李沐动手学深度学习v2/图像卷积,二维卷积

25李沐动手学深度学习v2/填充和步幅

# 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

27李沐动手学深度学习v2/池化层

单通道二维池化

# 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)

28李沐动手学深度学习v2/卷积神经网络,LeNet

# 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)

29李沐动手学深度学习v2/深度卷积神经网络,AlexNet

对比LeNet

  • 更大,更深
  • ReLU 激活函数
  • dropout 正则化

30李沐动手学深度学习v2/使用块的网络,VGG

对比AlexNet

  • 更大更深的AlexNet
  • VGG块,模型架构更加规范

结构:

  • VGG块

VGG

  • 使用可重复使用的卷积块,构建更大更深的网络

类比:

  • 学习的套路,AlexNet学习没有套路可言

31李沐动手学深度学习v2/网络中的网络,NiN

为什么出现:

  • 由于全连接层的参数量过大,考虑完全不使用全连接层,使用1x1的卷积层替代全连接层

结构:NiN块

NiN

  • 3部分组成,1个卷积层,2个1x1的卷积层(相当于全连接层,比全连接层的参数量少,不容易过拟合)
  • 最后1个NiN块的输出通道数等于类别数
  • 最后1个池化层是平均池化层,kernel_size=最后1个NiN块输出的图片大小
  • 最后使用展平层,可以获得所有通道的最大值
  • NiN块网络中没有使用任何全连接层

32李沐动手学深度学习v2/含并行连结的网络,GoogLeNet

  • idea:难以寻找合适的卷积层,我全都要,inception块各种方式的大杂烩
  • inception块用4条有不同超参数的卷积层和池化层来抽取不同的信息
  • GoogleNeti使用了9个Inception:块,是第一个达到上百层的网络
  • GoogLeNet实现复杂,不受欢迎
  • GoogLeNet V3,V4常使用

结构:

  • inception块,学习方式
  • stage,学习阶段

GoogLeNet总结

  • 学习分阶段,每个阶段做每个阶段的事情
  • 每个阶段以多种方式进行学习,多种学习方式并行
  • 一般,inception块,不改变宽高,改变通道数量

33李沐动手学深度学习v2/批量归一化 mini-batch normalization

u B = 1 B ∑ i ∈ B x i u_B=\frac{1}{B}\sum\limits_{i \in B}x_i uB=B1iBxi
σ 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=B1iB(xiuB)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,

总结数值稳定性:

  • 答:数值稳定性作用,防止梯度消失和梯度爆炸,保证正向每层输出的稳定性和反向梯度的稳定性
  • 答:权重初始化作用,训练开始时数值稳定性,保证训练开始时输出的稳定性,不能保证训练中输出的稳定性
  • 答:使用靠近y=x的激活函数作用,保证正向每层输出的稳定性和反向梯度的稳定性
  • 答:归一化作用,训练中数值稳定性,训练中网络层输入数据的稳定性

总结批量归一化

  • 批量归一化的作用:学习输入层的时候,输入层改变时,避免变化输出层
  • 批量归一化的作用:网络层输入数据的稳定性
  • 批量归一化固定小批量中的均值和方差,学习出合适的缩放gamma和偏移mean
  • 批量归一化层作用在全连接层的特征上:全连接层》批量归一化(作用在全连接层的特征上)》激活函数
  • 批量归一化层作用在卷积层的输出通道上:卷积层》批量归一化(作用在卷积层的输出通道上)》激活函数
  • 丢弃层:全连接层》批量归一化》激活函数》不使用丢弃层了
  • 各种归一化:https://blog.csdn.net/u013289254/article/details/99690730

34李沐动手学深度学习v2/残差网络,ResNet

必须要学习的神经网络
可实现千层网络

idea:

  • 把握学习的方向
  • 越复杂的模型不一定月靠近最优理想模型
  • 简单模型学习到的东西,复杂的模型一定能够学习到
  • 将简单的模型学习到的东西保留到复杂的模型中

实现

  • f(x)=x+g(x)
  • 本层 上层 下层,下层的输入=上层输出+本层输出
  • ResNet块参数的 输出通道数变成输入通道数的两倍,步幅=2,通道数翻倍,宽高减半,需要1x1的卷积来统一通道数量

为什么叫残差

  • 在前一层的基础上,一层层叠加训练。先得到一个简单的函数,再一层层的叠加,最终生成一个复杂的函数

结构

  • Residual_block块
  • ResNet_block块
  • 分stage

类比

  • 保留之前的知识
  • 把握学习的方向
  • 把握学习的阶段

35李沐动手学深度学习v2/多GPU训练,数据并行,从0开始实现

你可能感兴趣的:(深度学习,深度学习,python,机器学习)