线性回归

利用一个简单的线性回归的例子来了解神经网络的实现逻辑。
导入需要的包

# 导入工具包
%matplotlib inline  # 在plot时默认是嵌入到matplotlib里面
import random
import torch
from d2l import torch as d2l

1 线性回归-按照原理一步步实现

代码链接

1.1 收集数据和整理数据

以预测房价为例子。比如说房子有很多特征(比如房子的面积,地理位置,房间的数量等等),这些特征会影响到房子的价格。
我们首先会收集数据,包括样本和标签,样本就是房子的特征信息,标签就是房子卖出的价格。

我们利用正太分布来生成一个人工的数据集:

# 构造一个简单的人造数据集
def synthetic_data(w, b, num_examples):
    """生成y=Xw+b+noise"""
    X = torch.normal(0,1,(num_examples, len(w)))  #均值为0,标准差为1
    y = torch.matmul(X,w)+b
    y += torch.normal(0,0.01, y.shape) # 添加随机噪声
    return X, y.reshape((-1,1))  #将y作为一个列向量返回

true_w = torch.tensor([2,-3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
print('features:',features[0], '\nlabel',labels[0])

显示一下数据

# 显示一下数据集
d2l.set_figsize()
d2l.plt.scatter(features[:,1].detach().numpy(),
               labels.detach().numpy(),1)

Image

将数据进行分批,用于后续的批量训练

# 数据需要批量训练,构建一个提取批量数据函数
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))  # 生成每一个数据的标号
    random.shuffle(indices)  # 打乱标号
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(indices[i:min(i+batch_size, num_examples)]) # 使用min防止提取数据时超出
        yield features[batch_indices], labels[batch_indices]  # 使用yield不停的返回每一批数据
        
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break

1.2 定义线性回归模型

# 定义模型,初始化参数
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad = True)  # 两个参数都需要计算梯度

# 定义线性回归模型
def linreg(X, w, b):
    return torch.matmul(X, w)+b

1.3 定义损失函数和优化算法

均方误差损失函数

# 定义训练过程中的损失函数-使用均方损失
def squared_loss(y_hat, y):
    return (y_hat-y.reshape(y_hat.shape))**2/2  # 用reshape保持y与y_hat 维度一致

使用梯度下降算法

# 定义优化算法-小批量梯度下降
def sgd(params, lr, batch_size):
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad/batch_size     # 前面定义损失函数时没有求平均,这里除以batch_size
            param.grad.zero_()   # pytorch的机制中梯度不会清零,这里要手动清除

1.4 设定超参数,开始训练模型,并输出损失

# 开始定义超参数和训练函数
lr = 0.03 #学习率
num_epochs = 3  #学习多少回合
net = linreg   # 线性回归模型
loss = squared_loss  # 均方损失函数

for epoch in range(num_epochs):
    for X,y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y)   #这是一个批量的损失,l的形状是(batch_size,1)而不是一个标量
        l.sum().backward()  # 对l求和后再求梯度
        sgd([w,b], lr, batch_size)  # 使用梯度下降算法来更新参数
    
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)  # 训练完成后,计算误差
        print(f'epoch{epoch+1}, loss{float(train_l.mean()):f}')

结果
epoch1, loss0.045473
epoch2, loss0.000200
epoch3, loss0.000049

输出训练完成后w,b的误差

# 输出训练完成后w,b的误差
print(f'w的误差: {true_w-w.reshape(true_w.shape)}')
print(f'b的误差: {true_b-b}')

结果
w的误差: tensor([-0.0006, -0.0009])
b的误差: tensor([0.0014])

2 线性回归-利用torch中自带的方法进行简单实现

代码链接

2.1 生成数据

import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l

true_w = torch.tensor([2,-3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000) # 用d2l中的工具生成人工数据和标签
# 将数据导入到pytorch自带的dataset集中,然后用自带的dataloader函数进行批量随机提取
def load_array(data_arrays, batch_size, is_train=True):
    """构造一个pytorch数据迭代器"""
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)

batch_size = 10
data_iter = load_array((features, labels), batch_size)
next(iter(data_iter))

2.2 构建线性回归模型

# 利用torch自带的方法构建神经网络模型,并初始化参数
from torch import nn
net = nn.Sequential(nn.Linear(2,1))  # 一层的线性网络

net[0].weight.data.normal_(0,0.01)  # 使用正太分布函数初始化权重参数
net[0].bias.data.fill_(0)  

2.3 损失函数和优化方法

# 损失函数
loss = nn.MSELoss()

# 优化方法
trainer = torch.optim.SGD(net.parameters(), lr=0.03)

2.4 训练模型

# 开始训练
num_epochs = 3
for epoch in range(num_epochs):
    for X,y in data_iter:
        l = loss(net(X), y)
        trainer.zero_grad()
        l.backward()
        trainer.step()
        
    l = loss(net(features), labels)
    print(f'epoch{epoch+1}, loss{l: f}')

结果
epoch1, loss 0.000393
epoch2, loss 0.000100
epoch3, loss 0.000100