深度学习入门(四) 线性回归实现

前言

核心内容来自博客链接
希望大家多多支持作者
本文记录用,防止遗忘

线性回归实现

在了解线性回归的关键思想之后,我们可以开始通过代码来动手实现线性回归了

1 线性回归的从零实现

在这一节中,我们将从零开始实现整个方法, 包括数据流水线、模型、损失函数和小批量随机梯度下降优化器。 虽然现代的深度学习框架几乎可以自动化地进行所有这些工作,但从零开始实现可以确保你真正知道自己在做什么。 同时,了解更细致的工作原理将方便我们自定义模型、自定义层或自定义损失函数。 在这一节中,我们将只使用张量和自动求导。

需要的包如下

import random
import torch
from d2l import torch as d2l

1.1 生成数据集

为了简单起见,我们将根据带有噪声的线性模型构造一个人造数据集。 我们的任务是使用这个有限样本的数据集来恢复这个模型的参数。 我们将使用低维数据,这样可以很容易地将其可视化。 在下面的代码中,我们生成一个包含1000个样本的数据集, 每个样本包含从标准正态分布中采样的2个特征。 我们的合成数据集是一个矩阵 X ∈ R 1000 × 2 X∈R^{1000×2} XR1000×2
我们使用线性模型参数 w = [ 2 , − 3.4 ] T 、 b = 4.2 w=[2,-3.4]^T、b=4.2 w=[2,3.4]Tb=4.2和噪声项 ϵ ϵ ϵ生成数据集及其标签,
你可以将 ϵ ϵ ϵ视为模型预测和标签时的潜在观测误差。 在这里我们认为标准假设成立,即 ϵ ϵ ϵ服从均值为0的正态分布。 为了简化问题,我们将标准差设为0.01。 下面的代码生成合成数据集。

def synthetic_data(w, b, num_examples):  #@save
    """生成y=Xw+b+噪声"""
    X = torch.normal(0, 1, (num_examples, len(w)))
    y = torch.matmul(X, w) + b
    y += torch.normal(0, 0.01, y.shape)
    return X, y.reshape((-1, 1))

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

注意,features中的每一行都包含一个二维数据样本, labels中的每一行都包含一维标签值(一个标量)。

可以通过代码查看数据

print('features:', features[0],'\nlabel:', labels[0])

输出:features: tensor([-0.1413, 0.9253]) label: tensor([0.7524])
通过生成第二个特征features[:, 1]和labels的散点图, 可以直观观察到两者之间的线性关系。

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

输出:
在这里插入图片描述

1.2 读取数据集

回想一下,训练模型时要对数据集进行遍历,每次抽取一小批量样本,并使用它们来更新我们的模型。 由于这个过程是训练机器学习算法的基础,所以有必要定义一个函数, 该函数能打乱数据集中的样本并以小批量方式获取数据。

在下面的代码中,我们定义一个data_iter函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。 每个小批量包含一组特征和标签。

注意下列代码运用了python的生成器

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)])
        yield features[batch_indices], labels[batch_indices]

通常,我们利用GPU并行运算的优势,处理合理大小的“小批量”。 每个样本都可以并行地进行模型计算,且每个样本损失函数的梯度也可以被并行计算。 GPU可以在处理几百个样本时,所花费的时间不比处理一个样本时多太多。

我们直观感受一下小批量运算:读取第一个小批量数据样本并打印。 每个批量的特征维度显示批量大小和输入特征数。 同样的,批量的标签形状与batch_size相等。

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break

输出:

tensor([[-0.0929,  0.3136],
        [-0.4081,  0.5990],
        [ 1.2006, -0.8625],
        [ 2.8351,  1.2113],
        [ 0.4811,  1.6206],
        [-1.5946,  0.7590],
        [-0.7296,  2.0734],
        [ 1.4357, -0.4068],
        [-1.1405, -0.0359],
        [ 0.6749,  0.9677]])
 tensor([[ 2.9562],
        [ 1.3347],
        [ 9.5308],
        [ 5.7467],
        [-0.3549],
        [-1.5650],
        [-4.3218],
        [ 8.4510],
        [ 2.0353],
        [ 2.2612]])

当我们运行迭代时,我们会连续地获得不同的小批量,直至遍历完整个数据集。 上面实现的迭代对于教学来说很好,但它的执行效率很低,可能会在实际问题上陷入麻烦。 例如,它要求我们将所有数据加载到内存中,并执行大量的随机内存访问。 在深度学习框架中实现的内置迭代器效率要高得多, 它可以处理存储在文件中的数据和数据流提供的数据。

1.3 初始化模型参数

在我们开始用小批量随机梯度下降优化我们的模型参数之前, 我们需要先有一些参数。 在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

在初始化参数之后,我们的任务是更新这些参数,直到这些参数足够拟合我们的数据。 每次更新都需要计算损失函数关于模型参数的梯度。 有了这个梯度,我们就可以向减小损失的方向更新每个参数。 因为手动计算梯度很枯燥而且容易出错,所以没有人会手动计算梯度。 我们使用自动微分来计算梯度。

1.4 定义模型

下面,我们必须定义模型,将模型的输入和参数同模型的输出关联起来。 回想一下,要计算线性模型的输出, 我们只需计算输入特征X和模型权重w的矩阵-向量乘法后加上偏置b。 注意,上面的Xw是一个向量,而是b一个标量。 回想广播机制: 当我们用一个向量加一个标量时,标量会被加到向量的每个分量上。

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

1.5 定义损失函数

因为需要计算损失函数的梯度,所以我们应该先定义损失函数。 这里我们使用平方损失函数。 在实现中,我们需要将真实值y的形状转换为和预测值y_hat的形状相同。

def squared_loss(y_hat, y):  #@save
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

1.6 定义优化算法

正如之前讨论的,线性回归有解析解。 尽管线性回归有解析解,但本书中的其他模型却没有。 这里我们介绍小批量随机梯度下降。

在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。 接下来,朝着减少损失的方向更新我们的参数。 下面的函数实现小批量随机梯度下降更新。 该函数接受模型参数集合、学习速率和批量大小作为输入。每 一步更新的大小由学习速率lr决定。 因为我们计算的损失是一个批量样本的总和,所以我们用批量大小(batch_size) 来规范化步长,这样步长大小就不会取决于我们对批量大小的选择。

def sgd(params, lr, batch_size):  #@save
    """小批量随机梯度下降"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

疑问?

  # with torch.no_grad():
  #   for param in params:
  #     param = param - lr * param.grad / batch_size
  #     param.grad.zero_()
     
  with torch.no_grad():
    for param in params:
      param -= lr * param.grad / batch_size
      param.grad.zero_()

这一段,注释的代码会报错,下面的代码正常
报错如下:

AttributeError: 'NoneType' object has no attribute 'zero_'

1.7 训练

现在我们已经准备好了模型训练所有需要的要素,可以实现主要的训练过程部分了。 理解这段代码至关重要,因为从事深度学习后, 你会一遍又一遍地看到几乎相同的训练过程。 在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法sgd来更新模型参数。
概括一下,我们将执行以下循环:
在每个迭代周期(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):
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y)  # X和y的小批量损失
        # 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
        # 并以此计算关于[w,b]的梯度
        l.sum().backward()
        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}')

输出:

epoch 1, loss 0.026352
epoch 2, loss 0.000093
epoch 3, loss 0.000054

因为我们使用的是自己合成的数据集,所以我们知道真正的参数是什么。 因此,我们可以通过比较真实参数和通过训练学到的参数来评估训练的成功程度。 事实上,真实参数和通过训练学到的参数确实非常接近。

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

输出:

w的估计误差: tensor([ 0.0002, -0.0001], grad_fn=<SubBackward0>)
b的估计误差: tensor([0.0006], grad_fn=<RsubBackward1>)

注意,我们不应该想当然地认为我们能够完美地求解参数。 在机器学习中,我们通常不太关心恢复真正的参数,而更关心如何高度准确预测参数。 幸运的是,即使是在复杂的优化问题上,随机梯度下降通常也能找到非常好的解。 其中一个原因是,在深度网络中存在许多参数组合能够实现高度精确的预测。

2 线性回归的简洁实现

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

2.1 生成数据集

我们生成与上一节中相同的数据集。其中features是训练数据特征,labels是标签。

num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)

2.2 读取数据

1

我们可以调用框架中现有的API来读取数据。 我们将features和labels作为API的参数传递,并通过数据迭代器指定batch_size。 此外,布尔值is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据。

def load_array(data_arrays, batch_size, is_train=True):  #@save
    """构造一个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)

使用data_iter的方式与我们上一节中使用data_iter函数的方式相同。为了验证是否正常工作,让我们读取并打印第一个小批量样本。 与上一节不同,这里我们使用iter构造Python迭代器,并使用next从迭代器中获取第一项。

next(iter(data_iter))

输出:

[tensor([[ 0.7882, -0.7068],
         [ 0.5081,  0.2577],
         [-0.5769,  0.1545],
         [-0.3271, -0.6080],
         [-0.2716, -1.4628],
         [-1.1530, -1.4643],
         [ 0.1635, -0.2018],
         [-0.0753, -1.1161],
         [ 3.4251,  0.1953],
         [ 0.3589, -0.9478]]),
 tensor([[ 8.1742],
         [ 4.3357],
         [ 2.5157],
         [ 5.6106],
         [ 8.6395],
         [ 6.8726],
         [ 5.2155],
         [ 7.8377],
         [10.3918],
         [ 8.1590]])]
2

PyTorch提供了data包来读取数据。由于data常用作变量名,我们将导入的data模块用Data代替。在每一次迭代中,我们将随机读取包含10个数据样本的小批量。

import torch.utils.data as Data

batch_size = 10
# 将训练数据的特征和标签组合
dataset = Data.TensorDataset(features, labels)
# 随机读取小批量
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)

这里data_iter的使用跟上一节中的一样。让我们读取并打印第一个小批量数据样本。

for X, y in data_iter:
    print(X, y)
    break

输出:

tensor([[-2.7723, -0.6627],
        [-1.1058,  0.7688],
        [ 0.4901, -1.2260],
        [-0.7227, -0.2664],
        [-0.3390,  0.1162],
        [ 1.6705, -2.7930],
        [ 0.2576, -0.2928],
        [ 2.0475, -2.7440],
        [ 1.0685,  1.1920],
        [ 1.0996,  0.5106]]) 
 tensor([ 0.9066, -0.6247,  9.3383,  3.6537,  3.1283, 17.0213,  5.6953, 17.6279,
         2.2809,  4.6661])

2.3 定义模型

在上一节从零开始的实现中,我们需要定义模型参数,并使用它们一步步描述模型是怎样计算的。当模型结构变得更复杂时,这些步骤将变得更繁琐。其实,PyTorch提供了大量预定义的层,这使我们只需关注使用哪些层来构造模型。下面将介绍如何使用PyTorch更简洁地定义线性回归。

首先,导入torch.nn模块。实际上,“nn”是neural networks(神经网络)的缩写。顾名思义,该模块定义了大量神经网络的层。之前我们已经用过了autograd,而nn就是利用autograd来定义模型。nn的核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,撰写自己的网络/层。一个nn.Module实例应该包含一些层以及返回输出的前向传播(forward)方法。下面先来看看如何用nn.Module实现一个线性回归模型。

class LinearNet(nn.Module):
    def __init__(self, n_feature):
        super(LinearNet, self).__init__()
        self.linear = nn.Linear(n_feature, 1)
    # forward 定义前向传播
    def forward(self, x):
        y = self.linear(x)
        return y

net = LinearNet(num_inputs)
print(net) # 使用print可以打印出网络的结构

输出:

LinearNet(
  (linear): Linear(in_features=2, out_features=1, bias=True)
)

事实上我们还可以用nn.Sequential来更加方便地搭建网络,Sequential是一个有序的容器,网络层将按照在传入Sequential的顺序依次被添加到计算图中。

对于标准深度学习模型,我们可以使用框架的预定义好的层。这使我们只需关注使用哪些层来构造模型,而不必关注层的实现细节。 我们首先定义一个模型变量net,它是一个Sequential类的实例。 Sequential类将多个层串联在一起。 当给定输入数据时,Sequential实例将数据传入到第一层, 然后将第一层的输出作为第二层的输入,以此类推。 在下面的例子中,我们的模型只包含一个层,因此实际上不需要Sequential。 但是由于以后几乎所有的模型都是多层的,在这里使用Sequential会让你熟悉“标准的流水线”。
# 写法一(*)
net = nn.Sequential(
    nn.Linear(num_inputs, 1)
    # 此处还可以传入其他层
    )

# 写法二
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......

# 写法三
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
          ('linear', nn.Linear(num_inputs, 1))
          # ......
        ]))

print(net)
print(net[0])

输出:

Sequential(
  (linear): Linear(in_features=2, out_features=1, bias=True)
)
Linear(in_features=2, out_features=1, bias=True)

可以通过net.parameters()来查看模型所有的可学习参数,此函数将返回一个生成器。

for param in net.parameters():
    print(param)

输出:

Parameter containing:
tensor([[-0.0277,  0.2771]], requires_grad=True)
Parameter containing:
tensor([0.3395], requires_grad=True)

回顾线性回归在神经网络图中的表示。作为一个单层神经网络,线性回归输出层中的神经元和输入层中各个输入完全连接。因此,线性回归的输出层又叫全连接层。

注意:torch.nn仅支持输入一个batch的样本不支持单个样本输入,如果只有单个样本,可使用input.unsqueeze(0)来添加一维。

2.4 初始化模型参数

在使用net前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。PyTorch在init模块中提供了多种参数初始化方法。这里的initinitializer的缩写形式。我们通过init.normal_将权重参数每个元素初始化为随机采样于均值为0、标准差为0.01的正态分布。偏差会初始化为零。

from torch.nn import init

init.normal_(net[0].weight, mean=0, std=0.01)
init.constant_(net[0].bias, val=0)  # 也可以直接修改bias的data: net[0].bias.data.fill_(0)

注:如果这里的net是用一开始的代码自定义的,那么上面代码会报错,net[0].weight应改为net.linear.weightbias亦然。因为net[0]这样根据下标访问子模块的写法只有当net是个ModuleList或者Sequential实例时才可以,详见4.1节。

也可以如下初始化

net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

2.5 定义损失函数

PyTorch在nn模块中提供了各种损失函数,这些损失函数可看作是一种特殊的层,PyTorch也将这些损失函数实现为nn.Module的子类。我们现在使用它提供的均方误差损失作为模型的损失函数。

loss = nn.MSELoss()

2.6 定义优化算法

同样,我们也无须自己实现小批量随机梯度下降算法。torch.optim模块提供了很多常用的优化算法比如SGD、Adam和RMSProp等。下面我们创建一个用于优化net所有参数的优化器实例,并指定学习率为0.03的小批量随机梯度下降(SGD)为优化算法。

import torch.optim as optim

optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)

输出:

SGD (
Parameter Group 0
    dampening: 0
    lr: 0.03
    momentum: 0
    nesterov: False
    weight_decay: 0
)

我们还可以为不同子网络设置不同的学习率,这在finetune时经常用到。例:

optimizer =optim.SGD([
                # 如果对某个参数不指定学习率,就使用最外层的默认学习率
                {'params': net.subnet1.parameters()}, # lr=0.03
                {'params': net.subnet2.parameters(), 'lr': 0.01}
            ], lr=0.03)

有时候我们不想让学习率固定成一个常数,那如何调整学习率呢?主要有两种做法。一种是修改optimizer.param_groups中对应的学习率,另一种是更简单也是较为推荐的做法——新建优化器,由于optimizer十分轻量级,构建开销很小,故而可以构建新的optimizer。但是后者对于使用动量的优化器(如Adam),会丢失动量等状态信息,可能会造成损失函数的收敛出现震荡等情况。

# 调整学习率
for param_group in optimizer.param_groups:
    param_group['lr'] *= 0.1 # 学习率为之前的0.1倍

2.7 训练模型

回顾一下:在每个迭代周期里,我们将完整遍历一次数据集(train_data), 不停地从中获取一个小批量的输入和相应的标签。 对于每一个小批量,我们会进行以下步骤:

  • 通过调用net(X)生成预测并计算损失l(前向传播)。
  • 通过进行反向传播来计算梯度。
  • 通过调用优化器来更新模型参数。
在使用Gluon训练模型时,我们通过调用optim实例的step函数来迭代模型参数。按照小批量随机梯度下降的定义,我们在step函数中指明批量大小,从而对批量中样本梯度求平均。
num_epochs = 3
for epoch in range(1, num_epochs + 1):
    for X, y in data_iter:
        output = net(X)
        l = loss(output, y.view(-1, 1))
        optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
        l.backward()
        optimizer.step()
    print('epoch %d, loss: %f' % (epoch, l.item()))

输出:

epoch 1, loss: 0.000457
epoch 2, loss: 0.000081
epoch 3, loss: 0.000198

下面我们分别比较学到的模型参数和真实的模型参数。我们从net获得需要的层,并访问其权重(weight)和偏差(bias)。学到的参数和真实的参数很接近。

dense = net[0]
print(true_w, dense.weight)
print(true_b, dense.bias)

输出:

[2, -3.4] tensor([[ 1.9999, -3.4005]])
4.2 tensor([4.2011])

小结

  • 使用PyTorch可以更简洁地实现模型。
  • torch.utils.data模块提供了有关数据处理的工具,torch.nn模块定义了大量神经网络的层,torch.nn.init模块定义了各种初始化方法,torch.optim模块提供了很多常用的优化算法。

3 自己实现的从零实现

# 导包
import random
import torch
from d2l import torch as d2l
# 1 生成数据集

# 合成数据集
def synthetic_data(w, b, num_examples):
  # 生成输入、输出
  X = torch.normal(0, 1,(num_examples, len(w)))
  y = torch.matmul(X, w) + b
  # 加入噪声
  y = y + torch.normal(0, 0.01,(y.shape))
  # 注意:torch.normal(mean,std,shape),该函数必须给定shape,格式为(m×n)
  # 注意:y需要reshape一下使之形成列向量
  return X,y.reshape((-1,1))
  
  
# 设置w和b
num_examples = 1000
true_w = torch.tensor([2, -3.4])
true_b = 4.2
print(true_w)
print(true_b)
# 测试样例数据集
features, labels = synthetic_data(true_w, true_b, 1000)
print(features.shape)
print(labels.shape)
# 结果应该是
# y:100×1
# X:100×2
# w:2×1
# b:100×1
# 生成数据可视化(没啥实际意义)
d2l.set_figsize()
# 绘制散点图
# detach()从计算图中脱离出来,避免计算梯度
# 生成第一个特征和features的散点图,可以看出线性关系
d2l.plt.scatter(features[:,(0)].detach().numpy(),labels.detach().numpy(), 1)
# 生成第二个特征和features的散点图,可以看出线性关系
d2l.plt.scatter(features[:,(1)].detach().numpy(),labels.detach().numpy(), 1)

# 2 读取数据集

# 由于使用了小批量随机梯度下降,因此在读取数据时,需要输入一个batch_size批量大小
def data_iter(batch_size, features, labels):
  # num_examples = len(features) 可以用len()函数获得features的样本的个数

  # 下面实现随机读取batch_size个样本
  # 生成样例数个下标
  indices = list(range(num_examples))
  # 使用random库的shuffle函数打乱下标
  random.shuffle(indices)
  # 利用生成器返回对应的数据
  # 以batch_size=10,为例,i=0,20,30,...,990
  # 下述for循环可以参照下一段代码
  # 注意的是,当最后num_examples不能被batch_size整除时,会有小问题,但是我们在此忽略他
  for i in range(0, num_examples, batch_size):
    batch_indices = torch.tensor(indices[i:min(i+batch_size,num_examples)])
    yield features[batch_indices],labels[batch_indices]

# 参照下面例子看上述代码
a = list(range(53))
random.shuffle(a)
print(a)
for i in range(0, 53, 5):
  b = a[i:i+5]
  print(i,b)
  
# 测试读取数据集
batch_size = 10
for X,y in data_iter(batch_size, features, labels):
    print(X,'\n',y)
    break
# 3 初始化模型参数
# 因为需要计算梯度,因此requires_grad=True
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

# 4 定义模型
def my_linear_regression(X, w, b):
  # 线性回归模型
  return torch.matmul(X,w) + b
  # X:小批量样本 batch_size×2
  # w:权重 2×1
  # 注意:偏置b的定义为b = torch.zeros(1, requires_grad=True),在这里使用了广播机制

# 5 定义损失函数
# 这里使用平方损失函数,注意将真实值的形状转换为和预测值y_hat一样的形状
def squared_loss(y_hat, y):
  loss = (y_hat-y.reshape(y_hat.shape))**2/2
  return loss
# 6 定义优化算法

# 使用小批量随机梯度下降
def sgd(params, lr, batch_size):
  # with torch.no_grad():作用如下一段
  # with torch.no_grad():
  #   for param in params:
  #     param = param - lr * param.grad / batch_size
  #     param.grad.zero_()
      
      
  with torch.no_grad():
    for param in params:
      param -= lr * param.grad / batch_size
      param.grad.zero_()
      # 每一次迭代都需要清空梯度
      
      
'''
关于 torch.no_grad()
首先从requires_grad讲起:

requires_grad
在pytorch中,tensor有一个requires_grad参数,如果设置为True,则反向传播时,该tensor就会自动求导。
tensor的requires_grad的属性默认为False,若一个节点(叶子变量:自己创建的tensor)
requires_grad被设置为True,那么所有依赖它的节点requires_grad都为True(即使其他相依赖的tensor的requires_grad = False)

当requires_grad设置为False时,反向传播时就不会自动求导了,因此大大节约了显存或者说内存。

with torch.no_grad的作用
在该模块下,所有计算得出的tensor的requires_grad都自动设置为False。

即使一个tensor(命名为x)的requires_grad = True,在with torch.no_grad计算,由x得到的新tensor(命名为w-标量)
requires_grad也为False,且grad_fn也为None,即不会对w求导。

我们只是想看一下训练的效果,并不是想通过验证集来更新网络时,就可以使用with torch.no_grad()。
最终,torch.save就是保存的训练集的训练模型。
'''
# 7 训练
lr = 0.03
net = my_linear_regression
num_epochs = 5
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中所有元素加在一起并以此计算关于参数[w,b]的梯度
    l.sum().backward()
    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}')

4 自己的简洁实现

import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
# 合成数据集
def synthetic_data(w, b, num_examples):
  # 生成输入、输出
  X = torch.normal(0, 1,(num_examples, len(w)))
  y = torch.matmul(X, w) + b
  # 加入噪声
  y = y + torch.normal(0, 0.01,(y.shape))
  # 注意:torch.normal(mean,std,shape),该函数必须给定shape,格式为(m×n)
  # 注意:y需要reshape一下使之形成列向量
  return X,y.reshape((-1,1))
num_inputs = 2
num_examples = 1000
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)

# 1 读取数据

# 我们将features和labels作为API的参数传递,并通过数据迭代器指定batch_size。 
# 此外,布尔值is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据
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)
# data_iter是一个dataLoading,
# 这里我们使用iter构造Python迭代器,并使用next从迭代器中获取第一项。
# 通过iter(next(iter))返回一组数据
# 测试读取数据
print(next(iter(data_iter)))
# 或者读数据如下
for X, y in data_iter:
    print(X, y)
    break

# 2 定义模型
# PyTorch提供了大量预定义的层,这使我们只需关注使用哪些层来构造模型
# torch.nn模块定义了大量神经网络的层

from torch import nn

net = nn.Sequential(nn.Linear(2,1))


# 可以通过net.parameters()来查看模型所有的可学习参数,此函数将返回一个生成器。
for param in net.parameters():
    print(param)
# 3 初始化模型参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
# 4 定义损失函数
loss = nn.MSELoss()
# 5 定义优化算法
import torch.optim as optim

# 当我们实例化一个SGD实例时,我们要指定优化的参数 (可通过net.parameters()从我们的模型中获得)
# 以及优化算法所需的超参数字典。 小批量随机梯度下降只需要设置lr值,这里设置为0.03。
optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)
# 6 训练
num_epochs = 3
for epoch in range(num_epochs):
    for X, y in data_iter:
        l = loss(net(X) ,y)
        optimizer.zero_grad()
        l.backward()
        optimizer.step()
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l:f}')
w = net[0].weight.data
print(w)
b = net[0].bias.data
print(b)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值