目录
(一)线性回归
实现一个简单的线性回归模型,并使用最小二乘法来求解参数。
1数据集构建
y=wx+b构造一维的数据集合:生成 150 个带噪音、异常点的样本,其中 100 个训练样本,50 个测试样本,并打印出训练数据的可视化分布。假设输入特征和输出标签的维度都为 1。
可视化结果
具体代码:
import torch
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
# 设置支持中文的字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统下可以使用SimHei字体
# 设置正常显示负号
plt.rcParams['axes.unicode_minus'] = False
'''
输入参数:
func: 线性函数(可调用)
interval: 自变量x的取值范围(元组)
num: 样本数量(int类型)
noise: 噪声标准差(float类型,控制数据的随机扰动)
add_outlier: 是否添加异常点(布尔值)
outlier_ratio: 异常点的比例(float类型)
输出:
x: 生成的自变量(tensor)
y: 生成的因变量(tensor)
目标:
创建一个关于线性模型的数据集,并生成噪声和异常点
'''
def linear_func(x, w=1.2, b=0.5):
y = w * x + b
return y
def create_data(func, interval, num, noise=2, add_outlier=False, outlier_ratio=0.05):
# 均匀采样自变量x
x = torch.rand(num, 1) * (interval[1] - interval[0]) + interval[0] # 在指定区间内均匀采样
y = func(x)
# 生成高斯分布的噪声并添加到y中
epsilon = torch.normal(0, noise, y.shape)
y = y + epsilon
# 如果需要添加异常点
if add_outlier:
outlier_num = int(len(y) * outlier_ratio) # 计算异常点数量
if outlier_num > 0:
outlier_idx = torch.randint(0, len(y), (outlier_num,)) # 随机选择异常点索引
y[outlier_idx] = y[outlier_idx] * 5 # 将异常点的值放大5倍
return x, y
# 创建数据集,指定线性函数、取值范围、样本数量、噪声标准差、异常点占比
data_x, data_y = create_data(linear_func, (-10, 10), 150, noise=2, add_outlier=True, outlier_ratio=0.05)
# 将数据集分割为训练集和测试集
data_x_train, data_y_train = data_x[0:100], data_y[0:100] # 训练集前100个样本
data_x_test, data_y_test = data_x[100:150], data_y[100:150] # 测试集后50个样本
# 可视化生成的数据
plt.figure(1)
# 用红色点绘制训练集,用绿色点绘制测试集
plt.plot(data_x_train.numpy(), data_y_train.numpy(), '.r', label="训练集")
plt.plot(data_x_test.numpy(), data_y_test.numpy(), '.g', label="测试集")
plt.legend() # 添加图例
plt.title("线性模型生成的数据集(包含噪声和异常点)") # 添加标题
plt.show()
2 模型构建
import torch
import torch.nn as nn
class LinearModel(nn.Module):
def __init__(self, input_size):
super(LinearModel, self).__init__()
# 初始化模型参数
self.params = {}
self.params['w'] = nn.Parameter(torch.randn(size=(input_size, 1))) # 权重初始化为随机值
self.params['b'] = nn.Parameter(torch.zeros([1])) # 偏置初始化为零
def forward(self, X, y_true=None):
"""
前向传播函数
输入:
- X: tensor, shape=[N, D],N 为样本数量,D 为特征维度
- y_true: tensor, shape=[N, 1],真实标签(可选)
输出:
- y_pred: tensor, shape=[N, 1],模型的预测值
"""
N, D = X.shape
assert D == self.params['w'].shape[0], "输入维度与模型参数不匹配"
y_pred = torch.matmul(X, self.params['w']) + self.params['b']
pytorch实现线性回归
这里参考了这位博主的部分代码
3 损失函数
回归任务对连续值进行预测,采用均方误差
# 计算均方损失
mse_loss = None
if y_true is not None:
error = y_pred - y_true
mse_loss = torch.mean(error ** 2) # 计算均方损失
return y_pred, mse_loss # 返回预测值和损失
注意这里代码实现中没有除2
【原因】:因为我们计算时需要对损失函数求导,除2是为了求导后计算简单,但是计算机计算速度飞快,不除2得到的才是更加准确的结果。
构造一个简单样例测试
# 设置输入数据的维度
input_size = 3
N = 2
# 生成 2 个维度为 3 的随机数据
X = torch.randn(size=[N, input_size], dtype=torch.float32)
print("输入:", X)
# 实例化线性模型
model = LinearModel(input_size)
# 进行前向传播计算
y_pred, _ = model(X) # 只获取预测值
# 生成随机目标值,作为真实标签
y_true = torch.randn(size=[N, 1], dtype=torch.float32)
print("真实标签:", y_true) # 打印真实标签
# 进行前向传播计算并计算损失
y_pred, mse_loss = model(X, y_true)
# 打印输出
print("预测值:", y_pred) # 打印预测值
print("均方损失:", mse_loss.item()) # 打印损失值
运行结果:
输入: tensor([[-1.9075e+00, 2.6665e-02, 1.1212e-03],
[ 9.0487e-01, 4.4066e-01, -1.8906e-01]])
真实标签: tensor([[ 1.3988],
[-1.1177]])
预测值: tensor([[ 0.4216],
[-0.6875]], grad_fn=<AddBackward0>)
均方损失: 0.569969117641449
4 模型优化
采用经验风险最小化,线性回归可以通过最小二乘法求出参数w和b的解析解。
主要思路:计算特征均值-->中心化特征--->计算目标均值--->通过最小二乘法公式计算权重w--->计算偏置b---->更新参数。
def optimizer(self, x, y):
# 计算特征的均值
x_mean = torch.mean(x, dim=0, keepdim=True) # 特征均值,用于中心化
tmp = x - x_mean # 中心化特征,通过减去均值使特征均值为0
y_mean = torch.mean(y) # 计算目标变量的均值
# 计算权重 w
w = torch.matmul(
torch.matmul(torch.inverse(torch.matmul(tmp.T, tmp)), tmp.T), # (X^T * X)^-1 * X^T
(y - y_mean) # 目标变量去均值
)
# 计算偏置 b
b = y_mean - torch.matmul(x_mean, w) # 通过均值和权重计算偏置
# 更新模型参数
self.params['w'].data = w # 将计算得到的权重更新到模型参数
self.params['b'].data = b # 将计算得到的偏置更新到模型参数
5 模型训练
通过生成的数据集,损失函数,和优化器计算出线性模型中的参数,来拟合训练数据,并输出模型在训练集上的损失。
完整代码如下
import torch
import torch.nn as nn
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
# 设置支持中文的字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统下可以使用SimHei字体
# 设置正常显示负号
plt.rcParams['axes.unicode_minus'] = False
def linear_func(x, w=1.2, b=0.5):
y = w * x + b
return y
def create_data(func, interval, num, noise=2, add_outlier=False, outlier_ratio=0.05):
# 均匀采样自变量x
x = torch.rand(num, 1) * (interval[1] - interval[0]) + interval[0]
y = func(x)
# 生成高斯分布的噪声并添加到y中
epsilon = torch.normal(0, noise, y.shape)
y = y + epsilon
# 如果需要添加异常点
if add_outlier:
outlier_num = int(len(y) * outlier_ratio)
if outlier_num > 0:
outlier_idx = torch.randint(0, len(y), (outlier_num,))
y[outlier_idx] = y[outlier_idx] * 5
return x, y
class LinearModel(nn.Module):
def __init__(self, input_size):
super(LinearModel, self).__init__()
# 初始化模型参数
self.params = {}
self.params['w'] = nn.Parameter(torch.randn(size=(input_size, 1))) # 权重初始化为随机值
self.params['b'] = nn.Parameter(torch.zeros([1])) # 偏置初始化为零
def forward(self, X, y_true=None):
N, D = X.shape
assert D == self.params['w'].shape[0], "输入维度与模型参数不匹配"
y_pred = torch.matmul(X, self.params['w']) + self.params['b']
mse_loss = None
if y_true is not None:
error = y_pred - y_true
mse_loss = torch.mean(error ** 2) # 计算均方损失
return y_pred, mse_loss # 返回预测值和损失(如果有)
def optimizer(self, x, y):
x_mean = torch.mean(x, dim=0, keepdim=True) # 特征均值
tmp = x - x_mean
y_mean = torch.mean(y)
# 计算权重
w = torch.matmul(
torch.matmul(torch.inverse(torch.matmul(tmp.T, tmp)), tmp.T),
(y - y_mean)
)
b = y_mean - torch.matmul(x_mean, w)
# 更新模型参数
self.params['w'].data = w
self.params['b'].data = b
# 创建数据集
data_x, data_y = create_data(linear_func, (-10, 10), 150, noise=2, add_outlier=True, outlier_ratio=0.05)
data_x_train, data_y_train = data_x[0:100], data_y[0:100] # 训练集
data_x_test, data_y_test = data_x[100:150], data_y[100:150] # 测试集
# 实例化线性模型
model = LinearModel(input_size=1)
# 使用最优解析解更新模型参数
model.optimizer(data_x_train, data_y_train)
# 在训练集上进行前向传播计算并计算损失
y_pred_train, mse_loss_train = model(data_x_train, data_y_train)
# 打印输出
print("训练集损失:", mse_loss_train.item()) # 打印训练集损失
# 输出训练参数与真实参数的对比
true_w = 1.2 # 真实的权重
true_b = 0.5 # 真实的偏置
learned_w = model.params['w'].item() # 学习到的权重
learned_b = model.params['b'].item() # 学习到的偏置
print(f"真实权重: {true_w}, 学习到的权重: {learned_w}")
print(f"真实偏置: {true_b}, 学习到的偏置: {learned_b}")
在训练集上输出结果
训练集损失: 53.30013656616211
真实权重: 1.2, 学习到的权重: 1.4846314191818237
真实偏置: 0.5, 学习到的偏置: 0.5466461181640625
从输出结果看,预测结果与真实值w=1.2,b=0.5有一定的差距。
6 模型评估
用训练好的模型预测一下测试集的标签,并计算在测试集上的损失。
# 在测试集上进行前向传播计算并计算损失
y_pred_test, mse_loss_test = model(data_x_test, data_y_test)
# 打印输出测试集损失
print("测试集损失:", mse_loss_test.item())
# 输出测试参数与真实参数的对比
learned_w_test = model.params['w'].item() # 学习到的权重
learned_b_test = model.params['b'].item() # 学习到的偏置
print(f"真实权重: {true_w}, 学习到的权重: {learned_w_test}")
print(f"真实偏置: {true_b}, 学习到的偏置: {learned_b_test}")
# 可视化生成的数据
plt.figure(1)
plt.plot(data_x_train.numpy(), data_y_train.numpy(), '.r', label="训练集")
plt.plot(data_x_test.numpy(), data_y_test.numpy(), '.g', label="测试集")
plt.plot(data_x_train.numpy(), y_pred_train.detach().numpy(), color='blue', label='拟合线') # 绘制拟合线
plt.legend() # 添加图例
plt.title("线性模型生成的数据集(包含噪声和异常点)") # 添加标题
plt.show()
输出结果:
测试集损失: 25.790185928344727
真实权重: 1.2, 学习到的权重: 1.492882251739502
真实偏置: 0.5, 学习到的偏置: 0.6769941449165344
(二) 多项式回归
多项式回归是一种回归分析方法,旨在通过多项式函数来建模自变量与因变量之间的关系。与线性回归不同,多项式回归可以捕捉到更复杂的非线性关系。
当自变量和因变量之间并不是线性关系时,需要定义非线性基函数对特征进行变换,从而可以使得线性回归算法实现非线性的曲线拟合(基于特征维度为1的自变量介绍多项式回归实验)
1 数据集构建
假设我们要拟合的非线性函数为一个缩放后的sin函数。
import numpy as np
import torch
# 定义正弦函数
def sin(x):
# 计算正弦值,x范围为0到1之间的值,使用2π使得sin函数在[0, 1]范围内完成一个完整的周期
return np.sin(2 * np.pi * x)
# 创建数据集的函数
def create_data(func, interval, num, noise=0.5):
# 在指定的区间内均匀采样自变量x
x = np.random.rand(num, 1) * (interval[1] - interval[0]) + interval[0]
# 通过调用传入的函数生成因变量y
y = func(x)
# 生成高斯分布的噪声并添加到y中,以模拟实际数据中的随机扰动
epsilon = np.random.normal(0, noise, y.shape)
y = y + epsilon # 将噪声添加到真实的y值上
# 将x和y转换为PyTorch的张量,并返回
return torch.tensor(x, dtype=torch.float32), torch.tensor(y, dtype=torch.float32)
划分训练集和测试集
# 创建训练集和测试集
interval = (0, 1)
train_num = 15
test_num = 10
X_train, y_train = create_data(sin, interval, train_num)
X_test, y_test = create_data(sin, interval, test_num, noise=0) # 无噪声测试集
X_underlying = torch.linspace(interval[0], interval[1], steps=100).reshape(-1, 1) # 真实值
y_underlying = sin(X_underlying.numpy())
-
训练集 (
X_train
,y_train
):- 包含 15个样本,用于训练模型,具有噪声以模拟真实数据情况。
-
测试集 (
X_test
,y_test
):- 包含 10个样本,无噪声,用于评估模型的准确性。
2 模型构建
套用求解线性回归参数的方法来求解多项式回归参数。同样学习参数w,需要对输入特征根据多项式阶数进行变换。
# 多项式基函数
def polynomial_basis_function(X, degree):
return torch.cat([X**d for d in range(degree + 1)], dim=1)
# 线性模型类
class LinearModel(nn.Module):
def __init__(self, degree):
super(LinearModel, self).__init__()
self.params = nn.Parameter(torch.randn(degree + 1)) # 多项式系数
def forward(self, X):
return torch.matmul(X, self.params)
# 损失函数
def mse_loss(y_pred, y_true):
return torch.mean((y_pred - y_true) ** 2)
# 优化函数
def optimizer_lsm(model, X, y):
# 最小二乘法优化
w = torch.matmul(torch.inverse(torch.matmul(X.T, X)), torch.matmul(X.T, y))
model.params.data = w
return model
3 模型训练
设置多项式阶数为[0,1,3,8],在训练集上进行训练
# 设置多项式阶数
degrees = [0, 1, 3, 8]
# 绘图
plt.figure(figsize=(12, 8))
for i, degree in enumerate(degrees):
model = LinearModel(degree)
X_train_transformed = polynomial_basis_function(X_train, degree)
X_underlying_transformed = polynomial_basis_function(X_underlying, degree)
model = optimizer_lsm(model, X_train_transformed, y_train) # 拟合得到参数
y_underlying_pred = model(X_underlying_transformed).detach().numpy().squeeze()
# 计算损失
loss = mse_loss(torch.tensor(y_underlying_pred), torch.tensor(y_underlying.flatten()))
print(f"多项式阶数 {degree} 的损失: {loss.item()}")
# 绘制图像
plt.subplot(2, 2, i + 1)
plt.scatter(X_train.numpy(), y_train.numpy(), facecolor="none", edgecolor='#e4007f', s=50, label="训练数据")
plt.plot(X_underlying.numpy(), y_underlying, c='#000000', label=r"$\sin(2\pi x)$")
plt.plot(X_underlying.numpy(), y_underlying_pred, c='#f19ec2', label="拟合曲线")
plt.ylim(-2, 1.5)
plt.annotate("M={}".format(degree), xy=(0.95, -1.4))
plt.legend(loc='lower left', fontsize='x-large')
plt.suptitle("不同多项式阶的拟合结果")
plt.tight_layout()
plt.show()
输出训练结果
多项式阶数 0 的损失: 0.495100736618042
多项式阶数 1 的损失: 0.24220485985279083
多项式阶数 3 的损失: 0.034670744091272354
多项式阶数 8 的损失: 17.72734832763672
观察可视化结果分析可知:
- 当 M=0 或 M=1 时,拟合曲线较简单,模型欠拟合;
- 当 M=8 时,拟合曲线较复杂,模型过拟合;
- 当 M=3 时,模型拟合最为合理。
4 模型评估
通过均方误差来衡量训练误差、测试误差以及在没有噪音的加入下sin
函数值与多项式回归值之间的误差,多项式分布阶数从0到8进行遍历。
# 遍历多项式阶数
for i in range(9):
model = LinearModel(i)
X_train_transformed = polynomial_basis_function(X_train, i)
X_test_transformed = polynomial_basis_function(X_test, i)
X_underlying_transformed = polynomial_basis_function(X_underlying, i)
model = optimizer_lsm(model, X_train_transformed, y_train) # 拟合得到参数
y_train_pred = model(X_train_transformed).squeeze()
y_test_pred = model(X_test_transformed).squeeze()
y_underlying_pred = model(X_underlying_transformed).squeeze()
train_mse = mse_loss(y_train_pred, y_train).item()
training_errors.append(train_mse)
test_mse = mse_loss(y_test_pred, y_test).item()
test_errors.append(test_mse)
# 输出误差
print("训练误差: \n", training_errors)
print("测试误差: \n", test_errors)
# 绘制图片
plt.rcParams['figure.figsize'] = (8.0, 6.0)
plt.plot(training_errors, '-.', mfc="none", mec='#e4007f', ms=10, c='#e4007f', label="训练误差")
plt.plot(test_errors, '--', mfc="none", mec='#f19ec2', ms=10, c='#f19ec2', label="测试误差")
plt.legend(fontsize='x-large')
plt.xlabel("多项式阶数")
plt.ylabel("均方误差 (MSE)")
plt.title("训练误差与测试误差")
plt.show()
训练误差:
[0.41973090171813965, 0.7273364067077637, 0.7371339797973633, 0.8381550908088684, 0.8390923142433167, 0.860488772392273, 0.8347805738449097, 1.498320460319519, 1.1789517402648926]
测试误差:
[0.5488139986991882, 0.9985342621803284, 0.9210885763168335, 1.083980917930603, 1.0905184745788574, 1.1023352146148682, 1.1209158897399902, 2.2023000717163086, 1.4406342506408691]
Process finished with exit code 0
这里我觉得最后的结果不太正常,阶数很小的时候均方误差应该比较大,结果生成的图像反而是最小的。。o(╥﹏╥)o
(三)基于线性回归的波士顿房价预测
1 数据处理 :数据清洗、数据集划分、特征工程
数据清洗(缺失值和异常值处理)
import pandas as pd
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
# 读取数据
data = pd.read_csv('boston_house_prices.csv')
# 缺失值分析
missing_values = data.isna().sum()
print("缺失值统计:\n", missing_values)
# 绘制箱线图以检测异常值
def boxplot(data, fig_name):
"""绘制每个属性的箱线图,帮助识别异常值"""
num_plots = len(data.columns)
cols = 3 # 每行显示3个子图
rows = (num_plots + cols - 1) // cols # 计算需要的行数
plt.figure(figsize=(20, 5 * rows), dpi=300) # 调整图形大小
plt.subplots_adjust(wspace=0.4, hspace=0.6) # 调整子图间距
for i, col_name in enumerate(data.columns):
plt.subplot(rows, cols, i + 1)
plt.boxplot(data[col_name],
showmeans=True,
meanprops={"markersize": 1, "marker": "D", "markeredgecolor": "#C54680"},
medianprops={"color": "#946279"},
whiskerprops={"color": "#8E004D", "linewidth": 0.4, 'linestyle': "--"},
flierprops={"markersize": 0.4})
plt.title(col_name, fontdict={"size": 10}, pad=2)
plt.xticks([])
plt.savefig(fig_name)
plt.show()
# 绘制初始箱线图
boxplot(data, 'initial_boxplot.pdf')
# 四分位数异常值处理
num_features = data.select_dtypes(exclude=['object', 'bool']).columns.tolist()
for feature in num_features:
if feature == 'CHAS': # CHAS 特征不处理
continue
Q1 = data[feature].quantile(0.25) # 下四分位
Q3 = data[feature].quantile(0.75) # 上四分位
IQR = Q3 - Q1 # 四分位距
# 计算上下限
upper_limit = Q3 + 1.5 * IQR # 上限
lower_limit = Q1 - 1.5 * IQR # 下限
# 替换异常值
data[feature] = data[feature].clip(lower=lower_limit, upper=upper_limit)
# 再次绘制箱线图,查看异常值处理效果
boxplot(data, 'final_boxplot.pdf')
无缺失值
缺失值统计:
CRIM 0
ZN 0
INDUS 0
CHAS 0
NOX 0
RM 0
AGE 0
DIS 0
RAD 0
TAX 0
PTRATIO 0
B 0
LSTAT 0
MEDV 0
dtype: int64
异常值处理前后箱线图对比
前:
后:
(图源paddle)
数据集划分
将数据集划分为两份:训练集和测试集,不包括验证集。
# 定义 train_test_split 函数
def train_test_split(X, y, train_percent=0.8):
"""将数据集分割为训练集和测试集"""
n = len(X) # 数据集大小
shuffled_indices = np.random.permutation(n) # 随机排列索引
train_set_size = int(n * train_percent) # 训练集大小
train_indices = shuffled_indices[:train_set_size] # 训练集索引
test_indices = shuffled_indices[train_set_size:] # 测试集索引
X_train = X.values[train_indices] # 训练集特征
y_train = y.values[train_indices] # 训练集目标
X_test = X.values[test_indices] # 测试集特征
y_test = y.values[test_indices] # 测试集目标
return X_train, X_test, y_train, y_test
# 读取数据
data = pd.read_csv('boston_house_prices.csv')
# 分割特征和目标变量
X = data.drop(['MEDV'], axis=1) # 特征
y = data['MEDV'] # 目标变量
# 调用函数划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y)
特征工程
为了消除纲量对数据特征之间影响,在模型训练前,需要对特征数据进行归一化处理,将数据缩放到[0, 1]区间内.
# ==========================特征工程--归一化处理================================
X_train_min = X_train.min(axis=0)
X_train_max = X_train.max(axis=0)
X_train = (X_train - X_train_min) / (X_train_max - X_train_min)
X_test = (X_test - X_train_min) / (X_train_max - X_train_min)
# 将数据转换为 PyTorch 张量
X_train_tensor = torch.tensor(X_train, dtype=torch.float32)
X_test_tensor = torch.tensor(X_test, dtype=torch.float32)
y_train_tensor = torch.tensor(y_train, dtype=torch.float32).view(-1, 1) # 使目标变量为列向量
y_test_tensor = torch.tensor(y_test, dtype=torch.float32).view(-1, 1)
# 训练集构造
train_dataset = (X_train_tensor, y_train_tensor)
# 测试集构造
test_dataset = (X_test_tensor, y_test_tensor)
2 模型构建
构建一个线性回归模型,通过 Runner 类管理模型的训练、评估和预测过程。
· LinearRegressionModel 类继承自 nn.Module,定义了一个线性层用于实现线性回归。
· forward 方法定义了模型的前向传播过程。
· 损失函数和评估指标:使用均方误差损失 (nn.MSELoss) 作为损失函数,同时也用作评估指标。
· Runner 类实例化:创建 runner 对象,用于管理模型的训练和评估,传入模型、损失函数和评估指标。
# ==================模型构建======================
class LinearRegressionModel(nn.Module):
def __init__(self, input_size):
super(LinearRegressionModel, self).__init__()
self.linear = nn.Linear(input_size, 1) # 线性层
def forward(self, x):
return self.linear(x)
# 超参数
input_size = X_train_tensor.shape[1]
model = LinearRegressionModel(input_size)
# 损失函数和评估指标
mse_loss = nn.MSELoss()
q其中runner代码内容如下
# runner.py
import os
import torch
class Runner(object):
class Runner(object):
def __init__(self, model, optimizer, loss_fn, metric):
# 优化器和损失函数为None,不再关注
# 模型
self.model = model
# 评估指标
self.metric = metric
# 优化器
self.optimizer = optimizer
def train(self, dataset, reg_lambda, model_dir):
X, y = dataset
self.optimizer(self.model, X, y, reg_lambda)
# 保存模型
self.save_model(model_dir)
def evaluate(self, dataset, **kwargs):
X, y = dataset
y_pred = self.model(X)
result = self.metric(y_pred, y)
return result
def predict(self, X, **kwargs):
return self.model(X)
def save_model(self, model_dir):
if not os.path.exists(model_dir):
os.makedirs(model_dir)
params_saved_path = os.path.join(model_dir, 'params.pdtensor')
torch.save(model.params, params_saved_path)
def load_model(self, model_dir):
params_saved_path = os.path.join(model_dir, 'params.pdtensor')
self.model.params = torch.load(params_saved_path)
3 模型训练
调用 Runner
类的 train
方法,传入训练数据集和模型保存的目录。该方法将会使用指定的优化器和损失函数训练模型,并在训练完成后保存模型参数。
# 启动训练
runner.train(train_dataset, model_dir=saved_dir)
# 打印出训练得到的权重和偏置
weights = model.linear.weight.detach().numpy().flatten()
b = model.linear.bias.detach().numpy().item()
# 获取特征名
feature_names = X.columns.tolist()
for i in range(len(weights)):
print(f'{feature_names[i]}: 权重: {weights[i]}')
print(f'偏置: {b}')
CRIM weight:-5.2610182762146
ZN weight: 1.3627111911773682
INDUS weight:-0.024850845336914062
CHAS weight: 1.800213098526001
NoX weight: -7.556697368621826
RM weight:9.557083129882812
AGE weight: -1.3511630296707153
DIs weight:-9.967939376831055
RAD weight: 7.528402328491211
TAX weight:-5.082475662231445
PTRATI0 weight:-6.9966325759887695
LSTAT weight:-13.183658599853516
b:32.62158966064453
4 模型测试
# 加载模型权重
runner.load_model(saved_dir)
mse = runner.evaluate(test_dataset)
print('MSE:', mse.item())
5 模型预测
runner.load_model(saved_dir)
pred = runner.predict(X_test[:1])
print("真实房价:",y_test[:1].item())
print("预测的房价:",pred.item())
(四)Runner类简介
(图源paddle)
class Runner(object):
def __init__(self, model, optimizer, loss_fn, metric):
self.model = model # 模型
self.optimizer = optimizer # 优化器
self.loss_fn = loss_fn # 损失函数
self.metric = metric # 评估指标
# 模型训练
def train(self, train_dataset, dev_dataset=None, **kwargs):
pass
# 模型评价
def evaluate(self, data_set, **kwargs):
pass
# 模型预测
def predict(self, x, **kwargs):
pass
# 模型保存
def save_model(self, save_path):
pass
# 模型加载
def load_model(self, model_path):
pass
使用类实现机器学习模型基本要素方便整洁,更容易被人理解。让整体思路更加清晰,优化了许多后续的代码。同时也加强了代码的稳定性和正确性。
总结
第一次用类!!走了好多弯路,一开始自己尝试定义类做实验,看了好几篇博客,越看越感觉混,由于实验教材提供的代码是paddle框架,而实验用到的是pytorch框架,有很多地方需要改,包括那个nndl包,一开始尝试下载也下载不了,一直搞不懂这个runner类怎,自己组合的类最终的输出结果很离谱,最后结合cahtgpt的帮助改了好多遍代码结果数据才合理。(已经开始感觉到自己的愚笨了o(╥﹏╥)o)
线性模型的搭建还是比较简单的,已经学会了用类来存储程序以便后面用到的时候不用再重复写还是很方便的,。但是还是不够熟练。。这次实验和上次比难度增大,改了好久的代码www,一些很细节的地方还是需要再花费功夫看一看。代码多是借鉴的学长学姐的和老师的,还需继续努力。
时间原因实验报告写的比较急,课下仍然需要花时间好好学一学定义类(第一次接触感觉有点抽象o(╥﹏╥)o)