日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達者為師

網(wǎng)站首頁 編程語言 正文

線性回歸的從零開始實現(xiàn)(線性神經(jīng)網(wǎng)絡(luò))

作者:Gaolw1102 更新時間: 2022-09-25 編程語言

文章目錄

    • 線性回歸的從零開始實現(xiàn)
      • 生成數(shù)據(jù)集
      • 讀取數(shù)據(jù)集
      • 初始化模型參數(shù)
      • 定義模型
      • 定義損失函數(shù)
      • 定義優(yōu)化算法
      • 訓(xùn)練
      • 小結(jié)

線性回歸的從零開始實現(xiàn)

在這一小節(jié)中,我們將從零開始實現(xiàn)整個線性回歸網(wǎng)絡(luò)模型, 包括數(shù)據(jù)流水線、模型、損失函數(shù)和小批量隨機梯度下降優(yōu)化器。 雖然現(xiàn)代的深度學(xué)習(xí)框架幾乎可以自動化地進行所有這些工作,但從零開始實現(xiàn)可以確保你真正知道自己在做什么。 同時,了解更細致的工作原理將方便我們自定義模型、自定義層或自定義損失函數(shù)。 在這一節(jié)中,我們將只使用張量和自動求導(dǎo)。 在之后的章節(jié)中,我們會充分利用深度學(xué)習(xí)框架的優(yōu)勢,介紹更簡潔的實現(xiàn)方式。

import torch                                                         #引入torch庫
import random                                                        #引入random隨機庫

生成數(shù)據(jù)集

為了簡單起見,我們將根據(jù)帶有噪聲的線性模型構(gòu)造一個人造數(shù)據(jù)集。 我們的任務(wù)是使用這個有限樣本的數(shù)據(jù)集來恢復(fù)這個模型的參數(shù)。 我們將使用低維數(shù)據(jù),這樣可以很容易地將其可視化。 在下面的代碼中,我們生成一個包含1000個樣本的數(shù)據(jù)集, 每個樣本包含從標(biāo)準(zhǔn)正態(tài)分布中采樣的2個特征。 我們的合成數(shù)據(jù)集是一個矩陣 X ∈ R 1000 × 2 X \in R^{1000\times2} XR1000×2

我們使用線性模型參數(shù) w = [ 2 , ? 3 , 4 ] T , b = 4.2 w = [2, -3, 4]^{T}, b = 4.2 w=[2,?3,4]T,b=4.2 和噪聲項 ? \epsilon ? 生成數(shù)據(jù)集及其標(biāo)簽

y = X w + b + ? y = Xw + b + \epsilon y=Xw+b+?

你可以將 ? \epsilon ? 視為模型預(yù)測和標(biāo)簽時的潛在觀測誤差。在這里我們認為標(biāo)準(zhǔn)假設(shè)成立,即 ? \epsilon ? 服從均值為0的正態(tài)分布。 為了簡化問題,我們將標(biāo)準(zhǔn)差設(shè)為0.01。 下面的代碼生成合成數(shù)據(jù)集。

#生成符合正態(tài)分布的數(shù)據(jù)集
def synthetic_data(w, b, num_examples):
    #生成 y = Xw + b + 噪聲
    X = torch.normal(0, 1, (num_examples, len(w)))         #生成均值為0, 標(biāo)準(zhǔn)差為1的正態(tài)分布,即為標(biāo)準(zhǔn)正態(tài)分布
    y = torch.matmul(X, w) + b                             #matmul即矩陣乘法,即X與偏置w進行矩陣乘積后,再加上偏置量b
    y += torch.normal(0, 0.01, y.shape)                    #y的值再加上一個均值為0,標(biāo)準(zhǔn)差為0.01的正態(tài)分布
    
    return X, y.reshape(-1, 1)                            #返回數(shù)據(jù)集,其中X的形狀為(num_examples,len(w)), y的形狀為(num_examples)
    
    
true_w = torch.tensor([2, -3.4])                           #定義權(quán)重,也即向量w為一維數(shù)據(jù)
true_b = 4.2                                               #定義偏置量b = 4.2

features, labels = synthetic_data(true_w, true_b, 1000)    #生成數(shù)據(jù)集,其中w向量為權(quán)重,b為偏置量
features.shape, labels.shape                               #輸出樣本和標(biāo)簽的形狀
(torch.Size([1000, 2]), torch.Size([1000, 1]))

注意,features中的每一行都包含一個二維數(shù)據(jù)樣本, labels中的每一行都包含一維標(biāo)簽值(一個標(biāo)量)。

print('features:', features[0], '\nlabels:', labels[0])
features: tensor([1.2393, 1.5488]) 
labels: tensor([1.4208])

通過生成第二個特征features[:, 1]和labels的散點圖, 可以直觀觀察到兩者之間的線性關(guān)系。

import matplotlib.pyplot as plt

##生成第二個特征features[:, 1]和labels的散點圖
plt.scatter(features[:,(1)].detach().numpy(), labels.detach().numpy(), 1)
<matplotlib.collections.PathCollection at 0x23366079eb8>

在這里插入圖片描述

讀取數(shù)據(jù)集

回想一下,訓(xùn)練模型時要對數(shù)據(jù)集進行遍歷,每次抽取一小批量樣本,并使用它們來更新我們的模型。 由于這個過程是訓(xùn)練機器學(xué)習(xí)算法的基礎(chǔ),所以有必要定義一個函數(shù), 該函數(shù)能打亂數(shù)據(jù)集中的樣本并以小批量方式獲取數(shù)據(jù)

在下面的代碼中,我們定義一個data_iter函數(shù), 該函數(shù)接收批量大小、特征矩陣和標(biāo)簽向量作為輸入,生成大小為batch_size的小批量。 每個小批量包含一組特征和標(biāo)簽。

import random

def data_iter(batch_size, features, labels):
    num_examples = len(features)                                        #獲取數(shù)據(jù)集的大小
    indices = list(range(num_examples))                                 #獲取數(shù)據(jù)集索引的列表
    random.shuffle(indices)                                             #隨機打亂數(shù)據(jù)集索引的順尋
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(                                   #每次獲取batch_size個索引
            indices[i: min(i+batch_size, num_examples)]
        )
        yield features[batch_indices], labels[batch_indices]            #產(chǎn)生batch_size個數(shù)據(jù)集與對應(yīng)的標(biāo)簽
    

通常,我們利用GPU并行運算的優(yōu)勢,處理合理大小的“小批量”。 每個樣本都可以并行地進行模型計算,且每個樣本損失函數(shù)的梯度也可以被并行計算。 GPU可以在處理幾百個樣本時,所花費的時間不比處理一個樣本時多太多。

我們直觀感受一下小批量運算:讀取第一個小批量數(shù)據(jù)樣本并打印。 每個批量的特征維度顯示批量大小和輸入特征數(shù)。 同樣的,批量的標(biāo)簽形狀與batch_size相等。

batch_size = 10

#迭代器,每次返回10個數(shù)據(jù)集及10個對應(yīng)的標(biāo)簽
for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)                                                   #輸出一個小批量的數(shù)據(jù)集,此數(shù)據(jù)集的大小為 10
    break             
tensor([[-0.5891,  1.6614],
        [-0.3371, -1.5203],
        [ 0.5990, -0.0308],
        [-0.0863,  1.3714],
        [-1.6459,  1.2546],
        [-0.5164, -1.0317],
        [ 0.4163,  0.3304],
        [ 1.3406,  1.4694],
        [ 0.2425,  0.5404],
        [-1.1285,  0.0089]]) 
 tensor([[-2.6142],
        [ 8.6999],
        [ 5.5168],
        [-0.6438],
        [-3.3551],
        [ 6.6700],
        [ 3.9009],
        [ 1.8827],
        [ 2.8339],
        [ 1.9212]])

當(dāng)我們運行迭代時,我們會連續(xù)地獲得不同的小批量,直至遍歷完整個數(shù)據(jù)集。上面實現(xiàn)的迭代對于教學(xué)來說很好,但它的執(zhí)行效率很低,可能會在實際問題上陷入麻煩。例如,它要求我們將所有數(shù)據(jù)加載到內(nèi)存中,并執(zhí)行大量的隨機內(nèi)存訪問。

在深度學(xué)習(xí)框架中實現(xiàn)的內(nèi)置迭代器效率要高得多, 它可以處理存儲在文件中的數(shù)據(jù)和數(shù)據(jù)流提供的數(shù)據(jù)。

初始化模型參數(shù)

在我們開始用小批量隨機梯度下降優(yōu)化我們的模型參數(shù)之前, 我們需要先有一些參數(shù)。 在下面的代碼中,我們通過從均值為0、標(biāo)準(zhǔn)差為0.01的正態(tài)分布中采樣隨機數(shù)來初始化權(quán)重, 并將偏置初始化為0。

w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)            #定義模型參數(shù) W, 共2個分量,符合正態(tài)分布,可求梯度
b = torch.zeros(1, requires_grad=True)                                #定義模型參數(shù) b,初始化為0,可求梯度

在初始化參數(shù)之后,我們的任務(wù)是更新這些參數(shù),直到這些參數(shù)足夠擬合我們的數(shù)據(jù)。 每次更新都需要計算損失函數(shù)關(guān)于模型參數(shù)的梯度。 有了這個梯度,我們就可以向減小損失的方向更新每個參數(shù)。 因為手動計算梯度很枯燥而且容易出錯,所以沒有人會手動計算梯度。使用自動微分來計算梯度。

定義模型

接下來,我們必須定義模型,將模型的輸入和參數(shù)同模型的輸出關(guān)聯(lián)起來。

回想一下,要計算線性模型的輸出, 我們只需計算輸入特征 X X X 和模型權(quán)重 w w w 的矩陣-向量乘法后加上偏置。 注意,上面 X w Xw Xw 的是一個向量,而 b b b 是一個標(biāo)量。 回想一下之前描述的廣播機制: 當(dāng)我們用一個向量加一個標(biāo)量時,標(biāo)量會被加到向量的每個分量上。

def lineRegression(X, w, b):
    
    #計算預(yù)測的值 y
    return torch.matmul(X, w) + b                                   #注意矩陣X與向量w的結(jié)果為一個向量,b為一個標(biāo)量

定義損失函數(shù)

因為需要計算損失函數(shù)的梯度,所以我們應(yīng)該先定義損失函數(shù)。

這里我們使用 平方損失函數(shù) 。 在實現(xiàn)中,我們需要將真實值y的形狀轉(zhuǎn)換為和預(yù)測值y_hat的形狀相同。

#定義平方損失函數(shù)
def squared_loss(y_hat, y):
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

定義優(yōu)化算法

正如我們之前討論的,線性回歸有解析解。 盡管線性回歸有解析解,但本書中的其他模型卻沒有。 這里我們介紹小批量隨機梯度下降

在每一步中,使用從數(shù)據(jù)集中隨機抽取的一個小批量,然后根據(jù)參數(shù)計算損失的梯度。接下來,朝著減少損失的方向更新我們的參數(shù)。 下面的函數(shù)實現(xiàn)小批量隨機梯度下降更新。 該函數(shù)接受模型參數(shù)集合、學(xué)習(xí)速率和批量大小作為輸入。每一步更新的大小由學(xué)習(xí)速率lr決定。 因為我們計算的損失是一個批量樣本的總和,所以我們用批量大小(batch_size) 來規(guī)范化步長,這樣步長大小就不會取決于我們對批量大小的選擇。

#定義模型優(yōu)化算法
#其中params為待更新的參數(shù),lr為學(xué)習(xí)率,batch_size為小批量樣本大小
def sgd(params, lr, batch_size):
    
    #torch.no_grad() 是一個上下文管理器,被該語句 wrap 起來的部分將不會track 梯度
    with torch.no_grad():
        #使用優(yōu)化方法梯度下降算法分別更新w和b參數(shù)
        for param in params:
            
            param -= lr*(param.grad/batch_size)                           #使參數(shù)朝向梯度的反方向移動,試圖最小化損失函數(shù)
            param.grad.zero_()                                            #清空梯度,以免梯度進行累加

訓(xùn)練

現(xiàn)在我們已經(jīng)準(zhǔn)備好了模型訓(xùn)練所有需要的要素,可以實現(xiàn)主要的訓(xùn)練過程部分了。

理解這段代碼至關(guān)重要,因為從事深度學(xué)習(xí)后, 你會一遍又一遍地看到幾乎相同的訓(xùn)練過程。

1、 在每次迭代中,我們讀取一小批量訓(xùn)練樣本,并通過我們的模型來獲得一組預(yù)測。

2、 計算完損失后,我們開始反向傳播,存儲每個參數(shù)的梯度。 最后,我們調(diào)用優(yōu)化算法sgd來更新模型參數(shù)。

概括一下,我們將執(zhí)行以下循環(huán):

  • 初始化參數(shù)
  • 重復(fù)以下步驟:
    • 先計算樣本的預(yù)測值,再計算損失函數(shù)值,根據(jù)反向傳播函數(shù)得出梯度 g
    • 根據(jù)梯度下降算法,使用參數(shù)w和b分別減去它們的梯度(由學(xué)習(xí)率lr限制更新程度),更新參數(shù)(w,b)

在每個迭代周期(epoch)中,我們使用data_iter函數(shù)遍歷整個數(shù)據(jù)集, 并將訓(xùn)練數(shù)據(jù)集中所有樣本都使用一次(假設(shè)樣本數(shù)能夠被批量大小整除)。 這里的迭代周期個數(shù)num_epochs學(xué)習(xí)率lr都是超參數(shù),分別設(shè)為100.01。

lr = 0.01                                                                #定義學(xué)習(xí)率為 0.01
num_epochs = 10                                                          #定義迭代次數(shù)10
net = lineRegression                                                     #定義網(wǎng)絡(luò)模型為線性網(wǎng)絡(luò)模型
loss = squared_loss                                                      #定義損失函數(shù)為平方損失函數(shù)


#注意,我們的目標(biāo)是使  平方損失函數(shù)取得最小值(廣泛的講,即最優(yōu)化的結(jié)果)
for epoch in range(num_epochs):                                          #迭代訓(xùn)練10次
    
    for X, y in data_iter(batch_size, features, labels):                 #多次遍歷訓(xùn)練小批量數(shù)據(jù)集
        
        y_hat = net(X, w, b)                                             #調(diào)用線性網(wǎng)絡(luò)模型獲取小批量樣本的預(yù)測值
        l = loss(y_hat, y)                                               #調(diào)用平方損失函數(shù)計算損失值

        l.sum().backward()                                               #調(diào)用反向傳播函數(shù),計算出w和b關(guān)于損失函數(shù)的梯度
        
        sgd([w,b], lr, batch_size)                                       #執(zhí)行模型優(yōu)化函數(shù),使用梯度grad更新參數(shù)w和b
    
    with torch.no_grad():
        
        train_l = loss(net(features,w,b), labels)                         #優(yōu)化之后的損失
        print(f'epoch{epoch+1}, loss{float(train_l.mean()):f}')           #輸出優(yōu)化過后的損失值
        
epoch1, loss2.335004
epoch2, loss0.353009
epoch3, loss0.053931
epoch4, loss0.008335
epoch5, loss0.001331
epoch6, loss0.000249
epoch7, loss0.000082
epoch8, loss0.000055
epoch9, loss0.000051
epoch10, loss0.000051

因為我們使用的是自己合成的數(shù)據(jù)集,所以我們知道真正的參數(shù)是什么。

因此,我們可以通過比較真實參數(shù)和通過訓(xùn)練學(xué)到的參數(shù)來評估訓(xùn)練的成功程度。 事實上,真實參數(shù)和通過訓(xùn)練學(xué)到的參數(shù)確實非常接近。

print(f'w的估計誤差{true_w - w.reshape(true_w.shape)}')
print(f'b的估計誤差{true_b - b}')
w的估計誤差tensor([ 0.0004, -0.0006], grad_fn=<SubBackward0>)
b的估計誤差tensor([8.9645e-05], grad_fn=<RsubBackward1>)

可見,我們訓(xùn)練模型得到的參數(shù)w和b和真實的w和b十分相近。

注意,我們不應(yīng)該想當(dāng)然地認為我們能夠完美地求解參數(shù)。在機器學(xué)習(xí)中,我們通常不太關(guān)心恢復(fù)真正的參數(shù),而更關(guān)心如何高度準(zhǔn)確預(yù)測參數(shù)

幸運的是,即使是在復(fù)雜的優(yōu)化問題上,隨機梯度下降通常也能找到非常好的解。

小結(jié)

我們學(xué)習(xí)了深度網(wǎng)絡(luò)是如何實現(xiàn)和優(yōu)化的。在這一過程中只使用張量和自動微分,不需要定義層或復(fù)雜的優(yōu)化器。

原文鏈接:https://blog.csdn.net/weixin_43479947/article/details/127033823

欄目分類
最近更新