线性模型可以看作是一个单层的神经网络。
对于n个输入[x1, x2, ...., xn],由n个权重[w1, w2, ......, wn]以及一个偏置常数b得到的输出y,则称y = x1w1+x2w2+......+xnwn+b称为线性模型。
即 线性模型是对n维输入的加权外加偏差。
要利用线性模型进行预测,要先衡量真实值与估计值之间的损失。
这里我们采用的是平方损失,用来衡量单个样本的预测误差大小。
定义好模型与损失后,我们就可以利用数据集进行训练了。
假设有n个训练样本,x = [x1, x2, ......, xn]T, y = [y1, y2, ......, yn]T,每一个x是一个列向量,排列好后进行转置,得到的x每一行就是一个特征,每一个y是一个常数,即一个真实值。(T表示转置)
对于模型在每一个数据上的损失求均值,就能得到损失函数(均方误差)。
函数中的1/2来自平方损失函数,1/n说明求平均。
对于第i个样本,第i个真实值减去第i个特征x与权重w的内积减去偏差b再平方求和,最后乘以1/2n。
写成向量,即一个向量y减去矩阵X乘向量w减去标量b,再将其求平方和。
最后求损失的最小值,将最小值中的w和b作为模型的解。
以上为求解的过程,但线性回归模型是有显式解的,故我们可以简化过程。
首先在X的最右边加入一列1,再将偏差放置在w的末端,损失函数即可写成
求导后即
当一个模型没有显示解时,可以先随机生成参数的初始值w0,接下来利用梯度下降不断更新w。
更新规则为
新的w等于 上一次的w 减去损失函数关于上一次w的梯度 再乘学习率。
学习率可看作梯度下降过程中的下降步长,太大则下降效果过于粗糙,太小则运算次数需求过高。
学习率为超参数,即人为指定的参数
每一次梯度下降都要计算一次损失函数,运算量过大。可以随机采样b个样本来近似计算损失。
这里的b是批量大小,同样是超参数。
b的选择太小,不适合并行计算;选择太大则消耗内存太大。
总结:梯度下降通过不断沿着反梯度方向更新参数求解。
以下为pytorch实现代码:
import numpy as np
import torch
from torch.utils import data
from torch import nn # 神经网络模块
def synthetic_data(w, b, num_examples):
"""
生成合成数据,模拟线性回归问题。
参数:
w (torch.Tensor): 线性模型的权重。
b (float): 线性模型的偏置。
num_examples (int): 生成的样本数量。
device (str): 指定运行设备,默认为 'cuda'。
返回:
X (torch.Tensor): 生成的特征数据,形状为 (num_examples, len(w))。
y (torch.Tensor): 生成的标签数据,形状为 (num_examples, 1)。
"""
# 生成均值为0,标准差为1的正态分布随机数,形状为 (num_examples, len(w))
X = torch.normal(0, 1, (num_examples, len(w)))
# 使用线性模型计算预测值
y = torch.matmul(X, w) + b
# 向预测值中添加均值为0,标准差为0.01的正态分布噪声
y += torch.normal(0, 0.01, y.shape)
# 将 y 调整为列向量
return X, y.reshape((-1, 1))
def load_array(data_arrays, batch_size, is_train=True):
"""
构造一个PyTorch数据迭代器,用于加载和批量处理数据。
参数:
data_arrays (tuple): 包含特征张量和标签张量的元组。例如: (features, labels)。
batch_size (int): 每个批次的数据大小,即每次迭代时返回的样本数量。
is_train (bool): 是否在加载数据时打乱顺序 (shuffle)。
- 如果为 True:数据会在每个 epoch 开始前打乱顺序,常用于训练。
- 如果为 False:数据按照顺序加载,常用于验证或测试。
返回:
torch.utils.data.DataLoader: PyTorch 的数据加载器,支持按批次迭代数据。
"""
# 使用 TensorDataset 将特征张量和标签张量打包在一起
# TensorDataset 会将特征和标签一一对应,形成一个可以索引的数据集
dataset = data.TensorDataset(*data_arrays)
# 创建数据加载器 DataLoader:
# - dataset: 传入封装好的 TensorDataset。
# - batch_size: 每个批次返回的数据量。
# - shuffle: 决定是否在每个 epoch 前随机打乱数据顺序。
return data.DataLoader(dataset, batch_size, shuffle=is_train)
def train(net, true_w, true_b, features, labels, batch_size, num_epochs, lr):
"""
训练一个简单的线性回归模型,使用均方误差作为损失函数,并通过随机梯度下降优化。
参数:
net (torch.nn.Sequential): 神经网络模型,包含一个线性层。
true_w (torch.Tensor): 真实的权重向量,用于与模型学习到的参数进行比较。
true_b (float): 真实的偏置值,用于与模型学习到的参数进行比较。
features (torch.Tensor): 输入特征数据,形状为 (样本数, 特征数)。
labels (torch.Tensor): 输出标签数据,形状为 (样本数, ) 或 (样本数, 标签数)。
batch_size (int): 每个批次的样本数量。
num_epochs (int): 训练的轮数(数据集完整遍历的次数)。
lr (float): 学习率,控制梯度下降时的步长。
输出:
- 每轮训练的损失值。
- 训练完成后,打印学习到的权重和偏置与真实值的误差。
"""
# 构造数据迭代器,支持小批量随机梯度下降
data_iter = load_array((features, labels), batch_size)
# 初始化模型参数
net[0].weight.data.normal_(0, 0.01) # 将权重初始化为均值为 0,标准差为 0.01 的正态分布
net[0].bias.data.fill_(0) # 将偏置初始化为 0
# 定义损失函数:均方误差 (MSE)
loss = nn.MSELoss()
# 定义优化器:随机梯度下降 (SGD)
# net.parameters() 返回模型中所有需要优化的参数(权重和偏置)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
# 开始训练
for epoch in range(num_epochs): # 外层循环:遍历每个 epoch
for X, y in data_iter: # 内层循环:遍历每个小批量数据
# 前向传播:计算模型预测值和损失
l = loss(net(X), y) # l 是当前小批量的损失值
# 清空上一批次的梯度
trainer.zero_grad()
# 反向传播:计算当前损失对模型参数的梯度
l.backward()
# 更新参数:使用计算出的梯度对模型参数进行优化
trainer.step()
# 每个 epoch 结束后,计算整个数据集上的损失值
l = loss(net(features), labels)
# 打印当前 epoch 的训练损失
print(f'epoch {epoch + 1}, loss {l:f}')
# 打印模型参数的估计误差
# 将学习到的参数与真实值对比,衡量模型的学习效果
print(f'w的估计误差: {true_w - net[0].weight.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - net[0].bias}')
net = nn.Sequential(nn.Linear(2, 1))
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
batch_size = 10
num_epochs = 3
lr = 0.03
train(net, true_w, true_b, features, labels, batch_size, num_epochs, lr)
若想将权重保存到本地或加载本地权重文件,代码如下
# 保存文件
torch.save({
'weights': net[0].weight.data,
'bias': net[0].bias.data
}, 'linear_model_weights.pth')
checkpoint = torch.load('linear_model_weights.pth') # 加载保存的文件
loaded_weights = checkpoint['weights']
loaded_bias = checkpoint['bias']
# 将权重和偏置赋值回模型
net[0].weight.data = loaded_weights.clone()
net[0].bias.data = loaded_bias.clone()
以上是一个简易的线性回归模型训练方法。
懒得注释代码所以让ai生成了注释(目移)