# 3.2 線性回歸的從零開始實現
在了解了線性回歸的背景知識之后,現在我們可以動手實現它了。盡管強大的深度學習框架可以減少大量重復性工作,但若過于依賴它提供的便利,會導致我們很難深入理解深度學習是如何工作的。因此,本節將介紹如何只利用`Tensor`和`autograd`來實現一個線性回歸的訓練。
首先,導入本節中實驗所需的包或模塊,其中的matplotlib包可用于作圖,且設置成嵌入顯示。
``` python
%matplotlib inline
import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random
```
## 3.2.1 生成數據集
我們構造一個簡單的人工訓練數據集,它可以使我們能夠直觀比較學到的參數和真實的模型參數的區別。設訓練數據集樣本數為1000,輸入個數(特征數)為2。給定隨機生成的批量樣本特征 `$ \boldsymbol{X} \in \mathbb{R}^{1000 \times 2} $`,我們使用線性回歸模型真實權重 `$ \boldsymbol{w} = [2, -3.4]^\top $` 和偏差 `$ b = 4.2 $`,以及一個隨機噪聲項 `$ \epsilon $` 來生成標簽
```[tex]
\boldsymbol{y} = \boldsymbol{X}\boldsymbol{w} + b + \epsilon
```
其中噪聲項 $\epsilon$ 服從均值為0、標準差為0.01的正態分布。噪聲代表了數據集中無意義的干擾。下面,讓我們生成數據集。
``` python
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()))
```
注意,`features`的每一行是一個長度為2的向量,而`labels`的每一行是一個長度為1的向量(標量)。
``` python
print(features[0], labels[0])
```
輸出:
```
tensor([0.8557, 0.4793]) tensor(4.2887)
```
通過生成第二個特征`features[:, 1]`和標簽 `labels` 的散點圖,可以更直觀地觀察兩者間的線性關系。
``` python
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`。由于`plt`在`d2lzh_pytorch`包中是一個全局變量,我們在作圖前只需要調用`d2lzh_pytorch.set_figsize()`即可打印矢量圖并設置圖的尺寸。
> 原書中提到的`d2lzh`里面使用了mxnet,改成pytorch實現后本項目統一將原書的`d2lzh`改為`d2lzh_pytorch`。
## 3.2.2 讀取數據
在訓練模型的時候,我們需要遍歷數據集并不斷讀取小批量數據樣本。這里我們定義一個函數:它每次返回`batch_size`(批量大小)個隨機樣本的特征和標簽。
``` python
# 本函數已保存在d2lzh包中方便以后使用
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):
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),分別對應批量大小和輸入個數;標簽形狀為批量大小。
``` python
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])
```
## 3.2.3 初始化模型參數
我們將權重初始化成均值為0、標準差為0.01的正態隨機數,偏差則初始化成0。
``` python
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)
```
之后的模型訓練中,需要對這些參數求梯度來迭代參數的值,因此我們要讓它們的`requires_grad=True`。
``` python
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
```
## 3.2.4 定義模型
下面是線性回歸的矢量計算表達式的實現。我們使用`mm`函數做矩陣乘法。
``` python
def linreg(X, w, b): # 本函數已保存在d2lzh_pytorch包中方便以后使用
return torch.mm(X, w) + b
```
## 3.2.5 定義損失函數
我們使用上一節描述的平方損失來定義線性回歸的損失函數。在實現中,我們需要把真實值`y`變形成預測值`y_hat`的形狀。以下函數返回的結果也將和`y_hat`的形狀相同。
``` python
def squared_loss(y_hat, y): # 本函數已保存在d2lzh_pytorch包中方便以后使用
# 注意這里返回的是向量, 另外, pytorch里的MSELoss并沒有除以 2
return (y_hat - y.view(y_hat.size())) ** 2 / 2
```
## 3.2.6 定義優化算法
以下的`sgd`函數實現了上一節中介紹的小批量隨機梯度下降算法。它通過不斷迭代模型參數來優化損失函數。這里自動求梯度模塊計算得來的梯度是一個批量樣本的梯度和。我們將它除以批量大小來得到平均值。
``` python
def sgd(params, lr, batch_size): # 本函數已保存在d2lzh_pytorch包中方便以后使用
for param in params:
param.data -= lr * param.grad / batch_size # 注意這里更改param時用的param.data
```
## 3.2.7 訓練模型
在訓練中,我們將多次迭代模型參數。在每次迭代中,我們根據當前讀取的小批量數據樣本(特征`X`和標簽`y`),通過調用反向函數`backward`計算小批量隨機梯度,并調用優化算法`sgd`迭代模型參數。由于我們之前設批量大小`batch_size`為10,每個小批量的損失`l`的形狀為(10, 1)。回憶一下自動求梯度一節。由于變量`l`并不是一個標量,所以我們可以調用`.sum()`將其求和得到一個標量,再運行`l.backward()`得到該變量有關模型參數的梯度。注意在每次更新完參數后不要忘了將參數的梯度清零。
在一個迭代周期(epoch)中,我們將完整遍歷一遍`data_iter`函數,并對訓練數據集中所有樣本都使用一次(假設樣本數能夠被批量大小整除)。這里的迭代周期個數`num_epochs`和學習率`lr`都是超參數,分別設3和0.03。在實踐中,大多超參數都需要通過反復試錯來不斷調節。雖然迭代周期數設得越大模型可能越有效,但是訓練時間可能過長。而有關學習率對模型的影響,我們會在后面“優化算法”一章中詳細介紹。
``` python
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
```
訓練完成后,我們可以比較學到的參數和用來生成訓練集的真實參數。它們應該很接近。
``` python
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`模塊就可以很容易地實現一個模型。接下來,本書會在此基礎上描述更多深度學習模型,并介紹怎樣使用更簡潔的代碼(見下一節)來實現它們。
-----------
> 注:本節除了代碼之外與原書基本相同,[原書傳送門](https://zh.d2l.ai/chapter_deep-learning-basics/linear-regression-scratch.html)
- Home
- Introduce
- 1.深度學習簡介
- 深度學習簡介
- 2.預備知識
- 2.1環境配置
- 2.2數據操作
- 2.3自動求梯度
- 3.深度學習基礎
- 3.1 線性回歸
- 3.2 線性回歸的從零開始實現
- 3.3 線性回歸的簡潔實現
- 3.4 softmax回歸
- 3.5 圖像分類數據集(Fashion-MINST)
- 3.6 softmax回歸的從零開始實現
- 3.7 softmax回歸的簡潔實現
- 3.8 多層感知機
- 3.9 多層感知機的從零開始實現
- 3.10 多層感知機的簡潔實現
- 3.11 模型選擇、反向傳播和計算圖
- 3.12 權重衰減
- 3.13 丟棄法
- 3.14 正向傳播、反向傳播和計算圖
- 3.15 數值穩定性和模型初始化
- 3.16 實戰kaggle比賽:房價預測
- 4 深度學習計算
- 4.1 模型構造
- 4.2 模型參數的訪問、初始化和共享
- 4.3 模型參數的延后初始化
- 4.4 自定義層
- 4.5 讀取和存儲
- 4.6 GPU計算
- 5 卷積神經網絡
- 5.1 二維卷積層
- 5.2 填充和步幅
- 5.3 多輸入通道和多輸出通道
- 5.4 池化層
- 5.5 卷積神經網絡(LeNet)
- 5.6 深度卷積神經網絡(AlexNet)
- 5.7 使用重復元素的網絡(VGG)
- 5.8 網絡中的網絡(NiN)
- 5.9 含并行連結的網絡(GoogLeNet)
- 5.10 批量歸一化
- 5.11 殘差網絡(ResNet)
- 5.12 稠密連接網絡(DenseNet)
- 6 循環神經網絡
- 6.1 語言模型
- 6.2 循環神經網絡
- 6.3 語言模型數據集(周杰倫專輯歌詞)
- 6.4 循環神經網絡的從零開始實現
- 6.5 循環神經網絡的簡單實現
- 6.6 通過時間反向傳播
- 6.7 門控循環單元(GRU)
- 6.8 長短期記憶(LSTM)
- 6.9 深度循環神經網絡
- 6.10 雙向循環神經網絡
- 7 優化算法
- 7.1 優化與深度學習
- 7.2 梯度下降和隨機梯度下降
- 7.3 小批量隨機梯度下降
- 7.4 動量法
- 7.5 AdaGrad算法
- 7.6 RMSProp算法
- 7.7 AdaDelta
- 7.8 Adam算法
- 8 計算性能
- 8.1 命令式和符號式混合編程
- 8.2 異步計算
- 8.3 自動并行計算
- 8.4 多GPU計算
- 9 計算機視覺
- 9.1 圖像增廣
- 9.2 微調
- 9.3 目標檢測和邊界框
- 9.4 錨框
- 10 自然語言處理
- 10.1 詞嵌入(word2vec)
- 10.2 近似訓練
- 10.3 word2vec實現
- 10.4 子詞嵌入(fastText)
- 10.5 全局向量的詞嵌入(Glove)
- 10.6 求近義詞和類比詞
- 10.7 文本情感分類:使用循環神經網絡
- 10.8 文本情感分類:使用卷積網絡
- 10.9 編碼器--解碼器(seq2seq)
- 10.10 束搜索
- 10.11 注意力機制
- 10.12 機器翻譯