从零实现线性回归
线性回归是利用数理统计中回归分析,来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法。种回归分析称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且因变量和自变量之间是线性关系,则称为多元线性回归分析。
线性回归的基本模型可以表示为:y = w*x + b
此处从零开始实现线性回归模型,包括数据集准备、读取数据集、初始化模型参数、定义模型、定义损失函数、定义优化算法与模型训练
在文档最结尾,附上简洁实现的完整代码
#首先导入需要的库
import torch
import random
1. 生成数据集
使用线性模型参数w = [1,2]',b = 2.4 和噪声n(标准差为0.01,均值为0)
y
=
w
x
+
b
+
n
y = wx + b + n
y=wx+b+n
def generate_dataset(w,b,num): #此处的num为生成的样本数
#生成y = wx + b + n
x = torch.normal(0,1,(num,len(w))) #生成一个形状为(num, len(w))的张量x,其中的元素是从均值为0,标准差为1的正态分布中随机采样得到的
y = torch.matmul(x,w) + b #torch.matmul(x, w): 这是PyTorch库中的矩阵乘法函数
'''
此处说明, x的形状为[100,2],而w为[2,1] 矩阵乘法后y的形状为[100,1]
'''
y += torch.normal(0, 0.01, y.shape) #使用PyTorch库的torch.normal函数生成服从均值为0,标准差为0.01的正态分布噪声,并将其加到先前计算得到的张量y中
'''
解释returny.reshape((-1,1))
y.reshape((-1, 1))表示将y张量进行reshape操作, 将其形状修改为(num, 1)
( -1, 1 ) 的意思是将 y 调整为只有一列的二维张量,而行数则由张量 y 的元素数量来决定。其中 -1 表示该维度的大小由张量的原始形状和其它维度来推断
'''
return x, y.reshape((-1,1)) #
w = torch.tensor([1.0, 2.0]) #因为 torch.matmul 需要两个操作数具有相同的数据类型,此处将权重写为float型
b = 2.4
#生成数据
features, labels = generate_dataset(w,b,100) #x表示模型输入样本,也就是feature,y对应实际的模型输出值,也就是label
#输出前五个数据查看一下
print(features[:5,:])
print(labels[:5])
'''
此处可以理解为y = w1x1 + w2x2 + b + n
输入x = [1.1418, -0.3174] 时 y 实际为2.9325
我们后续要根据模型输出与y实际值的差距,优化模型
'''
2. 读取数据集
训练模型时,要对数据进遍历,每次抽取小批量样本,并使用它们来更新我们的模型,因此有必要定义一个函数来读取数据。
def data_reader(batch_size, features, labels):
num = len(features)
idx = list(range(num)) #生成与数据对应的索引列表
#随机读取样本,没有特定顺序
random.shuffle(idx)
for i in range(0,num,batch_size): #说明i从0到num, 每循环一次加步长batch_size
batch_idx = torch.tensor(
idx[i: min(i + batch_size, num)] #用min确保不会超出list范围
)
yield features[batch_idx], labels[batch_idx]
'''
#使用 yield 关键字返回特征和标签张量中对应批次索引的数据。
# 这样可以在每次迭代时生成一批新的数据,并且生成器会在此处暂停执行,等待下一次迭代时被唤醒。
'''
#输出一个batch_size数据看看效果
batch_size = 4
for x, y in data_reader(batch_size, features, labels):
print(x,'\n', y)
break
3. 初始化模型参数
在用数据训练模型参数之前,得有一些参数。
我们通过从均值0,标准差为0.01的正态分布中抽样随机数来初始化权重,并将偏执初始化为0.
model_w = torch.normal(0, 0.01, size = (2,1), requires_grad = True) #注意与全面的w区分,一个是实际值,一个是待优化值
model_b = torch.zeros(1,requires_grad = True)
4. 定义模型
注意wx是一个向量,而b是一个标量,当两者相加时,标量会被加到向量的每个分量上
def linear_reg(x, w, b):
'''线性回归模型'''
return torch.matmul(x, w) + b
5. 定义损失函数
模型需要损失函数来优化模型参数,因此需要先定义一个用于模型学习的损失函数
我们用y_pre来表示模型输出,与y区别,y_pre应该与y具有相同的形状
def squared_loss(y_pre, y):
'''均方损失'''
return (y_pre - y.reshape(y_pre.shape)) **2 / 2
6. 定义优化算法
在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。 接下来,朝着减少损失的方向更新我们的参数。下面的函数实现小批量随机梯度下降更新。 该函数接受模型参数集合、学习速率和批量大小作为输入。每一步更新的大小由学习速率lr决定。 因为我们计算的损失是一个批量样本的总和,所以我们用批量大小(batch_size) 来规范化步长,这样步长大小就不会取决于我们对批量大小的选择。
def sgd(parameters, learn_rate, barch_size):
'''小批量随机梯度下降'''
with torch.no_grad():
for param in parameters: #遍历模型的参数列表。
param -= learn_rate * param.grad / barch_size #:对每个参数进行更新,根据梯度下降的规则,用当前梯度乘以学习率除以批量大小来更新参数值。
param.grad.zero_() #将参数的梯度置零,为下一轮迭代做准备。
7. 模型训练
现在我们已经准备好了模型训练所有需要的要素,可以实现主要的训练部分了。 在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法sgd来更新模型参数。
概括一下,我们将执行以下循环:
初始化参数->计算梯度,优化参数->直到训练周期完成
在每个迭代周期(epoch)中,我们使用data_reader函数遍历整个数据集, 并将训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。 这里的迭代周期个数epochs和学习率learn_rate都是超参数,分别设为100和0.03。 设置超参数很棘手,需要通过反复试验进行调整。 现在暂时忽略这些细节。
#设置参数
epochs = 100
learn_rate = 0.03
model = linear_reg
loss = squared_loss
'''在Python中函数也是对象,因此可以像其他对象一样进行赋值操作。当我们将一个函数赋值给一个变量时,这个变量就引用了这个函数,可以像使用其他对象一样对其进行操作'''
for epoch in range(epochs):
for x, y in data_reader(batch_size, features, labels):
y_pre = model(x, model_w, model_b) #模型预测
l = loss(y_pre, y) #损失
l.sum().backward()
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
sgd([model_w, model_b], learn_rate, batch_size) #使用参数的梯度更新参数
with torch.no_grad():
train_l = loss(model(features, model_w, model_b), labels)
print('epoch: {}, loss: {:.6f}'.format(epoch + 1, float(train_l.mean())))
#训练完成,计算与实际值的误差并输出
print('w的估计误差: {}'.format(w - model_w.reshape(w.shape)))
print('b的估计误差: {}'.format(b - model_b))
线性回归简洁版实现(Pytorch)
#导入需要用到的库
import numpy as np
import torch
from torch.utils import data
import torch.nn as nn
#生成数据集
w = torch.tensor([1.0, 2.0]) #因为 torch.matmul 需要两个操作数具有相同的数据类型,此处将权重写为float型
b = 2.4
features, labels = generate_dataset(w,b,100) #x表示模型输入样本,也就是feature,y对应实际的模型输出值,也就是label
#用pytorch现有框架API读取数据
def load_array(data_array, batch_size, is_train = True):
'''构造一个pytorch数据迭代器'''
dataset = data.TensorDataset(*data_array)
'''*data_array 的作用是将 data_array 解包为 TensorDataset 的构造函数所需的参数。
在 Python 中,* 可以用来解包参数列表,将一个可迭代对象(比如列表、元组)中的元素逐个传递给函数或者构造函数。'''
return data.DataLoader(dataset, batch_size, shuffle = is_train)
batch_size = 4
data_iter = load_array((features, labels), batch_size)
#构建模型
net = nn.Sequential(nn.Linear(2, 1))
#初始化模型参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
#定义优化算法
Optimizer = torch.optim.SGD(net.parameters(), lr =0.03)
#定义损失函数
loss = nn.MSELoss()
#训练
epochs = 100
for epoch in range(epochs):
for x, y in data_iter:
y_pre = net(x) #模型预测
l = loss(y_pre, y) #损失
Optimizer.zero_grad()
l.backward()
Optimizer.step()
with torch.no_grad():
train_l = loss(net(features), labels)
print('epoch: {}, loss: {:.6f}'.format(epoch + 1, float(train_l.mean())))
#训练完成,计算与实际值的误差并输出
model_w = net[0].weight.data
model_b = net[0].bias.data
print('w的估计误差: {}'.format(w - model_w.reshape(w.shape)))
print('b的估计误差: {}'.format(b - model_b))