網站首頁 編程語言 正文
文章目錄
- 線性回歸的從零開始實現
- 生成數據集
- 讀取數據集
- 初始化模型參數
- 定義模型
- 定義損失函數
- 定義優化算法
- 訓練
- 小結
線性回歸的從零開始實現
在這一小節中,我們將從零開始實現整個線性回歸網絡模型, 包括數據流水線、模型、損失函數和小批量隨機梯度下降優化器。 雖然現代的深度學習框架幾乎可以自動化地進行所有這些工作,但從零開始實現可以確保你真正知道自己在做什么。 同時,了解更細致的工作原理將方便我們自定義模型、自定義層或自定義損失函數。 在這一節中,我們將只使用張量和自動求導。 在之后的章節中,我們會充分利用深度學習框架的優勢,介紹更簡潔的實現方式。
import torch #引入torch庫
import random #引入random隨機庫
生成數據集
為了簡單起見,我們將根據帶有噪聲的線性模型構造一個人造數據集。 我們的任務是使用這個有限樣本的數據集來恢復這個模型的參數。 我們將使用低維數據,這樣可以很容易地將其可視化。 在下面的代碼中,我們生成一個包含1000個樣本的數據集, 每個樣本包含從標準正態分布中采樣的2個特征。 我們的合成數據集是一個矩陣 X ∈ R 1000 × 2 X \in R^{1000\times2} X∈R1000×2 。
我們使用線性模型參數 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 ? 生成數據集及其標簽
y = X w + b + ? y = Xw + b + \epsilon y=Xw+b+?
你可以將 ? \epsilon ? 視為模型預測和標簽時的潛在觀測誤差。在這里我們認為標準假設成立,即 ? \epsilon ? 服從均值為0的正態分布。 為了簡化問題,我們將標準差設為0.01。 下面的代碼生成合成數據集。
#生成符合正態分布的數據集
def synthetic_data(w, b, num_examples):
#生成 y = Xw + b + 噪聲
X = torch.normal(0, 1, (num_examples, len(w))) #生成均值為0, 標準差為1的正態分布,即為標準正態分布
y = torch.matmul(X, w) + b #matmul即矩陣乘法,即X與偏置w進行矩陣乘積后,再加上偏置量b
y += torch.normal(0, 0.01, y.shape) #y的值再加上一個均值為0,標準差為0.01的正態分布
return X, y.reshape(-1, 1) #返回數據集,其中X的形狀為(num_examples,len(w)), y的形狀為(num_examples)
true_w = torch.tensor([2, -3.4]) #定義權重,也即向量w為一維數據
true_b = 4.2 #定義偏置量b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000) #生成數據集,其中w向量為權重,b為偏置量
features.shape, labels.shape #輸出樣本和標簽的形狀
(torch.Size([1000, 2]), torch.Size([1000, 1]))
注意,features中的每一行都包含一個二維數據樣本, labels中的每一行都包含一維標簽值(一個標量)。
print('features:', features[0], '\nlabels:', labels[0])
features: tensor([1.2393, 1.5488])
labels: tensor([1.4208])
通過生成第二個特征features[:, 1]和labels的散點圖, 可以直觀觀察到兩者之間的線性關系。
import matplotlib.pyplot as plt
##生成第二個特征features[:, 1]和labels的散點圖
plt.scatter(features[:,(1)].detach().numpy(), labels.detach().numpy(), 1)
<matplotlib.collections.PathCollection at 0x23366079eb8>
讀取數據集
回想一下,訓練模型時要對數據集進行遍歷,每次抽取一小批量樣本,并使用它們來更新我們的模型。 由于這個過程是訓練機器學習算法的基礎,所以有必要定義一個函數, 該函數能打亂數據集中的樣本并以小批量方式獲取數據。
在下面的代碼中,我們定義一個data_iter函數, 該函數接收批量大小、特征矩陣和標簽向量作為輸入,生成大小為batch_size的小批量。 每個小批量包含一組特征和標簽。
import random
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( #每次獲取batch_size個索引
indices[i: min(i+batch_size, num_examples)]
)
yield features[batch_indices], labels[batch_indices] #產生batch_size個數據集與對應的標簽
通常,我們利用GPU并行運算的優勢,處理合理大小的“小批量”。 每個樣本都可以并行地進行模型計算,且每個樣本損失函數的梯度也可以被并行計算。 GPU可以在處理幾百個樣本時,所花費的時間不比處理一個樣本時多太多。
我們直觀感受一下小批量運算:讀取第一個小批量數據樣本并打印。 每個批量的特征維度顯示批量大小和輸入特征數。 同樣的,批量的標簽形狀與batch_size相等。
batch_size = 10
#迭代器,每次返回10個數據集及10個對應的標簽
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y) #輸出一個小批量的數據集,此數據集的大小為 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]])
當我們運行迭代時,我們會連續地獲得不同的小批量,直至遍歷完整個數據集。上面實現的迭代對于教學來說很好,但它的執行效率很低,可能會在實際問題上陷入麻煩。例如,它要求我們將所有數據加載到內存中,并執行大量的隨機內存訪問。
在深度學習框架中實現的內置迭代器效率要高得多, 它可以處理存儲在文件中的數據和數據流提供的數據。
初始化模型參數
在我們開始用小批量隨機梯度下降優化我們的模型參數之前, 我們需要先有一些參數。 在下面的代碼中,我們通過從均值為0、標準差為0.01的正態分布中采樣隨機數來初始化權重, 并將偏置初始化為0。
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True) #定義模型參數 W, 共2個分量,符合正態分布,可求梯度
b = torch.zeros(1, requires_grad=True) #定義模型參數 b,初始化為0,可求梯度
在初始化參數之后,我們的任務是更新這些參數,直到這些參數足夠擬合我們的數據。 每次更新都需要計算損失函數關于模型參數的梯度。 有了這個梯度,我們就可以向減小損失的方向更新每個參數。 因為手動計算梯度很枯燥而且容易出錯,所以沒有人會手動計算梯度。使用自動微分來計算梯度。
定義模型
接下來,我們必須定義模型,將模型的輸入和參數同模型的輸出關聯起來。
回想一下,要計算線性模型的輸出, 我們只需計算輸入特征 X X X 和模型權重 w w w 的矩陣-向量乘法后加上偏置。 注意,上面 X w Xw Xw 的是一個向量,而 b b b 是一個標量。 回想一下之前描述的廣播機制: 當我們用一個向量加一個標量時,標量會被加到向量的每個分量上。
def lineRegression(X, w, b):
#計算預測的值 y
return torch.matmul(X, w) + b #注意矩陣X與向量w的結果為一個向量,b為一個標量
定義損失函數
因為需要計算損失函數的梯度,所以我們應該先定義損失函數。
這里我們使用 平方損失函數 。 在實現中,我們需要將真實值y的形狀轉換為和預測值y_hat的形狀相同。
#定義平方損失函數
def squared_loss(y_hat, y):
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
定義優化算法
正如我們之前討論的,線性回歸有解析解。 盡管線性回歸有解析解,但本書中的其他模型卻沒有。 這里我們介紹小批量隨機梯度下降。
在每一步中,使用從數據集中隨機抽取的一個小批量,然后根據參數計算損失的梯度。接下來,朝著減少損失的方向更新我們的參數。 下面的函數實現小批量隨機梯度下降更新。 該函數接受模型參數集合、學習速率和批量大小作為輸入。每一步更新的大小由學習速率lr決定。 因為我們計算的損失是一個批量樣本的總和,所以我們用批量大小(batch_size) 來規范化步長,這樣步長大小就不會取決于我們對批量大小的選擇。
#定義模型優化算法
#其中params為待更新的參數,lr為學習率,batch_size為小批量樣本大小
def sgd(params, lr, batch_size):
#torch.no_grad() 是一個上下文管理器,被該語句 wrap 起來的部分將不會track 梯度
with torch.no_grad():
#使用優化方法梯度下降算法分別更新w和b參數
for param in params:
param -= lr*(param.grad/batch_size) #使參數朝向梯度的反方向移動,試圖最小化損失函數
param.grad.zero_() #清空梯度,以免梯度進行累加
訓練
現在我們已經準備好了模型訓練所有需要的要素,可以實現主要的訓練過程部分了。
理解這段代碼至關重要,因為從事深度學習后, 你會一遍又一遍地看到幾乎相同的訓練過程。
1、 在每次迭代中,我們讀取一小批量訓練樣本,并通過我們的模型來獲得一組預測。
2、 計算完損失后,我們開始反向傳播,存儲每個參數的梯度。 最后,我們調用優化算法sgd來更新模型參數。
概括一下,我們將執行以下循環:
- 初始化參數
- 重復以下步驟:
- 先計算樣本的預測值,再計算損失函數值,根據反向傳播函數得出梯度 g
- 根據梯度下降算法,使用參數w和b分別減去它們的梯度(由學習率lr限制更新程度),更新參數(w,b)
在每個迭代周期(epoch)中,我們使用data_iter函數遍歷整個數據集, 并將訓練數據集中所有樣本都使用一次(假設樣本數能夠被批量大小整除)。 這里的迭代周期個數num_epochs和學習率lr都是超參數,分別設為10和0.01。
lr = 0.01 #定義學習率為 0.01
num_epochs = 10 #定義迭代次數10
net = lineRegression #定義網絡模型為線性網絡模型
loss = squared_loss #定義損失函數為平方損失函數
#注意,我們的目標是使 平方損失函數取得最小值(廣泛的講,即最優化的結果)
for epoch in range(num_epochs): #迭代訓練10次
for X, y in data_iter(batch_size, features, labels): #多次遍歷訓練小批量數據集
y_hat = net(X, w, b) #調用線性網絡模型獲取小批量樣本的預測值
l = loss(y_hat, y) #調用平方損失函數計算損失值
l.sum().backward() #調用反向傳播函數,計算出w和b關于損失函數的梯度
sgd([w,b], lr, batch_size) #執行模型優化函數,使用梯度grad更新參數w和b
with torch.no_grad():
train_l = loss(net(features,w,b), labels) #優化之后的損失
print(f'epoch{epoch+1}, loss{float(train_l.mean()):f}') #輸出優化過后的損失值
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
因為我們使用的是自己合成的數據集,所以我們知道真正的參數是什么。
因此,我們可以通過比較真實參數和通過訓練學到的參數來評估訓練的成功程度。 事實上,真實參數和通過訓練學到的參數確實非常接近。
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>)
可見,我們訓練模型得到的參數w和b和真實的w和b十分相近。
注意,我們不應該想當然地認為我們能夠完美地求解參數。在機器學習中,我們通常不太關心恢復真正的參數,而更關心如何高度準確預測參數。
幸運的是,即使是在復雜的優化問題上,隨機梯度下降通常也能找到非常好的解。
小結
我們學習了深度網絡是如何實現和優化的。在這一過程中只使用張量和自動微分,不需要定義層或復雜的優化器。
原文鏈接:https://blog.csdn.net/weixin_43479947/article/details/127033823
相關推薦
- 2024-03-20 SpringBoot使用dynamic-datasource實現多數據源方案
- 2023-05-15 golang判斷結構體為空的問題_Golang
- 2022-06-01 ASP.Net?Core中的日志與分布式鏈路追蹤_實用技巧
- 2022-04-25 C#使用NPOI讀取excel轉為DataSet_C#教程
- 2023-03-19 一文掌握匯編語言?halt?命令_匯編語言
- 2022-06-21 C語言詳解無頭單向非循環鏈表各種操作方法_C 語言
- 2023-07-06 mybatis-plus 3.5.x Cannot resolve method ‘setUseDe
- 2022-10-28 Go語言開發保證并發安全實例詳解_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支