目录
写在前面的一些内容
- 本文为HBU_神经网络与深度学习实验(2022年秋)实验3的实验报告,此文的基本内容参照 [1]神经网络与深度学习:案例与实践 - 第2章(上):线性回归理论解读 和 [2]NNDL 实验2(下),检索时请按对应题号进行检索。
- 本实验编程语言为Python 3.10,使用Pycharm进行编程。
- 本实验报告目录标题级别顺序:一、1. (1)
- 水平有限,难免有误,如有错漏之处敬请指正。
一、实现一个简单的线性回归模型
进行实验前,我们需要先导入torch包。
import torch
1. 数据集构建
首先,我们构造一个小的回归数据集。假设输入特征和输出标签的维度都为1,那么我们来定义一个被拟合的函数。
def linefunc(x, w=1.8, b=0.5):
y = w * x + b
return y
接下来的内容都将以 y = 1.8 x + 0.5 \ y=1.8x+0.5 y=1.8x+0.5 这个被拟合函数为基准进行操作。
# create_data.py
def create_data(func, interval, sample_num, noise, add_outlier=False, outlier_ratio=0.001):
"""
根据给定的函数,生成样本
输入:
- func:函数
- interval: x的取值范围
- sample_num: 样本数目
- noise: 噪声均方差
- add_outlier:是否生成异常值
- outlier_ratio:异常值占比
输出:
- X: 特征数据,shape=[n_samples,1]
- y: 标签数据,shape=[n_samples,1]
"""
# 均匀采样
# 使用paddle.rand在生成sample_num个随机数
X = torch.rand(sample_num) * (interval[1] - interval[0]) + interval[0]
y = func(X)
# 生成高斯分布的标签噪声
# 使用paddle.normal生成概率分布为N~(0,noise^2)的数据
epsilon = torch.normal(0, noise, y.shape)
y = y + epsilon
if add_outlier: # 生成额外的异常点
outlier_num = int(len(y) * outlier_ratio)
if outlier_num != 0:
# 使用paddle.randint生成服从均匀分布的、范围在[0, len(y))的随机张量
outlier_idx = torch.randint(len(y), [outlier_num])
y[outlier_idx] = y[outlier_idx] * 5
return X, y
利用上面的生成样本函数,生成150个带噪音的样本,其中100个训练样本,50个测试样本,并打印出训练数据的可视化分布。
from matplotlib import pyplot as plt
from create_data import create_data
def data_processing(func, interval, train_num, test_num, step, noise):
X_train, y_train = create_data(func=func, interval=interval, sample_num=train_num, noise=noise, add_outlier=False)
X_test, y_test = create_data(func=func, interval=interval, sample_num=test_num, noise=noise, add_outlier=False)
# paddle.linspace返回一个张量,张量的值为在区间start和stop上均匀间隔的num个值,输出张量的长度为num
X_underlying = torch.linspace(interval[0], interval[1], step)
y_underlying = func(X_underlying)
return X_train, y_train, X_test, y_test, X_underlying, y_underlying
func = linefunc
interval = (-10, 10) # x的取值范围
train_num, test_num = 100, 50 # 训练样本数目和测试样本数目
noise = 3 # 标准差
step = 100
X_train, y_train, X_test, y_test, X_underlying, y_underlying = data_processing(func, interval, train_num, test_num, step, noise)
# 绘制数据
plt.scatter(X_train, y_train, marker='*', facecolor="none", edgecolor='#9932CC', s=50, label="train data") # 训练数据标点颜色为暗紫色 (darkorchid)
plt.scatter(X_test, y_test, facecolor="none", edgecolor='#00BFFF', s=50, label="test data") # 测试数据标点颜色为深天蓝色 (deepskyblue)
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()
代码执行结果如下图所示:
这里的data_processing中的内容在本次实验后续内容还会再次使用,所以将其写成函数。
2. 模型构建
在线性回归中,自变量为样本的特征向量 x ∈ R D \ \boldsymbol{x} \in \mathbb{R}^D x∈RD(每一维对应一个自变量),因变量是连续值的标签 y ∈ R \ y \in R y∈R。
线性模型定义为:
f ( x ; w , b ) = w T x + b \begin{align} f(\boldsymbol{x}; \boldsymbol{w},b)= \boldsymbol{w}^T \boldsymbol{x}+b \end{align} f(x;w,b)=wTx+b其中权重向量 w ∈ R D \ \boldsymbol{w} \in \mathbb{R}^D w∈RD和偏置 b ∈ R \ b \in \mathbb{R} b∈R都是可学习的参数。[1]
在实践中,为了提高预测样本的效率,我们通常会将 N \ N N 样本归为一组进行成批地预测,这样可以更好地利用GPU设备的并行计算能力。
y = X w + b \begin{align} \boldsymbol{y} = \boldsymbol{X} \boldsymbol{w} + b \end{align} y=Xw+b其中 X ∈ R N × D \ \boldsymbol{X} \in \mathbb{R}^{N \times D} X∈RN×D 为 N \ N N 个样本的特征矩阵, y ∈ R N \ \boldsymbol{y} \in \mathbb{R}^N y∈RN 为 N \ N N 个预测值组成的列向量。[1]
好了,接下来我们先定义一个算子(op.py):
# op.py
import torch
torch.manual_seed(10) # 设置随机种子
class Op(object):
def __init__(self):
pass
def __call__(self, inputs):
return self.forward(inputs)
def forward(self, inputs):
raise NotImplementedError
def backward(self, inputs):
raise NotImplementedError
# 线性算子
class Linear(Op):
def __init__(self, input_size):
"""
输入:
- input_size:模型要处理的数据特征向量长度
"""
self.input_size = input_size
# 模型参数
self.params = {
}
self.params['w'] = torch.randn(self.input_size, 1)
self.params['b'] = torch.zeros([1])
def __call__(self, X):
return self.forward(X)
# 前向函数
def forward(self, X):
"""
输入:
- X: tensor, shape=[N,D]
注意这里的X矩阵是由N个x向量的转置拼接成的,与原教材行向量表示方式不一致
输出:
- y_pred: tensor, shape=[N]
"""
N, D = X.shape
if self.input_size == 0:
return torch.full(size=[N, 1], fill_value=self.params['b'])
assert D == self.input_size # 输入数据维度合法性验证
# 使用torch.matmul计算两个tensor的乘积
y_pred = torch.matmul(X, self.params['w']) + self.params['b']
return y_pred
然后我们来构建一个线性回归模型:
import op
# 注意这里我们为了和后面章节统一,这里的X矩阵是由N个x向量的转置拼接成的,与原教材行向量表示方式不一致
input_size = 3
N = 2
X = torch.randn(N, input_size) # 生成2个维度为3的数据
model = op.Linear(input_size)
y_pred = model(X)
print("y_pred:", y_pred) # 输出结果的个数也是2个
代码执行结果:
y_pred: tensor([[1.8529],
[0.6011]])
3. 损失函数
回归任务是对连续值的预测,希望模型能根据数据的特征输出一个连续值作为预测值。因此回归任务中常用的评估指标是均方误差。
令 y ∈ R N \ \boldsymbol{y} \in \mathbb{R}^N y∈RN, y ^ ∈ R N \ \hat{\boldsymbol{y}} \in \mathbb{R}^N y^∈RN分别为 N \ N N 个样本的真实标签和预测标签,均方误差的定义为:
L ( y , y ^ ) = 1 2 N