目录
2.2 线性回归
2.2.1 数据集构建
构造一个小的回归数据集:
生成 150 个带噪音的样本,其中 100 个训练样本,50 个测试样本,并打印出训练数据的可视化分布。
import torch
from matplotlib import pyplot as plt
# 真实函数的参数缺省值为 w=1.2,b=0.5
def linear_func(x, w=1.2, b=0.5):
y = w*x + b
return y
def create_toy_data(func, interval, sample_num, noise=0.0, add_outlier=False, outlier_ratio=0.001):
# 均匀采样
X = torch.rand([sample_num]) * (interval[1]-interval[0]) + interval[0]
y = func(X)
# 生成高斯分布的标签噪声
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(len(y), [outlier_num])
y[outlier_idx] = y[outlier_idx] * 5
return X, y
func = linear_func
interval = (-10, 10)
train_num = 100 # 训练样本数目
test_num = 50 # 测试样本数目
noise = 2
X_train, y_train = create_toy_data(func=func, interval=interval, sample_num=train_num, noise=noise, add_outlier=False)
X_test, y_test = create_toy_data(func=func, interval=interval, sample_num=test_num, noise=noise, add_outlier=False)
X_train_large, y_train_large = create_toy_data(func=func, interval=interval, sample_num=5000, noise=noise, add_outlier=False)
X_underlying = torch.linspace(interval[0], interval[1], train_num)
y_underlying = linear_func(X_underlying)
# 绘制数据
plt.scatter(X_train, y_train, marker='*', facecolor="none", edgecolor='red', s=50, label="train data")
plt.scatter(X_test, y_test, facecolor="none", edgecolor='blue', s=50, label="test data")
plt.plot(X_underlying, y_underlying, c='#000000', label=r"underlying distribution")
plt.legend(fontsize='x-large') # 给图像加图例
plt.savefig('ml-vis.pdf') # 保存图像到PDF文件中
plt.show()
运行结果:
(注:使用torch.normal(0,noise,y.shape)生成0均值,noise标准差的数据)
2.2.2 模型构建
线性模型定义为:
其中权重向量w和偏置b都是可学习的参数。
在实践中,为了提高预测样本的效率,我们通常会将N样本归为一组进行成批地预测,这样可以更好地利用GPU设备的并行计算能力。
y=Xw+b
其中X为N个样本的特征矩阵,y为N个预测值组成的列向量。
(注:在实践中,样本的矩阵X是由N个x的行向量组成。)
import torch
torch.manual_seed(11) # 设置随机种子
# 线性算子
class Linear( ):
def __init__(self, input_size):
self.input_size = input_size
# 模型参数
self.params = {}
self.params['w'] = torch.randn(self.input_size, 1, dtype=torch.float32)
self.params['b'] = torch.zeros(1, dtype=torch.float32)
def __call__(self, X):
return self.forward(X)
# 前向函数
def forward(self, X):
N, D = X.shape
if self.input_size == 0:
return torch.full((N, 1), self.params['b'])
assert D == self.input_size # 输入数据维度合法性验证
y_pred = torch.matmul(X, self.params['w']) + self.params['b']
return y_pred
input_size = 3
N = 2
X = torch.randn(N, input_size, dtype=torch.float32) # 生成2个维度为3的数据
model = Linear(input_size)
y_pred = model(X)
print("y_pred:", y_pred) # 输出结果的个数也是2个
运行结果:
2.2.3 损失函数
回归任务中常用的评估指标是均方误差,均方误差是反映估计量与被估计量之间差异程度的一种度量。(回归任务是对连续值的预测)
import torch
def mean_squared_error(y_true, y_pred):
assert y_true.shape[0] == y_pred.shape[0]
error = torch.mean(torch.square(y_true - y_pred))
return error
# 构造一个简单的样例进行测试:[N,1], N=2
y_true = torch.tensor([[-0.2], [4.9]], dtype=torch.float32)
y_pred = torch.tensor([[1.3], [2.5]], dtype=torch.float32)
error = mean_squared_error(y_true=y_true, y_pred=y_pred).item()
print("error:", error)
运行结果:(注:代码实现中没有除2)
思考:没有除2合理么?
合理,除以2作用是消除均方误差中平方项时存在的2的倍数,不除以2也能够在优化时进行正确计算。
2.2.4 模型优化
经验风险即在训练集上的平均损失。
def optimizer_lsm(model, X, y, reg_lambda=0): # 优化模型
N, D = X.shape
# 对输入特征数据所有特征向量求平均
x_bar_tran = torch.mean(X, axis=0).T
# 求标签的均值,shape=[1]
y_bar = torch.mean(y)
x_sub = torch.subtract(X, x_bar_tran)
if torch.all(x_sub) == 0:
model.params['b'] = y_bar
model.params['w'] = torch.zeros(D)
return model
tmp = torch.inverse(torch.matmul(x_sub.T, x_sub) + reg_lambda * torch.eye(D))
w = torch.matmul(torch.matmul(tmp, x_sub.T), (y - y_bar))
b = y_bar - torch.matmul(x_bar_tran, w)
model.params['b'] = b
model.params['w'] = torch.squeeze(w, axis=-1)
return model
(注:torch.eye(n,m)函数的作用为:返回一个2维张量,对角线位置全1,其它位置全0,n表示行数,m表示列数,默认等于n)
思考1:为什么省略了不影响效果?
1/N为常数, 求偏导后不会影响收敛结果。
思考2:什么是最小二乘法?
最小二乘法是基于均方误差最小化进行模型求解的方法。
2.2.5 模型训练
在准备了数据、模型、损失函数和参数学习的实现之后,开始模型的训练。在回归任务中,模型的评价指标和损失函数一致,都为均方误差。
通过上文实现的线性回归类来拟合训练数据,并输出模型在训练集上的损失。
input_size = 1
model = Linear(input_size)
model = optimizer_lsm(model, X_train.reshape([-1, 1]), y_train.reshape([-1, 1]))
print("w_pred:", model.params['w'].item(), "b_pred: ", model.params['b'].item())
y_train_pred = model(X_train.reshape([-1, 1])).squeeze()
train_error = mean_squared_error(y_true=y_train, y_pred=y_train_pred).item()
print("train error: ", train_error)
运行结果: