主题
深度学习
一、深度学习简介
1. 什么是深度学习
机器学习是实现人工智能的一种途径
深度学习是机器学习的一个子集,也就是说深度学习是实现机器学习的一种方法。
2. 深度学习的特点
- 多层非线性变换:输入层(由特征决定)---- 隐藏层 ---- 输出层
- 自动特征提取:自动从原始数据中学习到有用的特征
- 大数据和计算能力:深度学习模型通常需要大量的标注数据和强大的计算资源(如GPU)来进行训练
- 可解释性差:深度学习模型内部的运作机制相对不透明,被称为“黑箱”
3. 深度学习的模块
文本 - RNN - 循环神经网络 - 词嵌入曾 + 循环层 + 全连接层
图像 - CNN - 卷积神经网络 - 卷积层 + 池化层 + 全连接层
全连接层 - ANN - 人工神经网络
人工神经网络 - 张量
4. 深度学习发展历史
早期探索:1940s - 1980s
挑战与瓶颈:1980s - 1990s 反向传播(Backpropagation, BP)算法
复兴与突破:2000s - 2010s 深度置信网络(DBN),卷积神经网络(CNN)
爆发期:2016 - 至今 Transformer,Bert和GPT
二、PyTorch基本使用
anaconda管理python环境
python# 通过管理员模式,打开anaconda prompt(终端) # 查看电脑anaconda已有版本 - conda env list # 创建新的环境 - conda create -n <环境名称> python=<版本号> # 切换anaconda环境(终端切换) - conda activate <环境名称> # 回到base环境(终端) - conda deactivate # 删除环境 - conda env remove -n <环境名称>
PyTorch一个基于Python语言的深度学习框架,它将数据封装成张量(Tensor)来进行处理。
张量:元素为同一种数据类型的多维矩阵。
创建者:Meta(Facebook) ---> 管理权转移(2022年):PyTorch基金会
安装:
python# conda search torch 查看当前conda版本支持的pytorch版本 # conda list torch 查看当前conda版本安装的pytorch版本 # pip install torch==版本号 安装
1. 张量的创建
1.1 张量的基本创建
1.1.1 小写tensor创建张量(数据)
python
import numpy as np
import torch
# 1 多种维度的张量
# 1.1 0维张量
t1 = torch.tensor(10)
# 1.2 1维张量
t2 = torch.tensor([1, 2, 3])
# 1.3 2维张量(2行3列)
t3 = torch.tensor([[1, 2, 3], [4, 5, 6]])
# 2 基于numpy和列表创建张量
data = np.random.randn(2, 3) # 2行3列的np数据
t4 = torch.tensor(data) # 小数数据 torch.float64
# 3 指定维度创建张量(报错,tensor不支持指定维度创建张量)
t5 = torch.tensor(2, 3)1.1.2 大写Tensor创建张量(形状 + 数据)
python
import numpy as np
import torch
# 1 多种维度的张量
# 1.1 0维张量 - 创建一个形状为10的张量(1行10列)
t1 = torch.Tensor(10)
# 1.2 1维张量
t2 = torch.Tensor([1, 2, 3])
# 1.3 2维张量(2行3列)
t3 = torch.Tensor([[1, 2, 3], [4, 5, 6]])
# 2 基于numpy和列表创建张量
data = np.random.randn(2, 3) # 2行3列的np数据
t4 = torch.Tensor(data) # 小数数据 torch.float32
# 3 指定维度创建张量 - 创建一个2行3列的数据
t5 = torch.Tensor(2, 3)1.1.3 tensor与Tensor的比较
python
总结:
1. 小写 tensor 主要根据数据创建张量,默认数据类型为 int64。
2. 大写 Tensor 可以根据形状和数据创建张量,默认数据类型为 float32。
2.1 如果只给数字,表示按照维度创建张量。
2.2 如果给具体的数据,表示将数据转换为张量。1.1.4 通过指定类型创建张量
python
import torch
# 指定类型:即支持形状又支持数据
# IntTensor
t1 = torch.IntTensor(2, 3)
d1 = torch.IntTensor([1, 2.1]) # 自动转为int型,直接截取
# FloatTensor
t2 = torch.FloatTensor(2, 3)
# DoubleTensor
t3 = torch.DoubleTensor(2, 3)
# torch.ShortTensor Int16
# torch.IntTensor Int32
# torch.LongTensor Int64
# torch.HalfTensor Float16
# torch.FloatTensor Float32
# torch.DoubleTensor Float641.2 创建线性和随机张量
1.2.1 线性张量
python
import torch
# 线性张量 - 步长划分,均分划分
# arange(起始,结束,步长) 包头不包尾
t1 = torch.arange(0, 5) # Int64
t2 = torch.arange(0, 5, 2)
# linspace(起始,结束,分成几份) 包头包尾
t3 = torch.linspace(0, 5, 5) # Float32
t4 = torch.linspace(0, 5, 2)1.2.2 随机张量
python
import torch
# 如何查看随机种子
print(torch.random.initial_seed())
# 如何设置随机种子(设置每次随机的结果为一个固定的值)
# 设置的是下方所有的随机数
torch.random.manual_seed(29274227369900)
# 随机数
t1 = torch.rand(2, 3) # 0-1
t2 = torch.randn(2, 3) # 正态分布
# 随机整数 - 包头不包尾
# 语法:torch.randint(low(起始), high(结束), size(形状))
t3 = torch.randint(0, 10, (2, 3))1.3 创建全0、全1、指定值张量
1.3.1 全0、全1张量
python
import torch
# 全0:形状,数据
t1 = torch.zeros(2, 3)
data = torch.tensor([[1, 2], [3, 4], [5, 6]])
t2 = torch.zeros_like(data)
# 全1:形状,数据
t3 = torch.ones(2, 3)
t4 = torch.ones_like(data)
# 总结:
# ones/zeros 基于形状创建张量
# ones_like/zeros_like 基于数据创建相同形状的张量1.3.2 指定值张量
python
import torch
# 语法:torch.full(size, fill_value)
t1 = torch.full([2, 3], 3.14) # 默认为float32
data = torch.tensor([[1, 2], [3, 4], [5, 6]])
# 语法:torch.full_like(张量, fill_value)
t2 = torch.full_like(data, 3.14) # 默认和data类型一致
# full支持的三种类型写法
# 1. tumple
t3 = torch.full((2, 3), 3.14)
# 2. list
t4 = torch.full([2, 3], 3.14)
# 3. Size
t5 = torch.full(data.size(), 3.14)1.4 张量元素类型转换
1.4.1 通过"type"方式转换
python
import torch
data = torch.tensor([[1, 2], [3, 4], [5, 6]]) # 默认int64
# 转int16
data = data.type(torch.int16)
# 转float32
data = data.type(torch.float32)
# 转int32(指定类型转,了解)
data = data.type(torch.IntTensor)1.4.2 通过指定函数名转换
python
import torch
data = torch.tensor([[1, 2], [3, 4], [5, 6]]) # 默认int64
# int 16 32 64 / short int long
# float 16 32 64 / half float double
data = data.double()2. 张量的类型转换
2.1 tensor转numpy(数据共享)
python
import torch
import numpy as np
# 准备tensor数据
data_tensor = torch.tensor([[1, 2], [3, 4], [5, 6]])
# 转换成numpy数据
data_numpy = data_tensor.numpy()2.2 tensor转numpy(数据不共享tensor.copy)
python
import torch
import numpy as np
# 准备tensor数据
data_tensor = torch.tensor([[1, 2], [3, 4], [5, 6]])
# 转换成numpy数据
data_numpy = data_tensor.numpy().copy()2.3 numpy转tensor(数据共享)
python
import torch
import numpy as np
# 准备numpy数据
data_numpy = np.random.randint(0, 10, (2, 3))
# 转换成tensor数据
data_tensor = torch.from_numpy(data_numpy)2.4 numpy转tensor(数据不共享numpy.copy)
python
import torch
import numpy as np
# 准备numpy数据
data_numpy = np.random.randint(0, 10, (2, 3))
# 转换成tensor数据
data_tensor = torch.from_numpy(data_numpy.copy())2.5 numpy转tensor(数据不共享 新创建)
python
import torch
import numpy as np
# 准备numpy数据
data_numpy = np.random.randint(0, 10, (2, 3))
# 转换成tensor数据
data_tensor = torch.tensor(data_numpy)2.6 tensor只有一个值,使用item取出(了解)
python
data_tensor1 = torch.tensor([10])
print(data_tensor1.item()) # 10
data_tensor2 = torch.tensor(10)
print(data_tensor2.item()) # 103. 张量的数值计算
3.1 基本运算
加减乘除取负号 add sub mul div neg
+ - * / 取反
Pytorch中,带下划线的都表示更新原数据
python
import torch
data = torch.tensor([[1, 2], [3, 4], [5, 6]])
# 加法:加常数
print(data + 1)
# 加法:加tensor
t1 = torch.tensor([[1, 2], [3, 4], [5, 6]])
print(data + t1)
print(data.add(t1)) # 同上,data不变
print(data)
# 加法:更新原数据
data.add_(t1)
print(data)
# 其他运算与加法运算相同,都是对应位数做计算
# 取负号
print(data.neg_())
print(-data)
print(data * -1)3.2 点乘矩阵积
点乘:* mul - 前提:相同形状的张量对应位置的元素相乘
矩阵乘:@ matmul - 前提:符合矩阵运算规则
补充:
sign:如果输入>0,返回1如果输入==0,返回0,如果输入<0,返回-1
square:平方
fmod:取余
python
import torch
t1 = torch.tensor([[1, 2], [3, 4]])
t2 = torch.tensor([[2, 2], [4, 4]])
# 点乘
print(t1 * t2)
print(t1.mul(t2))
t3 = torch.tensor([[1, 2], [4, 5], [7, 8]])
# 矩阵乘
print(t3.matmul(t1))
print(t3 @ t1)
# 补充
print(t3.sign()) # 如果输入>0,返回1如果输入==0,返回0,如果输入<0,返回-1
print(t3.square()) # 平方
print(t3.fmod(2)) # 取余4. 张量的运算函数
求和sum
平均值mean、最大值max、最小值min
开方sqrt、平方square、幂pow
绝对值abs/absolute
指数exp、对数log/log2/log10
python
import torch
data = torch.tensor(
# 第0维度:将下面括号内的数据进行求和,合并成一条数据
[
# 第一维度:将下面括号中的数据进行求和,合并成一条数据
[1, 2, 3],
[4, 5, 6]
]
)
# 1. 求和sum - 维度
print(data.sum(dim=0)) # 列求和
print(data.sum(dim=1)) # 行求和
print(data.sum()) # 所有元素求和
# 2.平均,最大值,最小值
data = data.type(torch.float32)
print(data.mean()) # 平均数一般不是整数,所以要输入float类
print(data.max())
print(data.min())
# 3.开方 平方 幂 绝对值
data.sqrt_() # 平方根
print(data)
data.square_() # 平方
print(data)
data.pow_(3) # 3次幂
print(data)
data.neg_()
print('取反', data)
data.abs_() # 绝对值
data.absolute_() # 绝对值
print('绝对值', data)
# 4.指数
print(data.exp()) # e的次方
# 5.对数
print(data.log()) # 以e为底
print(data.log10()) # 以10为底
print(data.log2()) # 以2为底5. 张量的索引使用
python
# 0. 准备数据
torch.manual_seed(29274227369900)
data = torch.randint(0, 10, (4, 5))
# [
# [4, 2, 6, 3, 8],
# [7, 7, 9, 9, 7],
# [5, 9, 1, 1, 8],
# [6, 0, 6, 0, 4]
# ]5.1 简单行列索引
python
print(data[0]) # 获取第0行
print(data[:, 1]) # 获取所有行的第1列5.2 通过列表索引获取数据
语法:data[[行索引],[列索引]]
python
# 2.1 需求:返回(0,1)(1,2)的数据
print(data[[0, 1], [1, 2]])
# 2.2 需求:返回(1,3)(2,4)(1,4)(2,3)的数据
print(data[[1, 2, 1, 3], [3, 4, 4, 3]])
# 2.3 拓展:第0行的第2,3个,第2行的第2,3个数据
print(data[[[0], [2]], [2, 3]])5.3 通过范围索引获取数据
语法:data[start: end :step, start: end :step] - 切片,包头不包尾
python
# 需求:第0行的第2,3个,第2行的第2,3个数据
print(data[:3:2, 2:4:1])
print(data[:3:2, [2, 3]])
# 需求1: 前4行 前3列
print(data[:4, :3])
# 需求2:第2行到最后一行,前3列
print(data[2:, :3])5.4 通过布尔索引获取数据
python
# 布尔索引使用的示例
print(data > 5) # 每一项数据对应的布尔值
print(data[data > 5]) # 为True保留,为False删除
# 需求1:第3列大于5的行数据
print(data[:, 3] > 5) # 获取对应布尔索引
print(data[data[:, 3] > 5])
# 需求2:第2列大于5的列数据
print(data[:, 2] > 5) # 获取对应布尔索引
print(data[data[:, 2] > 5, 2])5.5 通过多维索引获取数据
第0维数据([]里面的内容) - 0轴 [ 第1维数据([]里面的内容) - 1轴 [ 第2维数据([]里面的内容) - 2轴 [7, 9, 2, 3, 5], [9, 5, 7, 5, 3], [4, 4, 6, 7, 3], [1, 4, 0, 7, 5] ]
]
python
mul_data = torch.randint(0, 10, (3, 4, 5)) # 3个4行5列数据
# 需求1:取出0轴的第0个数据(所有1轴2轴)
print(mul_data[0])
# 需求2:取出1轴的第0个数据(所有0轴2轴)
print(mul_data[:, 0])
# 需求3:取出2轴的第0个数据(所有0轴1轴)
print(mul_data[:, :, 0])6. 张量的形状操作
6.1 reshape
reshape 函数可以在保证张量数据不变的前提下改变数据的维度,将其转换成指定的形状。
【拓展】张量数据创建的时候,在底层已经分配内存,将对应的值写入内存中
张量数据不变 ===> 内存中的内容没有发生变化
数据不变但形状变化 ===> 步长计算公式变了
data_pointer 数据指针,
stride 表示在内存中读取数据的步长
python
import torch
data = torch.tensor([[1, 2, 3], [4, 5, 6]])
# 1. 使用reshape修改数据 (2,3 -> 1,6)
t1 = data.reshape(1, 6)
print(t1, t1.data_ptr(), t1.stride())
# 2. 使用reshape修改数据 (2,3 -> 3,?)
t2 = data.reshape(3, -1)
print(t2, t2.data_ptr(), t2.stride())
# 3. 使用reshape修改数据 (2,3 -> ?,2)
t3 = data.reshape(-1, 2)
print(t3, t3.data_ptr(), t3.stride())6.2 升维降维
python
import torch
t1 = torch.tensor([1, 2, 3, 4, 5])
# 1. 升维
# 在0维上提升一个维度(行维度)
t2 = t1.unsqueeze(dim=0)
print(t2) # 一行六列
# 在1维上提升一个维度(列维度)
t3 = t1.unsqueeze(dim=1)
print(t3) # 六行一列
# 2. 降维 - 将所有维度为1的删除
t4 = torch.Tensor(3, 1, 5, 1, 3)
t5 = t4.squeeze()
print(t5, t5.size()) # (3,5,3)6.3 维度交换
交换两个维度 - transpose(原始数据的第几维度,原始数据的第几维度)里面填要交换的两个维度
交换多个维度 - permute(原始数据的第几维度,原始数据的第几维度,...)
【拓展】交换维度数据自身不变,改变的是读取数据的步长
python
import torch
data = torch.randint(0, 10, (3, 4, 5))
print(data.data_ptr(), data.stride())
# 1. 交换2个维度
# 语法:transpose(原始数据的第几维度,原始数据的第几维度)里面填要交换的两个维度
t1 = data.transpose(1, 2)
print(t1.shape) # (3,5,4)
print(t1.data_ptr(), t1.stride())
# 2. 交换多个维度
# 语法:permute(原始数据的第几维度,原始数据的第几维度,...)
t2 = data.permute(2, 0, 1)
print(t2.shape) # (5,3,4)
print(t2.data_ptr(), t2.stride())6.4 连续性数据
view报错就先用contiguous调整后在使用view
python
import torch
torch.random.manual_seed(29274227369900)
data1 = torch.randint(1, 5, (3, 4, 5))
print(data1.data_ptr(), data1.stride())
# 1. 用view修改数据(只能修改连续的张量 - 表现形式与存储形式一致)
new_data1 = data1.view(3, 5, 4)
print(new_data1.data_ptr(), new_data1.stride())
# 2. 数据维度交换后,表现形式和存储形式不一致,则不能用view修改数据
new_data2 = data1.transpose(0, 1)
print(new_data2.data_ptr(), new_data2.stride())
# new_data2 = new_data2.view(3, 5, 4) # 报错
# contiguous() - 将表现形式与存储形式调整一致(从修改存储形式方向调整)
new_data2 = new_data2.contiguous()
print(new_data2.data_ptr(), new_data2.stride()) # 指针指向被修改
new_data3 = new_data2.view(3, 5, 4)
print(new_data3.data_ptr(), new_data3.stride())7. 张量的拼接操作
cat拼接 - 按指定维度拼接,拼接的数据的指定维度可以不一致,但其他维度必须一样
stack拼接 - 升维,指定那个维度,那个维度就是新升上来的,其他不变,所有输入张量的形状必须完全相同
python
import torch
data1 = torch.randint(1, 5, (3, 4, 5))
# 1. cat拼接 - 按指定维度拼接,拼接的数据的指定维度可以不一致,但其他维度必须一样
# 按照dim = 0
data2 = torch.randint(1, 5, (2, 4, 5))
data = torch.cat([data1, data2], dim=0)
print(data.shape) # (5,4,5)
# 按照dim = 1
data3 = torch.randint(1, 5, (3, 2, 5))
data = torch.cat([data1, data3], dim=1)
print(data.shape) # (3,6,5)
# 按照dim = 2
data4 = torch.randint(1, 5, (3, 4, 2))
data = torch.cat([data1, data4], dim=2)
print(data.shape) # (3,4,7)
# 2. stack拼接 - 升维,指定那个维度,那个维度就是新升上来的,其他不变
data5 = torch.tensor([[1, 2, 3], [4, 5, 6]])
data6 = torch.tensor([[7, 8, 9], [10, 11, 12]])
# dim =0 -> 2,3 + 2,3 = [2],2,3
data = torch.stack([data5, data6], dim=0)
print(data.shape)
print(data)
# dim =1 -> 2,3 + 2,3 = 2,[2],3
data = torch.stack([data5, data6], dim=1)
print(data.shape)
print(data)
# dim =2 -> 2,3 + 2,3 = 2,3,[2]
data = torch.stack([data5, data6], dim=2)
print(data.shape)
print(data)8. 自动微分模块
8.1 梯度基本计算
PyTorch不支持向量张量对向量张量的求导,只支持标量张量对向量张量的求导
函数必须设置requires_grad表示启用梯度计算
y.backward - 对损失函数自动微分,计算梯度,y必须是一个标量
w.grad:梯度 - 会累加上一次的梯度值 - 用w.grad.zeros_()清零
w.data:权重
注:每个损失函数只能自动微分一次,否则报错
w新 = w旧 - lr(学习率)*w.grad
python
import torch
# 定义W旧,requires_grad=True表示启用梯度计算
W = torch.tensor(10, dtype=torch.float32, requires_grad=True)
for i in range(20):
# 模拟一个损失函数
Loss = 2 * W ** 2
# 对损失函数自动微分
Loss.backward()
# 自动微分后梯度值自动更新,保存在W.grad属性中
# print(W.grad)
# 利用梯度下降公式求出W新(假设学习率是0.1)
# W.data是W的数值(表示当前的数值)
W.data = W.data - 0.1 * W.grad
print(f'第{i + 1}次迭代后的W值为:{W.data:.20f}')
# 梯度清零,防止梯度叠加
W.grad.zero_()8.2 自动微分注意事项
注:如果一个张量设置了自动微分,那么就不支持转numpy,否则报错,要先detach()再转
python
import torch
t1 = torch.tensor(10)
t2 = torch.tensor(10, requires_grad=True, dtype=torch.float32)
print(t1.numpy())
# print(t2.numpy()) # 报错
print(t2.detach().numpy())8.3 自动微分的应用
python
import torch
# 思路
# 1. 准备数据
# 1.1 准备输入的训练集(x_train, y_train)
X = torch.randn(2, 5) # 样本数量2,特征数量5
W = torch.randn(5, 3, dtype=torch.float32, requires_grad=True) # 权重为5行3列的数据
B = torch.randn(3, dtype=torch.float32, requires_grad=True) # 偏置为3列的数据
Z = X @ W + B # 预测值(计算)
Y = torch.randn(2, 3) # 真实值(准备)
# 2.损失函数定义
loss_func = torch.nn.MSELoss()
loss = loss_func(Z, Y)
print(loss)
# 3.自动微分(pytorch只支持标量张量的求导)
loss.backward()
print(W.grad) # 得到权重下降的梯度
print(B.grad) # 得到偏执下降的梯度9.线性回归案例
准备数据 - make_regression(n_samples样本数量,n_features特征数量,n_targets目标数量,bias偏执,noise噪声,coef斜率)
数据 -> 张量 -> 数据集 -> 数据加载器
训练模型
3.1 准备工作(创建模型 - 参数:in_features输入特征数量,out_features输出特征数量),优化器创建 - 参数:model.parameters()模型参数, lr学习率)
3.2 模型预测
y_pre = model(x)
3.3 定义损失函数
lose_fn = torch.nn.MSELoss()
3.4 计算损失
loss = loss_fn(y_pre,y)
3.5 自动微分
loss.backward()
3.6 更新梯度
optimizer.step()
3.7 清空梯度
optimizer.zero_grad()
python
from sklearn.datasets import make_regression # 创建线性回归数据
import torch
from torch.utils.data import DataLoader, TensorDataset
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
plt.rcParams['axes.unicode_minus'] = False # 显示负号
# 1. 数据准备模块
# 1.1 创建数据
def create_data():
# coef,bias 真实值
# x,y 训练模型,计算出k,b
x, y, coef = make_regression(
n_samples=100, # 样本数量
n_features=1, # 特征数量
n_targets=1, # 目标数量
bias=3, # 偏置
noise=10, # 噪声
coef=True, # 是否计算斜率
random_state=6 # 随机种子
)
print(coef)
return x, y.reshape(-1, 1), coef
# 2. 模型训练
def train(x, y):
# 数据 -> 张量 -> 数据集 -> 数据加载器(多轮训练的依赖)
# 2.1 数据 -> 张量
x = torch.tensor(x, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32)
# 2.2 张量 -> 数据集
dataset = TensorDataset(x, y)
# 2.3 数据集 -> 数据加载器
# dataset 数据集 - 100条数据
# batch_size 批次大小 - 16条数据,每次从100条中取16条,训练七次才能结束
# shuffle 是否打乱数据
dataloader = DataLoader(dataset, batch_size=16, shuffle=True)
# # 查看数据
# for x_train, y_train in dataloader:
# print(x_train.shape)
# print(y_train.shape)
# 2.4 模型训练
# 2.4.0. 准备工作
# 模型创建 - 参数说明:输入特征数量,输出特征数量
model = torch.nn.Linear(1, 1)
# 优化器创建 - 参数说明:模型参数,学习率
optimizer = torch.optim.SGD(model.parameters(), lr=0.03)
# 2.5 计算损失
# 2.5.1 总损失 - 记录每一批的损失
loss_list = []
# 100轮 = 每轮7次
for i in range(100):
# 2.5.2 每一轮的损失 :总损失,平均损失
total_loss, train_count = 0, 0
# 训练7次,每批16条数据
for x_train, y_train in dataloader:
# ---------正向传播---------------
# 2.4.1. 模型预测
y_pre = model(x_train)
# 2.4.2. 损失函数
lose_fn = torch.nn.MSELoss()
loss = lose_fn(y_pre, y_train)
total_loss += loss.item() # 每一轮的累加损失
train_count += 1 # 训练轮数
# loss_list.append(loss)
# ---------反向传播-----------------
# 2.4.3. 自动微分
loss.backward()
# 2.4.4. 更新梯度
optimizer.step()
# 2.4.5. 梯度清零
optimizer.zero_grad()
print(f'第{i + 1}轮的平均损失为:{total_loss / train_count}')
loss_list.append(total_loss / train_count) # 每一轮的平均损失
# 训练结束,训练结果在模型身上
print(f'查看模型结果:{model.state_dict()}')
# 2.6 数据可视化
# 2.6.1 绘制损失下降的曲线
# plt.scatter(range(len(loss_list)), [i.detach().numpy() for i in loss_list]) # 每一批损失
plt.scatter(range(len(loss_list)), loss_list) # 每一轮平均
plt.title('损失下降曲线')
plt.xlabel('训练轮数')
plt.ylabel('损失大小')
plt.show()
# 2.6.2 绘制样本数据的点
plt.scatter(x, y)
# 2.6.3 绘制真实直线
# 注:报错原因:'numpy.ndarray' and 'Tensor' -> numpy数据不能和tensor数据进行运算,但是tensor数据可以和numpy数据进行运算
plt.plot(x, x * coef + 3, color='r')
# 2.6.4 绘制预测直线
plt.plot(x, [i.detach().numpy() for i in model(x)], color='g')
plt.title('真实值和预测值对比')
plt.xlabel('特征值')
plt.ylabel('目标值')
plt.legend(['样本数据', '真实直线', '预测直线'])
plt.show()
if __name__ == '__main__':
x, y, coef = create_data()
train(x, y)三、神经网络基础(ANN)
1. 神经网络
1.1 人工神经网络
人工神经网络( Artificial Neural Network, 简写为ANN)也简称为神经网络(NN),⼀种模仿⽣物神经⽹络结构和功能的计算模型,由神经元构成(加权和+激活函数)
构成:输入层 | 隐藏层 | 输出层
全连接层:每一个神经元都是和上一层的所有神经元相连接,每一层的神经元互不连接
全连接神经网络接收的样本数据是二维的,数据在每一层之间需要以二维的形式传递
1.2 激活函数
激活函数用于对每层的输出数据进行变换, 进而为整个网络注入了非线性因素
常见激活函数:隐藏层(ReLU 、tanh),输出层(Sigmoid、Softmax)
1.2.1 Sigmoid激活函数(输出层二分类)
$$ 公式:f(x) = \frac{1}{1 + e^{-x}} $$
$$ 导数:f'(x) = \left( \frac{1}{1+e^{-x}} \right)' = \frac{1}{1+e^{-x}} \left( 1 - \frac{1}{1+e^{-x}} \right) = f(x)(1 - f(x)) $$
定义域:(-∞,+∞)
值域:(0,1)
有效定义域:(-6,6)
高效定义域:(-3,3)
导数值域:(0,0.25)
python
import torch
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 画板分多份
# 1行2列(左右分)返回值是两个,第一个是画板,第二个是画笔
fig, axis = plt.subplots(1, 2)
x1 = torch.linspace(-20, 20, 1000)
# 第一个画板 - sigmoid函数
y1 = torch.sigmoid(x1)
axis[0].plot(x1, y1)
axis[0].grid()
axis[0].set_title('sigmoid函数图像')
# 第二个画板 - sigmoid导函数
x2 = torch.linspace(-20, 20, 1000, requires_grad=True)
y2 = torch.sigmoid(x2).sum().backward()
axis[1].plot(x2.detach(), x2.grad)
axis[1].grid()
axis[1].set_title('sigmoid导函数图像')
plt.show()1.2.2 Tanh激活函数(隐藏层)
$$ 公式:f(x) = \frac{1 - e^{-2x}}{1 + e^{-2x}} $$
$$ 导数:f'(x) = \left( \frac{1 - e^{-2x}}{1 + e^{-2x}} \right)' = 1 - f^2(x) $$
值域:(-1,1)
导数值域:(0,1)
python
import torch
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 画板分多份
# 1行2列(左右分)返回值是两个,第一个是画板,第二个是画笔
fig, axis = plt.subplots(1, 2)
x1 = torch.linspace(-20, 20, 1000)
# 第一个画板 - tanh函数
y1 = torch.tanh(x1)
axis[0].plot(x1, y1)
axis[0].grid() # 网格
axis[0].set_title('tanh函数图像')
# 第二个画板 - tanh导函数
x2 = torch.linspace(-20, 20, 1000, requires_grad=True)
torch.tanh(x2).sum().backward()
axis[1].plot(x2.detach(), x2.grad)
axis[1].grid()
axis[1].set_title('tanh导函数图像')
plt.show()1.2.3 ReLU激活函数(隐藏层)
$$ 公式:f(x) = \max(0, x) $$
$$ 导数:f'(x) = 0 \text{ 或 } 1 $$
python
import torch
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 画板分多份
# 1行2列(左右分)返回值是两个,第一个是画板,第二个是画笔
fig, axis = plt.subplots(1, 2)
x1 = torch.linspace(-20, 20, 1000)
# 第一个画板 - relu函数
y1 = torch.relu(x1)
axis[0].plot(x1, y1)
axis[0].grid() # 网格
axis[0].set_title('relu函数图像')
# 第二个画板 - relu导函数
x2 = torch.linspace(-20, 20, 1000, requires_grad=True)
torch.relu(x2).sum().backward()
axis[1].plot(x2.detach(), x2.grad)
axis[1].grid()
axis[1].set_title('relu导函数图像')
plt.show()1.2.4 softmax激活函数(输出层多分类)
$$ \text{softmax}(z_i) = \frac{e^{z_i}}{\sum_j e^{z_j}} $$
python
import torch
data = torch.tensor([[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]], dtype=torch.float32) # 自己准备数据
# 概率的映射
pre_status = torch.softmax(data, dim=-1)
# 查看结果
print(pre_status)
# 总结:
# 数据如果不是一维,dim为0的时候会横跨多条数据,可能有污染的情况
# 一般给-1,表示最内层的数据
# softmax:多分类,将数据映射成概率,取概率最高的那一个1.3 参数初始化
参数初始化指的是初始化神经网络中每个层的权重和偏置
注:隐藏层有初始化的权重
- 均匀分布(默认0,1) - uniform
- 正态分布 - normal
- 全0 - zeros
- 全1 - ones
- 固定值 - constant,需要自己指定一个值
- kaiming uniform(以 0 为中心、对称的均匀分布区间)/normal - 比普通的方法好
- xavier uniform(以 0 为中心、对称的均匀分布区间)/normal
python
import torch
# 创建隐藏层fc1
fc = torch.nn.Linear(3, 5)
# 神经网络的权重是随机生成的
print(fc.weight)
# 修改权重:torch.nn.init.方法(fc1.weight)
# 方法1:均匀分布(默认0,1) - uniform
torch.nn.init.uniform_(fc.weight)
print(fc.weight)
# 方法2:正态分布 - normal
torch.nn.init.normal_(fc.weight)
print(fc.weight)
# 方法3:全0 - zeros
torch.nn.init.zeros_(fc.weight)
print(fc.weight)
# 方法4:全1 - ones
torch.nn.init.ones_(fc.weight)
print(fc.weight)
# 方法5:固定值 - constant,需要自己指定一个值
torch.nn.init.constant_(fc.weight, 3.14)
print(fc.weight)
# 方法6:kaiming uniform(以 0 为中心、对称的均匀分布区间)/normal - 比普通的方法好
torch.nn.init.kaiming_uniform_(fc.weight)
print(fc.weight)
# 方法7:xavier uniform(以 0 为中心、对称的均匀分布区间)/normal
torch.nn.init.xavier_normal_(fc.weight)
print(fc.weight)1.4 搭建神经网络
定义一个模型类,必须继承自nn.Module
给这个类重写__init__()方法,并且定义神经网络的层数
2.0 继承父类的初始化方法
2.1 定义神经网络的层数(隐藏层和输出层)
2.2 初始化每层的权重
2.3 初始化每层的偏置
给这个类重写forward()方法,并且定义神经网络的前向传播
计算模型参数 - summary
查看每一层模型参数 - for name, parameter in model.named_parameters(): print(name, parameter)
python
import torch.nn
from torchsummary import summary
# 1. 定义一个模型类,必须继承自nn.Module
class Model(torch.nn.Module):
# 2. 给这个类重写__init__()方法,并且定义神经网络的层数
def __init__(self):
# 2.0 继承父类的初始化方法
super().__init__()
# 2.1 定义神经网络的层数(隐藏层和输出层)
self.fc1 = torch.nn.Linear(3, 3) # 隐藏层1
self.fc2 = torch.nn.Linear(3, 2) # 隐藏层2
self.out = torch.nn.Linear(2, 2) # 输出层
# 2.2 初始化每层的权重
torch.nn.init.xavier_uniform_(self.fc1.weight)
torch.nn.init.kaiming_uniform_(self.fc2.weight)
# 2.3 初始化每层的偏置
torch.nn.init.ones_(self.fc1.bias)
torch.nn.init.ones_(self.fc2.bias)
# 3. 给这个类重写forward()方法,并且定义神经网络的前向传播
def forward(self, x):
# x1为第一层加权求和和激活函数的结果
x1 = torch.sigmoid(self.fc1(x))
# x2为第一层加权求和和激活函数的结果
x2 = torch.relu(self.fc2(x1))
# 将x2转化为输出的结果
result = torch.softmax(self.out(x2), dim=-1)
return result
if __name__ == '__main__':
# 测试模型正向传播
model = Model()
# 预测 - 传递特征3
data = torch.randn(6, 3)
print(model(data))
# 参数:要查看的模型,input_size - 输入特征数量,batch_size - 样本数量
# 也可以写一起input_size(样本数量,输入特征数量)
# summary(model, input_size=(6, 3))
summary(model, input_size=(3,), batch_size=6)
# 查看每一层模型参数
for name, parameter in model.named_parameters():
print(name, parameter)2. 损失函数
2.1 分类任务的损失函数
多分类任务损失函数 - CrossEntropyLoss
y是样本x属于某一个类别的真实概率
f(x)是样本属于某一类别的预测分数
S是softmax激活函数
$$ 多分类:\mathcal{L} = -\sum_{i=1}^n y_i \log(S(f_\theta(x_i))) $$
python
# 损失函数:衡量预测值和真实值之间的差异
# 计算多分类的预测和真实的差异(多分类交叉熵损失) - CrossEntropyLoss
import torch
# 预测值
y_pre = torch.tensor([0.1, 0.2, 0.7], dtype=torch.float32, requires_grad=True)
# 真实值: 可以是热编码后的结果也可以不进行热编码
# y_true = torch.tensor([1, 0, 0], dtype=torch.float32)
y_true = torch.tensor(0, dtype=torch.int64) # 热编码
# 定义损失函数
loss_func = torch.nn.CrossEntropyLoss()
# 计算损失
loss = loss_func(y_pre, y_true)
print(loss)二分类任务损失函数 - BCELoss
y是样本x属于某一个类别的真实概率
y^是样本属于某一类别的预测概率
$$ 二分类:L = -y \log \hat{y} - (1 - y) \log(1 - \hat{y}) $$
python
import torch
# 定义预测值
y_pre = torch.tensor([0.1, 0.2, 0.7], requires_grad=True)
# 定义真实值
y_true = torch.tensor([1, 0, 0], dtype=torch.float32)
# 定义损失函数 - BCELoss
loss_func = torch.nn.BCELoss()
# 计算损失
loss = loss_func(y_pre, y_true)
print(loss)2.2 回归任务的损失函数
MAE损失函数(也称L1 Loss) - L1Loss
y是样本x属于某一个类别的真实概率
f(x)是样本属于某一类别的预测分数
$$ \mathcal{L} = \frac{1}{n} \sum_{i=1}^n |y_i - f_\theta(x_i)| $$
MSE损失函数(也称L2 loss) - MSELoss
$$ \mathcal{L} = \frac{1}{n} \sum_{i=1}^n (y_i - f_\theta(x_i))^2 $$
Smooth L1损失函数 - SmoothL1Loss
其中:𝑥=f(x)−y 为真实值和预测值的差值。
$$ \text{smooth}_{L_1}(x) = \begin{cases} 0.5x^2 & \text{if } |x| < 1 \ |x| - 0.5 & \text{otherwise} \end{cases} $$
python
import torch
y_pre = torch.tensor([0.1, 0.2, 0.7])
y_true = torch.tensor([0.08, 0.16, 0.75])
# 1. 使用L1Loss(MAE)函数计算损失 - 不会梯度爆炸
loss_fn = torch.nn.L1Loss()
loss = loss_fn(y_pre, y_true)
print(loss)
# 2. 使用MSELoss(L2Loss)函数计算损失 - 0点可导
loss_fn = torch.nn.MSELoss()
loss = loss_fn(y_pre, y_true)
print(loss)
# 3. 使用SmoothL1Loss函数计算损失 - 综合
loss_fn = torch.nn.SmoothL1Loss()
loss = loss_fn(y_pre, y_true)
print(loss)3. 网格优化方法
3.1 梯度下降算法回顾
梯度下降法是一种寻找使损失函数最小化的方法。
Epoch:使用全部数据对模型进行以此完整训练,训练批次
Batch_size:使用训练集中的小部分样本对模型权重进行以此反向传播的参数更新,每次训练每批次样本数量
Iteration:更新一次参数的过程。每处理完一个 Batch,模型就更新一次权重
3.2 反向传播算法
3.2.1 前向传播
数据输入到神经网络中,逐层向前传输,一直运算到输出层为止
举例(激活函数为sigmoid)一层输入,一层隐藏,一层输出,每层两个神经元:
隐藏层输入:net~h1~ = w~1~ (权重)* i~1~(输入特征1) + w~2~ * i~2~ + b~1~ (偏置)* 1
隐藏层输出:out~h1~ = sigmoid(net~h1~)
同理:隐藏层输出: out~h2~ = sigmoid(net~h2~)
输出层输入:net~o1~ = w~5~ * out~h1~ +w~6~ * out~h2~ + b~2~ *1
输出层输出:out~o1~ = sigmoid(net~o1~)
同理:输出层输出:out~o2~ = sigmoid(net~o2~) $$ 损失函数E_{total} =E_{o1}+E_{o2} = \frac{1}{2}\sum(target - output)^2 $$
3.2.2 反向传播
利用损失函数 ERROR(差值)值,从后往前,结合梯度下降算法,依次求各个参数的偏导,并进行参数更新
举例(激活函数为sigmoid)一层输入,一层隐藏,一层输出,每层两个神经元: $$ 误差E对w1的偏导:\frac{\partial E_{total}}{\partial w_1} = \left( \sum_{o} \frac{\partial E_{total}}{\partial out_o} \cdot \frac{\partial out_o}{\partial net_o} \cdot \frac{\partial net_o}{\partial out_{h1}} \right) \cdot \frac{\partial out_{h1}}{\partial net_{h1}} \cdot \frac{\partial net_{h1}}{\partial w_1} $$
3.3 梯度下降的优化算法
为什么要优化:
- 碰到平缓区域,梯度值较小,参数优化变慢
- 碰到 “鞍点” ,梯度为 0,参数无法优化
- 碰到局部最小值,参数不是最优
3.3.1 指导思想
指数加权平均:距离越远的数字对平均数(一阶矩)计算的贡献就越小(权重较小),距离越近则对平均数的计算贡献就越大(权重越大)
$$ S_t = \begin{cases} \beta \cdot S_{t-1} + (1 - \beta) \cdot Y_t, & t > 0 \ Y_1, & t = 0 \end{cases} $$
• S~t~表示指数加权平均值;
• Y~t~ 表示 t 时刻的值;
• β 调节权重系数,该值越大平均数越平缓。
3.3.2 动量算法Momentum
从优化梯度方面优化
一定程度上克服平缓、“鞍点” 问题
API:optimizer = optim.SGD(params=[w], lr=0.01, momentum=0.9)
$$ 梯度计算公式:s_t = \beta s_{t-1} + (1-\beta)g_t $$
$$ 参数更新公式:w_t = w_{t-1} - \eta s_t $$
参数说明:
s~t~是当前时刻指数加权平均梯度值
s~t-1~是历史指数加权平均梯度值
g~t~是当前时刻的梯度值
β 是调节权重系数,通常取 0.9 或 0.99
η是学习率
w~t~是当前时刻模型权重参数
python
import torch
from torch import optim
# 准备一个初始化的w
w = torch.tensor(10, requires_grad=True, dtype=torch.float32)
# 准备优化
# 参数说明:1.要更新的权重,是一个列表 2.学习率 3.度量的系数β - 0.9~0.99
optimizer = optim.SGD(params=[w], lr=0.01, momentum=0.9)
for i in range(100):
# 准备一个loss(w会一直更新)
loss = w ** 2 / 2
# loss自动微分
loss.backward()
# 参数更新
# w.data = w.data - 0.01 * w.grad # 手动更新
optimizer.step() # 自动更新(度量法)
# 梯度清空
optimizer.zero_grad()
print(f'度量法更新结果:{w.data}')
# 优点:不会频繁的梯度震荡
# 缺点:当前的变化会收之前梯度的影响,可能出现变化反应慢的情况3.3.3 AdaGrad
从优化学习率方面优化
缺点:可能会使得学习率过早、过量的降低,导致模型训练后期学习率太小,较难找到最优解。
API:optimizer = optim.Adagrad(params=[w], lr=0.6)
$$ 累计平方梯度:{s}{t} ={s} + {g}{t} \odot{g} $$
$$ 学习率计算:\eta = \frac{\eta}{\sqrt{s_t} + \sigma} $$
$$ 权重参数更新:w_t = w_{t-1} - \frac{\eta}{\sqrt{s_t} + \sigma} \cdot g_t $$
参数说明:
初始化学习率 η、初始化参数w、小常数 σ = e^-10^
初始化梯度累计变量 s = 0
梯度g~t~,⊙ 表示各个分量相乘
python
import torch
from torch import optim
# 准备一个初始化的w
w = torch.tensor(10, requires_grad=True, dtype=torch.float32)
# 准备优化
# 参数说明:1.要更新的权重,是一个列表 2.初始化学习率
optimizer = optim.Adagrad(params=[w], lr=0.6)
for i in range(100):
# 准备一个loss(w会一直更新)
loss = w ** 2 / 2
# loss自动微分
loss.backward()
# 参数更新
optimizer.step() # 自动更新(度量法)
# 梯度清空
optimizer.zero_grad()
print(f'AdaGrad更新结果:{w.data}')
# 开始时更新跨度大,后面更新跨度小3.3.4 RMSProp
从优化学习率方面优化
API:optimizer = optim.RMSprop(params=[w], lr=0.6, alpha=0.9)
$$ 指数加权累计平方梯度:s_t = \beta s_{t-1} + (1-\beta)g_t \odot g_t $$
$$ 学习率计算:\eta = \frac{\eta}{\sqrt{s_t} + \sigma} $$
$$ 权重参数更新:w_t = w_{t-1} - \frac{\eta}{\sqrt{s_t} + \sigma} \cdot g_t $$
参数说明:
初始化学习率 η、初始化参数w、小常数 σ = e^-10^
初始化梯度累计变量 s = 0
梯度g~t~,⊙ 表示各个分量相乘
python
import torch
from torch import optim
# 准备一个初始化的w
w = torch.tensor(10, requires_grad=True, dtype=torch.float32)
# 准备优化
# 参数说明:1.要更新的权重,是一个列表 2.初始化学习率 3.度量的系数α0.9~0.99
optimizer = optim.RMSprop(params=[w], lr=0.6, alpha=0.9)
for i in range(100):
# 准备一个loss(w会一直更新)
loss = w ** 2 / 2
# loss自动微分
loss.backward()
# 参数更新
optimizer.step() # 自动更新(度量法)
# 梯度清空
optimizer.zero_grad()
print(f'RMSProp更新结果:{w.data}')
# 与AdaGrad算法相比,RMSProp使用的是指数加权平均梯度,AdaGrad使用了累计梯度平方3.3.5 Adam
将 Momentum 和 RMSProp 算法结合在一起,从梯度和学习率两方面优化:
修正梯度:使⽤梯度的指数加权平均
修正学习率: 使⽤**梯度平⽅**的指数加权平均
API:optimizer = optim.Adam(params=[w], lr=0.6, betas=(0.9, 0.99))
β1 - 梯度更新 - 历史数据的参考长度
β2 - 学习率更新 - 历史数据的参考程度
$$ 梯度的有偏一阶矩(平均值)估计:m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t $$
$$ 梯度的有偏二阶矩(方差)估计s_t = \beta_2 s_{t-1} + (1 - \beta_2) g_t^2 $$
$$ 矫正偏差后的估计:\hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \quad \hat{s}_t = \frac{s_t}{1 - \beta_2^t} $$
$$ 权重更新公式:w_t = w_{t-1} - \frac{\eta}{\sqrt{\hat{s}_t} + \epsilon} \hat{m}_t $$
python
import torch
from torch import optim
# 准备一个初始化的w
w = torch.tensor(10, requires_grad=True, dtype=torch.float32)
# 准备优化
# 参数说明:1.要更新的权重,是一个列表 2.初始化学习率 3.影响梯度更新的系数β1,影响学习率的系数β2
optimizer = optim.Adam(params=[w], lr=0.6, betas=(0.9, 0.99))
for i in range(100):
# 准备一个loss(w会一直更新)
loss = w ** 2 / 2
# loss自动微分
loss.backward()
# 参数更新
optimizer.step() # 自动更新(度量法)
# 梯度清空
optimizer.zero_grad()
print(f'Adam更新结果:{w.data}')
# 衰减速率:先快后慢
# 变化周期:偏长,会有延迟3.3.6 总结
SGD/度量 - demo,少样本
RMSProp/AdaGrad - 文本处理
Adam(掌握) - 大多数情况,多样本
3.4 学习率衰减方法
3.4.1 等间隔学习率衰减
1.设置学习率衰减的策略 scheduler = optim.lr_scheduler.StepLR(优化器,step-size=,gamma=) 2.更新学习率 scheduler.step() 3.查看学习率 scheduler.get_last_lr()
python
import torch
# 准备真实值
y_true = torch.tensor(10, dtype=torch.float32, requires_grad=True)
# 准备特征
x = torch.tensor(2, dtype=torch.float32, requires_grad=True)
# 准备权重
w = torch.tensor(2, dtype=torch.float32, requires_grad=True)
# 准备优化器
optimizer = torch.optim.SGD(params=[w], lr=0.1, momentum=0.9)
# 设置学习率下降策略(步长step_size,衰减率gamma)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
for i in range(50):
# 查看当前学习率
print(f'第{i + 1}学习率:{scheduler.get_last_lr()}')
# 损失函数
loss = (y_true - w * x) ** 2 / 2
# 自动微分
loss.backward()
# 更新梯度
optimizer.step()
# 更新学习率
scheduler.step()3.4.2 指定间隔学习率衰减
1.设置学习率衰减的策略 scheduler = optim.lr_scheduler.MultiStepLR(优化器,milestones=[],gamma=) 2.更新学习率 scheduler.step() 3.查看学习率 scheduler.get_last_lr()
python
import torch
# 准备真实值
y_true = torch.tensor(10, dtype=torch.float32, requires_grad=True)
# 准备特征
x = torch.tensor(2, dtype=torch.float32, requires_grad=True)
# 准备权重
w = torch.tensor(2, dtype=torch.float32, requires_grad=True)
# 准备优化器
optimizer = torch.optim.SGD(params=[w], lr=0.1, momentum=0.9)
# 设置学习率下降策略(调整伦次milestones,衰减率gamma)
scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[5, 20, 49], gamma=0.1)
for i in range(50):
# 查看当前学习率
print(f'第{i + 1}学习率:{scheduler.get_last_lr()}')
# 损失函数
loss = (y_true - w * x) ** 2 / 2
# 自动微分
loss.backward()
# 更新梯度
optimizer.step()
# 更新学习率
scheduler.step()3.4.3 按指数学习率衰减
1.设置学习率衰减的策略 scheduler = optim.lr_scheduler.ExponentialLR(优化器,gamma=0.9) 2.更新学习率 scheduler.step() 3.查看学习率 scheduler.get_last_lr()
python
import torch
# 准备真实值
y_true = torch.tensor(10, dtype=torch.float32, requires_grad=True)
# 准备特征
x = torch.tensor(2, dtype=torch.float32, requires_grad=True)
# 准备权重
w = torch.tensor(2, dtype=torch.float32, requires_grad=True)
# 准备优化器
optimizer = torch.optim.SGD(params=[w], lr=0.1, momentum=0.9)
# 设置学习率下降策略(衰减率gamma0.9~0.99)
scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.9)
for i in range(50):
# 查看当前学习率
print(f'第{i + 1}学习率:{scheduler.get_last_lr()}')
# 损失函数
loss = (y_true - w * x) ** 2 / 2
# 自动微分
loss.backward()
# 更新梯度
optimizer.step()
# 更新学习率
scheduler.step()4. 正则化方法
作用:缓解过拟合
4.1 Dropout正则化
原理:让神经元以超参数p的概率停止工作或者激活被置为0,未被置为0的进行缩放,缩放比例为1/(1-p)
作用时机:1. 训练过程中使用 2. 用在激活函数后
语法:
创建实例对象dropout写法:
drop = torch.nn.Dropout(p=0.3) data = drop(data)
直接使用函数写法
data = torch.dropout(data, 0.3, True)
参数:1. 要进行dropout的数据 2. 失活的概率 3. 是否训练时开启
python
import torch
data = torch.randint(1, 10, (3, 4), dtype=torch.float32) # 3个样本4个特征
print('初始', data)
# 搭建隐藏层
fc = torch.nn.Linear(4, 5)
# 1. 加权求和
data = fc(data)
# 2. 激活函数
data = torch.tanh(data)
# 3. dropout正则化 p一般是0.2~0.5
# 3.1 创建示例对象dropout写法
drop = torch.nn.Dropout(p=0.3)
data = drop(data)
# 3.2 直接使用函数写法
# 参数:1. 要进行dropout的数据 2. 失活的概率 3. 是否训练时开启
data = torch.dropout(data, 0.3, True)
print('dropout后', data)4.2 批量归一化
作用时机:激活函数前,加权求和后
逻辑:先标准化,再归一化
参数:
1.λ 和 β 是可学习的参数,它相当于对标准化后的值做了一个线性变换**,**λ为系数,β 为偏置;
2.eps 通常指为 1e-5,避免分母为 0;
3.E(x) 表示变量的均值;
4.Var(x) 表示变量的方差;
语法:
torch.nn.BatchNorm1d(2条数据,3个特征) # 全连接层
torch.nn.BatchNorm2d(1张,3通道,30宽,30高) # 图像 卷积层 池化层
torch.nn.BatchNorm3d(1张,2层数,3通道,200宽,200高) # 医学图像
$$ f(x) = \lambda \cdot \frac{x - E(x)}{\sqrt{\text{Var}(x)} + \epsilon} + \beta $$
python
import torch
data1 = torch.randn(2, 3)
data2 = torch.randn(1, 3, 30, 30)
# 隐藏层
fc = torch.nn.Linear(3, 5)
# 批量归一化
# torch.nn.BatchNorm1d(2条数据,3个特征) # 全连接层
bn1 = torch.nn.BatchNorm1d(num_features=5)
# torch.nn.BatchNorm2d(1张,3通道,30宽,30高) # 图像 卷积层 池化层
bn2 = torch.nn.BatchNorm2d(num_features=3)
# torch.nn.BatchNorm3d(1张,2层数,3通道,200宽,200高) # 医学图像
# 1. 加权求和
data1 = fc(data1)
# 2. 批量归一化
data1 = bn1(data1)
data2 = bn2(data2)
# 3. 激活函数
data1 = torch.nn.ReLU()(data1)
print(data1)
print(data2)5. 案例 - 手机价格分类
总结(步骤): 1.准备数据 2.数据预处理(划分训练集和测试集) 3.准备模型(全连接神经网络) 4.训练模型(前向传播+反向传播) 5.保存模型 6.模型预测 7.模型评估(评估数据的转换) 8.模型优化
优化总结: 1.修改优化器的类型(Adam) 2.新添加隐藏层 3.添加BN层/数据标准化 4.按指数衰减学习率 5.增加训练轮数 6.学习率
python
import time
import numpy as np
import pandas as pd
import torch
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from torch.utils.data import TensorDataset, DataLoader
import torch.optim as optim
from torchsummary import summary
# 1. 准备数据
# 2. 数据预处理
def create_data():
# 1.1 加载csv数据
data = pd.read_csv('手机价格预测.csv', sep=',', encoding='utf-8')
# 1.2 划分特征和标签
x = data.iloc[:, :-1]
y = data.iloc[:, -1]
# 1.3 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=42)
# 一般不与批量归一化一起使用
# # -------------优化 - 数据标准化-----------------
# scaler = StandardScaler()
# x_train = scaler.fit_transform(x_train)
# x_test = scaler.transform(x_test)
# # -------------优化 - 数据标准化--------------------
# 1.4 封装训练集和测试集
# 数据 -> 张量 ->数据集(训练/测试)
train_dataset = TensorDataset(torch.tensor(x_train.values, dtype=torch.float32),
torch.tensor(y_train.values))
test_dataset = TensorDataset(torch.tensor(x_test.values, dtype=torch.float32),
torch.tensor(y_test.values))
# 1.5 搭建神经网络还需要知道入参和出参是多少
return train_dataset, test_dataset, x.shape[1], len(np.unique(y))
# 3. 准备模型
class PhonePriceModel(torch.nn.Module):
def __init__(self, input_size, output_size):
super().__init__()
# 输入层 - 20 ,隐藏层 - ? ,输出层 - 4
# 需求:两个隐藏 1个输出
# 优化:再加一层隐藏层(不一定是优化)
self.hidden1 = torch.nn.Linear(input_size, 128)
self.hidden2 = torch.nn.Linear(128, 256)
self.hidden3 = torch.nn.Linear(256, 512)
# -------------------批量归一化-------------------------
self.bn1 = torch.nn.BatchNorm1d(num_features=128)
self.bn2 = torch.nn.BatchNorm1d(num_features=256)
self.bn3 = torch.nn.BatchNorm1d(num_features=512)
# -------------------批量归一化-------------------------
self.out = torch.nn.Linear(512, output_size)
# 权重与偏置初始值使用默认的
def forward(self, x):
# 批量归一化优化前
# x1 = torch.relu(self.hidden1(x))
# x2 = torch.relu(self.hidden2(x1))
# x3 = torch.relu(self.hidden3(x2))
# 批量归一化优化
x1 = torch.relu(self.bn1(self.hidden1(x)))
x2 = torch.relu(self.bn2(self.hidden2(x1)))
x3 = torch.relu(self.bn3(self.hidden3(x2)))
# 后续梯度下降优化中多分类交叉熵损失函数中内置了softmax,所以这里不需要使用softmax
result = self.out(x3)
return result
# 4. 训练模型
def train(data, input_size, output_size):
# 4.1 数据集 -> 数据加载器
dataloader = DataLoader(data, batch_size=16, shuffle=True)
# 4.2 创建模型 + 优化器
model = PhonePriceModel(input_size, output_size)
optimizer = optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999))
# 优化 - 学习率按指数衰减
scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.9)
# 4.3 训练模型
# model.train - 模型开启训练模式 model.eval - 模型开启测试模式
model.train()
# 多轮训练 - 100轮
for i in range(100):
total_loss, total_num, start = 0, 0, time.time()
# 一轮训练 - 2000条数据
for x_train, y_train in dataloader:
# 4.3.1. 模型预测
pre = model(x_train)
# 4.3.2. 损失函数
loss_fn = torch.nn.CrossEntropyLoss()
# 4.3.3 计算损失
loss = loss_fn(pre, y_train)
# 4.3.4 自动微分
loss.backward()
# 4.3.5 更新梯度
optimizer.step()
# 4.3.6 梯度清零
optimizer.zero_grad()
# 4.3.7 累计损失
total_loss += loss.item()
total_num += 1
# 一轮更新一次学习率
scheduler.step()
print(f'第{i + 1}轮,平均损失为:{total_loss / total_num:.6f},训练时间:{time.time() - start:.4f}')
# 4.4 保存训练模型的参数
torch.save(model.state_dict(), '手机价格预测.pth')
# 5. 测试模型
def predict(data, input_size, output_size):
# 加载模型和参数
model = PhonePriceModel(input_size, output_size)
model.load_state_dict(torch.load('手机价格预测.pth', weights_only=True))
# 数据集 -> 数据加载器
dataloader = DataLoader(data, batch_size=8)
# 测试模式
model.eval()
# 模型预测
score = 0
for x_test, y_test in dataloader:
# pre是预测,y是真实(评估准确率)
pre = model(x_test)
print(pre) # 预测的结果,8行4列
print(torch.argmax(pre, dim=1)) # 从数据找出最大的索引,索引就是分类(预测的)
print(torch.argmax(pre, dim=1) == y_test) # 比较真实和预测的值得到布尔索引(列表)
print(sum(torch.argmax(pre, dim=1) == y_test)) # 累加True的值得到猜对的个数
score += sum(torch.argmax(pre, dim=1) == y_test).item()
# 正确率 = 猜对的 / 总数
print(f"预测结束,当前的准确率:{score / len(data)}")
if __name__ == '__main__':
# 1. 准备数据
train_datasets, test_datasets, input_size, output_size = create_data()
# 2. 模型准备
model = PhonePriceModel(input_size, output_size)
# 查看模型
summary(model, input_size=(input_size,))
# 3. 训练模型
train(train_datasets, input_size, output_size)
# 4. 模型加载,预测
predict(test_datasets, input_size, output_size)四、卷积神经网络(CNN)
1. 图像基础知识
1.1 基本概念
图像是由像素点组成的,每个像素点的取值范围为: [0, 255] 。像素值越接近于0,颜色越暗,接近于黑色;像素值越接近于255,颜色越亮,接近于白色。
深度学习中,我们使用的图像大多是彩色图,彩色图由RGB3个通道组成
1.2 图像绘制
plt.imshow()
python
import numpy as np
import matplotlib.pyplot as plt
# 图片格式:HWC 高宽通道
# 图片绘制:plt.imshow()
# 1. 准备数据(两张照片200*200,黑和白)
img1 = np.zeros((200, 200, 3)) # 黑色
img2 = np.full((200, 200, 3), 255) # 白色
img3 = np.random.randint(0, 255, (200, 200, 3))
# 2. 绘制图片
fig, axis = plt.subplots(1, 3)
axis[0].imshow(img1)
axis[1].imshow(img2)
axis[2].imshow(img3)
plt.show()1.3 图像读取
plt.imread()
python
import matplotlib.pyplot as plt
import torch
# 1. 读取图片上的内容
data = plt.imread('img.jpg')
# 2. data一定是符合HWC格式的
print(data.shape)
# 【拓展】3. 后续可能需要对图片进行维度转换
# 维度转换
data = torch.permute(torch.tensor(data), (2, 0, 1))
print('维度转换后:', data.shape)
# 升维
data = torch.unsqueeze(data, 0)
print(data.shape)2. CNN概述
卷积神经网络CNN:含有卷积层的神经网络
组成部分:卷积层、池化层、全连接层
卷积层:提取图像中的局部特征
池化层:用来大幅降低参数量级(降维) - 从而减少计算量、减少内存消耗,并提高模型的鲁棒性
全连接层:用来输出想要的结果
3. 卷积层
API : conv = torch.nn.Conv2d(in_channels,out_channels,kernel_size, stride, padding)
参数:
in_channels : int # 输入的通道数(图片一般是3)
out_channels : int # 输出的通道数(卷积核的数量)
kernel_size : _size_2_t # 卷积核大小
stride : _size_2_t = 1 # 步长,默认值为1
padding : Union[str,_size_2_t] = 0 # 填充,默认值为0
python
import matplotlib.pyplot as plt
import torch
data = plt.imread('img.jpg')
data = torch.tensor(data, dtype=torch.float32)
# HWC -> CWH
data = data.permute(2, 0, 1)
# 升维
data = torch.unsqueeze(data, 0)
# 新建卷积层
conv = torch.nn.Conv2d(in_channels=3, out_channels=3, kernel_size=(3, 3))
# 将图片送入卷积层
data = conv(data)
# 提取图片内容
data = data[0] # 降维
# 还原HWC
data = data.permute(1, 2, 0)
# plt.imshow(data.detach().numpy()) # 查看3通道所有特征
# plt.imshow(data[:,:,0].detach().numpy())
# plt.imshow(data[:,:,1].detach().numpy())
plt.imshow(data[:,:,2].detach().numpy())
plt.show()4. 池化层
API:
最大池化:torch.nn.MaxPool2d()
平均池化:torch.nn.AvgPool2d()
参数:
kernel_size : _size_2_t # 卷积核大小
stride: Optional[_size_2_t] = None # 步长,不传则使用默认值
padding: _size_2_t = 0 # 填充,默认值为0
【拓展】:
imshow 只接受两种 RGB 格式: (H, W, 3) —— RGB 彩色图像 (H, W, 4) —— RGBA 彩色图像(带透明度) (H, W) —— 灰度图像
python
import matplotlib.pyplot as plt
import torch
data = plt.imread('img.jpg')
# 1. 图片转换为张量
data = torch.tensor(data, dtype=torch.float32)
# 2. 维度形状转换
data = data.permute(2, 0, 1)
data.unsqueeze_(dim=0)
# 3. 创建卷积层
conv = torch.nn.Conv2d(in_channels=3, out_channels=4, kernel_size=4, stride=2)
# 4. 创建池化层
# 参数:池化大小
# pool = torch.nn.MaxPool2d((3, 3)) # 最大池化
pool = torch.nn.AvgPool2d((3, 3)) # 平均池化
# 5. 进入卷积和池化的处理
conv_data = conv(data)
pool_data = pool(conv_data)
# 绘制图像
data = pool_data[0]
data = data.permute(1, 2, 0)
# 4通道
plt.imshow(data[:, :, 0].detach().numpy())
# plt.imshow(data[:, :, 1].detach().numpy())
# plt.imshow(data[:, :, 2].detach().numpy())
# plt.imshow(data[:, :, 3].detach().numpy())
plt.show()5. 案例-图片识别分类
python
import time
import torch
from torch.utils.data import DataLoader
from torchsummary import summary
from torchvision.datasets import CIFAR10
from torchvision.transforms import ToTensor
# 1. 准备图片数据(训练5w,测试1w)
def create_dataset():
# 参数:1.数据存放的问题 2.是否为训练集 3.转换为张量
train = CIFAR10(root='.', train=True, transform=ToTensor())
test = CIFAR10(root='.', train=False, transform=ToTensor())
return train, test
# 2. 准备模型
class ImageModel(torch.nn.Module):
def __init__(self):
super().__init__()
# 创建卷积层和池化层
self.conv1 = torch.nn.Conv2d(in_channels=3, out_channels=6, kernel_size=(3, 3))
self.pool1 = torch.nn.MaxPool2d((2, 2), stride=2)
self.conv2 = torch.nn.Conv2d(in_channels=6, out_channels=16, kernel_size=(3, 3))
self.pool2 = torch.nn.MaxPool2d((2, 2), stride=2)
# 全连接层
self.fc1 = torch.nn.Linear(576, 120)
self.fc2 = torch.nn.Linear(120, 84)
self.out = torch.nn.Linear(84, 10)
def forward(self, x):
x1 = self.pool1(torch.relu(self.conv1(x)))
x2 = self.pool2(torch.relu(self.conv2(x1)))
x3 = torch.relu(self.fc1(x2.reshape(-1, 576)))
x4 = torch.relu(self.fc2(x3))
result = self.out(x4)
return result
# 3. 训练模型
def train(data):
# 准备模型
model = ImageModel()
# 准备数据加载器
dataloader = DataLoader(data, batch_size=8, shuffle=True)
# 准备损失函数
loss_fn = torch.nn.CrossEntropyLoss()
# 准备优化器
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 5轮训练
for i in range(5):
# 一轮50000条数据
total_loss, count, start = 0, 0, time.time()
for x, y in dataloader:
# 预测
pre = model(x)
# 损失
loss = loss_fn(pre, y)
# 自动微分
loss.backward()
# 更新梯度
optimizer.step()
# 梯度清零
optimizer.zero_grad()
# 累计损失
total_loss += loss.item()
count += 1
print(f'第{i + 1}轮训练结束,总耗时:{time.time() - start:.4f},平均损失:{total_loss / count:.6f}')
# 保存模型参数
torch.save(model.state_dict(), 'ImageModel.pth')
# 4. 模型预测
def predict(data):
# 数据加载器
dataloader = DataLoader(data, batch_size=8, shuffle=False)
# 模型加载
model = ImageModel()
model.load_state_dict(torch.load('ImageModel.pth', weights_only=True))
# 模型测试
model.eval()
score, count = 0, 0
for x, y in dataloader:
# 预测
pre = model(x)
# 预测结果索引
pre = torch.argmax(pre, dim=1)
# 与真实值比对
score += sum(pre == y).item()
count += len(x)
print(f'预测准确率:{score / count:.4f}')
if __name__ == '__main__':
# 1. 准备数据
train_data, test_data = create_dataset()
# # 2. 准备模型,查看模型
# model = ImageModel()
# summary(model, (3, 32, 32))
# 3. 模型训练
train(train_data)
# 4. 模型预测
predict(test_data)五、循环神经网络(RNN)
1. RNN介绍
循环神经网络(Recurrent Neural Network, RNN):是一种专门处理序列数据的神经网络,具有“循环”结构,能够处理和记住前面时间步的信息
序列数据:后面的数据跟前面的数据有关系,比如时间序列,文字序列
应用:自然语言处理(NLP)、时间序列预测、语音识别、音乐生成
2. 词嵌入层
作用:将文本转为向量
在RNN中的作用:输入表示、降低维度、捕捉语义相似性
API:nn.Embedding(num_embeddings=,embedding_dim=)
参数:num_embeddings - 词的数量 embedding_dim - 词的维度
输出形状: batch_size(批次大小),seq_len(句子的长度),input_size(词向量的维度)
2.1 工作流程
- 初始化词向量
- 输入索引:文本先被分词,再被转为相应的索引
- 查找词向量
- 输入到RNN
2.2 案例
python
import jieba
import torch
# 1. jieba切词
words = jieba.lcut('你好,世界,你不好,世界,你好不好,世界,你好好,世界')
# 去重
words = list(set(words))
# 2. 词嵌入层进行转换
# 参数:1. 词的数量 2. 词嵌入维度
embedding = torch.nn.Embedding(len(words), 5)
# 3. 打印词嵌入层中的信息
# enumerate 拿索引和内容
for i, words in enumerate(words):
print(i, words) # i是每一个词的索引,要将词转成词向量,将索引向量化
words_to_vector = embedding(torch.tensor(i)) # Embedding只支持long类型的值作为词向量转化的输入
print(words_to_vector)3. 循环网络层
3.1 RNN网络原理
RNN:输入 → 隐藏层(结合上一次的隐藏状态)→ 输出
隐藏状态作用:记忆功能、上下文理解、连接不同时间步
API:
- 创建RNN层
RNN = torch.nn.RNN(input_size,hidden_size,num_layers)
参数:
input_size:输入数据的维度,一般设为词向量的维度
hidden_size:隐藏层h的维度,也是当前层神经元的输出维度
num_layers: 隐藏层h的层数,默认为1
- 使用RNN层
output, h~n~ = RNN(x, h~0~)
x表现形式:[seq_len, batch_size, input_size],即[句子的长度,批次大小,词向量的维度]
h~0~,h~n~表现形式:[num_layers, batch_size, hidden_size],即[隐藏层的层数,批次大小,隐藏层h的维数]
output表现形式:[seq_len,batch_size, hidden_size],即[句子的长度,批次大小,输出向量的维度]
$W_{xh}$ 表示输入数据的权重
$b{h}$ 表示偏置
$W_{hh}$ 表示输入隐藏状态的权重
$h_{t-1}$ 表示输入隐藏状态
$h_t$ 表示输出隐藏状态
$x_t$当前时间步的输入
$$ 隐藏状态计算:h_t = \tanh(W_{xh} \cdot x_t + W_{hh} \cdot h_{t-1} + b_h) $$
1.$y_t$ 是当前时刻的输出(通常是一个向量,表示当前时刻的预测值,RNN层的预测值)
2.$h_t$ 是当前时刻的隐藏状态
- $W_{hy}$是从隐藏状态到输出的权重矩阵
4.$b_y$ 是输出层的偏置项
$$ 当前时刻输出:y_t = W_{hy} \cdot h_t + b_y $$
输出y~t~是一个向量,该向量经过全连接层后输出得到最终预测结果Y~pred~,Y~pred~中每个元素代表当前时刻生成词汇表中某个词的得分(或概率,通过激活函数如softmax)。词汇表有多少个词,Y~pred~就有多少个元素值,最大元素值对应的词就是当前时刻预测生成的词。
3.2 案例
python
import torch
# 1. 创建RNN层
rnn = torch.nn.RNN(input_size=128, hidden_size=256)
# 2. 准备x与h0
# 参数:1. seq_len(句子长度) 2. batch_size(批次大小) 3. input_size(词向量维度)
x = torch.randn(7, 32, 128)
# 参数说明:1.隐藏层层数 2.batch_size批次大小 3.hidden_size输出向量维度
h0 = torch.randn(1, 32, 256)
# 3. 进入RNN层,得到h1和output
# 注1:先给输入,后给状态
# 注2: 先拿结果,后拿状态
output, h1 = rnn(x, h0)
print(output.shape, h1.shape)4. 案例 - 歌词生成
python
import time
import jieba
import torch
from torch.utils.data import DataLoader
# 1. 读取歌词 - txt文件
def read_lyric():
# 去重后词
unique_words = []
# 所有的词
all_words = []
with open('jaychou_lyrics.txt', 'r', encoding='utf-8') as f:
# 遍历每一行
for i in f.readlines():
# 分词
words = jieba.lcut(i)
for word in words:
# 添加到all_words
all_words.append(word)
# 去重
if word not in unique_words:
unique_words.append(word)
# 词表 字典{词:索引}
words_to_index = {word: i for i, word in enumerate(unique_words)}
# 遍历所有词,索引化
all_words_to_index = [words_to_index[word] for word in all_words]
# print('all_words_to_index:', all_words_to_index)
return unique_words, words_to_index, all_words_to_index
# 2. 构建数据集对象
class LyricsDataset(torch.utils.data.Dataset):
# 参数:1. 所有词的索引化列表 2. 每句话的个数
def __init__(self, all_words_to_index, num_chars):
self.all_words_to_index = all_words_to_index
# 每句话的个数
self.num_chars = num_chars
# 文本共有多少个词(不去重)
self.num_words = len(all_words_to_index)
# 歌词文本能生成多少句话
self.num_sentence = self.num_words // self.num_chars
# 重写len魔法方法 len -> 输出返回值
def __len__(self):
return self.num_sentence
# 重写getitem魔法方法
def __getitem__(self, index):
# 开始索引
start = min(max(index, 0), self.num_words - self.num_chars - 1)
# 结束索引
end = start + self.num_chars
# 定义特征x,标签y
x = self.all_words_to_index[start:end]
y = self.all_words_to_index[start + 1:end + 1]
return torch.tensor(x), torch.tensor(y)
# 3. 创建模型
class LyricsModel(torch.nn.Module):
def __init__(self, words_to_index):
super().__init__()
# 词嵌入层
# 参数:1.词数量 2.词维度
self.embedding = torch.nn.Embedding(len(words_to_index), 128)
# 循环层
# 参数:1.输入维度 2.隐藏层维度 3.层数
self.rnn = torch.nn.RNN(128, 256, 1)
# 输出层
# 参数:1. 输入特征(与隐藏层维度相同) 2. 输出特征(词数量,每个词都有相应的概率)
self.out = torch.nn.Linear(256, len(words_to_index))
def forward(self, x, hidden):
# 先经过词嵌入层
x1 = self.embedding(x)
# Embedding输出的形状:(样本数量,句子长度,词维度)
# 通过RNN层
# 需要的x维度为(句子长度,样本数量,词维度)
# 需要的hidden维度为(隐藏层层数,样本数量,隐藏层维度)
output, hidden = self.rnn(x1.transpose(0, 1), hidden) # 更新隐藏状态hidden
# 通过输出层
# output形状为(句子长度,样本数量,输出向量(隐藏层)维度)
# output.shape[-1] output最后一个维度大小
result = self.out(output.reshape(-1, output.shape[-1]))
return result, hidden
def init_hidden(self, batch_size):
# 需要的hidden维度为(隐藏层层数,样本数量,隐藏层维度)
hidden = torch.zeros(1, batch_size, 256)
return hidden
# 4. 训练模型
def train():
# 读取歌词
unique_words, words_to_index, all_words_to_index = read_lyric()
# 创建数据集(每句话词的个数num_chars)
train_dataset = LyricsDataset(all_words_to_index, num_chars=32)
# 创建模型
model = LyricsModel(words_to_index)
# 创建数据加载器
train_loader = DataLoader(train_dataset, batch_size=10, shuffle=True)
# 创建损失函数
loss_function = torch.nn.CrossEntropyLoss()
# 创建优化器
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 训练多轮
for epoch in range(10):
# 开始时间
start = time.time()
# 累计损失
total_loss = 0
# 累计批次
total_num = 0
# 训练一轮
for x_train, y_train in train_loader:
# 初始化隐藏状态
hidden = model.init_hidden(x_train.size(0))
# 模型预测
result, hidden = model(x_train, hidden)
# 计算损失
# print(x_train.shape) # [32,40] 样本数量,每句话的长度
# print(result.shape) # [1280,5703]
# print(y_train.shape) # [32,40] -> 展平成一维 [1280]
# y形状为(batch, seq_len)
# result形状为(seq_len, batch, 词向量维度)
# 需要先将y进行维度交换(和output保持一致)再改变形状
loss = loss_function(result, y_train.transpose(0, 1).reshape(-1, ))
total_loss += loss.item()
total_num += 1
# 梯度清零
optimizer.zero_grad()
# 自动微分
loss.backward()
# 梯度更新
optimizer.step()
print(f"第{epoch + 1}轮训练完成,平均损失为{total_loss / total_num:.6f},用时为{time.time() - start:.4f}")
# 保存模型
torch.save(model.state_dict(), './lyricsmodel.pth')
# 5. 模型测试
# 开始词start_ word 包含词数量sentence_len
def predict(start_word, sentence_len):
# 读取歌词
unique_words, words_to_index, all_words_to_index = read_lyric()
# 创建模型
model = LyricsModel(words_to_index)
# 加载参数
model.load_state_dict(torch.load('./lyricsmodel.pth'))
# 初始化隐藏状态
hidden = model.init_hidden(batch_size=1)
# 获取开始词的索引
word_index = words_to_index[start_word]
# 最终结果索引
result_index = [word_index]
# 遍历长度,生成每一个歌词
for i in range(sentence_len):
result, hidden = model(torch.tensor([[word_index]]), hidden)
# 通过索引拿结果
word_index = torch.argmax(result)
result_index.append(word_index)
# 索引转单词
for i in result_index:
print(unique_words[i], end=' ')
if __name__ == '__main__':
# 训练模型
# train()
# 模型预测
predict('分手', 50)