线性回归的从零开始实现Pytorch

《动手学深度学习pytorch》部分学习笔记,仅用作自己复习。

线性回归的从零开始实现

%matplotlib inline
import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random

生成数据集

num_inputs = 2  # 特征值
num_examples = 1000 # 数据及样本数
true_w = [2, -3.4] # 线性回归真实权重
true_b = 4.2 # 偏差
features = torch.from_numpy(np.random.normal(0, 1, (num_examples,num_inputs)))# 随机正态分布
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b # 生成标签
labels += torch.from_numpy(np.random.normal(0, 0.01,size=labels.size())) # 标准差0.01

 注意, features 的每一行是一个⻓度为2的向量,而 labels 的每一行是一个长度为1的向量(标量)

print(features[0], labels[0]) 

输出:tensor([0.8557, 0.4793]) tensor(4.2887)

通过生成第二个特征 features[:, 1] 和标签 labels 的散点图,可以更直观地观察两者间的线性关系。

def use_svg_display():# ⽤矢量图显示
    display.set_matplotlib_formats('svg')
def set_figsize(figsize=(3.5, 2.5)):
    use_svg_display()
    # 设置图的尺⼨寸
    plt.rcParams['figure.figsize'] = figsize
# # 在../d2lzh_pytorch⾥里里⾯面添加上⾯面两个函数后就可以这样导⼊入
# import sys
# sys.path.append("..")
# from d2lzh_pytorch import *
set_figsize()

plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);

将 上 面 的 plt 作 图 函 数 以 及 use_svg_display 函 数 和 set_figsize 函 数 定 义在 d2lzh_pytorch 包里。以后在作图时,我们将直接调⽤用 d2lzh_pytorch.plt 。由于 pltd2lzh_pytorch 包 中 是 一 个 全 局 变 量 , 我 们 在 作 图 前 只 需 要 调用 d2lzh_pytorch.set_figsize() 即可打印⽮量图并设置图的尺⼨。

读取数据

在训练模型的时候,我们需要遍历数据集并不断读取⼩批量数据样本。这⾥我们定义一个函数:它每次返回 batch_size (批量⼤小)个随机样本的特征标签

# 本函数已保存在d2lzh包中⽅便以后使⽤用
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples)) # 索引
    random.shuffle(indices) # 样本的读取顺序是随机的 shuffle洗牌打乱
    for i in range(0, num_examples, batch_size):
        j = torch.LongTensor(indices[i: min(i + batch_size,num_examples)])
        # 最后一次可能不一个batch
        yield features.index_select(0, j), labels.index_select(0,j)

读取第⼀个⼩批量数据样本并打印。每个批量的特征形状为(10, 2),分别对应批量⼤小输⼊个数标签形状为批量⼤小

batch_size = 10
for X, y in data_iter(batch_size, features, labels):
    print(X, y)
    break

输出:(特征,标签)

tensor([[-1.4239, -1.3788],
            [ 0.0275, 1.3550],
            [ 0.7616, -1.1384],
            [ 0.2967, -0.1162],
            [ 0.0822, 2.0826],
            [-0.6343, -0.7222],
            [ 0.4282, 0.0235],
            [ 1.4056, 0.3506],
            [-0.6496, -0.5202],
            [-0.3969, -0.9951]])
tensor([ 6.0394, -0.3365, 9.5882, 5.1810, -2.7355, 5.3873,4.9827, 5.7962,4.6727, 6.7921])   

初始化模型参数

我们将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0。

# 随机均值为0标准差0.01标准正态分布
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)),dtype=torch.float32)
# 偏差0
b = torch.zeros(1, dtype=torch.float32)

之 后 的 模 型 训 练 中 , 需 要 对 这 些 参 数 求 梯 度 来 迭 代 参 数 的 值 , 因 此 我 们 要 让 它 们 的requires_grad=True 。

Tensor 是这个包的核心类,如果将其属性 .requires_grad 设置为 True ,它将开始追踪(track)在其上的所有操作(这样就可以利用链式法则进行梯度传播了)。完成计算后,可以调用 .backward() 来完成所有梯度计算。此 Tensor 的梯度将累积到 .grad 属性中。

w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)

定义模型

下⾯面是线性回归的⽮矢量量计算表达式的实现。我们使⽤ mm 函数做矩阵乘法。

def linreg(X, w, b): # 本函数已保存在d2lzh_pytorch包中⽅方便便以后使⽤用
    return torch.mm(X, w) + b

定义损失函数

我们使⽤上⼀节描述的平方损失来定义线性回归的损失函数。在实现中,我们需要把真实值 y 变形成预测值 y_hat 的形状。以下函数返回的结果也将和 y_hat 的形状相同。

def squared_loss(y_hat, y): # 本函数已保存在d2lzh_pytorch包中⽅便以后使⽤用
# 注意这里返回的是向量, 另外, pytorch里的MSELoss并没有除以 2
    return (y_hat - y.view(y_hat.size())) ** 2 / 2

定义优化算法

以下的 sgd 函数实现了上⼀节中介绍的⼩批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。这⾥自动求梯度模块计算得来的梯度是⼀个批量样本的梯度和。我们将它除以批量⼤小来得到平均值。

def sgd(params, lr, batch_size): 
# 本函数已保存在d2lzh_pytorch包中⽅便以后使⽤
    for param in params:
        param.data -= lr * param.grad / batch_size 
        # 注意这⾥更改param时⽤的param.data
        # 这⾥自动求梯度模块计算得来的梯度是⼀个批量样本的梯度和。    
        # 我们将它除以批量⼤小来得到平均值。

训练模型

在训练中,我们将多次迭代模型参数。在每次迭代中,我们根据当前读取的⼩批量数据样本(特征 X 和标签 y ),通过调⽤反向函数 backward 计算⼩批量随机梯度,并调⽤优化算法 sgd 迭代模型参数。由于我们之前设批量⼤小 atch_size 为10,每个小批量的损失 l 的形状为(10, 1)。回忆⼀下⾃动求梯度⼀节。由于变量 l 并不是⼀个标量,所以我们可以调⽤ .sum() 将其求和得到⼀一个标量,再运⾏ l.backward() 得到该变量有关模型参数的梯度。注意在每次更新完参数后不要忘了将参数的梯度清零。
在⼀个迭代周期(epoch)中,我们将完整遍历⼀遍 data_iter 函数,并对训练数据集中所有样本都使⽤⼀次(假设样本数能够被批量大⼩整除)。这⾥的迭代周期个数 num_epochs 和学习率 lr 都是超参数,分别设3和0.03。在实践中,⼤多超参数都需要通过反复试错来不断调节。虽然迭代周期数设得越⼤模型可能越有效,但是训练时间可能过⻓。⽽有关学习率对模型的影响,我们会在后⾯面“优化算法”⼀章中详细介绍。

lr = 0.03 # 学习率
num_epochs = 3 # 迭代周期个数
net = linreg # 线性网络模型
loss = squared_loss # 平方损失函数
for epoch in range(num_epochs): # 训练模型⼀共需要num_epochs个迭代周期
    # 在每⼀个迭代周期中,会使⽤训练数据集中所有样本⼀次(假设样本数能够被批量量⼤小整除)。
    # x和y分别是⼩小批量量样本的特征和标签
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y).sum() # l是有关⼩批量X和y的损失
        l.backward() # ⼩批量的损失对模型参数求梯度
        sgd([w, b], lr, batch_size) # 使⽤用⼩批量随机梯度下降迭代模型参数
        # 不要忘了梯度清零
        w.grad.data.zero_()
        b.grad.data.zero_()
    train_l = loss(net(features, w, b), labels)
    print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))

输出:

epoch 1, loss 0.028127
epoch 2, loss 0.000095
epoch 3, loss 0.000050

训练完成后,我们可以比较学到的参数和⽤来⽣成训练集的真实参数。它们应该很接近。

print(true_w, '\n', w)
print(true_b, '\n', b)

输出:

[2, -3.4]
tensor([[ 1.9998],
[-3.3998]], requires_grad=True)
4.2
tensor([4.2001], requires_grad=True)

可以看出,仅使⽤ Tensor 和 autograd 模块就可以很容易易地实现一个模型。

猜你喜欢

转载自blog.csdn.net/dujuancao11/article/details/108322784