Demo
基于pytorch框架编写模型训练
实现一个自行构造的找规律(机器学习)任务
规律:x是一个5维向量,如果第1个数>第5个数,则为正样本,反之为负样本
代码
python
import time
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
from dask.bag.text import delayed
"""
基于pytorch框架编写模型训练
实现一个自行构造的找规律(机器学习)任务
规律:x是一个5维向量,如果第1个数>第5个数,则为正样本,反之为负样本
"""
# 生成一个样本, 样本的生成方法,代表了我们要学习的规律
# 随机生成一个5维向量,如果第一个值大于第五个值,认为是正样本,反之为负样本
def build_sample():
# np.random.random(5):生成一个长度为5的一维数组,每个元素是[0,1)区间的随机浮点数
# 例如,[0.3, 0.6, 0.1, 0.9, 0.2]
x = np.random.random(5)
# 四舍五入-保留小数点后3位
x = np.around(x, 3)
# 判断第一个数是否大于第五个数
# 返回输入特征向量和标签
# 也就是返回[0.3, 0.6, 0.1, 0.9, 0.2] 和 (1或0)
if x[0] > x[4]:
# 正样本
return x, 1
else:
# 负样本
return x, 0
# 生成total_sample_num个样本
def build_dataset(total_sample_num):
X = []
Y = []
# 循环
for i in range(total_sample_num):
# 生成一个样本
x, y = build_sample()
X.append(x)
Y.append([y])
return torch.FloatTensor(X), torch.FloatTensor(Y)
# 总样本数
total_sample_num = 5000
train_x, train_y = build_dataset(total_sample_num)
print("本次预测集中共有%d个正样本,%d个负样本" % (sum(train_y), total_sample_num - sum(train_y)))
class TorchModel(nn.Module):
def __init__(self, input_size):
super(TorchModel, self).__init__()
self.linear = nn.Linear(input_size, 1) # 线性层
self.activation = torch.sigmoid # sigmoid归一化函数
self.loss = nn.functional.mse_loss # loss函数采用均方差损失
# 当输入真实标签,返回loss值;无真实标签,返回预测值
def forward(self, x, y=None):
x = self.linear(x) # (batch_size, input_size) -> (batch_size, 1)
y_pred = self.activation(x) # (batch_size, 1) -> (batch_size, 1)
if y is not None:
return self.loss(y_pred, y) # 预测值和真实值计算损失
else:
return y_pred # 输出预测结果
# 用来获取每轮模型的准确率
def evaluate(model):
# 将模型切换到评估模式, 确保评估结果的一致性和可重复性
model.eval()
# 重新生成数据可以检验模型的泛化能力(处理新数据的能力), 避免模型只是"记住"了训练数据,而是真正学会了规律
x, y = build_dataset(total_sample_num)
print("本次预测集中共有%d个正样本,%d个负样本" % (sum(y), total_sample_num - sum(y)))
# 初始化正确和错误计数
correct, wrong = 0, 0
# 使用torch.no_grad()上下文管理器,在该区块内关闭梯度计算, 这里不需要更新模型参数,因此不需要计算梯度
with torch.no_grad():
# 将测试数据 x输入模型,得到模型预测结果 等价于 model(x)
y_pred = model.forward(x)
# 与真实标签进行对比
# 作用:同时遍历预测结果 y_pred和真实标签 y
# zip()函数:将多个可迭代对象"打包"成元组序列
# 示例:如果 y_pred = [0.8, 0.3], y = [1, 0],则循环两次:
# 第一次:y_p = 0.8, y_t = 1
# 第二次:y_p = 0.3, y_t = 0
for y_p, y_t in zip(y_pred, y):
# 负样本正确:预测概率 < 0.5 且真实标签 = 0
if float(y_p) < 0.5 and int(y_t) == 0:
correct += 1
# 正样本正确:预测概率 ≥ 0.5 且真实标签 = 1
elif float(y_p) >= 0.5 and int(y_t) == 1:
correct += 1
# 预测错误
else:
wrong += 1
# 计算准确率:correct / (correct + wrong) 正确预测数占总数的比例
print("正确预测个数:%d, 正确率:%f" % (correct, correct / (correct + wrong)))
return correct / (correct + wrong)
def main():
# 配置参数
epoch_num = 20 # 训练轮数
batch_size = 20 # 每次训练样本个数
input_size = 5 # 输入向量维度
learning_rate = 0.001 # 学习率
# 建立模型
# 实例化TorchModel类,input_size作为参数传入,定义模型结构
model = TorchModel(input_size)
# 选择优化器:使用Adam优化算法来更新模型参数,learning_rate为学习率
optim = torch.optim.Adam(model.parameters(), lr=learning_rate)
# 初始化一个空列表log,用于记录每一轮训练后的准确率(acc)和平均损失(loss)
log = []
# 训练过程
# 循环epoch_num轮
for epoch in range(epoch_num):
# 将模型设置为训练模式。此模式会启用Dropout、BatchNorm等训练特有的层
model.train()
# 初始化一个空列表watch_loss,用于记录每个小批次(batch)损失值的一个列表
watch_loss = []
# 内层循环:将训练集分成多个batch进行训练,
# batch的数量 = total_sample_num // batch_size 计算(总样本数量)除以(每次训练样本个数)后商的整数部分,小数会被舍弃计算
for batch_index in range(total_sample_num // batch_size):
# 拿到分页的训练集数据
x = train_x[batch_index * batch_size : (batch_index + 1) * batch_size]
y = train_y[batch_index * batch_size : (batch_index + 1) * batch_size]
# 将数据输入模型, 计算损失, loss函数(前向传播),等价于 model(x, y)
loss = model.forward(x, y)
# 反向传播:计算损失函数关于模型参数的梯度
loss.backward()
# 优化器步进:根据梯度更新模型参数, 更新权重
optim.step()
# 清空优化器的梯度,防止梯度累积, 梯度归零
optim.zero_grad()
# 将当前batch的损失值添加到watch_loss列表中
# loss.item()将张量转换为Python数字
watch_loss.append(loss.item())
# epoch + 1 当前第几轮训练
# watch_loss 每个小批次(batch)损失值的一个列表
# np.mean(watch_loss),np.mean(...)计算 watch_loss列表中所有数值的平均值
print("=========\n第%d轮 平均loss:%f" % (epoch + 1, float(np.mean(watch_loss))))
# 本轮模型准确率结果
acc = evaluate(model)
# 记录每一轮训练后的准确率(acc)和平均损失(loss)
log.append([acc, float(np.mean(watch_loss))])
# 保存模型
# 将训练好的模型参数保存到文件"model.pt"
# state_dict()只保存模型参数,不保存整个模型结构
# 这样下次可以直接加载使用训练好的模型
torch.save(model.state_dict(), "model.pt",)
# 画图
print(log)
"""
len(log):获取log列表长度
range():生成整数序列
range(5)会生成一个类似于 [0, 1, 2, 3, 4]的序列
[l[0] for l in log] 叫做列表推导式,是Python的简洁语法
等价于:
acc_list = []
for l in log:
acc_list.append(l[0]) # 取出每个子列表的第一个元素(acc)
acc_list.append(l[1]) # 取出每个子列表的第二个元素(loss)
"""
# 绘制准确率曲线:横轴是轮次,纵轴是准确率
plt.plot(range(len(log)), [l[0] for l in log], label="acc")
# 绘制损失曲线:横轴是轮次,纵轴是损失值
plt.plot(range(len(log)), [l[1] for l in log], label="loss")
# 显示图例(区分acc和loss曲线)
plt.legend()
# 显示图形窗口
plt.show()
return
"""
使用训练好的模型做预测
model_path:训练好的模型文件保存路径(如 "model.pt")
input_vec:要进行预测的输入数据(多个5维向量组成的列表)
"""
def predict(model_path, input_vec):
# 定义输入向量的维度 因为测试的是5维输入向量,所以这里硬编码为5
# 在实际项目中,这个值通常从模型配置或数据中动态获取
input_size = 5
# 创建一个新的模型实例
model = TorchModel(input_size)
# 加载训练好的权重
model.load_state_dict(torch.load(model_path))
# 打印模型的所有参数(权重和偏置)
print("打印训练好的模型所有参数(权重和偏置):", model.state_dict())
# 将模型切换到评估模式, 确保评估结果的一致性和可重复性
model.eval()
# 使用torch.no_grad()上下文管理器,在该区块内关闭梯度计算, 这里不需要更新模型参数,因此不需要计算梯度
with torch.no_grad():
# 模型预测
# torch.FloatTensor(input_vec):将数组转换为PyTorch浮点数张量
# model.forward(...):调用模型的前向传播方法进行预测
result = model.forward(torch.FloatTensor(input_vec))
# 同时遍历输入向量和预测值
for vec, res in zip(input_vec, result):
# float(res):将张量转换为Python
# round(...):四舍五入到最接近的整数(0或1)
"""
举例
输入向量: [0.3, 0.9, 0.7, 0.6, 0.6]
第一个数0.3 < 第五个数0.6(应该是负样本)
模型预测: 0.23
round(0.23) = 0 → 预测类别: 0
"""
print("输入:%s, 预测类别:%d, 预测值:%f" % (vec, round(float(res)), res)) # 打印结果
if __name__ == "__main__":
# # 执行主函数训练保存模型
# main()
# 模拟一些数据,用上面主函数训练好的模型做预测
test_vec = [[0.07889086, 0.15229675, 0.31082123, 0.03504317, 0.18920843],
[0.94963533, 0.5524256, 0.95758807, 0.95520434, 0.84890681],
[0.78797868, 0.67482528, 0.13625847, 0.34675372, 0.19871392],
[0.79349776, 0.59416669, 0.92579291, 0.41567412, 0.1358894]]
predict("model.pt", test_vec)