線性回歸:
線性:自變量和應變量之間是線性關系,如:y = wx +b
回歸:擬合一條曲線,使真實值和擬合值差距盡可能小
目標:求解參數w和b? ? ? ? ?所用算法:梯度下降算法
梯度下降:向著梯度方向(下降最快的方向)走一步,不停迭代
梯度下降過程:
訓練循環(核心步驟):
- 前向傳播:
- 將訓練集中的一批數據(一個批次)輸入到模型中,通過模型的各層計算得到輸出。
- 這個過程中,數據按照模型的架構順序依次通過各層,每層根據其權重和偏置對數據進行計算,如在全連接層中,計算是通過矩陣乘法和加法實現的。
- 計算損失:
- 使用定義好的損失函數,計算模型預測輸出與該批次數據真實標簽之間的損失。
- 損失值反映了當前模型在這一批次數據上的預測誤差大小。
- 反向傳播:
- 根據計算得到的損失,通過鏈式法則從最后一層開始,逐層計算損失對每個模型參數(權重和偏置)的梯度。
- 反向傳播算法使得模型能夠知道每個參數對損失的影響程度,從而為參數更新提供依據。
- 更新參數:
- 使用選擇的優化器,根據計算得到的梯度更新模型的參數。例如,在使用 SGD 優化器時,參數更新公式為:參數 = 參數 - 學習率 * 梯度。
- 更新后的參數將用于下一次迭代的前向傳播,通過不斷重復這個過程,模型的參數逐漸調整,使得損失函數不斷減小。
import torch #深度學習框架 import matplotlib.pyplot as plt #畫圖 import random #隨機def create_data(w, b, data_num): #生成數據x = torch.normal(0, 1, (data_num, len(w))) #平均數為0,方差為1,長度為data_num,寬度為len(w)y = torch.matmul(x, w) + b #通過矩陣乘法將輸入數據x與權重w相乘,然后加上偏置項b,生成新的輸出ynoise = torch.normal(0, 0.01, y.shape) #噪聲要加到y上y+= noise #模擬真實數據的不確定性、防止模型過擬合return x, ynum = 500 # 生成的數據數量true_w = torch.tensor([8.1, 2, 2, 4]) # 真實的權重 true_b = torch.tensor(1.1) # 真實的偏置X, Y = create_data(true_w, true_b, num) # 生成數據plt.scatter(X[:, 0], Y, 1) #對x張量進行切片,選擇所有行、第一列 plt.show()def data_provider(data, label, batchsize): #每次訪問這個函數,就能提供一批數據,傳入參數依次為:數據、標簽、步長length = len(label) # 獲取標簽數據的長度,由于數據和標簽一一對應,以此代表整個數據集的長度indices = list(range(length)) # 創建一個從0到length - 1的索引列表,每個索引對應數據集中的一個樣本,用于后續操作random.shuffle(indices) # 對索引列表進行隨機打亂,確保每次取數據批次時是隨機順序,避免數據順序依賴,增強模型訓練效果# 按照指定的批量大小batchsize遍歷整個數據集,每次取出一個批次的數據范圍for each in range(0, length, batchsize):get_indices = indices[each: each+batchsize] # 從打亂后的索引列表中取出當前批次對應的索引范圍get_data = data[get_indices] # 根據取出的索引范圍從數據張量data中獲取當前批次的數據get_label = label[get_indices] # 根據取出的索引范圍從標簽張量label中獲取當前批次對應的標簽yield get_data, get_label # 使用yield關鍵字返回當前批次的數據和標簽,使函數成為生成器,下次調用繼續返回下一批次batchsize = 16 #步長設置為16 # for batch_x, batch_y in data_provider(X, Y, batchsize): # print(batch_x, batch_y) # #break# 定義函數fun,用于根據輸入數據x、權重w和偏置b進行線性變換計算,得到預測輸出 def fun(x, w, b):pred_y = torch.matmul(x, w) + b # 使用torch.matmul對輸入數據x和權重w進行矩陣乘法運算,然后加上偏置b,得到預測輸出pred_yreturn pred_y# 定義函數maeloss,用于計算預測值pre_y和真實值y之間的平均絕對誤差(MAE)損失 def maeloss(pre_y, y):return torch.sum(abs(pre_y-y))/len(y) # 先計算預測值和真實值之間差值的絕對值,再對所有差值的絕對值求和,最后除以數據數量len(y),得到平均絕對誤差作為損失值并返回# 定義函數sgd,實現隨機梯度下降算法,用于更新模型參數 def sgd(paras, lr): #隨機梯度下降,更新參數# 使用torch.no_grad()上下文管理器,在這個范圍內的操作不會進行梯度計算,因為參數更新階段不需要對更新操作本身計算梯度with torch.no_grad(): #屬于這句代碼的部分,不計算梯度for para in paras:# 遍歷要更新的參數列表paras中的每一個參數# 根據隨機梯度下降算法規則,將當前參數para減去其梯度para.grad與學習率lr的乘積,實現參數更新(注意要用 -= 操作符進行原位更新)para -= para.grad* lr #不能寫成 para = para - para.grad*lrpara.grad.zero_() #使用過的梯度,歸0,避免下一次迭代時梯度累積,導致參數更新錯誤lr = 0.03 #學習率 w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True) # 使用torch.normal函數按照正態分布來初始化權重參數w_0,其中均值為0,方差為0.01,形狀與之前定義的真實權重true_w保持一致,并且設置requires_grad為True,意味著這個參數在后續的計算中需要跟蹤計算梯度,以便進行自動求導來更新它 b_0 = torch.tensor(0.01, requires_grad=True) # 初始化偏置參數b_0,將其設置為值是0.01的標量張量,同時設置requires_grad為True,這樣該參數就能參與到梯度計算以及后續的參數更新過程中 print(w_0, b_0)epochs = 50#訓練多少輪# 按訓練輪數epochs循環,每輪訓練模型 for epoch in range(epochs):data_loss = 0 # 初始化本輪累計損失為0for batch_x, batch_y in data_provider(X, Y, batchsize): # 按批次獲取數據,循環處理每批次pred_y = fun(batch_x, w_0, b_0) # 用當前參數預測本批次數據,得預測值loss = maeloss(pred_y, batch_y) # 計算預測值與真實值的損失loss.backward() # 反向傳播求梯度sgd([w_0, b_0], lr) # 用sgd更新模型參數data_loss += loss # 累加本批次損失到本輪累計損失print("epoch %03d: loss: %.6f"%(epoch, data_loss)) # 打印本輪輪數和累計損失,觀察訓練情況print("真實的函數值是", true_w, true_b) print("訓練得到的參數值是", w_0, b_0)idx = 0 # 初始化一個索引變量idx為0,這個索引通常用于選擇數據張量X中的某一列數據,后續可能用于可視化等相關操作 plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy()) # 繪制擬合直線,取X第idx列數據轉numpy作橫坐標,按線性回歸公式用訓練參數算縱坐標來繪制 plt.scatter(X[:, idx], Y, 1) # 繪制散點圖,以輸入數據張量X的第idx列數據作為橫坐標,以對應的輸出數據Y作為縱坐標,展示原始數據的分布情況 plt.show()
Tips: 1、yield get_data, get_label當函數執行到?yield?語句時,函數會暫停執行,將?yield?后面的值返回給調用者,但函數并沒有結束。下次調用這個函數時,它會從上次暫停的地方繼續執行,直到遇到下一個?yield?或者函數結束。這意味著在一個生成器函數中,可以通過多個?yield?語句多次返回不同的值。就像在?data_provider?函數中,每次調用會返回一個新的數據批次和標簽批次,直到所有批次都返回完。 2、para.grad.zero_() 如果我們不清零這個梯度,在第二次訓練批次進行反向傳播時,新計算出來的梯度會和第一次遺留下來的梯度相加。就好像你在走迷宮,第一次得到的指示(梯度)是向左走三步,但是你沒記住這個指示,第二次又得到一個指示(新的梯度)是向右走兩步,但是你把兩次的指示混在一起,變成了向左走一步(假設梯度相加的情況),這樣就會讓你的方向(參數更新方向)變得混亂。 3、plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy())①繪制一條直線,用于表示擬合的線性關系(在二維平面上展示線性回歸擬合情況)。②首先從輸入數據張量X中取出第idx列數據(通過X[:,?idx]),并將其轉換為numpy數組(使用detach().numpy()方法,目的是從計算圖中分離出來并轉為numpy格式方便繪圖),作為橫坐標。 ③然后根據線性回歸的公式y?=?w?*?x?+?b,計算對應的縱坐標,這里使用當前訓練得到的權重w_0的第idx個元素(w_0[idx])和偏置b_0,同樣轉換為numpy數組后參與計算,以此繪制出擬合的直線。