这是我学习李沐老师《动手学深度学习》课程陆陆续续记下来的一些笔记,还有很多不完善之处,希望大家海涵。以后有空还会继续更新~
预备知识
首先,张量的维数等价于张量的阶数。
0维的张量就是标量,1维的张量就是向量,2维的张量就是矩阵,大于等于3维的张量没有名称,统一叫做张量。下面举例:
标量:很简单,就是一个数,1,2,5,108等等
向量:[1,2],[1,2,3],[1,2,3,4],[3,5,67,·······,n]都是向量
矩阵:[[1,3],[3,5]],[[1,2,3],[2,3,4],[3,4,5]],[[4,5,6,7,8],[3,4,7,8,9],[2,11,34,56,18]]是矩阵
3维张量:[[[1,2],[3,4]],[[1,2],[3,4]]]
————————————————
原文链接:https://blog.youkuaiyun.com/shenggedeqiang/article/details/84856051
3-D RGB图片 宽 * 高 * 通道
4-D 一个RGB图片批量 批量 * 宽 * 高 * 通道
5-D 一个视频批量 批量大小 * 时间 * 宽 * 高 * 通道
行和列从零开始
选择一行:[1,:]
选择一列:[:,1]
选择局部:[1:3, 1: ] 意思为选择1,2行内容 选择1列和之后的内容(方括号内左闭右开)
2.1 数据操作
import torch
注:在anaconda prompt里面装包: conda install…
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y #注:**运算是幂运算*
x.reshape(-1, 1) # -1被理解为unspecified value,意思是未指定的,这里表示为很多行,一列
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
我们也可以把多个张量连结(concatenate)在一起, 把它们端对端地叠起来形成一个更大的张量。 我们只需要提供张量列表,并给出沿哪个轴连结。 上面的例子分别演示了当我们沿行(轴-0,形状的第一个元素) 和按列(轴-1,形状的第二个元素)连结两个矩阵。
2.2 数据预处理
从csv文件中加载数据集
import pandas as pd
data = pd.read_csv(data_file)
处理缺失值(插值法和删除法)见P48
2.3 线性代数
长度,维度和形状
向量的长度通常称为向量的维度 (dimension) ,调用len()来访问张量的长度
shape为一个元素组,列出了张量沿着每个轴的长度(维数),对于只有一个轴的张量,shape只有一个元素
x = torch.arange(12).reshape(3,4)
x.shape # output: torch.Size([3, 4])
len(x) # output: 3
矩阵的转置
A.T
张量是描述具有任意数量轴的n维数组的通用方法
两个矩阵按元素乘法成为Hadmard积
2.3.6 降维
x.sum(axis = a) # a为轴的标号 0或1
默认情况下,调用求和的函数会沿所有的轴降低张量的维度,使它变成一个标量
也可以只求同一个轴上的元素,即同一列(轴0)或同一行(轴1)。 如果X
是一个形状为(2,3)的张量,我们对列进行求和, 则结果将是一个具有形状(3,)的向量。
当选择axis = a时,即为把某个axis = a的轴去掉,输入轴a的维度在输出形状中消失
例如:当张量形状为[2,5,4]时,axis = 1 时,输出[2,4],又譬如下图所示
又如果设置 keepdims = True ,此时张量的轴数不变,具体的数值发生变化,如下图所示
**范数:**L2范数是向量元素平方和的平方根,L1范数是向量元素的绝对值之和
2.5 自动微分
符号求导(常规求导)和数值求导(定义法)
显式构造:先给公式再给值;隐式构造:先给值再给公式 (pytorch 隐式构造)
链式法则:正向累计和反向传递
反向传递:从相反方向执行图,并且剪枝
正向传递和反向传递的区别:计算复杂度都为O(n),但正向的内存复杂度也为O(n),因为要存储正向的所有中间结果;而反向传递的内存复杂度为O(1)
x.grad.zero_() # 在默认情况下,Pytorch会累积梯度,我们需要清除之前的值
提问:需要正向和反向都算一遍吗? 答:需要,神经网络需要正向先算对应的值,再反向算一遍
3.1 线性回归
线性模型:线性模型可以看成是单层神经网络
目标:找到使得损失函数最小的参数
梯度下降法:计算损失函数关于模型参数的导数(在这里也可称之为梯度**)**
**小批量随机梯度下降:**在整个训练集上训练太复杂,随机采样b个样本(b为批量大小)来近似损失
批量的选择不能太小,否则不适合并行最大利用计算资源;也不能太大,内存增加浪费计算,如大量样本相似的情况。
两个重要的超参数是批量大小和学习率
3.2 线性回归从零开始实现
见书上P95 较复杂
data_iter函数:该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量,每个小批量包含一组特征和标签。
3.3 线性回归的简洁实现
import random
import torch
from d2l import torch as d2l
from torch.utils import data
true_w = torch.tensor([2, -3.4]) # 定义参数
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000) # 生成数据集
读取数据集
def load_array(data_arrays, batch_size, is_train=True): # 布尔值代表是否需要打乱
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
'''DataLoader 每次取batch_size个 ,shuffle = is_train 意为需要打乱 '''
batch_size = 10
data_iter = load_array((features, labels), batch_size)
data_iter函数:该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量,每个小批量包含一组特征和标签。 在这里使用iter构造Python迭代器,并使用next从迭代器中获取第一项
next(iter(data_iter))
Out:
[tensor([[ 0.2863, 1.6227],
[ 0.1879, 0.8806],
[ 1.6453, -0.2103],
[ 0.0401, 0.0172],
[-0.0684, -0.0581],
[ 0.9568, -0.1471],
[-1.1562, -0.0381],
[ 2.1958, 1.4927],
[-2.0519, -0.6888],
[-1.0362, 0.0165]]),
tensor([[-0.7478],
[ 1.5829],
[ 8.1802],
[ 4.2334],
[ 4.2692],
[ 6.6218],
[ 2.0134],
[ 3.5133],
[ 2.4458],
[ 2.0711]])]
使用框架预定义好的层
# nn是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1)) # Sequential类将多层串联在一起,意为list of layers
初始化模型参数
net[0].weight.data.normal_(0,0.01) # net[0]是指容器里的第一层 权重参数均值为0,方差为0.01的正态分布
net[0].bias.data.fill_(0) # 偏置参数初始化为0
Out:
tensor([0.])
loss = nn.MSELoss() # L2范数,均方误差
trainer = torch.optim.SGD(net.parameters(), lr=0.03) # 定义优化算法 SGD(stochastic gradient descent 随机梯度下降)
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X), y)
trainer.zero_grad()
l.backward()
trainer.step()
l = loss(net(features),labels)
print(f'epoch {epoch + 1}, loss {l:f}')
输出结果:
epoch 1, loss 0.000356
epoch 2, loss 0.000097
epoch 3, loss 0.000096
提问1:平方差值和绝对差值的区别? **答:**绝对差值在零点处不可导
提问2:损失为什么要求平均? **答:**让梯度不至于太大
**提问3:**不管是GD还是SGD如何找到合适的学习率? **答:**① 找一个对lr不敏感的模型 ② 合理的参数初始化
3.4 softmax回归
独热编码(one-hot encoding):独热编码是一个向量,分量和类别一样多。类别对应的分量设置为1,其他所有分量设置为0。在我们的例⼦中,标签y将是⼀个三维向量,其中(1, 0, 0)对应于 “猫”、(0, 1, 0)对应于“鸡”、(0, 0, 1)对应于“狗”。
此时的编码: y ∈ {(1, 0, 0), (0, 1, 0), (0, 0, 1)}
由于一方面我们没有限制,输出的预测o
**softmax函数:**能够将未规范化的预测变换为非负数并且总和为1。为了完成该目标,先求幂,然后将每个求幂后的结果除以它们的总和。
交叉熵损失:
3.5 图像分类数据集
(MNIST)
In [1]:
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
d2l.use_svg_display()
读取数据集
In [2]:
trans = transforms.ToTensor() # 通过ToTensor把数据图像从PIL类型转换为32位浮点数格式
mnist_train = torchvision.datasets.FashionMNIST(
root='./data', train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(
root='./data', train=False, transform=trans, download=True)
In [3]:
len(mnist_train), len(mnist_test)
Out[3]:
(60000, 10000)
In [4]:
mnist_train[0][0].shape # 通道数为1(黑白),尺寸为28*28
Out[4]:
torch.Size([1, 28, 28])
In [6]:
def get_fashion_mnist_labels(labels):
'''返回Fashion-MNIST数据集的文本标签'''
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
In [7]:
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):
figsize = (num_cols * scale, num_rows * scale)
_, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
axes = axes.flatten()
for i, (ax, img) in enumerate(zip(axes,imgs)):
if torch.is_tensor(img):
# 图片张量
ax.imshow(img.numpy())
else:
ax.imshow(img)
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(titles[i])
return axes
In [10]:
X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y))
Out[10]:
array([<AxesSubplot:title={'center':'ankle boot'}>,
<AxesSubplot:title={'center':'t-shirt'}>,
<AxesSubplot:title={'center':'t-shirt'}>,
<AxesSubplot:title={'center':'dress'}>,
<AxesSubplot:title={'center':'t-shirt'}>,
<AxesSubplot:title={'center':'pullover'}>,
<AxesSubplot:title={'center':'sneaker'}>,
<AxesSubplot:title={'center':'pullover'}>,
<AxesSubplot:title={'center':'sandal'}>,
<AxesSubplot:title={'center':'sandal'}>,
<AxesSubplot:title={'center':'t-shirt'}>,
<AxesSubplot:title={'center':'ankle boot'}>,
<AxesSubplot:title={'center':'sandal'}>,
<AxesSubplot:title={'center':'sandal'}>,
<AxesSubplot:title={'center':'sneaker'}>,
<AxesSubplot:title={'center':'ankle boot'}>,
<AxesSubplot:title={'center':'trouser'}>,
<AxesSubplot:title={'center':'t-shirt'}>], dtype=object)
In [14]:
batch_size = 256
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=4)
timer = d2l.Timer()
for X, y in train_iter:
continue
f'{timer.stop():.2f} sec' # 数据读取尽可能比训练速度快一些
Out[14]:
'4.12 sec'
3.6 softmax回归从零开始实现
回想一下,[实现softmax]由三个步骤组成:
-
对每个项求幂(使用exp);
-
对每一行求和(小批量中每个样本是一行),得到每个样本的规范化常数;
-
将每一行除以其规范化常数,确保结果的和为1。
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
注意用法:torch.normal(mean, std, size)
定义模型:
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
注释:torch.matmul是指点乘,假设X输入的大小为28*28的图像,而W.shape为(784,10),reshape(-1,a)中-1代表自动计算
X.reshape((-1, W.shape[0])点乘W,则reshape后尺寸为(256,784)
4.1 多层感知机
**感知机:**二分类问题,求解算法等价于使用批量大小为1的梯度下降
**感知机的缺陷:**不能够拟合XOR(异或:两者相同则为真9)函数,只能产生线性分割面
解决办法:多层感知机能够实现异或,加入隐藏层,隐藏层的大小是超参数。
如果按照单层感知机,没有激活函数,那么多层感知机仍然线性。因此在仿射变换之后对每个隐藏单元使用非线性的激活函数,多层感知机就不会退化为线性模型。
常用激活函数:sigmoid函数,tanh函数,ReLU函数(计算快,不需要指数运算)
**多隐藏层:**超参数为隐藏层数和每层隐藏层的大小
隐藏层做宽和隐藏层做深,前面的隐藏层比后面的隐藏层宽,设计时通常从前到后隐藏层宽度变窄,最后几层可以适当扩宽。通常选择2的若干次幂作为层的宽度,因为内存在硬件中的分配和寻址方式,这样做可以在计算上更高效。
4.2 多层感知机从零开始实现
较复杂,具体见书P137
4.3 多层感知机的简洁实现
import torch
from torch import nn
from torch import torch as d2l
net = nn.Sequential(nn.Flatten(),
nn.Linear(784,256), # 指输入784,输出256
nn.ReLU(),
nn.Linear(256,10))
'''m为当前层数 为线性层赋初值'''
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
batch_size, lr, num_epochs = 25, 0.1, 10
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=lr)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
最后一段会报错:AttributeError: module ‘torch’ has no attribute ‘load_data_fashion_mnist’,网上有解决办法,见:
d2lzh_pytorch包报错问题_whtli的博客-优快云博客
4.4 模型选择、欠拟合和过拟合
训练误差和泛化误差:在训练数据上的误差和新数据上的误差
模型容量:低容量的模型难以拟合训练数据,高容量的模型可以记住所有的数据
实际运用中,在足够大的模型情况下抑制过拟合
VC维:对于一个分类模型,VC等于一个最大的数据集的大小,不管如何给定标号,都存在一个模型对它进行完美分类。例如2维输入的感知机,VC维 = 3,能够分类任何三个点。支持N维输入的感知机VC维为N+1,但计算深度学习模型的VC维很困难。
几个影响模型泛化的因素:
-
可调整参数的数量(有时也称为自由度),当可调整参数的数量很大时,模型更容易过拟合。
-
参数采用的值:当权重的取值范围较大时,模型可能更容易过拟合(权重衰退解决)
-
训练样本的数量:即使模型很简单,也很容易过拟合一个只包含一两个样本的数据集。
4.5 权重衰退
背景补充:B站 王木头学科学
拉格朗日乘数法(在有条件的情况下)
**权重衰退(weight decay)**是常用的一种处理过拟合的方法,通过限制参数值的选择范围
min L(w,b) 为需要优化的损失函数,加入限制条件: w的L2范数的平方 ≤ θ (自行选取),小的θ意味着更强的正则项,通常不限制偏执项b(偏置项对模型复杂度没有影响)
4.7 前向传播、反向传播和计算图
前向传播(forward propagation或forward pass)指的是:按顺序(从输⼊层到输出层)计算和存储神经⽹ 络中每层的结果。
反向传播(backward propagation或backpropagation)指的是计算神经⽹络参数梯度的⽅法。
对于前向传播,我们沿着依赖的⽅向遍历计算图并计算 其路径上的所有变量。然后将这些⽤于反向传播,其中计算顺序与计算图的相反。反向传播重复利⽤前向传播中存储的中间值,以避免重复计算。**带来的影响之 ⼀是我们需要保留中间值,直到反向传播完成。这也是训练⽐单纯的预测需要更多的内存(显存)的原因之⼀。**此外,这些中间值的⼤⼩与⽹络层的数量和批量的⼤⼩⼤致成正⽐。因此,使⽤更⼤的批量来训练更深 层次的⽹络更容易导致内存不⾜(out of memory)错误。
4.8 数值稳定和模型初始化
sigmoid梯度消失:如图所示,输入过大或过小,梯度就会消失。
梯度爆炸:
要解决梯度消失和爆炸的问题,需要做好参数初始化
5.1 层和块
- 将输⼊数据作为其前向传播函数的参数。
- 通过前向传播函数来⽣成输出。请注意,输出的形状可能与输⼊的形状不同。例如,我们上⾯模型中的 第⼀个全连接的层接收⼀个20维的输⼊,但是返回⼀个维度为256的输出。
- 计算其输出关于输⼊的梯度,可通过其反向传播函数进⾏访问。通常这是⾃动发⽣的。
- 存储和访问前向传播计算所需的参数。
- 根据需要初始化模型参数。
7.2 使用块的网络(VGG)
⼀个VGG块由⼀系列卷积层组成,后⾯再加上⽤于空间下采样的最⼤汇聚层。
import torch
from torch import nn
from d2l import torch as d2l
def vgg_block(num_convs, in_channels, out_channels):
'''卷积层数量num_convs,输入通道数in_channels,输出通道的数量out_channels'''
layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1))
layers.append(nn.ReLU())
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
return nn.Sequential(*layers)
有超参数变量conv_arch。该变量指定了每个VGG块⾥卷积层个数和输出通道数。
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
7.4 含并行连结的网络(GoogleNet)
8.1 序列模型
潜变量自回归模型
8.4 循环神经网络
循环神经网络:当前时刻输出预测当前时刻观察,输出发生在当前观察之前(观察、隐变量、输出),输出取决于当下输入和前一个时间的隐变量
困惑度:衡量一个语言模型的好坏可以用平均交叉熵
RNN的简洁实现:
num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens) # vocab 输入输出大小, num_hidden隐藏输出
pytorch调用RNN时只有隐藏层,没有输出层,所以要手动构造输出层
9.1 门控循环单元(GRU)
不是每一个观察值都同等重要
更新门:能关注的机制,重置门:能遗忘的机制
9.2 长短期记忆网络(LSTM)
忘记门:将值朝0减少
输入门:决定是不是忽略掉输入数据
输出门:决定是不是使用隐状态
9.6 编码器解码器架构
编码器(Encoder):输入变成中间表达形式,可以是双向的
解码器(Decoder):中间表示解码成输出
机器翻译:Seq2seq
编码器的最后的隐藏状态 为解码器的初始隐状态,训练时解码器使用目标句子作为输入
BLEU:精度
10.1 注意力机制
随意**(随自己意志)**线索:关注想要的东西,例如我想读书,从桌上找到一本书
不随意线索:无意识地关注到的东西,例如桌上很多东西,我先看到红色的杯子
卷积、全连接、池化层都只考虑不随意线索,注意力机制则考虑随意线索
随意线索被称为查询**(query),每个输入是一个值(value)和不随意线索(key)**(如其他不关注的东西)的配对对。
加入查询。
10.3 注意力评分函数
上式中,如果有一个查询q,m个键值对,那么注意力汇聚函数f表示为值v的加权和
其中,查询q和键k的注意力权重(标量)是通过注意力评分函数a将两个向量映射成标量,再经过softmax得到
常见的注意力评分函数:加性注意力(additive attention) 和 缩放点积注意力(scale dot-product attention)
缩放点积注意力:
10.5 多头注意力
我们可以⽤独⽴学习得到的h组不同的线性投影(linear projections)来变换查询、键和值。然后,这h组变换后的查询、键和值将并⾏地送到注意⼒汇聚中。最后,将这h个注意力汇聚的输出拼接在⼀起,并且通过另⼀个可以学习的线性投影进⾏变换,以产⽣最终输出。这种设计被称为多头注意力(multi-head attention)。
对于同一key,value,query,希望抽取不同的信息—例如短距离和长距离关系
合并各个头的输出得到最终输出
10.7 Transformer
10.7.1 Transformer 架构
有掩码的多头注意力
基于位置的前馈网络(FFN): 输入形状(b,n,d)— batch size, seq_length ,dimension 变换成(b,n,d)
层归一化(Layer Norm):对每个样本的元素进行归一化,batch_norm导致序列长度不稳定
信息传递:编码器最终输出y1,…,yn 将其作为解码器第i个Transformer块中的key和value
预测:预测第t+1个输出时,前t个预测值作为key和value,第t个预测值还能作为query
10.7.2 位置编码
参考: 理解Transformer :Positional Encoding - 知乎 (zhihu.com)
循环神经网络(RNN)固有地考虑了单词的顺序;他们按顺序逐字分析句子。但Transformer架构放弃了使用循环的方式捕获词的位置特征,而是使用多头自注意力机制以避免RNNs的循环方法导致的训练时间大幅加大。
句子中的每个单词同时流经Transformer的编码器/解码器堆栈,模型本身对每个单词没有任何位置/顺序感知。因此,需要一种方法将单词的顺序合并到我们的模型中。
让模型能感知位置的一个可能的解决方案是在每个单词中添加一条关于其在句子中位置的信息,也就是位置编码(position al encoding) 。
def get_positional_encoding(emb_size, max_seq_len):
"""Compute the positional encoding.
Args:
emb_size (int): the dimension of positional encoding
max_seq_len (int): the maximum allowed length of a sequence
Returns:
torch.tensor: positional encoding, size=(max_seq_len, emb_size)
"""
PE = torch.zeros(max_seq_len, emb_size)
for pos in range(max_seq_len):
for i in range(emb_size):
if i % 2 == 0:
PE[pos, i] = torch.sin(pos / (10000 ** (i / emb_size)))
else:
PE[pos, i] = torch.cos(pos / (10000 ** ((i-1) / emb_size)))
return PE
BERT
Bidirectional Encoder Representation from Transformers
使用预训练好的模型来抽取词和句子的特征:例如word2vec(embedding层,需要重新设计网络)
BERT的动机—预训练模型抽取了足够多的信息,只需要增加一个简单的输出层
BERT是一个只有编码器的Transformer
对输入的更改:每个样本是一个句子对,加入额外的片段嵌入,位置编码可学习
预训练:带掩码的语言模型
Bert针对微调设计—模型更大、训练数据更多