深度学习(一):基础知识
深度学习(一):基础知识
参考书为:https://zh.d2l.ai/chapter_preface/index.html
基础
Python
- list前加*号的作用:将该list转化为一个个分散的值
- enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
pytorch工具函数
- softmax运算
nn.functional.softmax(input_tensor, dim=xxx)
沿着dim做softmax函数,输出的张量形状和输入相同。
- repeat_interleave函数
torch.repeat_interleave(input, repeats, dim=None) → Tensor
将input重复,其中repeats指定了每个元素的重复次数。默认情况下,先将input展平为一维张量,然后重复。
- torch.bmm函数
torch.bmm(input, mat2, *, out=None) → Tensor
输入张量input和mat2必须是3维张量,如果inputs的形状为,mat2的形状为,其中b是batch_size维,真正会相乘的矩阵是后面那两维,即输出结果是。该函数实现小批量样本的乘法。
- unsqueeze函数
torch.unsqueeze(input, dim) → Tensor
将一个大小为1的维度插入到input的特定位置上。其中dim参数表示插入维度的位置。
- transpose函数
torch.transpose(input, dim0, dim1) → Tensor
该函数是一个将张量转置的函数,要转置的维度分别为dim0和dim1。
- one_hot函数
torch.nn.functional.one_hot(tensor, num_classes=- 1) → LongTensor
将输入形状为(*)的张量变为(*, num_classes)形状的张量并输出。输出的张量除了位置为元素的值所在处为1,其余都是0.
张量
- 张量的创建
import torch
x = torch.arange(12)
X = x.reshape(3, 4) #等价于x.reshape(-1,4)
torch.zeros((2, 3, 4))
torch.ones((2, 3, 4))
torch.randn(3, 4)# 均值为0,标准差为1
torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
- 张量的连接
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) #按列连接,即将两个tensor横着拼在一起,列数会翻倍
-
广播机制:复制某些元素,满足相应的操作
-
索引和切片
X[0:2, :] = 12 #第0到第1行,:表示按列的所有元素
-
避免内存泄漏
Z[:] = X + Y和 X+=Y
数据处理
读取数据使用pandas。
import pandas as pd
data = pd.read_csv(data_file)
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] #分别表示data的前两列和最后一列
inputs = inputs.fillna(inputs.mean())
#将类别行转化为one-hot编码
inputs = pd.get_dummies(inputs, dummy_na=True)
#将其转化为张量
X, y = torch.tensor(inputs.values), torch.tensor(outputs.values)
线性代数
单个向量的默认方向是列向量
axis=0表示沿着轴0
沿某个轴计算A
元素的累积总和:
A.cumsum(axis=0)
矩阵按元素乘:A*A
矩阵乘法:torch.mm(A, B)
- 范数:描述张量的大小
L1范数:向量元素的绝对值之和:torch.abs(u).sum()
L2范数:向量元素的平方的和的平方根:torch.norm(u)。一般表示L2范数
微积分
- 梯度
- 链式法则
自动微分
根据设计好的模型,系统会构建一个计算图(computational graph), 来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 自动微分使系统能够随后反向传播梯度。 这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
- 单独计算批量中每个样本的偏导数之和:
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad
- 分离计算
有时,我们希望将某些计算移动到记录的计算图之外。 例如,假设y
是作为x
的函数计算的,而z
则是作为y
和x
的函数计算的。 想象一下,我们想计算z
关于x
的梯度,但由于某种原因,希望将y
视为一个常数, 并且只考虑到x
在y
被计算后发挥的作用。
这里可以分离y
来返回一个新变量u
,该变量与y
具有相同的值, 但丢弃计算图中如何计算y
的任何信息。 换句话说,梯度不会向后流经u
到x
。 因此,下面的反向传播函数计算z=u*x
关于x
的偏导数,同时将u
作为常数处理, 而不是z=x*x*x
关于x
的偏导数。
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x
z.sum().backward()
x.grad == u
总结:深度学习框架可以自动计算导数:我们首先将梯度附加到想要对其计算偏导数的变量上,然后记录目标值的计算,执行它的反向传播函数,并访问得到的梯度。
线性神经网络
线性回归
我们要求一组权重和偏置使得损失函数最小。
损失函数
i表示样本i


梯度下降
通过不断地在损失函数递减的方向上更新参数来降低误差。
- 小批量随机梯度下降:
步骤:(1)初始化模型参数的值,如随机初始化; (2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。

表示每个小批量中的样本数,这也称为批量大小(batch size)。表示学习率(learning rate)。这些可以调整但不在训练过程中更新的参数称为超参数(hyperparameter)。 调参(hyperparameter tuning)是选择超参数的过程。 超参数通常是我们根据训练迭代结果来调整的, 而训练迭代结果是在独立的验证数据集(validation dataset)上评估得到的。
def sgd(params, lr, batch_size): #@save
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
训练过程
lr = 0.03
num_epochs = 3#迭代周期
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l.sum().backward()
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
with torch.no_grad(): #???什么意思
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
使用Pytorch
-
读取数据集:
def load_array(data_arrays, batch_size, is_train=True): #@save """构造一个PyTorch数据迭代器""" dataset = data.TensorDataset(*data_arrays) return data.DataLoader(dataset, batch_size, shuffle=is_train) batch_size = 10 data_iter = load_array((features, labels), batch_size)#features为特征,即X;labels即Y
-
模型定义:
# nn是神经网络的缩写 from torch import nn net = nn.Sequential(nn.Linear(2, 1))
nn.Linear为全连接层,2表示输入的个数,1表示输出的个数。
-
初始化模型参数
net[0].weight.data.normal_(0, 0.01) net[0].bias.data.fill_(0)
-
损失函数
loss = nn.MSELoss()#均方误差,即平方L2范数
-
定义优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
-
训练
num_epochs = 3 for epoch in range(num_epochs): for X, y in data_iter: l = loss(net(X) ,y) #计算损失 trainer.zero_grad() #清除前一次batch的梯度 l.backward() #对损失函数求梯度 trainer.step() #调用优化器来更新模型参数 l = loss(net(features), labels) print(f'epoch {epoch + 1}, loss {l:f}')
softmax回归
解决分类问题。
输出为:

损失函数为,也称为交叉熵损失:
Softmax导数的计算:
使用Pytorch
import torch
from torch import nn
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
- 初始化模型参数:
# PyTorch不会隐式地调整输入的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整网络输入的形状
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.apply(init_weights);
- 交叉熵损失函数
loss = nn.CrossEntropyLoss(reduction='none')
- 优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
- 训练:
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
多层感知机
在网络中加入隐藏层可以克服线性模型的限制, 使其能处理更普遍的函数关系类型。要做到这一点,最简单的方法是将许多全连接层堆叠在一起。 每一层都输出到上面的层,直到生成最后的输出。这种架构称为多层感知机(MLP)

多层感知机在输出层和输入层之间增加一个或多个全连接隐藏层,并通过激活函数转换隐藏层的输出。
激活函数
ReLu
最受欢迎的激活函数是修正线性单元,给定元素x,有:
常用于隐藏层
Sigmoid函数
Sigmoid常称为挤压函数,将定义域为R的输入映射到(0,1)。
sigmoid在隐藏层中已经较少使用。

tanh
tanh(双曲正切)函数也能将其输入压缩转换到区间(-1, 1)上。

从零开始实现
- 初始化模型参数
num_inputs, num_outputs, num_hiddens = 784, 10, 256#隐藏单元常设为2的幂次
W1 = nn.Parameter(torch.randn(
num_inputs, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(
num_hiddens, num_outputs, requires_grad=True) * 0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
params = [W1, b1, W2, b2]
- 激活函数
def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a)
- 模型
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(X@W1 + b1) # 这里“@”代表矩阵乘法
return (H@W2 + b2)
- 损失函数
loss = nn.CrossEntropyLoss(reduction='none')
- 训练
num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
- 评估
d2l.predict_ch3(net, test_iter)
Pytorch实现
- 模型定义
net = nn.Sequential(nn.Flatten(), #展平,从第1维(第0维是样本数那一维)到最后一维展平张量
nn.Linear(784, 256),#隐藏层
nn.ReLU(),
nn.Linear(256, 10)) #输出层
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
权重衰减
为了缓解过拟合的情况,即模型过于复杂,可以采用正则化的方式,其中最广泛使用的方法是权重衰减,也被称为L2正则化。即将权重的范数作为惩罚项加到最小化损失的问题中,可以得到损失函数变为:
则小批量随机梯度下降算法对应的更新步骤为:

通常,网络输出层的偏置项不会被正则化。
- Pytorch框架版
为了实现权重衰减,可以在优化器中增加相关参数,下面的例子只为权重赋予了正则化:
trainer = torch.optim.SGD([
{"params":net[0].weight,'weight_decay': wd},#wd表示lambda
{"params":net[0].bias}], lr=lr)
暂退法
DropOut方法是为了提高模型的稳健性,使其不过分依赖任何一个参数。它的做法是随机将几个隐藏层的单元省去,并且不计算它们的梯度。但测试时一般不删去,只在训练时删去。

暂退法对每个h施加作用,使其变为h':

暂退法的代码实现:
def dropout_layer(X, dropout): #dropout为概率p
assert 0 <= dropout <= 1
# 在本情况中,所有元素都被丢弃
if dropout == 1:
return torch.zeros_like(X)
# 在本情况中,所有元素都被保留
if dropout == 0:
return X
mask = (torch.rand(X.shape) > dropout).float()
return mask * X / (1.0 - dropout)
- 模型定义,一遍靠近输入层的地方设置较低的暂退率:
dropout1, dropout2 = 0.2, 0.5
class Net(nn.Module):
def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2,
is_training = True):
super(Net, self).__init__()
self.num_inputs = num_inputs
self.training = is_training
self.lin1 = nn.Linear(num_inputs, num_hiddens1)
self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)
self.lin3 = nn.Linear(num_hiddens2, num_outputs)
self.relu = nn.ReLU()
def forward(self, X):
H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
# 只有在训练模型时才使用dropout
if self.training == True:
# 在第一个全连接层之后添加一个dropout层
H1 = dropout_layer(H1, dropout1)
H2 = self.relu(self.lin2(H1))
if self.training == True:
# 在第二个全连接层之后添加一个dropout层
H2 = dropout_layer(H2, dropout2)
out = self.lin3(H2)
return out
net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
- Pytorch简洁实现:
net = nn.Sequential(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
# 在第一个全连接层之后添加一个dropout层
nn.Dropout(dropout1),
nn.Linear(256, 256),
nn.ReLU(),
# 在第二个全连接层之后添加一个dropout层
nn.Dropout(dropout2),
nn.Linear(256, 10))
nn.Dropout层在训练时,Dropout
层将根据指定的暂退概率随机丢弃上一层的输出(相当于下一层的输入)。 在测试时,Dropout
层仅传递数据。
前向传播、后向传播、计算图
前向传播(forward propagation或forward pass) 指的是:按顺序(从输入层到输出层)计算和存储神经网络中每层的结果。
反向传播(backward propagation或backpropagation)指的是计算神经网络参数梯度的方法。 简言之,该方法根据微积分中的链式规则(链式法则就是按相反的顺序从因变量到自变量求导数),按相反的顺序从输出层到输入层遍历网络。 该算法存储了计算某些参数梯度时所需的任何中间变量(偏导数)。
深度学习计算
块
块是层的集合,可以表示模型的一部分或者整个模型。通过一个类来表示块,例如我们可以定义一个MLP的块:
from torch.nn import functional as F
class MLP(nn.Module):
# 用模型参数声明层。这里,我们声明两个全连接的层
def __init__(self):
# 调用MLP的父类Module的构造函数来执行必要的初始化。
# 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
super().__init__()
self.hidden = nn.Linear(20, 256) # 隐藏层
self.out = nn.Linear(256, 10) # 输出层
# 定义模型的前向传播,即如何根据输入X返回所需的模型输出
def forward(self, X):
# 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
return self.out(F.relu(self.hidden(X)))
net = MLP()
net(X)
注意,除非我们实现一个新的运算符, 否则我们不必担心反向传播函数或参数初始化, 系统将自动生成这些。
参数管理
-
访问参数
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1)) print(net[2].state_dict())
每个参数都表示为参数类的一个实例,包含梯度、值和额外信息:
print(type(net[2].bias)) print(net[2].bias) print(net[2].bias.data)
结果:
<class 'torch.nn.parameter.Parameter'> Parameter containing: tensor([-0.0291], requires_grad=True) tensor([-0.0291])
目前还没有调用反向传播,所以参数梯度处于初始状态:
net[2].weight.grad == None
-
参数初始化
默认情况下,PyTorch会根据一个范围均匀地初始化权重和偏置矩阵, 这个范围是根据输入和输出维度计算出的。
- 高斯正态分布初始化:
def init_normal(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=0, std=0.01)
nn.init.zeros_(m.bias)
net.apply(init_normal)
- 初始化为常数
nn.init.constant_(m.weight, 1)
- 也可以直接设置参数
net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
延后初始化
有时输入特征数未知时,可以采用延后初始化:
net = nn.Sequential(nn.LazyLinear(256), nn.ReLU(), nn.LazyLinear(10))#只定义了linear层的out_features的大小
X = torch.rand(2, 20)
net(X)#当执行前向传播时,参数就被初始化了
print(net)
# 输出结果
Sequential(
(0): Linear(in_features=20, out_features=256, bias=True)
(1): ReLU()
(2): Linear(in_features=256, out_features=10, bias=True)
)
延迟初始化非常方便,允许框架自动推断参数形状,使修改体系结构变得容易,并消除了一个常见的错误来源。我们可以通过模型传递数据,使框架最终初始化参数。
自定义层
class MyLinear(nn.Module):
def __init__(self, in_units, units):#输入维度和输出维度
super().__init__()
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)
读写文件
- 加载和保存张量
x = torch.arange(4)
torch.save(x, 'x-file') #将x保存在当前文件夹的x-file中
x2 = torch.load('x-file')
# 用列表保存
torch.save([x, y],'x-files')
x2, y2 = torch.load('x-files')
# 用字典保存
mydict = {'x': x, 'y': y}
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
- 加载和保存模型参数
将net的参数保存:
net = MLP()
torch.save(net.state_dict(), 'mlp.params')
将net的参数传入到模型中:
clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))
clone.eval()
GPU
这两个函数允许我们在不存在所需所有GPU的情况下运行代码。
def try_gpu(i=0): #@save
"""如果存在,则返回gpu(i),否则返回cpu()"""
if torch.cuda.device_count() >= i + 1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')
def try_all_gpus(): #@save
"""返回所有可用的GPU,如果没有GPU,则返回[cpu(),]"""
devices = [torch.device(f'cuda:{i}')
for i in range(torch.cuda.device_count())]
return devices if devices else [torch.device('cpu')]
默认情况下,张量是在CPU上创建的。下面将张量创建在GPU上:
X = torch.ones(2, 3, device=try_gpu()) # X在GPU0 上
如果我们要将不同GPU上的数据相加时,需要先复制到同一个GPU上,再相加:
Z = X.cuda(1) #Z是在GPU 1上的,值和X相同
神经网络和GPU
神经网络模型可以指定设备。 下面的代码将模型参数放在GPU上。
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())
只要所有的数据和参数都在同一个设备上, 我们就可以有效地学习模型。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)