import torch # PyTorch深度学习框架
import matplotlib.pyplot as plt # 画图的
import random # 生成随机数
# 生成模拟数据的函数。也就是实现f(xw+b);之后再在外面套上逻辑回归的外函数
def create_data(w, b, data_num): # 生成数据
"""
生成带噪声的线性回归数据
参数:
w: 权重向量,形状为(n_features,)
b: 偏置项,标量
data_num: 生成样本数量
返回:
x: 特征矩阵,形状为(data_num, n_features)
y: 目标值,形状为(data_num,)
"""
# 生成服从标准正态分布(均值为0,标准差1)的样本的特征矩阵,形状(横纵维度)为(样本数data_num,特征数len(w))
x = torch.normal(0, 1, (data_num, len(w)))
# 计算线性回归目标值(矩阵乘法实现批量计算);先生成f(xw+b)
y = torch.matmul(x, w) + b # matmul表示矩阵相乘,这个就是神经网络矩阵相乘的公式
# 添加高斯噪声(均值为0,标准差0.01),形状与y相同。模拟真实世界场景(真实一般满足正态分布),防止过拟合
noise = torch.normal(0, 0.01, y.shape) # 噪声要加到y上,添加到目标值。这是因为标准线性回归是y = Xβ + ε
# 当噪声在y上时,模型学习目标是找到最能抵抗这些观测噪声的权重参数。如果噪声在X上,问题会转变为误差在变量模型(Errors-in-variables),需要不同的处理方法。
y += noise
return x, y
# 设置生成参数。并实例化上面定义的线性回归运算
num = 500 # 样本数量
# 因为torch是对张量操作,而不是序列,所以下面要用tensor进行操作
true_w = torch.tensor([8.1, 2, 2, 4]) # 真实的权重参数(四维特征)
true_b = torch.tensor(1.1) # 真实的偏置参数
# 生成数据实例化.也就是进行线性回归
X, Y = create_data(true_w, true_b, num)
# 可视化第4个特征(索引3)与目标值的关系
'''
plt.scatter(
X[:, 3], # x轴数据:所有样本的第4个特征(索引3)
Y, # y轴数据:所有样本的目标值
1 # s=1:散点的大小(单位:像素)
)
'''
plt.scatter(X[:, 3], Y, 1) # 参数1表示点的大小
plt.xlabel('Feature 3') # x轴标签
plt.ylabel('Target') # y轴标签
plt.show() # 显示图形
# 关于data_provider
'''
它的主要功能是将数据集分成小批次,便于训练时逐步处理,避免一次性加载所有数据导致内存不足。
打乱索引是为了让每个epoch的数据顺序不同,增加模型的泛化能力。生成器的使用可以节省内存,特别是在处理大数据集时。
用户可能不太明白为什么需要这样的函数,或者想确认它的正确性。需要指出,虽然这个自定义函数可行,
但通常推荐使用PyTorch内置的DataLoader,因为它更高效且功能更全面,比如支持多线程数据加载等。
此外,用户可能在训练循环中调用了这个函数,所以需要说明在训练过程中如何逐批次获取数据,并更新模型参数。
还要提到,使用生成器的好处是可以惰性加载数据,不会一次性占用太多内存。
最后,要确保解释清晰,涵盖函数的关键步骤:打乱数据、分批处理、生成器返回。同时,对比标准做法,
指出用户自定义实现的优缺点,帮助用户理解为什么有时候需要自己写这样的函数,或者建议使用库函数提升效率。
'''
# 将数据集分成小批次,便于训练时逐步处理
def data_provider(data, label, batchsize): # 每次访问这个函数, 就能提供一批数据
"""自定义数据加载器
参数格式:
data: 特征数据张量
label: 标签数据张量
batchsize: 批次大小
返回:
生成器,每次产生一个批次的(特征,标签)
"""
length = len(label) # 获取总样本数
indices = list(range(length)) # 创建顺序索引列表 [0,1,2,...n-1]
# 不能按顺序取,这样才符合现实世界,防止模型学习到顺序偏差。 把数据打乱。打乱索引顺序(随机化数据)
random.shuffle(indices)
# 按批次大小遍历数据。批次大小为batchsize,舍去最后不符合批次大小的。
for each in range(0, length, batchsize):
# 获取当前批次的索引(防止越界)
get_indices = indices[each: each + batchsize]
# 通过索引获取对应数据及标签
get_data = data[get_indices] # 形状:[batchsize, n_features]
get_label = label[get_indices] # 形状:[batchsize]
# 使用yield生成器返回批次数据(保持内存高效),其实就是便于更新参数使用。使用生成器( yield )避免一次性加载全部数据
yield get_data, get_label
# 设置训练参数
batchsize = 16 # 每个批次的样本数
# for batch_x, batch_y in data_provider(X, Y, batchsize):
# print(batch_x, batch_y)
# break
# 神经网络前向传播计算函数,其实也就是矩阵计算。
def fun(x, w, b):
"""线性回归前向计算函数
参数:
x: 输入特征
w: 权重参数
b: 偏置参数
返回:
预测值 = x*w + b
"""
pred_y = torch.matmul(x, w) + b
return pred_y
# 计算损失函数,我们的目标是让损失最小,所以后面要对其求梯度
def maeLoss(pre_y, y):
"""平均绝对误差损失函数
参数:
pre_y: 预测值
y: 真实值
返回:
平均绝对误差 = Σ|预测-真实| / 样本数
"""
return torch.sum(abs(pre_y-y))/len(y)
# 反向传播实现
# 使用loss.backward()方法计算损失的结果loss是一个张量
# 随机梯度下降优化器,作用是更新参数。在反向传播的时候使用。
def sgd(paras, lr):
"""随机梯度下降优化器
参数:
paras: 需要更新的参数列表
lr: 学习率
"""
# 禁用梯度计算(节省内存)
with torch.no_grad(): # 属于这句代码的部分,不计算梯度
for para in paras:
# 参数更新:新参数 = 旧参数 - 学习率 * 梯度
para -= para.grad * lr #不能写成 para = para - para.grad*lr
# 清空梯度(防止累积)
para.grad.zero_() #使用过的梯度,归0
#没有返回值
# 初始化模型参数
lr = 0.03 # 学习率
# 随机初始化权重(启用梯度计算);
w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True) # 这个w要计算梯度
# 初始化偏置(启用梯度计算)
b_0 = torch.tensor(0.01, requires_grad=True)
print(w_0, b_0)
# 训练循环,并取最小损失
epochs = 50 # 训练轮数50次
for epoch in range(epochs):
data_loss = 0 # 累计损失
# 每一轮的训练方式是按批次训练,每次训练调用自定义的data_provider小批量训练函数。积累每一轮的损失并更新至取最小值
for batch_x, batch_y in data_provider(X, Y, batchsize):
# 前向传播计算预测值
pred_y = fun(batch_x, w_0, b_0)
# 计算损失
loss = maeLoss(pred_y, batch_y)
# 反向传播计算梯度
loss.backward()
# 参数更新
sgd([w_0, b_0], lr)
# 累计损失值
data_loss += loss
# 每一轮训练结束,打印训练进度(格式化输出3位epoch编号(第几次训练),6位小数损失)
print("epoch %03d: loss: %.6f" % (epoch, data_loss))
# 输出最终训练结果
print("真实的函数值是", true_w, true_b)
print("训练得到的参数值是", w_0, b_0)
# 可视化第4个特征的拟合结果
idx = 3 # 选择第4个特征(索引3)
# 绘制拟合直线:y = w*x + b
# 将PyTorch张量转为numpy数组.Matplotlib 的绘图函数是基于 NumPy 数组设计的,PyTorch 张量需要先转换为 NumPy 数组才能被正确解析。
plt.plot(X[:, idx].detach().numpy(), # x轴值(需要从张量转换为numpy)
X[:, idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy(),
color='red') # 拟合直线
# 绘制原始数据散点图
plt.scatter(X[:, idx], Y, 1) # 参数1表示点的大小
plt.xlabel('Feature 3') # x轴标签
plt.ylabel('Target') # y轴标签
plt.show() # 显示图形
李哥深度学习第二节 简单线性的实现
最新推荐文章于 2025-06-11 15:22:04 发布