系列文章目录
01-PyTorch新手必看:张量是什么?5 分钟教你快速创建张量!
02-张量运算真简单!PyTorch 数值计算操作完全指南
03-Numpy 还是 PyTorch?张量与 Numpy 的神奇转换技巧
04-揭秘数据处理神器:PyTorch 张量拼接与拆分实用技巧
05-深度学习从索引开始:PyTorch 张量索引与切片最全解析
06-张量形状任意改!PyTorch reshape、transpose 操作超详细教程
07-深入解读 PyTorch 张量运算:6 大核心函数全面解析,代码示例一步到位!
08-自动微分到底有多强?PyTorch 自动求导机制深度解析
09-从零手写线性回归模型:PyTorch 实现深度学习入门教程
10-PyTorch 框架实现线性回归:从数据预处理到模型训练全流程
文章目录
- 系列文章目录
- 前言
- 一、构建数据集
- 1.1 示例代码
- 1.2 示例输出
- 二、构建假设函数
- 2.1 示例代码
- 三、损失函数
- 3.1 示例代码
- 四、优化方法
- 4.1 示例代码
- 五、训练函数
- 5.1 示例代码
- 5.2 绘制结果
- 六、调用训练函数
- 6.1 示例输出
- 七、小结
- 7.1 完整代码
前言
在机器学习的学习过程中,我们接触过 线性回归 模型,并使用过如 Scikit-learn 这样的工具来快速实现。但在本文中,将深入理解线性回归的核心思想,并使用 PyTorch 从零开始手动实现一个线性回归模型。这包括:
- 数据集的构建;
- 假设函数的定义;
- 损失函数的设计;
- 梯度下降优化方法的实现;
- 模型训练和损失变化的可视化。
一、构建数据集
线性回归需要一个简单的线性数据集,我们将通过sklearn.datasets.make_regression
方法生成。
1.1 示例代码
import torch
from sklearn.datasets import make_regression
import matplotlib.pyplot as plt
import random
def create_dataset():
"""
使用 make_regression 生成线性回归数据集,并转换为 PyTorch 张量。
"""
x, y, coef = make_regression(
n_samples=120, # 样本数量,即数据点个数
n_features=1, # 每个样本只有一个特征
noise=15, # 添加噪声,模拟真实场景
coef=True, # 是否返回生成数据的真实系数
bias=12.0, # 偏置值,即 y = coef * x + bias
random_state=42 # 随机种子,保证结果一致性
)
# 转换为 PyTorch 张量
x = torch.tensor(x, dtype=torch.float32) # 输入特征张量
y = torch.tensor(y, dtype=torch.float32).reshape(-1, 1) # 输出标签张量转换为二维
return x, y, coef # 返回输入特征张量、输出标签张量和真实系数
# 数据加载器,用于批量获取数据
def data_loader(x, y, batch_size):
"""
数据加载器,按批次随机提取训练数据。
参数:
x: 输入特征张量
y: 输出标签张量
batch_size: 批量大小
"""
data_len = len(y) # 数据集长度
indices = list(range(data_len)) # 创建索引列表
random.shuffle(indices) # 随机打乱索引,保证数据随机性
num_batches = data_len // batch_size # 计算批次数
for i in range(num_batches): # 遍历每个批次
start = i * batch_size # 当前批次起始索引
end = start + batch_size # 当前批次结束索引
# 提取当前批次的数据
batch_x = x[indices[start:end]] # 当前批次输入
batch_y = y[indices[start:end]] # 当前批次输出
yield batch_x, batch_y # 返回当前批次数据
1.2 示例输出
运行 create_dataset
后的数据分布为线性趋势,同时包含噪声点。例如:
x:
tensor([[-1.3282],
[ 0.1941],
[ 0.8944],
...])
y:
tensor([-40.2345, 15.2934, 45.1282, ...])
coef:
tensor([35.0])
二、构建假设函数
线性回归的假设函数可以表示为:
y
=
w
⋅
x
+
b
y ^ =w⋅x+b
y=w⋅x+b
其中,( w ) 是权重,( b ) 是偏置。使用 PyTorch 张量定义这些参数。
2.1 示例代码
# 模型参数初始化
w = torch.tensor(0.5, requires_grad=True, dtype=torch.float32) # 权重
b = torch.tensor(0.0, requires_grad=True, dtype=torch.float32) # 偏置
# 假设函数
def linear_regression(x):
return w * x + b
三、损失函数
我们使用均方误差(MSE)作为损失函数,其公式为:
L = 1 n ∑ i = 1 n ( y ^ i − y i ) 2 L = \frac{1}{n} \sum_{i=1}^n (\hat{y}_i - y_i)^2 L=n1i=1∑n(y^i−yi)2
3.1 示例代码
def square_loss(y_pred, y_true):
return (y_pred - y_true) ** 2
四、优化方法
为了更新模型参数 ( w ) 和 ( b ),使用 随机梯度下降(SGD) 算法,其更新公式为:
w
=
w
−
η
⋅
∂
L
∂
w
,
b
=
b
−
η
⋅
∂
L
∂
b
w = w - \eta \cdot \frac{\partial L}{\partial w}, \quad b = b - \eta \cdot \frac{\partial L}{\partial b}
w=w−η⋅∂w∂L,b=b−η⋅∂b∂L
其中,η 是学习率。
4.1 示例代码
def sgd(lr=0.01, batch_size=16):
# 更新权重和偏置
w.data = w.data - lr * w.grad.data / batch_size
b.data = b.data - lr * b.grad.data / batch_size
五、训练函数
将前面定义的所有组件组合在一起,构建训练函数,通过多个 epoch 来优化模型。
5.1 示例代码
# 模型训练函数
def train():
"""
训练线性回归模型。
"""
# 加载数据集
x, y, coef = create_dataset()
# 设置训练参数
epochs = 50 # 训练轮次
learning_rate = 0.01 # 学习率
batch_size = 16 # 每批次的数据大小
# 使用 PyTorch 内置优化器
optimizer = torch.optim.SGD([w, b], lr=learning_rate)
epoch_loss = [] # 用于记录每轮的平均损失
for epoch in range(epochs): # 遍历每一轮
total_loss = 0.0 # 累计损失
for batch_x, batch_y in data_loader(x, y, batch_size): # 遍历每个批次
# 使用假设函数计算预测值
y_pred = linear_regression(batch_x)
# 计算损失
loss = square_loss(y_pred, batch_y) # 当前批次的平均损失
total_loss += loss.item() # 累加总损失
# 梯度清零
optimizer.zero_grad()
# 反向传播计算梯度
loss.backward()
# 使用随机梯度下降更新参数
optimizer.step()
# 记录当前轮次的平均损失
epoch_loss.append(total_loss / len(y))
print(f"轮次 {epoch + 1}, 平均损失: {epoch_loss[-1]:.4f}") # 打印损失
# 可视化训练结果
plot_results(x, y, coef, epoch_loss)
5.2 绘制结果
# 可视化训练结果
def plot_results(x, y, coef, epoch_loss):
"""
绘制训练结果,包括拟合直线和损失变化曲线。
参数:
x: 输入特征张量
y: 输出标签张量
coef: 数据生成时的真实权重
epoch_loss: 每轮的平均损失
"""
# 绘制训练数据点和拟合直线
plt.scatter(x.numpy(), y.numpy(), label='数据点', alpha=0.7) # 数据点
x_line = torch.linspace(x.min(), x.max(), 100).unsqueeze(1) # 连续 x 值
y_pred = linear_regression(x_line).detach().numpy() # 模型预测值
coef_tensor = torch.tensor(coef, dtype=torch.float32) # 将 coef 转换为 PyTorch 张量
y_true = coef_tensor * x_line + 12.0 # 真实直线(生成数据时的公式)
plt.plot(x_line.numpy(), y_pred, label='拟合直线', color='red') # 拟合直线
plt.plot(x_line.numpy(), y_true.numpy(), label='真实直线', color='green') # 真实直线
plt.legend()
plt.grid()
plt.title('线性回归拟合')
plt.xlabel('特征值 X')
plt.ylabel('标签值 Y')
plt.show()
# 绘制损失变化曲线
plt.plot(range(len(epoch_loss)), epoch_loss)
plt.title('损失变化曲线')
plt.xlabel('轮次')
plt.ylabel('损失')
plt.grid()
plt.show()
六、调用训练函数
在主程序中调用 train
函数,训练模型并观察输出。
if __name__ == "__main__":
train()
6.1 示例输出
- 拟合直线:
- 损失变化曲线:
七、小结
本文通过手动实现线性回归模型,完成了以下内容:
- 构建数据集并设计数据加载器;
- 定义线性假设函数;
- 设计均方误差损失函数;
- 实现随机梯度下降优化方法;
- 训练模型并可视化损失变化和拟合直线。
7.1 完整代码
import torch
from sklearn.datasets import make_regression
import matplotlib.pyplot as plt
import random
# 设置 Matplotlib 支持中文
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 数据集生成函数
def create_dataset():
"""
使用 make_regression 生成线性回归数据集,并转换为 PyTorch 张量。
"""
x, y, coef = make_regression(
n_samples=120, # 样本数量,即数据点个数
n_features=1, # 每个样本只有一个特征
noise=15, # 添加噪声,模拟真实场景
coef=True, # 是否返回生成数据的真实系数
bias=12.0, # 偏置值,即 y = coef * x + bias
random_state=42 # 随机种子,保证结果一致性
)
# 转换为 PyTorch 张量
x = torch.tensor(x, dtype=torch.float32) # 输入特征张量
y = torch.tensor(y, dtype=torch.float32).reshape(-1, 1) # 输出标签张量转换为二维
return x, y, coef # 返回输入特征张量、输出标签张量和真实系数
# 数据加载器,用于批量获取数据
def data_loader(x, y, batch_size):
"""
数据加载器,按批次随机提取训练数据。
参数:
x: 输入特征张量
y: 输出标签张量
batch_size: 批量大小
"""
data_len = len(y) # 数据集长度
indices = list(range(data_len)) # 创建索引列表
random.shuffle(indices) # 随机打乱索引,保证数据随机性
num_batches = data_len // batch_size # 计算批次数
for i in range(num_batches): # 遍历每个批次
start = i * batch_size # 当前批次起始索引
end = start + batch_size # 当前批次结束索引
# 提取当前批次的数据
batch_x = x[indices[start:end]] # 当前批次输入
batch_y = y[indices[start:end]] # 当前批次输出
yield batch_x, batch_y # 返回当前批次数据
# 模型参数初始化
w = torch.tensor(0.5, requires_grad=True, dtype=torch.float32) # 权重,初始值为 0.5
b = torch.tensor(0.0, requires_grad=True, dtype=torch.float32) # 偏置,初始值为 0
# 线性假设函数
def linear_regression(x):
"""
线性回归假设函数。
参数:
x: 输入特征张量
返回:
模型预测值
"""
return w * x + b # 线性模型公式
# 损失函数(均方误差)
def square_loss(y_pred, y_true):
"""
均方误差损失函数。
参数:
y_pred: 模型预测值
y_true: 数据真实值
返回:
每个样本的平方误差
"""
return ((y_pred - y_true) ** 2).mean() # 返回均方误差
# 模型训练函数
def train():
"""
训练线性回归模型。
"""
# 加载数据集
x, y, coef = create_dataset()
# 设置训练参数
epochs = 50 # 训练轮次
learning_rate = 0.01 # 学习率
batch_size = 16 # 每批次的数据大小
# 使用 PyTorch 内置优化器
optimizer = torch.optim.SGD([w, b], lr=learning_rate)
epoch_loss = [] # 用于记录每轮的平均损失
for epoch in range(epochs): # 遍历每一轮
total_loss = 0.0 # 累计损失
for batch_x, batch_y in data_loader(x, y, batch_size): # 遍历每个批次
# 使用假设函数计算预测值
y_pred = linear_regression(batch_x)
# 计算损失
loss = square_loss(y_pred, batch_y) # 当前批次的平均损失
total_loss += loss.item() # 累加总损失
# 梯度清零
optimizer.zero_grad()
# 反向传播计算梯度
loss.backward()
# 使用随机梯度下降更新参数
optimizer.step()
# 记录当前轮次的平均损失
epoch_loss.append(total_loss / len(y))
print(f"轮次 {epoch + 1}, 平均损失: {epoch_loss[-1]:.4f}") # 打印损失
# 可视化训练结果
plot_results(x, y, coef, epoch_loss)
# 可视化训练结果
def plot_results(x, y, coef, epoch_loss):
"""
绘制训练结果,包括拟合直线和损失变化曲线。
参数:
x: 输入特征张量
y: 输出标签张量
coef: 数据生成时的真实权重
epoch_loss: 每轮的平均损失
"""
# 绘制训练数据点和拟合直线
plt.scatter(x.numpy(), y.numpy(), label='数据点', alpha=0.7) # 数据点
x_line = torch.linspace(x.min(), x.max(), 100).unsqueeze(1) # 连续 x 值
y_pred = linear_regression(x_line).detach().numpy() # 模型预测值
coef_tensor = torch.tensor(coef, dtype=torch.float32) # 将 coef 转换为 PyTorch 张量
y_true = coef_tensor * x_line + 12.0 # 真实直线(生成数据时的公式)
plt.plot(x_line.numpy(), y_pred, label='拟合直线', color='red') # 拟合直线
plt.plot(x_line.numpy(), y_true.numpy(), label='真实直线', color='green') # 真实直线
plt.legend()
plt.grid()
plt.title('线性回归拟合')
plt.xlabel('特征值 X')
plt.ylabel('标签值 Y')
plt.show()
# 绘制损失变化曲线
plt.plot(range(len(epoch_loss)), epoch_loss)
plt.title('损失变化曲线')
plt.xlabel('轮次')
plt.ylabel('损失')
plt.grid()
plt.show()
# 调用训练函数
if __name__ == "__main__":
train()