08 線性回歸 + 基礎(chǔ)優(yōu)化算法【動手學(xué)深度學(xué)習(xí)v2】

線性回歸
房價預(yù)測

線性模型

- b:標(biāo)量偏差

- 單層神經(jīng)網(wǎng)絡(luò):帶權(quán)重的層只有一個(輸出層)
真實的神經(jīng)元

衡量預(yù)估質(zhì)量

- 平方損失:衡量真實值與預(yù)測值之間的誤差
- 1 / 2:主要是為了求導(dǎo)的時候方便抵消平方的導(dǎo)數(shù)所產(chǎn)生的系數(shù)2
訓(xùn)練數(shù)據(jù)

參數(shù)學(xué)習(xí)

- 1 / 2:來自損失函數(shù)
- 1 / n:求平均
- 最小損失函數(shù)來學(xué)習(xí)參數(shù)
顯示解

- 因為是線性模型,所以是有顯示解的,并且損失是一個凸函數(shù)
- 凸函數(shù)的最優(yōu)解滿足梯度等于0
總結(jié)
- 線性回歸是對n維輸入的加權(quán),外加偏差(對輸出值的預(yù)估)
- 使用平方損失來衡量預(yù)測值和真實值的差異
- 線性回歸有顯示解(一般來說,模型都沒有顯示解,有顯示解的模型過于簡單,復(fù)雜度有限,很難衡量復(fù)雜的數(shù)據(jù))
- 線性回歸可以看做是單層神經(jīng)網(wǎng)絡(luò)(最簡單的神經(jīng)網(wǎng)絡(luò))
基礎(chǔ)優(yōu)化算法
梯度下降

- η:標(biāo)量,表示學(xué)習(xí)率,代表沿著負(fù)梯度方向一次走多遠(yuǎn),即步長。他是一個超參數(shù)(需要人為的指定值)
- 學(xué)習(xí)率的選擇不能太小,也不能太大(太小會導(dǎo)致計算量大,求解時間長;太大的話或?qū)е潞瘮?shù)值振蕩,并沒有真正的下降)

- l:損失函數(shù)
- 圓圈代表函數(shù)值的等高線,每個圓圈上的點處的函數(shù)值相等
- 梯度:使得函數(shù)值增加最快的方向,負(fù)梯度就是使函數(shù)下降最快的方向,如圖中黃線所示
小批量隨機(jī)梯度下降

- 梯度下降時,每次計算梯度,要對整個損失函數(shù)求導(dǎo),損失函數(shù)是對所有樣本的平均損失,所以每求一次梯度,要對整個樣的本的損失函數(shù)進(jìn)行重新計算,計算量大且耗費(fèi)時間長,代價太大
- 用 b 個樣本的平均損失來近似所有樣本的平均損失,當(dāng) b 足夠大的時候,能夠保證一定的精確度
- 批量大小的選擇

總結(jié)
- 梯度下降通過不斷沿著負(fù)梯度方向更新參數(shù)求解(好處是不需要知道顯示解是什么,只需要不斷的求導(dǎo)就可以了)
- 小批量隨機(jī)梯度下降是深度學(xué)習(xí)默認(rèn)的求解算法(雖然還有更穩(wěn)定的,但是他是最穩(wěn)定、最簡單的)
- 兩個重要的超參數(shù)是批量大小和學(xué)習(xí)率
線性回歸的實現(xiàn)
1、導(dǎo)入所需要的包
%matplotlib inline
import random
import torch
from d2l import torch as d2l
- random:導(dǎo)入random包用于隨機(jī)初始化權(quán)重
- d2l:將用過的或者實現(xiàn)過的算法放在d2l的包里面
- matplotlib inline:在plot的時候默認(rèn)是嵌入到matplotlib中
- 報錯 No module named 'matplotlib' :在命令行中使用 pip install matplotlib 安裝 matplotlib 包即可
- 報錯 No module named 'd2l' :在命令行中使用 pip install d2l 安裝 d2l 包即可,安裝完成之后可能需要重新打開程序才能生效
2、根據(jù)帶有噪聲的線性模型構(gòu)造一個人造的數(shù)據(jù)集

- 構(gòu)造人造數(shù)據(jù)集的好處是知道真實的 w 和 b
- X = torch.normal(0,1,(num_examples,len(w))):X 是一個均值為 0 ,方差為 1 的隨機(jī)數(shù),他的行數(shù)等于樣本數(shù),列數(shù)等于 w 的長度
- y += torch.normal(0,0.01,y.shape):給 y 加上了一個均值為 0 ,方差為 0.01 形狀和 y 相同的噪聲
- return X,y.reshape((-1,1)):最后把 X 和 y 做成一個列向量返回
- true_w:真實的 w
- true_b:真實的 b
- features,labels = synthetic_data(true_w,true_b,1000),根據(jù)函數(shù)來生成特征和標(biāo)注
def synthetic_data(w,b,num_examples):
? ? """生成 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)
3、feature中每一行都包含一個二維數(shù)據(jù)樣本,labels中的每一行都包含一維標(biāo)簽值
print('features:',features[0],'\nlabels',labels[0])
輸出:
features: tensor([0.1605, 0.2305])
labels tensor([3.7450])
d2l.set_figsize()
d2l.plt.scatter(features[:1].detach().numpy(),labels.detach().numpy(),1);
輸出:

- detach():在pytorch的一些版本中,需要從計算圖中detach出來才能轉(zhuǎn)到numpy中去
4、實現(xiàn)一個函數(shù)讀取小批量
定義一個data_iter函數(shù),該函數(shù)接收批量大小、特征矩陣和標(biāo)簽向量作為輸入,生成大小為batch_size的小批量
def data_iter(batch_size,features,labels):
? ? num_examples = len(features)
? ? indices = list(range(num_examples))
? ? # 這些樣本是隨機(jī)讀取的,沒有特定順序
? ? 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]
batch_size = 10
for X,y in data_iter(batch_size,features,labels):
? ? print(X,'\n',y)
? ? break
- batch_size:批量大小
- num_examples:樣本數(shù)
- random.shuffle(indices):將下標(biāo)打亂,實現(xiàn)對樣本的隨機(jī)訪問
- for i in range(0,num_examples,batch_size):從 0 開始到樣本數(shù)結(jié)束,每次跳批量大小
- yield features[batch_indices],labels[batch_indices]:通過indices,每次產(chǎn)生隨機(jī)順序的特征和其對應(yīng)的隨即順序標(biāo)號
- yield是python中的一個迭代器
輸出:
tensor([[-0.3785, -0.5235],
[ 0.0674, 0.6504],
[-0.9654, 1.3349],
[ 0.6241, -0.1228],
[-1.4924, -1.4087],
[ 0.7791, -0.5478],
[-0.6711, -0.5625],
[-0.0553, -0.1775],
[-0.7085, 0.6464],
[ 2.4625, -1.1113]])
tensor([[ 5.2227],
[ 2.1381],
[-2.2681],
[ 5.8792],
[ 6.0258],
[ 7.6395],
[ 4.7813],
[ 4.6846],
[ 0.5918],
[12.8976]])
5、定義初始化模型參數(shù)
w = torch.normal(0,0.01,size=(2,1),requires_grad=True)
b = torch.zeros(1,requires_grad=True)
6、定義模型
def linreg(X,w,b):
? ? """線性回歸模型"""
? ? return torch.matmul(X,w) + b
7、定義損失函數(shù)
def squared_loss(y_hat,y):
? ? """均方損失"""
? ? return (y_hat - y.reshape(y_hat.shape))**2 / 2
- y_hat:預(yù)測值
- y:真實值
- 雖然 y_hat 和 y 元素個數(shù)是一樣的,但是可能他們一個是行向量一個是列向量,因此需要使用reshape進(jìn)行統(tǒng)一
8、定義優(yōu)化算法
def sgd(params,lr,batch_size):
? ? """小批量梯度下降"""
? ? with torch.no_grad():
? ? ? ? for param in params:
? ? ? ? ? ? param -= lr * param.grad / batch_size
? ? ? ? ? ? param.grad.zero_()
- params:給定的所有參數(shù),包含 w 和 b ,他是一個list
- lr:學(xué)習(xí)率
- param.grad.zero_():手動將梯度設(shè)置成 0 ,在下一次計算梯度的時候就不會和上一次相關(guān)了
9、訓(xùn)練過程
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epoch):
? ? for X,y in data_iter(batch_size,features,labels):
? ? ? ? l = loss(net(X,w,b),y) # X 和 y 的小批量損失
? ? ? ? # 因為 l 的形狀是(batch_size,1),而不是一個標(biāo)量。l 中的所有元素被加到一起求梯度
? ? ? ? # 并以此計算關(guān)于[w,b]的梯度
? ? ? ? l.sum().backward()
? ? ? ? sgd([w,b],lr,batch_size) # 使用參數(shù)的梯度更新
? ? with torch.no_grad():
? ? ? ? train_l = loss(net(features,w,b),labels)
? ? ? ? print(f'epoch{epoch + 1},loss{float(train_l.):f}')
- num_epoch=3:將整個數(shù)據(jù)掃描三遍
- net:之前定義的模型
- loss:均方損失
- 每一次對數(shù)據(jù)掃描一遍,掃描的時候拿出一定批量的X和y?
輸出:
epoch1,loss0.034571
epoch2,loss0.000130
epoch3,loss0.000049
10、比較真實參數(shù)和通過訓(xùn)練學(xué)到的參數(shù)來評估訓(xùn)練的成功程度
print(f'w的估計誤差:{true_w - w.reshape(true_w.shape)}')
print(f'b的估計誤差:{true_b - b}')
輸出:
w的估計誤差:tensor([ 0.0003, -0.0004], grad_fn=<SubBackward0>)
b的估計誤差:tensor([-7.7724e-05], grad_fn=<RsubBackward1>)
線性回歸的實現(xiàn)(簡潔版)
使用深度學(xué)習(xí)框架來簡潔的實現(xiàn)線性回歸模型生成數(shù)據(jù)集,使用pytorch的nn的moudule提供的一些數(shù)據(jù)預(yù)處理的模塊來使實現(xiàn)更加簡單
1、導(dǎo)包
import numpy as np
import torch
import torch.utils.data as 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)
- 這里沐神導(dǎo)包的時候?qū)懙氖?import torch.utils as data ,我在運(yùn)行的時候會報錯 AttributeError: module 'torch.utils' has no attribute 'TensorDataset' ,當(dāng)我改成 import torch.utils.data as data 這種方式來進(jìn)行導(dǎo)包之后就能夠繼續(xù)運(yùn)行了
2、調(diào)用框架中現(xiàn)有的API來讀取數(shù)據(jù)
def load_array(data_arrays,batch_size,is_train=True):
? ? """構(gòu)造一個PyTorch的數(shù)據(jù)迭代器"""
? ? 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))
輸出:
[tensor([[-0.8249, 1.9358],
[-0.9235, -0.2573],
[-1.6639, -1.0040],
[ 0.5258, 1.1980],
[-0.4612, -0.8816],
[ 2.1845, -1.4419],
[-0.0317, 1.7666],
[-0.0510, -0.9682],
[-1.8175, 0.0168],
[ 0.5344, 0.5456]]),
tensor([[-4.0278],
[ 3.2316],
[ 4.2774],
[ 1.1869],
[ 6.2739],
[13.4859],
[-1.8532],
[ 7.3907],
[ 0.5091],
[ 3.4219]])]
3、使用框架的預(yù)定義好的層
from torch import nn
net = nn.Sequential(nn.Linear(2,1))
- nn 是神經(jīng)網(wǎng)絡(luò)的縮寫
- nn.Linear(2,1):輸入的維度是2,輸出的維度是1
- sequential:可以理解成是層的排列,將一層一層的神經(jīng)網(wǎng)絡(luò)進(jìn)行排列在一起
4、初始化模型參數(shù)
net[0].weight.data.normal_(0,0.01)
net[0].bias.data.fill_(0)
- normal_:使用正態(tài)分布來替換掉data的值(均值為0,方差為0.01)
- fill_:將偏差直接設(shè)置為0
輸出:
tensor([0.])
5、計算均方誤差使用的是MESLoss類,也稱為L2范數(shù)
loss = nn.MSELoss()
6、實例化SGD實例
trainer = torch.optim.SGD(net.parameters(),lr=0.03)
7、訓(xùn)練過程
和之前的訓(xùn)練過程是一樣的
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}')
- trainer.step():調(diào)用step函數(shù)來對模型進(jìn)行更新
輸出:
epoch 1, loss 0.000304
epoch 2, loss 0.000100
epoch 3, loss 0.000100
Q&A
1、我想請問可以在Google colab上安裝d2l庫進(jìn)行環(huán)境配置嗎?或者老師推薦一些對學(xué)生黨比較便宜的平臺
2、為啥使用平方損失而不是絕對插值呢?

3、損失為什么要求平均?
4、線性回歸損失函數(shù)是不是通常都是mse?
5、關(guān)于神經(jīng)網(wǎng)絡(luò),我們是通過誤差反饋修改參數(shù),但是神經(jīng)元沒有反饋誤差這一說,請問這一點是人為推導(dǎo)的嗎?
6、物理實驗中經(jīng)常使用 n-1 代替 n 求誤差,請問這里求誤差也能用 n-1 代替n嗎?
7、老師,不管是gd還是sgd怎么找到合適的學(xué)習(xí)率?有什么好的方法嗎?
8、batchsize是否會最終影響模型結(jié)果?batchsize過小是否可能導(dǎo)致最終累積的梯度計算不準(zhǔn)確?
9、在訓(xùn)練過程中,過擬合和欠擬合情況下,學(xué)習(xí)率和批次該如何進(jìn)行調(diào)整呢?有什么常見的策略嗎?
10、針對batchsize大小的數(shù)據(jù)集進(jìn)行網(wǎng)絡(luò)訓(xùn)練的時候,網(wǎng)絡(luò)中每個參數(shù)更新時的減去的梯度是batchsize中每個樣本對應(yīng)參數(shù)梯度求和后取得的平均值嗎?
11、隨機(jī)梯度下降中的“隨機(jī)”是值得批量大小是隨機(jī)的嗎?
12、在深度學(xué)習(xí)上,設(shè)置損失函數(shù)的時候,需要考慮正則嗎?
13、為什么機(jī)器學(xué)習(xí)優(yōu)化算法都采取梯度下降(一階導(dǎo)算法),而不是采用牛頓法(二階導(dǎo)算法),收斂速度更快,一般都能算出一階導(dǎo),二階導(dǎo)也應(yīng)該能算?
14、學(xué)習(xí)率怎么除N,設(shè)置學(xué)習(xí)率的時候嗎?
15、detach()是什么作用?
16、detach和pytorch中的用法一樣嗎?梯度為什么要去掉,這里的梯度是怎么產(chǎn)生的?
17、這樣的data-iter寫法,每次都把所有輸入load進(jìn)去,如果數(shù)據(jù)較多的話,最后內(nèi)存會爆炸吧?有什么好的辦法嗎?
18、這里的indices為什么要轉(zhuǎn)化成tensor,直接用列表不行嗎?
19、每次都是隨機(jī)取出一部分,怎么保證最后所有數(shù)據(jù)都被拿過了?
20、這里使用生成器生成數(shù)據(jù)有什么優(yōu)勢呢,相比return?
21、如果樣本大小不是批量數(shù)的整數(shù)倍,那么需要隨機(jī)剔除多余的樣本嗎?
22、優(yōu)化算法里+batchsize但是最后一個batch里的樣本個數(shù)沒有這么多呀?
?
23、這里學(xué)習(xí)率不做衰減嗎?有什么好的學(xué)習(xí)率衰減方法嗎?
24、老師請問這里面是沒有進(jìn)行收斂判斷嗎?直接人為設(shè)置epoch的大小嗎?
25、本質(zhì)上我們?yōu)槭裁匆肧GD,是因為大部分的實際loss太復(fù)雜,推到不出導(dǎo)數(shù)為0的解嗎?只能逐個batch去逼近?
26、請問 w 為什么要隨機(jī)初始化,不能用相同的值呢?
27、實際中有時候網(wǎng)絡(luò)會輸出nan,nan到底是怎么出現(xiàn)的?為什么不是inf?數(shù)值穩(wěn)定性不是會導(dǎo)致inf嗎?
28、定義網(wǎng)絡(luò)層后一定要手動設(shè)置參數(shù)初始值嗎?
29、l.baxkward()這里是調(diào)用pytorch自定義的back propogation嗎?
30、外層for循環(huán)中最后一行l(wèi)=loss(net(),labels)就是為了print嗎?這里梯度要不要清零呢?會不會有什么影響?
31、每個batch計算的時候,為什么要把梯度先清零?
32、學(xué)習(xí)率設(shè)置過大會不會導(dǎo)致梯度爆炸(數(shù)值為NAN),但從數(shù)學(xué)理解上,如若上一次根據(jù)偏導(dǎo)數(shù)乘以學(xué)習(xí)率調(diào)整參數(shù),就算向相反的方向在調(diào),下一次不是就可以糾正過來嗎?為什么還會爆炸的呢?
----end----