神經網絡代碼入門解析
import torch
import matplotlib.pyplot as pltimport randomdef create_data(w, b, data_num): # 數據生成x = torch.normal(0, 1, (data_num, len(w)))y = torch.matmul(x, w) + b # 矩陣相乘再加bnoise = torch.normal(0, 0.01, y.shape) # 為y添加噪聲y += noisereturn x, ynum = 500true_w = torch.tensor([8.1, 2, 2, 4])
true_b = 1.1X, Y = create_data(true_w, true_b, num)# plt.scatter(X[:, 3], Y, 1) # 畫散點圖 對X取全部的行的第三列,標簽Y,點大小
# plt.show()def data_provider(data, label, batchsize): # 每次取batchsize個數據length = len(label)indices = list(range(length))# 這里需要把數據打亂random.shuffle(indices)for each in range(0, length, batchsize):get_indices = indices[each: each+batchsize]get_data = data[get_indices]get_label = label[get_indices]yield get_data, get_label # 有存檔點的returnbatchsize = 16
# for batch_x, batch_y in data_provider(X, Y, batchsize):
# print(batch_x, batch_y)
# break# 定義模型
def fun(x, w, b):pred_y = torch.matmul(x, w) + breturn pred_y# 定義loss
def maeLoss(pre_y, y):return torch.sum(abs(pre_y-y))/len(y)# sgd(梯度下降)
def sgd(paras, lr):with torch.no_grad(): # 這部分代碼不計算梯度for para in paras:para -= para.grad * lr # 不能寫成 para = para - paras.grad * lr !!!! 這句相當于要創建一個新的para,會導致報錯para.grad.zero_() # 將使用過的梯度歸零lr = 0.01
w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True)
b_0 = torch.tensor(0.01, requires_grad=True)
print(w_0, b_0)epochs = 50
for epoch in range(epochs):data_loss = 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)data_loss += lossprint("epoch %03d: loss: %.6f" % (epoch, data_loss))print("真實函數值:", true_w, true_b)
print("訓練得到的函數值:", w_0, b_0)idx = 0
plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy())
plt.scatter(X[:, idx].detach().numpy(), Y, 1)
plt.show()
逐步分析代碼
1.數據生成
首先設計一個函數create_data
,提供我們所需要的數據集的x與y
def create_data(w, b, data_num): # 數據生成x = torch.normal(0, 1, (data_num, len(w))) # 生成特征數據,形狀為 (data_num, len(w))y = torch.matmul(x, w) + b # 計算目標值 y = x * w + bnoise = torch.normal(0, 0.01, y.shape) # 生成噪聲,形狀與 y 相同y += noise # 為 y 添加噪聲,模擬真實數據中的隨機誤差return x, y
-
torch.normal() 生成一個張量
torch.normal(0, 1, (data_num, len(w)))
:生成一個形狀為(data_num, len(w))
的張量,其中的元素是從均值為0
、標準差為1
的正態分布中隨機采樣的。
-
torch.matmul() 讓矩陣相乘
matmul: matrix multiply
-
再使用torch.normal()生成一個張量,添加到y上,相當于為y添加了隨機的噪聲
噪聲的引入是為了模擬真實數據中的隨機誤差,使生成的數據更接近現實場景。
2.設計一個數據加載器
def data_provider(data, label, batchsize): # 每次取 batchsize 個數據length = len(label)indices = list(range(length))random.shuffle(indices) # 打亂數據順序,避免模型學習到順序特征for each in range(0, length, batchsize):get_indices = indices[each: each+batchsize] # 獲取當前批次的索引get_data = data[get_indices] # 獲取當前批次的數據get_label = label[get_indices] # 獲取當前批次的標簽yield get_data, get_label # 返回當前批次的數據和標簽
data_provider可以分批提供數據,并通過yield來返回已實現記憶功能
首先把list y順序打亂,這樣就相當于從生成的訓練集y中隨機讀取,若不打亂數據,可能造成訓練結果的不理想
打亂數據可以避免模型在訓練過程中學習到數據的順序特征,從而提高模型的泛化能力。
之后分段遍歷打亂的y,返回對應的局部的數據集來給神經網絡進行訓練
3.定義模型函數
def fun(x, w, b):pred_y = torch.matmul(x, w) + b # 計算預測值 y = x * w + breturn pred_y
fun(x, w, b)
是一個線性模型,形式為y = x * w + b
,其中x
是輸入特征,w
是權重,b
是偏置。
4.定義Loss函數
def maeLoss(pre_y, y):return torch.sum(abs(pre_y - y)) / len(y) # 計算平均絕對誤差 (MAE)
maeLoss
是平均絕對誤差(Mean Absolute Error, MAE),它計算預測值pre_y
和真實值y
之間的絕對誤差的平均值。- 公式為:
MAE = (1/n) * Σ|pre_y - y|
,其中n
是樣本數量。
5.梯度下降sgd函數
# sgd(梯度下降)
def sgd(paras, lr):with torch.no_grad(): # 這部分代碼不計算梯度for para in paras:para -= para.grad * lr # 不能寫成 para = para - paras.grad * lr !!!! 這句相當于要創建一個新的para,會導致報錯para.grad.zero_() # 將使用過的梯度歸零
這里需要使用torch.no_grad()來避免重復計算梯度
在前向過程中已經累計過一次梯度了,如果在梯度下降過程中又累計了梯度,那么就會造成不必要的麻煩
PyTorch 會累積梯度,如果不手動清零,梯度會不斷累積,導致參數更新錯誤。
para -= para.grad * lr
就是將參數w修正的過程(w=w-(dy^/dw)*learningRate)
torch.no_grad()
是一個上下文管理器,用于禁用梯度計算。在參數更新時,禁用梯度計算可以避免不必要的計算和內存占用。
5.開始訓練
epochs = 50
for epoch in range(epochs):data_loss = 0num_batches = len(Y) // batchsize # 計算批次數量for 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) # 更新參數data_loss += loss.item() # 累積損失print("epoch %03d: loss: %.6f" % (epoch, data_loss / num_batches)) # 打印平均損失
先定義一個訓練輪次epochs=50,表示訓練50輪
在每輪訓練中將loss記錄下來,以此評價訓練的效果
首先用data_provider
來獲取數據集中隨機的一部分
接著傳入相應數據給模型函數,通過前向傳播獲得預測y值pred_y
調用Loss計算函數,獲取這次的loss,再通過反向傳播loss.backward()
計算梯度
loss.backward()
是反向傳播的核心步驟,用于計算損失函數對模型參數的梯度。
再通過梯度下降sgd([w_0, b_0], lr)
來更新模型的參數
最終將這組數據的loss累加到這輪數據的loss中
6.結果繪制
idx = 0
plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy() * w_0[idx].detach().numpy() + b_0.detach().numpy()) # 繪制預測直線
plt.scatter(X[:, idx].detach().numpy(), Y, 1) # 繪制真實數據點
plt.show()