个人学习笔记,如有错误欢迎指正!
课程必备网站:[课程主页][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/]下载。
课程结构:
课程内容:
学习目标:
适合人群:现在就开始的人!
这个在我另外一篇笔记中有详细的跟安记载。下面仅为听课笔记。
使用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连接,安装提示所需的插件,不需要配环境,上面的代码就都可以运行了!
机器学习和神经网络中主要的数据结构:N维数组。
其中4-d可以表示一次读取很多张图片,其中的批量大小就是batch-size
5-d可以表示视频的批量
创建数组需要内容如下:
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)
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)
# 这样全部转换为数值,就可以转换为张量了
一个值,可以进行简单数学计算,长度就是其绝对值。
一行值,也可以进行简单操作,各个对应元素操作,得到一行新值。
长度计算如下:
向量可以点乘也可以正交:
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
向量的扩展,也是每个元素相加,每个元素乘以一个系数、每个元素求sin值,得到一个矩阵结果。但是矩阵乘法不一样。
实现了空间上的扭曲(可以参考PCA降维)
矩阵的长度
特殊矩阵:对称、反对称矩阵、正交矩阵(所有的行都相互正交且都有单位长度,乘以自己的转置得1)、置换矩阵(置换矩阵是正交矩阵)
置换阵:
正定矩阵乘以任意一个列向量、行向量都大于等于0
不会被矩阵改变方向的向量,对称矩阵总是可以找到特征向量。
标量就是一个值的张量
x = torch.tensor([3.0])
向量就是标量值组成的列表
x = torch.arange(4)
# 可以通过索引访问
len(x)
x.shape
A = torch.arange(20).reshape(5,4) # 创建
A.T # 转置
对于对称矩阵,矩阵=其转置
向量是标量的推广,矩阵是向量的推广,我们可以构建具有更多轴的数据结构。
最小矩阵一行有多少个是最后一维,最小矩阵一共多少行是倒数第二维。
给定具有相同形状的任何张量,任何按元素二元运算的结果都将是相同形状的张量。
# 重新分内存(区别:copy分深浅,不一定新分配内存)
B = A.clone()
*
两个矩阵的按元素乘法成为哈达玛积(Hadamard product),数学符号⊙
一个张量一个标量运算得到的结果是这个标量和张量中每个值进行相乘。
# 矩阵求和
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))) # 是矩阵元素的平方和的平方根,相当于先将矩阵拉成一个列向量,然后开根号。
按哪一个轴求sum就是将哪一个轴“拍扁”,结果中没有这一个轴了。
保持就是将求和的那个轴变为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^Tv <u,v>=uTv
同理,上向量下标量时:
这里分子布局,相当于分子是列向量,分母是行向量,对分母进行了一个转置
最后一个是因为 x T A = A T x x^TA=A^Tx xTA=ATx
被挡住部分是(m, l, k, n)
维度:分子不变,分母转置
记法:
上面的维度是正着的,下面的维度要反过来,在上下都是矩阵时,前两个维度来自分子矩阵,后两个来自转置后的分母矩阵。
机器学习处理NP问题,一般不处理P问题,也就很难达到最优解。
对于几百层的神经网络,需要自动求导。
自动求导计算一个函数在指定值上的导数,有别于公式推出的符号求导和用lim x和x+h数值拟合的数值求导。
自动求导需要使用计算图。
首先将代码分解成操作子。
将计算表示成一个无环图。
例如:
计算图可以通过显示和隐式构造。
tensorflow/theano/mxnet
form mxnet import sym
a=sym.var()
b=sym.var()
c=2*a+b
# 先给出定义,给出ab数值求出c,常见于数学上
pytorch/mxnet
from mxnet import autograd, nd
with autograd.record():
a=nd.ones((2,1))
b=nd.ones((2,1))
c=2*a+b
# 放在一个地方,把构造记录下来
# 好处:对于动态计算图,可以更好地适应,因为是先正着计算一边然后倒着求
# 缺点:慢
有了计算图后两种自动求导的方式:
反向传递很重要!
例子:
其中操作子表示神经网络的层数
内存复杂度是神经网络耗内存的原因。
正向累计从叶节点开始计算,每个叶节点要从叶到根扫完整的一遍;而反向每个节点只用扫一遍。
假设我们需要对函数 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,反正我理解了,就这吧。
就像下山一样,每次沿着梯度最大方向前进一小步
从底层开始实现
需要内容:训练集&训练集划分&模型初始化&模型定义&损失函数&优化算法&训练过程
可以使用深度学习框架(nn)来简洁地实现线性回归模型,数据预处理更加简单
回归估计一个连续值
分类预测一个离散值(常见的:mnist、imagenet、kaggle上分类问题)
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)
又称一位有效编码,其方法是使用N位 状态寄存器 来对N个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候,其中只有一位有效。
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
l ( y , y ‘ ) = ∣ y − y ‘ ∣ l(y,y`)=|y-y`| l(y,y‘)=∣y−y‘∣
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
其中y_hat就是[[0,1], [0,2]]相当于访问[0,0]和[1,2],但是将最终结果放在一个张量中
具体实现细节:
*
的使用:比如一个list有五个元素,可以去掉某几个后,剩下的连续数值都会放在带星号的变量里;也可以用作函数传参,最后一个可以有多个输入(之后的需要指定参数名)zip
将多个list一次各取一个,压成一对返回enumerate
将list元素与其序号一起压包返回_
里,可以让读代码的知道这个元素是不要的大概:实现模型、损失函数、优化算法、开始训练(对于每一epoch:1.训练,转到训练模式,计算net得到y_hat,计算损失,清空梯度,backward计算梯度,优化算法更新参数;2.测试)
loss曲线不出来,将交叉熵损失加上参数reduction='none'
给定输入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(
比线性回归多了一个激活函数,相当于在之前几个网络里加上”一层“,类似于二分类模型,输出不再是实数或者概率,而是一个离散的量。
数据范围有限,存在将全部数据正确分割的范围,那么感知机可以在有限步骤中找到其中的解。
感知机不能拟合XOR函数,只能产生线性分割面。
学习很多层,多层结果共同判断,解决XOR问题。
除了输入层和输出层,中间还要有个隐藏层,隐藏层的大小是超参数
输入-》激活-》输出层(上图中 w 2 w_2 w2的函数)
将输入投影到(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
将输入投影到(-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)
rectified linear unit
R e L U ( x ) = m a x ( x , 0 ) ReLU(x)=max(x,0) ReLU(x)=max(x,0)
好处:计算块
俩者几乎是一样的,多层感知机实现的多类分类相当于在softmax回归上加上一层隐藏层。
相比单层,输出变多个&最后还是要有一个softmax进行处理
超参数:
每一个隐藏层都有自己的参数,隐藏层的激活函数不能少,少了层数就少了,输出不需要激活函数,因为不用避免层数塌陷
一般情况下,单隐藏层的话会设置很大;多隐藏层则不需要设置的和单隐藏层那么大,并且越接近输入层应该越大,因为输出一般较小,层层缩小会较好,损失信息较小。
深度学习的好处,模型变化大,但是代码变化小。mlp(可以转卷积、rnn、transformer)比svn(容易调,但不容易变使用的模型)的好处~
等价于决定超参数
模型训练很容易被一些偶然的特征”吸引“
模型在新数据上的误差
模型在训练数据(有标签的)上的误差
使用验证数据集和测试数据集来计算两种误差:
验证数据集上调出模型,因此有可能虚高,在测试数据集上可能不是这样!!!不能代表在新数据集上的泛化能力
当没有足够多的数据时使用(这是常态)
算法:
训练k次,每次选一块为验证集,其余为训练集,k次取平均作为误差
就是模型的复杂度,越复杂容量越大
调参策略:从最简单的模型开始慢慢复杂
常用泛化误差与训练误差之差来衡量过拟合与欠拟合程度,同时降下来泛化误差,一定程度的过拟合是可以接受的
模型容量足够时,使用一定手段,控制模型容量使得泛化误差下降。
也就是模型可以完美记住的数据集大小。
例如:
多个因素衡量:
记住了噪声
不能正确分类
正常:
欠拟合:
过拟合:
最常见的处理过拟合的方法
模型大小比较小or模型参数可选范围小(权重衰退)
上面那个优化起来有点麻烦
m i n l ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 min\quad l(w,b)+\frac{λ}{2}||w||^2 minl(w,b)+2λ∣∣w∣∣2
在w大的点,惩罚项的梯度影响更大;w小的点,损失项的梯度影响大–>最终两者平衡。
图中 w t + 1 = w t − ( 对 w t 的 梯 度 ) w_{t+1}=w_t-(对w_t的梯度) wt+1=wt−(对wt的梯度),得到更新参数,与之前不同的是多了 η λ w t ηλw_t ηλwt,相当于将梯度变小了一点点,等价于对权重进行了衰退。
不再将decay变成loss的计算,依靠loss变大来限制W,而是将梯度计算时,w向下走的程度减小一点,也是在限制权重
正则:可以避免过拟合,因此丢弃法是一种正则
对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
通常将丢弃法作用在隐藏全连接层的输出上
理解:
h=dropout(h)
神经网络很深时数值容易不稳定
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 ∂ 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
上述多次的矩阵乘法可能带来数值稳定性的常见两个问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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很大这个值会很大
假设此时用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))
导数越远越小,那么得到的梯度很小,累乘后更小
均值:
方差: D ( X ) = E ( X 2 ) − E ( X ) 2 D(X)=E(X^2)-E(X)^2 D(X)=E(X2)−E(X)2
就是研究对象产生样本或者研究人员挑选样本时会使用的两个假设。
按照上图中的分布进行初始化结果更好
通常激活函数不会是线性的,为了更简单的理解,这里以线性为例:
如下图,激活函数需要过原点,并且系数平方为1
综上所述,激活函数必须是 f ( x ) = x f(x)=x f(x)=x
在x=0处,根据泰勒展开,tanh和relu接近于f(x)=x,sigmodi则需要进行调整。
合理的权重初始化和激活函数的选取可以提升数值稳定性。(使得每一层的输出和梯度都是均值为0,方差为固定值;初始化方法如上图,激活函数的选择如上图)
nan、inf的产生与解决:
准确率突然掉下来,并稳定,说明参数坏掉了,说明数据稳定性不行;可以先变小学习率,再将数据稳定性变好
理解能力:数学公式看不懂,需要学数学,决定走多远;代码能力:会调,决定能不能走。
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暂略