个人学习笔记,如有错误欢迎指正!
预备课
-
课程必备网站:[课程主页][https://courses.d2l.ai/zh-v2],[教材][https://zh-v2.d2l.ai/],[课程论坛讨论][https://discuss.d2l.ai/c/16],[Pytorch论坛][https://discuss.pytorch.org/]
-
深度学习是人工智能最热门的领域,核心是神经网络,应该想学习一门语言一样学习它。
-
课程中每一个章节是一个jupyter记事本,可以在[这里][https://zh.d2l.ai/]下载。
-
课程结构:
- 介绍深度学习的经典和最新模型
- 机器学习基础
- 代码实践
-
课程内容:
- 深度学习基础
- 卷积神经网络
- 循环神经网络
- 注意力机制
- 优化算法
- 高性能计算
- 计算机视觉
- 自然语言处理
-
学习目标:
- what–有哪些技术
- how–如何实现和调参
- why–背后的原因
-
适合人群:现在就开始的人!
一、深度学习介绍
- AI地图:
- 深度学习在应用上的突破:图片分类–属于哪一类图片、物体检测–检测到图片内什么物体在哪里、物体分割–一个像素属于那个物体、样式迁移–图片给风格融合、人脸合成–生成假的人脸、文字生成图片、文字生成–问一个问题,机器给出回答或者提出要求机器生成代码、无人驾驶。
- 具体应用:广告点击,在搜索框中搜索东西,给出广告。
- 第一步触发:得到用户输入,准备广告。
- 第二步点击率预估:人会点击广告的概率。是一个机器学习的模型,将广告的展现和用户点击作为训练集,提取广告特征和用户点击量,训练模型。
- 第三步排序:广告点击率*厂商给的竞价==排序参考。
- 可能的职业规划:
二、安装
这个在我另外一篇笔记中有详细的跟安记载。下面仅为听课笔记。
-
使用conda环境:
conda env remove d2l-zh conda creat -n -y d2l-zh python=3.8 pip conda activate d2l-zh
-
安装需要的包:
pip install -y jupyter d2l torch torchvision
-
下载代码并执行:
wget https://zh-v2.dal.ai/d2l-zh.zip # 全部代码 unzip d2l-zh.zip jupyter notebook
03节,这节后面是在新服务器上运行的操作~
sudo apt update
sudo apt install build-essential
sudo apt install python-3.8
sudo apt install zip
# 安装miniconda
wget 安装连接
bash 文件名.sh
bash # 进入conda环境
# 创建环境
# 安装软件
# 下记事本
# 解压
unzip 文件名
# 文件分为四个版本,这里使用的是pytorch版本
# 课程讲解用的幻灯片版本的,在github可以下载,使用时需要一个插件:pip install rise
jupyter notebook
# 会显示一个目录,这个连接是在远端机器上的,需要map到本地(或者配置文件让它允许远程访问)首先找到机器的ip地址
ssh -L8888:localhost:8888 ubuntu@机器ip地址 # 其中-L表示映射
# 可以在课程处点击colab连接,安装提示所需的插件,不需要配环境,上面的代码就都可以运行了!
三、数据操作和数据预处理
1. 数据操作
1.1 N维数组
机器学习和神经网络中主要的数据结构:N维数组。
其中4-d可以表示一次读取很多张图片,其中的批量大小就是batch-size
5-d可以表示视频的批量
1.2 创建数组
创建数组需要内容如下:
- 形状
- 每个元素的数据类型
- 每个元素的值
1.3 访问与赋值
- 一个元素–正常访问
- 多个元素–切片–可以跳着访问(设置步长)–可以倒着访问–可以多个元素一起赋值
1.4 实现
import torch # 导入包
张量表示一个数值组成的数组,这个数组可能有多个维度,创建:
# 张量是数学上一个严格的定义;array是计算机语言
x = torch.arange(12, dtype=torch.float32)
通过shape来访问张量的形状和元素总数
x.shape
x.numel()
x.dim() # 快速查看维数
改变一个张量的形状而不改变元素数量和元素值
x2 = x1.reshape(a, b) # 变为a行b列的张量
# 改变形状,创建一个view
# 这里两个x用的一个地址,改变x2,x1也变
创建使用全0、全1、其他常量、特定分布中随机采样的数字、由列表为每个元素赋确定的值
torch.zeros((维度))
torch.ones((维度))
torch.tensor([[数值],[数值]...]) # 里面有浮点数就是浮点张量,只有整数就是整数张量
常见的标准算术运算符(+
-
*
/
**
torch.exp(张量)
),以及可以按元素运算
也可以将多个张量连结在一起
# 首先得到两个张量x y
torch.cat((x,y), dim=指定合并的维度,两个张量在该维度上会变成两个张量该维度的值相加后的值)
通过逻辑运算符构建二元张量
x == y # 生成和x y一样形状的张量,每个值是表示x y是否相等的bool量
张量求和,得到只有一个元素的张量
x.sum()
形状不同,但维度相同,且同一维度上的大小是整数倍关系,时仍然可以操作,广播机制
广播机制
(1,2)+(3,1)-->(3,2)+(3,2) # 其中第一个张量的第一个维度复制3份,第二个张量的第二个维度复制2份
执行原地操作
对于很大的张量需要避免不断复制,浪费内存
z = torch.zeros_like(y)
z[:] = x + y
# 这样z之前的内存就不会被析构掉,而仅仅是改写z内的数值
转为NumPy张量,大小为1的张量会变成标量
a = x.numpy()
b = torch.tensor(a)
2.数据预处理
2.1读取数据
-
csv
文件的含义:每一行是一个数据,每一列是用逗号分开的。# 首先编写 import os os.makedirs(os.path.join('..', 'data'), exist_ok=True) # 前一个表示路径 data_file = os.path.join('..', 'data','house_tiny.csv') with open(date_file, 'w') as f: f.write('Num, Place, Price\n') # 列名 f.write('Na,W,100\n') f.write('2,Na,200\n')
-
使用
import pandas as pd data = pd.read_csv(data_file)
-
处理:为了处理缺失数据,典型的方法包括差值、删除:
字符:独热编码
inputs = data.iloc[:, 0:2] inputs = imputs.fillna(inputs.mean()) # 将全部的缺失值填成该类的其他值的平均值 # 对于没有值的类别值或离散值,将NaN识别为一个类别,其他字符识别为一个类别 # 是哪一类的值就为1--类似独热编码 inputs = pd.get_dummies(inputs, dummy_na=True) # 这样全部转换为数值,就可以转换为张量了
3. 线性代数基础
3.1 标量
一个值,可以进行简单数学计算,长度就是其绝对值。
3.2向量
一行值,也可以进行简单操作,各个对应元素操作,得到一行新值。
长度计算如下:
向量可以点乘也可以正交:
点成:
a T b = ∑ i a i b i a^Tb=\sum_ia_ib_i aTb=i∑aibi
正交:
a T b = ∑ i a i b i = 0 a^Tb=\sum_ia_ib_i=0 aTb=i∑aibi=0
3.3 矩阵
向量的扩展,也是每个元素相加,每个元素乘以一个系数、每个元素求sin值,得到一个矩阵结果。但是矩阵乘法不一样。
矩阵乘法
实现了空间上的扭曲(可以参考PCA降维)
范数
矩阵的长度
特殊矩阵:对称、反对称矩阵、正交矩阵(所有的行都相互正交且都有单位长度,乘以自己的转置得1)、置换矩阵(置换矩阵是正交矩阵)
置换阵:
正定
正定矩阵乘以任意一个列向量、行向量都大于等于0
特征向量和特征值
不会被矩阵改变方向的向量,对称矩阵总是可以找到特征向量。
4. 线性代数实现
4.1 标量
标量就是一个值的张量
x = torch.tensor([3.0])
4.2 向量
向量就是标量值组成的列表
x = torch.arange(4)
# 可以通过索引访问
len(x)
x.shape
4.3矩阵
A = torch.arange(20).reshape(5,4) # 创建
A.T # 转置
对于对称矩阵,矩阵=其转置
4.4多轴数据
向量是标量的推广,矩阵是向量的推广,我们可以构建具有更多轴的数据结构。
最小矩阵一行有多少个是最后一维,最小矩阵一共多少行是倒数第二维。
给定具有相同形状的任何张量,任何按元素二元运算的结果都将是相同形状的张量。
# 重新分内存(区别:copy分深浅,不一定新分配内存)
B = A.clone()
4.4.1 按元素乘
*
两个矩阵的按元素乘法成为哈达玛积(Hadamard product),数学符号⊙
一个张量一个标量运算得到的结果是这个标量和张量中每个值进行相乘。
4.4.2其他运算
- 求和
# 矩阵求和
x.sum()
# 任意形状张量求和
# 求和张量的指定轴
# 求和一个维度
x1 = x.sum(axis=0) # 结果x1中没了x.shape[0]表示的这一维度
# 求和两个维度
x2 = x.sum(axis=[0,1]) # x2中没了x中前两个维度,相当于把那两个维度“拍扁”
- 求均值
A.mean() # 或average
A.sum() / A.numel()
# 按照某一维度求均值
A.mean(axis=0)
A.sum(axis=0) / A.shape[0]
-
不丢掉维度进行计算
根据上面的方法,在某一维度上计算会丢掉某一维度,先当与三维矩阵按一个维度计算后得到二维矩阵。
保留下来其实也只剩下一个元素了,好处就是保留了这一维度就可以使用广播机制了
A.sum(axis=1, keepdims=True) # 计算完后还有原来的shape[1],不过只剩下一个元素了
-
累加求和
A.cumsum(axis=0)
- 点积:相同位置按元素乘积的和,结果是一个标量
# 两个同型矩阵
torch.dot(x, y)
- 矩阵乘以向量
# 矩阵m*n和向量n*1,得到m*1的
torch.mv(A, t)
- 矩阵乘以矩阵
# m*n和n*s得到m*s
torch.mm(A,B)
- 范数:一二范数、佛罗贝尼乌斯范数
# 向量的
torch.norm(x) # 二范数所有元素平方和开根号
torch.abs(x).sum() # 一范数所有元素绝对值和求和
# 矩阵的
torch.norm(torch.ones((a, b))) # 是矩阵元素的平方和的平方根,相当于先将矩阵拉成一个列向量,然后开根号。
4.4.3补充
按哪一个轴求sum就是将哪一个轴“拍扁”,结果中没有这一个轴了。
保持就是将求和的那个轴变为1。
四、矩阵运算
主要讲矩阵如何求导数,所有优化模型的求解都是在求导数。
1. 导数
就是切线的斜率
2. 亚导数
就是“角”,不可求导,亚导数将导数拓展到不可微的函数。
简单说来就是分段求导。
3. 梯度
将导数拓展到向量。
梯度指向值变化最大的方向
以下表明向量与标量在求导的不同位置时,不同的结果:
下图中对应结论“形状”如下:
标量 向量
向量 矩阵
3.1上标量,下向量
图中下面是一个例子: y = x 1 2 + 2 x 2 2 , x = [ x 1 , x 2 ] y=x_1^2+2x_2^2,x=[x_1,x_2] y=x12+2x22,x=[x1,x2],其中切线的垂直方向就是梯度,这里梯度就是和等高线正交的方向,显然这个方向就是值变化最大的方向。
结论
最后一个表示内积(就是向量元素相乘再相加), < u , v > = u T v <u,v>=u^Tv <u,v>=uTv
同理,上向量下标量时:
这里分子布局,相当于分子是列向量,分母是行向量,对分母进行了一个转置
3.2 向量关于向量
结论
最后一个是因为 x T A = A T x x^TA=A^Tx xTA=ATx
3.3 将输入拓展到矩阵
被挡住部分是(m, l, k, n)
维度:分子不变,分母转置
记法:
上面的维度是正着的,下面的维度要反过来,在上下都是矩阵时,前两个维度来自分子矩阵,后两个来自转置后的分母矩阵。
补充
机器学习处理NP问题,一般不处理P问题,也就很难达到最优解。
五、自动求导
1.向量的链式法则
拓展到向量
例子:
2. 自动求导
对于几百层的神经网络,需要自动求导。
自动求导计算一个函数在指定值上的导数,有别于公式推出的符号求导和用lim x和x+h数值拟合的数值求导。
自动求导需要使用计算图。
2.1 计算图
首先将代码分解成操作子。
将计算表示成一个无环图。
例如:
计算图可以通过显示和隐式构造。
2.1.1 显式构造
tensorflow/theano/mxnet
form mxnet import sym
a=sym.var()
b=sym.var()
c=2*a+b
# 先给出定义,给出ab数值求出c,常见于数学上
2.1.2 隐式构造
pytorch/mxnet
from mxnet import autograd, nd
with autograd.record():
a=nd.ones((2,1))
b=nd.ones((2,1))
c=2*a+b
# 放在一个地方,把构造记录下来
# 好处:对于动态计算图,可以更好地适应,因为是先正着计算一边然后倒着求
# 缺点:慢
2.2 自动求导两种模式
有了计算图后两种自动求导的方式:
-
链式法则:
链式法则有两种计算方法:
- 正向积累:从x出发
- 反向积累、又称反向传递:从y出发
反向传递很重要!
反向传递总结:
- 构造计算图
- 前向:从叶到根,执行图,存储中间结果。
- 反向:从根到叶,相反方向执行图,求导,需要去除不需要的枝,因为对谁求导才需要谁,不对谁求导谁的叶不需要。
例子:
计算复杂度:
其中操作子表示神经网络的层数
内存复杂度是神经网络耗内存的原因。
正向累计从叶节点开始计算,每个叶节点要从叶到根扫完整的一遍;而反向每个节点只用扫一遍。
3. 自动求导的实现
假设我们需要对函数 y = 2 x T x y=2x^Tx y=2xTx关于列向量x求导:
import torch
x=torch.arange(4.0)
计算y关于x的梯度前,需要一个地方来存储梯度。
x.requires_grad_(True) # 等价于x=torch.arange(4.0, requires_grad=True)
x.grad # 默认值是None,这样就可以
计算y
y=2*torch.dot(x,x)
放到jupyter里学习啦:
这里最后应该是ture,看了很久不知道为啥我的输出false,反正我理解了,就这吧。
六、线性回归+基础优化算法
1. 线性模型
- 线性模型有最优解,也是神经网络能够处理的唯一有最优解的问题
- 线性模型可以看作是单层神经网络
- 线性模型,输出加权和+标准偏差: y = < w , x > + b y=<w,x>+b y=<w,x>+b
- 训练损失,也就是评定标准,最小化损失来学习参数。(平方通常除以2来方便求导)
- 显式求解:
总结:
- 线性回归是对n维输入的加权,外加偏差
- 使用平方损失来衡量预测值和真实值的差异
- 线性回归有显式解
- 线性回归可以看做是单层神经网络
2. 基础优化算法
2.1 梯度下降
就像下山一样,每次沿着梯度最大方向前进一小步
- 首先挑选一个初始值 w 0 w_0 w0
- 迭代重复参数,
w
t
=
w
t
−
1
−
η
∂
l
∂
w
t
−
1
w_t=w_{t-1}-\eta \frac{\partial l}{\partial w_t-1}
wt=wt−1−η∂wt−1∂l
- 沿梯度方向将增加损失函数值,因为是负的,因此是梯度下降最快的方向
- 学习率:步长的超参数(人为指定,不能太小,也不能太大)
2.2 小批量随机梯度下降
- 在整个训练集上计算梯度太贵,我们可以随机采样b个样本 i 1 , i 2 , . . . , i b i_1,i_2,...,i_b i1,i2,...,ib来近似损失 1 b ∑ i ∈ I b l ( x i , y i , w ) \frac{1}{b}\sum_{i\in I_b}l(x_i,y_i,w) b1∑i∈Ibl(xi,yi,w)
- b是批量大小,另一个重要的超参数(不能太小:不适合并行计算利用计算资源&不准确;不能太大:内存消耗增加,如果样本类似会浪费计算资源)
总结:
- 梯度下降通过不断沿着反梯度方向更新参数求解,不需要知道显式解,只需要知道如何求导即可
- 小批量随机梯度下降是深度学习默认的求解算法
- 两个重要的超参数是批量大小和学习率,如何选择非常重要
3. 线性回归的全实现
从底层开始实现
需要内容:训练集&训练集划分&模型初始化&模型定义&损失函数&优化算法&训练过程
4. 线性回归的调包实现
可以使用深度学习框架(nn)来简洁地实现线性回归模型,数据预处理更加简单
总结
- 损失求平均:数值会小一些,也可以将学习率除以n ,没有别的影响
- 平方损失的好处在于可以很简单地求导
- 学习率的确定:找一个平滑的函数,学习率影响不大;初始化合理一些(数据稳点性,后面会讲);快速找到最合适的学习率的方法,有待补充。
- batchsize其实越小对收敛越好,采样小噪音大,噪音对神经网络还是有好处的,防止过拟合,泛化性更好。
- 学习率和批次一般不太影响收敛
- 随机梯度下降的随机是批次随机的截取
- 求一阶而非二阶的原因是,两个模型损失&优化:都是错的,因为求不到最优解,关心的是最终收敛到哪里。
- yield比return的好处是不需要每要一个跑一遍
- 样本大小不是批量数的整数倍,最后一搏不完整;丢掉;去别的批次里随机取出一些补全。
- 学习率衰减:学习率不断下降。后面会自动更新学习率,因此不做衰减问题不大。
- 收敛判断:两个epoch后参数变化不大,或者取验证数据集进行验证。
- 一般情况下实际loss太复杂,推导不出导数为0的解,因此只能逐个batch逼近。
- 参数初始化可以随机,可以固定。
- 除法就可能出现nan
- 最后一个只有forward没有backward因此不需要梯度清零
七、softmax回归+损失函数+图片分类数据集
1. softmax回归
1.1 回归于分类的区别
-
回归估计一个连续值
- 单连续数值输出
- 自然区间R
- 跟真实值的区别作为损失
-
分类预测一个离散值(常见的:mnist、imagenet、kaggle上分类问题)
- 通常多个输出
- 输出i是预测为第i类的置信度
1.2 回归到多类分类
1.2.1均方损失
- 对类别进行一位有效编码
- 使用均方损失训练
- 最大值为预测
1.2.2无校验比例
- 对类别进行一位有效编码
- 最大值作为预测
- 需要更置信的识别正确类(也就是正确类的值大于非正确类)
1.2.3校验比例
- 输出为o的向量, y ^ \hat{y} y^输出匹配概率(非负,和为1)
y ^ = s o f t m a x ( o ) , h i ^ = e x p ( o i ) ∑ k e x p ( o k ) \hat{y}=softmax(o),\hat{h_i}=\frac{exp(o_i)}{\sum_k exp(o_k)} y^=softmax(o),hi^=∑kexp(ok)exp(oi)
- 概率y于 y ^ \hat{y} y^的区别作为损失
1.3 Softmax和交叉熵损失
- 交叉熵损失常用来衡量两个概率的区别 H ( p , q ) = ∑ i − p i l o g ( q i ) H(p,q)=\sum_i-p_ilog(q_i) H(p,q)=∑i−pilog(qi)
- 将它作为损失 l ( y , y ^ ) = − ∑ i y i l o g y i ^ = − l o g y y ^ l(y,\hat{y})=-\sum_iy_ilog\hat{y_i}=-log\hat{y_y} l(y,y^)=−∑iyilogyi^=−logyy^,其中 y i y_i yi是只有一个类为1,其余类为0,因此只需要拿出为1的那个类取-log
- 其梯度是真实概率和预测概率的区别 σ o i ( y , y ^ ) = s o f t m a x ( o ) i − y i \sigma_{o_i}(y,\hat{y})=softmax(o)_i-y_i σoi(y,y^)=softmax(o)i−yi
总结
- softmax回归是一个多类分类模型
- 使用softmax操作子得到每个类的预测置信度
- 使用交叉熵来衡量预测和标号的区别,作为损失函数
补充独热编码(one-hot):
又称一位有效编码,其方法是使用N位 状态寄存器 来对N个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候,其中只有一位有效。
2. 常见的损失函数
2.1 L2 loss
- 又叫均方损失
l ( y , y ‘ ) = 1 2 ( y − y ‘ ) 2 l(y,y`)=\frac{1}{2}(y-y`)^2 l(y,y‘)=21(y−y‘)2
似然函数
e − L e^{-L} e−L
- 展示:
- 蓝色:损失函数曲线,y=0时l的值
- 绿色: e − l e^{-l} e−l
- 橙色:梯度,可以看到,越远时梯度更大
2.2 L1 loss
- 不一定需要很远梯度很大,绝对值损失函数
l ( y , y ‘ ) = ∣ y − y ‘ ∣ l(y,y`)=|y-y`| l(y,y‘)=∣y−y‘∣
- 展示:
- 梯度永远是常数,稳定
- 但是零点处不可导,末期不稳定
2.3 Huber`s Robust loss
- 解决求导和梯度突变问题,保留L1 L2优点
l ( y , y ‘ ) = { ∣ y − y ‘ ∣ − 1 2 i f ∣ y − y ‘ ∣ > 1 1 2 ( y − y ‘ ) 2 o t h e r w i s e l(y,y`)=\big\{{|y-y`|-\frac{1}{2}\quad if|y-y`|>1\atop \frac{1}{2}(y-y`)^2\quad otherwise} l(y,y‘)={21(y−y‘)2otherwise∣y−y‘∣−21if∣y−y‘∣>1
3. 图像分类数据集
常见的性能瓶颈:读数据起码要比训练快一点
4. softmax全实现
补充:多维张量的下标访问
其中y_hat就是[[0,1], [0,2]]相当于访问[0,0]和[1,2],但是将最终结果放在一个张量中
具体实现细节:
补充:python的*解包
- 星号
*
的使用:比如一个list有五个元素,可以去掉某几个后,剩下的连续数值都会放在带星号的变量里;也可以用作函数传参,最后一个可以有多个输入(之后的需要指定参数名)
- 星号与双星号区别:当函数的参数是双星号时,表明这个参数是字典,但是这条只能在函数定义中使用。
- 压包过程:
zip
将多个list一次各取一个,压成一对返回 - 压编号:
enumerate
将list元素与其序号一起压包返回 - 解包与压包一起可以实现转置:
- 一些元素不用时,保存到
_
里,可以让读代码的知道这个元素是不要的
5. softmax调包实现
大概:实现模型、损失函数、优化算法、开始训练(对于每一epoch:1.训练,转到训练模式,计算net得到y_hat,计算损失,清空梯度,backward计算梯度,优化算法更新参数;2.测试)
loss曲线不出来,将交叉熵损失加上参数reduction='none'
八、多层感知机
1. 感知机
1.1 定义
给定输入x,权重w,偏移b,感知机输出
o
=
σ
(
<
w
,
x
>
+
b
)
举
例
可
以
是
:
σ
(
x
)
=
{
1
i
f
x
>
0
0
o
t
h
e
r
w
i
s
e
o=\sigma(<w,x>+b)\quad举例可以是:\sigma(x)=\big\{{1\quad if\quad x>0\atop 0\quad otherwise}
o=σ(<w,x>+b)举例可以是:σ(x)={0otherwise1ifx>0
比线性回归多了一个激活函数,相当于在之前几个网络里加上”一层“,类似于二分类模型,输出不再是实数或者概率,而是一个离散的量。
1.2 训练
1.3 收敛定理
数据范围有限,存在将全部数据正确分割的范围,那么感知机可以在有限步骤中找到其中的解。
1.4 感知机的问题:XOR问题
感知机不能拟合XOR函数,只能产生线性分割面。
总结:
- 感知机是一个二分类模型,是最早的AI模型之一
- 求解算法等价于使用批量大小为1的梯度下降
- 不能拟合XOR函数,导致第一次AI寒冬
2. 多层感知机
-
学习很多层,多层结果共同判断,解决XOR问题。
-
除了输入层和输出层,中间还要有个隐藏层,隐藏层的大小是超参数
2.1 单隐藏层-单分类
输入-》激活-》输出层(上图中 w 2 w_2 w2的函数)
2.1.1激活函数
- 激活函数一定是非线性的,要不可以“化简”,相当于一个线性模型
1. sigmoid激活函数
将输入投影到(0,1)是一个”软的“(曲线)
s
i
g
m
o
i
d
(
x
)
=
1
1
+
e
x
p
(
−
x
)
sigmoid(x)=\frac{1}{1+exp(-x)}
sigmoid(x)=1+exp(−x)1
2. Tanh激活函数
将输入投影到(-1,1)的“软的”
t
a
n
h
(
x
)
=
1
−
e
x
p
(
−
2
x
)
1
+
e
x
p
(
−
2
x
)
tanh(x)=\frac{1-exp(-2x)}{1+exp(-2x)}
tanh(x)=1+exp(−2x)1−exp(−2x)
3. ReLu激活函数
rectified linear unit
R
e
L
U
(
x
)
=
m
a
x
(
x
,
0
)
ReLU(x)=max(x,0)
ReLU(x)=max(x,0)
好处:计算块
2.2 单隐藏层-多类分类
俩者几乎是一样的,多层感知机实现的多类分类相当于在softmax回归上加上一层隐藏层。
相比单层,输出变多个&最后还是要有一个softmax进行处理
2.3 多隐藏层
超参数:
- 隐藏层数
- 每层隐藏层的大小
每一个隐藏层都有自己的参数,隐藏层的激活函数不能少,少了层数就少了,输出不需要激活函数,因为不用避免层数塌陷
一般情况下,单隐藏层的话会设置很大;多隐藏层则不需要设置的和单隐藏层那么大,并且越接近输入层应该越大,因为输出一般较小,层层缩小会较好,损失信息较小。
总结
- 多层感知机使用隐藏层和激活函数来得到非线性模型,解决了XOR问题
- 常用sigmoid、Tanh、ReLU(最常用)
- 使用softmax来处理多类分类
- 超参数为隐藏层数,和各个隐藏层大小
3.代码实现
3.1 全实现
3.2 简洁实现
深度学习的好处,模型变化大,但是代码变化小。mlp(可以转卷积、rnn、transformer)比svn(容易调,但不容易变使用的模型)的好处~
九、模型选择
1. 模型选择
等价于决定超参数
1.1 泛化误差与训练误差
模型训练很容易被一些偶然的特征”吸引“
1.1.1 泛化误差
模型在新数据上的误差
1.1.2 训练误差
模型在训练数据(有标签的)上的误差
1.2 计算两种误差
使用验证数据集和测试数据集来计算两种误差:
- 验证数据集:一个用来评估模型好坏的数据集。(例如:确定多层感知机有多大)
- 例如拿出一般的训练数据
- 不要跟训练数据混在一起
- 测试数据集:只用一次的数据集。
验证数据集上调出模型,因此有可能虚高,在测试数据集上可能不是这样!!!不能代表在新数据集上的泛化能力
1.2.1 K-则交叉验证
当没有足够多的数据时使用(这是常态)
算法:
- 将训练数据分割成k块(常用K=5或10)
- for i=1,…,k
- 使用第i块作为验证数据集,其余的作为训练数据集
- 报告k个验证机误差的平均
训练k次,每次选一块为验证集,其余为训练集,k次取平均作为误差
2. 过拟合与欠拟合
2.1 模型容量
就是模型的复杂度,越复杂容量越大
- 是指拟合各种函数的能力
- 低容量的模型难以拟合训练数据
- 高容量的模型可以记住所有的训练数据
2.1.1 模型容量的影响
调参策略:从最简单的模型开始慢慢复杂
常用泛化误差与训练误差之差来衡量过拟合与欠拟合程度,同时降下来泛化误差,一定程度的过拟合是可以接受的
模型容量足够时,使用一定手段,控制模型容量使得泛化误差下降。
2.1.2 估计模型容量
- 难以在不同的种类算法之间作比较(例如:树模型与神经网络)
- 但是对于给定一个模型种类,两个主要因素:
- 参数的个数
- 参数值的选择范围
2.1.3 VC维
- 对于一个分类模型,VC等于一个最大的数据集的大小,不管如何给定标号,都存在一个模型来对它进行完美分类。
也就是模型可以完美记住的数据集大小。
例如:
- 作用:
- 提供为什么一个模型好的理论依据:可以衡量训练误差和泛化误差之间的间隔
- 深度学习很少用:衡量不够准确、深度学习模型的VC维计算起来很困难
2.2 数据复杂度
多个因素衡量:
- 样本个数
- 每个样本的元素个数
- 时间、空间结构
- 多样性:分类多样性
2.3 数据与模型容量的关系
2.4 过拟合
记住了噪声
2.5 欠拟合
不能正确分类
总结
- 模型容量需要匹配数据复杂度,否则可能导致欠拟合和过拟合
- 统计机器学习提供数学工具来衡量模型复杂度
- 实际中一般靠观察训练误差和验证误差
3. 代码实现
正常:
欠拟合:
过拟合:
补充:
- svm算起来不容易,很难大数据量&可以调节的参数不多;多层感知机就很容易处理大数据量。
- 训练误差:training dataset;泛化误差:testing dataset(只能用一次)
- 时序序列:不能中间采样,必须连续,可以用一个时间点前的作为训练集,其他作为测试集。
- 数据集的处理,可以训练集验证集一起处理,也可以分开处理,要看能不能拿到验证及数据。
- K则交叉验证(k由可以接受的计算成本决定),由于深度学习训练成本太高,因此没什么应用。
- cross validation可以确定超参数,但是不能解决数据问题,最终结果是取很多快的平均。
- 模型参数:w、b这种模型训练需要解决的问题;超参数:是模型选择、设置的确定。
- 总是要调参的,但是过拟合欠拟合告诉我们什么是好的参数
- 可以通过搜索有效设计超参数,间隔不能太小也不能太大(hpo)
- 数据分布不平衡:数据量足够大,随便;数据量不够大,现实世界中是平均的:不平衡数据出现应该一样;现实世界中也不平均:按不平均的来。
- K则交叉验证:
- 首先确定超参数,然后对全部数据训练
- 直接找最好的模型
- 对全部模型留用,结果取均值,增加稳定性(比赛较好,初始化是随机的,多几个可能效果好)
- validation是验证误差
- loss曲线图每个点对应一个模型,一个epoch结果
- 模型容量:模型能拟合函数能力
- 随机深林梯度不好传,可以进深度学习
- 神经网络是一门语言:一层神经网络理论上就可以拟合全部函数,但是我们训练不出来,因此只能引用新的模型,来帮助训练。
- 剪枝和蒸馏可以提高模型性能。
- 蒸馏:把模型变小,但是性能不要下降太多,相比直接训练出的小模型,性能更好。
- 随机初始化,因此最后集成效果方差会更小,效果更好
- 实际数据消除噪声是最好的
九、权重衰退
最常见的处理过拟合的方法
模型大小比较小or模型参数可选范围小(权重衰退)
1. 使用均方范数作为硬性限制
-
通过限制参数值的选择范围来控制模型容量
-
下图中l是我们的损失函数,w、b是参数:
2. 使用均方范数作为柔性限制(常用)
上面那个优化起来有点麻烦
- 对每个θ都可以找到λ使得之前的目标函数等价于下面(加入惩罚项,可以通过拉格朗日乘子来证明):
m i n l ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 min\quad l(w,b)+\frac{λ}{2}||w||^2 minl(w,b)+2λ∣∣w∣∣2
- 超参数λ控制了正则项的重要程度(λ越大,模型限制越大,可取值范围越小)
- λ=0:无作用,相当于θ为∞
- λ->∞,惩罚项作用很大,渐渐地w*->0
在w大的点,惩罚项的梯度影响更大;w小的点,损失项的梯度影响大–>最终两者平衡。
2.1 参数更新法则
图中 w t + 1 = w t − ( 对 w t 的 梯 度 ) w_{t+1}=w_t-(对w_t的梯度) wt+1=wt−(对wt的梯度),得到更新参数,与之前不同的是多了 η λ w t ηλw_t ηλwt,相当于将梯度变小了一点点,等价于对权重进行了衰退。
总结
- 权重衰退通过L2正则项使得模型参数不至于过大,从而控制了模型的复杂度
- 正则项权重是控制模型复杂度的超参数
3. 代码实现
3.1 全实现
3.2 简洁实现
不再将decay变成loss的计算,依靠loss变大来限制W,而是将梯度计算时,w向下走的程度减小一点,也是在限制权重
补充:
- 目前神经网络还不支持复数,但是可以通过将数据变成两维来解决
- 参数只能在小范围内取参数,那复杂度就会低
- 因为数据有噪音,才会出现过拟合、欠拟合什么的,训练一般都不会训练到最优解,因此才需要权重衰退,训练的很好不需要权重衰退
- 常用权重衰退值: e − 2 , e − 3 , e − 4 e^{-2},e^{-3},e^{-4} e−2,e−3,e−4,影响不大
- 噪音越大,W越大
- 权重衰退对卷积层、全连接层都能用
十、丢弃法
1. 动机
- 一个好的模型需要对输入数据的扰动鲁棒
- 使用有噪音的数据等价于Tikhonov正则
- 丢弃法:在层之间加入噪音
正则:可以避免过拟合,因此丢弃法是一种正则
2. 无偏差的加入噪音
-
对x加入噪音得到x’,我们希望
E[x']=x
-
丢弃法对每个元素进行如下扰动:(一定概率下变为0,一定概率下变大,这样期望是不变的)
x i ′ = { 0 w i t h p r o b a b l i t y p x i 1 − p o t h e r i s e x'_i=\big\{{0\quad with\quad probablity\quad p\atop \frac{x_i}{1-p}\quad otherise} xi′={1−pxiotherise0withprobablityp
3. 使用丢弃法
通常将丢弃法作用在隐藏全连接层的输出上
理解:
4. 推理(测试)中的丢弃法
- 正则项只在训练中使用:他们影响模型参数的更新(对权重产生影响,权重不需要变化时不使用)
- 在推理过程中,丢弃法直接返回输入
h=dropout(h)
- 这样也能保证确定性的输出
总结:
- 丢弃法将一些输出项随机置0来控制模型复杂度
- 常作用在多层感知机(全连接层)的隐藏层输出上
- 丢弃概率(0.5、0.9、0.1最为常见)是控制模型复杂度的超参数
5. 代码实现
5.1 全实现
5.2 简洁实现
补充:
- dropout随机置0,梯度就不会更新;需要确保概率一定,这样保证不会欠拟合或者过拟合
- 机器学习没有正确性,只有效果好不好
- dropout随机丢弃,深度学习的可重复性很重要,但是只要固定住随机种子,即可保证可重复性;当然对于神经网络,权重初始化也是随机的,需要固定;此外cudnn也会造成随机(需要禁掉),因为精度不够,加的顺序不同,得到的结果可能不同。但是没必要可重复,随机性让模型更稳定~。
- 丢弃法训练时随机丢弃神经元,训练时不会。
- 每层在调用前向运算时,丢弃一次
- BN是给卷积层用的,dropout是给全连接层用的
- 由于丢弃时保证了期望不变,因此推理时不丢弃等价于一个平均而非多个神经元的”叠加“
- dropout随机选几个子网络平均,思想类似于随机深林多决策树做投票。
- 多种解决过拟合问题的方法可以一起使用,dropout的优点在于好调参
- 需要模型够强(甚至可以设置的稍微大一点点,dropout也设置大一点点),但是通过正则让模型不要学偏,例如64层训练效果很不错,那可以通过变成128层,丢弃一半,达到相同的效果~这样可能效果更好。
- dropout可能会导致参数收敛更慢,但是不必非要调学习率
十一、数值稳定性+模型初始化和激活函数
1. 数值稳定性
神经网络很深时数值容易不稳定
1.1 神经网络的梯度
- 考虑如下有d层的神经网络,t表示层数,h表示隐藏层的输出,y表示目标函数,f(.)表示隐藏层的函数
h t = f t ( h t − 1 ) a n d y = l ⋅ f d ⋅ . . . ⋅ f 1 ( x ) h^t=f_t(h^{t-1})\quad and\quad y=l·f_d·...·f_1(x) ht=ft(ht−1)andy=l⋅fd⋅...⋅f1(x)
- 计算损失 l l l关于某一层的参数 W t W_t Wt的梯度(h是向量,向量对向量的导数是矩阵,因此一共有d-t次矩阵乘法,太过复杂)
∂ l ∂ W t = ∂ l ∂ h d ∂ h d ∂ h d − 1 . . . ∂ h t + 1 ∂ h t ∂ h t ∂ W t \frac{\partial l}{\partial W^t}=\frac{\partial l}{\partial h^d}\frac{\partial h^d}{\partial h^{d-1}}...\frac{\partial h^{t+1}}{\partial h^t}\frac{\partial h^t}{\partial W^t} ∂Wt∂l=∂hd∂l∂hd−1∂hd...∂ht∂ht+1∂Wt∂ht
1.1.1 梯度爆炸与梯度消失
上述多次的矩阵乘法可能带来数值稳定性的常见两个问题
- 梯度爆炸:梯度均大于1,多层运算后,累乘会变得巨大无比。
- 梯度消失:梯度均小于1,多层运算后,累乘会变得巨小无比
例子:MLP中ReLU作为激活函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qrF5litj-1652086477293)(https://gitee.com/tianyiduan/figurebed/raw/master/img/IMG_0365(20220501-210951)].JPG)
假设此时使用ReLU作为激活函数,导数不是0就是1,最终梯度是 ∑ i = t d − t ( W i ) T \sum^{d-t}_{i=t}(W^i)^T ∑i=td−t(Wi)T中没有变为0的数,如果d-t很大这个值会很大
例子:MLP中Sigmoid作为激活函数
假设此时用sigmoid作为激活函数
σ
(
x
)
=
1
1
+
e
−
x
σ
′
(
x
)
=
σ
(
x
)
(
1
−
σ
(
x
)
)
\sigma (x)=\frac{1}{1+e^{-x}}\quad \sigma'(x)=\sigma (x)(1-\sigma(x))
σ(x)=1+e−x1σ′(x)=σ(x)(1−σ(x))
导数越远越小,那么得到的梯度很小,累乘后更小
1.1.2 梯度爆炸产生的问题
- 值超出值域:对于16位浮点数(常用)尤为严重
- 对学习率敏感:
- 学习率太大->大参数值->更大的梯度
- 学习率太小->训练无进展
- 我们可能需要在训练过程中不断调整学习率
1.1.3 梯度消失产生的问题
- 梯度值变成0:对于16位浮点数尤为严重
- 训练没有进展:不管如何选择学习率都不行
- 对于底层尤为严重
- 仅仅顶部层训练的较好(梯度反传时由顶向下传,下面累乘的多)
- 无法让神经网络更深
总结:
- 当数值太大或者过小时会导致数值问题
- 常发生在深度模型中,因为其会对n个数累乘
2. 让训练更加稳定
- 目标:让梯度值在合理的范围内
- 将乘法变加法
- 归一化:梯度归一化,梯度裁剪
- 合理的权重初始和激活函数(本节)
2.1 让每层的方差是一个常数
- 我们希望将每层的输出和梯度都看做随机变量,从而将它们的均值(平均值)和方差(离散程度)都保持一致
2.1.1 权重初始化
- 在合理值区间里随机初始参数:会在一个合理的值区间内随机初始化参数
- 原因:在训练开始的时候更容易有数值不稳定
- 远离最优解的地方损失函数表面可能很复杂,这时梯度可能很大,导致梯度很大,梯度变化很大
- 最优解附近表面会比较平
- 使用 N ( 0 , 0.01 ) N(0,0.01) N(0,0.01)来初始可能对小网络没问题,但不能保证深度神经网络
例子:继续MLP
-
均值:
- 假设参数 w i , j t w^t_{i,j} wi,jt是一个独立的同分布,那么我们可以认为其均值为0,方差为 γ t γ_t γt
- 那么隐藏层的输入 h t h_t ht将独立于 w i , j t w^t_{i,j} wi,jt,也就是说当前层的权重与当前层的输入是独立的
- 假设没有激活函数,那么这一层的出入等价于上一层函数处理上一层输入的结果,显然由于系数和输入均值均为0,因此得到的本层的均值还是0。
-
方差: D ( X ) = E ( X 2 ) − E ( X ) 2 D(X)=E(X^2)-E(X)^2 D(X)=E(X2)−E(X)2
- 因此后一项为0,前一项计算如上图
独立的同分布
就是研究对象产生样本或者研究人员挑选样本时会使用的两个假设。
- 反向均值和方差:
- 可以看到均值为0很好办,方差为固定值还有两个特别条件
- 下图第一个=1使得输出满足条件
- 下图中第二个=1使得反向方差一致
- 这两个条件很难同时满足,因为 n t − 1 和 n t n_{t-1}和n_{t} nt−1和nt分别为输入和输出,除非素输入等于输出,那么下面这个可以折中满足:
Xavier初始化
按照上图中的分布进行初始化结果更好
假设线性的激活函数
通常激活函数不会是线性的,为了更简单的理解,这里以线性为例:
如下图,激活函数需要过原点,并且系数平方为1
- 反向时同理:
综上所述,激活函数必须是 f ( x ) = x f(x)=x f(x)=x
在x=0处,根据泰勒展开,tanh和relu接近于f(x)=x,sigmodi则需要进行调整。
总结:
合理的权重初始化和激活函数的选取可以提升数值稳定性。(使得每一层的输出和梯度都是均值为0,方差为固定值;初始化方法如上图,激活函数的选择如上图)
补充:
-
nan、inf的产生与解决:
- 产生:inf–太大;nan–除0
- 解决:合理初始化、学习率别大、激活函数对
-
准确率突然掉下来,并稳定,说明参数坏掉了,说明数据稳定性不行;可以先变小学习率,再将数据稳定性变好
-
理解能力:数学公式看不懂,需要学数学,决定走多远;代码能力:会调,决定能不能走。
-
34、68位浮点运算会比16位好很多,更不容易出错,但是计算较慢
-
sigmoid更容易引起梯度消失,但是不是不用就没了,只是可能的原因之一
-
符合正态分布只是好计算,其他分布也可以(我们需要有一个合理的输出值),独立同分布也只是为了计算简单;而且这些仅仅针对初始化时的参数,对后面不一定还是这样,这里只是在找如何初始化更好
-
对于将每一层输出进行限制,相当于做了一个区间的限制(数据的缩放,数值是一个区间,将其拉到任何地方都没关系,只需要在一个合适的区间内表达,硬件处理起来更容易),模型表达能力不会有改变
-
随机初始化,没有最好的选择,Xavier就很不错啦!
-
sigmoid经过调整 4 ∗ s i g m o i d ( x ) − 2 4*sigmoid(x)-2 4∗sigmoid(x)−2后,可以在0附近更接近于 f ( x ) = x f(x)=x f(x)=x,以此增强数据稳定性
-
每次迭代(iterate)后进行过更新,每个batch进行更新,而每个epoch后就已经更新很多次了
-
对于数值稳定性,我们的方法全部都在缓解,但是没有能完全解决的,深度学习发展就是在解决数值稳定性
-
对每一层的方差、均值限制,方差稳定,出现极值概率降低,异常值变少
十二、实战–房价预测
15暂略