從頭學pytorch(三) 線性迴歸

關於什麼是線性迴歸,很少作介紹了.能夠參考我之前的博客http://www.javashuo.com/article/p-towfilsl-ha.htmlhtml

實現線性迴歸

分爲如下幾個部分:python

  • 生成數據集
  • 讀取數據
  • 初始化模型參數
  • 定義模型
  • 定義損失函數
  • 定義優化算法
  • 訓練模型

生成數據集

咱們構造一個簡單的人工訓練數據集,它可使咱們可以直觀比較學到的參數和真實的模型參數的區別。設訓練數據集樣本數爲1000,輸入個數(特徵數)爲2。給定隨機生成的批量樣本特徵 \(\boldsymbol{X} \in \mathbb{R}^{1000 \times 2}\),咱們使用線性迴歸模型真實權重 \(\boldsymbol{w} = [2, -3.4]^\top\) 和誤差 \(b = 4.2\),以及一個隨機噪聲項 \(\epsilon\) 來生成標籤
\[ \boldsymbol{y} = \boldsymbol{X}\boldsymbol{w} + b + \epsilon \]算法

其中噪聲項 \(\epsilon\) 服從均值爲0、標準差爲0.01的正態分佈。噪聲表明了數據集中無心義的干擾。網絡

%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)))
print(type(features),features.shape)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
print(type(labels),labels.shape)
labels += torch.from_numpy(np.random.normal(0, 0.01, size=labels.size()))

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
    
set_figsize()
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);

讀取數據

每次讀取batch_size個樣本.注意亂序讀取.以使得每一個batch的樣本多樣性足夠豐富.數據結構

def data_iter(batch_size, features, labels):
    num_examples = len(features)
    #print(num_examples)
    indices = list(range(num_examples))
    random.shuffle(indices)  # 樣本的讀取順序是隨機的
    #print(indices)
    for i in range(0, num_examples, batch_size):
        j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) # 最後一次可能不足一個batch
        #print(j)
        yield  features.index_select(0, j), labels.index_select(0, j)
        
batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    #print(X, y)
    #break
    pass

關於yiled用法參考:http://www.javashuo.com/article/p-wykkoqyz-hb.html中yield部分.
關於torch的index_select用法參考:https://pytorch-cn.readthedocs.io/zh/latest/package_references/torch/#torchindex_select

features是[1000,2]的Tensor。因此features.index_select(0, j)即在第0維度上對索引爲j的輸入進行切片.也即選取第j(j爲一個長度爲batch_size的tensor)個樣本.閉包

初始化模型參數

權重值有2個.因此咱們初始化一個shape爲[2,1]的Tensor.咱們將其隨機初始化爲符合均值0,標準差0.01的正態分佈隨機數,bias初始化爲0.dom

w=torch.from_numpy(np.random.normal(0,0.01,(num_inputs,1)))
b = torch.zeros(1, dtype=torch.float64)
print(w.dtype,b.dtype)

ndarray的類型是float64,因此w的類型是float64,在生成b的時候咱們指定dtype=float64.svg

以後的模型訓練中,須要對這些參數求梯度來迭代參數的值,所以咱們要讓它們的requires_grad=True函數

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

定義模型

下面是線性迴歸的矢量計算表達式的實現。咱們使用mm函數作矩陣乘法。
在咱們的例子中,X是[1000,2]的矩陣,w是[2,1]的矩陣,相乘獲得[1000,1]的矩陣.工具

def linreg(X, w, b):  # 本函數已保存在d2lzh_pytorch包中方便之後使用
    return torch.mm(X, w) + b

定義損失函數

咱們使用平方損失來定義線性迴歸的損失函數。在實現中,咱們須要把真實值y變造成預測值y_hat的形狀。如下函數返回的結果也將和y_hat的形狀相同。

def squared_loss(y_hat, y):
    # 注意這裏返回的是向量, 另外, pytorch裏的MSELoss並無除以 2
    return (y_hat - y.view(y_hat.size())) ** 2 / 2

定義優化算法

如下的sgd函數實現了上一節中介紹的小批量隨機梯度降低算法。它經過不斷迭代模型參數來優化損失函數。這裏自動求梯度模塊計算得來的梯度是一個批量樣本的梯度和。咱們將它除以批量大小來獲得平均值。均值反映了平均而言,對單個樣本,朝着哪一個梯度方向去更新參數可使得loss最小

def sgd(params, lr, batch_size):
    for param in params:
        param.data -= lr * param.grad / batch_size # 注意這裏更改param時用的param.data

這裏的params傳入的即w,b

訓練模型

咱們建立一個循環,每次傳入batch_size個樣本,計算損失.反向傳播,計算w,b的梯度,而後更新w,b.循環往復.注意每次方向傳播後清空梯度. 以及l是一個向量. 調用.sum()將其轉換爲標量,再計算梯度.
一個epoch即全部樣本均計算一次損失.
代碼以下:

lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
batch_size=10

for epoch in range(num_epochs):
    for X,y in data_iter(batch_size,features,labels):
        l = loss(linreg(X,w,b),y).sum()
        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()))

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

輸出以下:

epoch 1, loss 0.051109
epoch 2, loss 0.000217
epoch 3, loss 0.000049
[2, -3.4] 
 tensor([[ 1.9996],
        [-3.3993]], dtype=torch.float64, requires_grad=True)
4.2 
 tensor([4.1995], dtype=torch.float64, requires_grad=True)

能夠看到獲得的w和b都已經很是接近true_w,true_b了.


以前咱們是手寫代碼構建模型,建立損失函數,定義隨機梯度降低等等.用pytorch裏提供的類和函數,能夠更方便地實現線性迴歸.

線性迴歸的簡潔實現

生成數據集

與前面沒有區別.

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()))

數據讀取

用torch.utils.data模塊,主要使用TensorDataset類和DataLoader類

import torch.utils.data as Data
batch_size=10
dataset = Data.TensorDataset(features,labels)
data_iter = Data.DataLoader(dataset,batch_size=batch_size,shuffle=True)
for X,y in data_iter:
    print(X,y)
    break

建立網絡結構

在上一節從零開始的實現中,咱們須要定義模型參數,並使用它們一步步描述模型是怎樣計算的。當模型結構變得更復雜時,這些步驟將變得更繁瑣。其實,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 = 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.2956, -0.2817]], requires_grad=True)
Parameter containing:
tensor([-0.1443], requires_grad=True)

做爲一個單層神經網絡,線性迴歸輸出層中的神經元和輸入層中各個輸入徹底鏈接。所以,線性迴歸的輸出層又叫全鏈接層。

注意:torch.nn僅支持輸入一個batch的樣本不支持單個樣本輸入,若是隻有單個樣本,可以使用input.unsqueeze(0)來添加一維。

初始化模型參數

在使用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)

定義優化算法

一樣,咱們也無須本身實現小批量隨機梯度降低算法。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中對應的學習率
# 調整學習率
for param_group in optimizer.param_groups:
    param_group['lr'] *= 0.1 # 學習率爲以前的0.1倍
  • 另外一種是更簡單也是較爲推薦的作法——新建優化器,因爲optimizer十分輕量級,構建開銷很小,故而能夠構建新的optimizer。可是後者對於使用動量的優化器(如Adam),會丟失動量等狀態信息,可能會形成損失函數的收斂出現震盪等狀況。

訓練

全部的optimizer都實現了step()方法,這個方法會更新全部的參數。它能按兩種方式來使用:

  • optimizer.step()
    這是大多數optimizer所支持的簡化版本。一旦梯度被如backward()之類的函數計算好後,咱們就能夠調用這個函數。
for input, target in dataset:
    optimizer.zero_grad()
    output = model(input)
    loss = loss_fn(output, target)
    loss.backward()
    optimizer.step()
  • optimizer.step(closure)
    一些優化算法例如Conjugate Gradient和LBFGS須要重複屢次計算函數,所以你須要傳入一個閉包去容許它們從新計算你的模型。這個閉包應當清空梯度, 計算損失,而後返回。
for input, target in dataset:
    def closure():
        optimizer.zero_grad()
        output = model(input)
        loss = loss_fn(output, target)
        loss.backward()
        return loss
    optimizer.step(closure)

具體參考https://pytorch-cn.readthedocs.io/zh/latest/package_references/torch-optim/

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()))

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

輸出:

epoch 1, loss: 0.000227
epoch 2, loss: 0.000160
epoch 3, loss: 0.000136
[2, -3.4] Parameter containing:
tensor([[ 2.0007, -3.4010]], requires_grad=True)
4.2 Parameter containing:
tensor([4.1998], requires_grad=True)

總結:

  • 使用PyTorch能夠更簡潔地實現模型。
  • torch.utils.data模塊提供了有關數據處理的工具,torch.nn模塊定義了大量神經網絡的層,torch.nn.init模塊定義了各類初始化方法,torch.optim模塊提供了模型參數優化的各類方法。
相關文章
相關標籤/搜索