吳恩(en)達深度學習課程一:神經網(wang)絡和深度學習 第(di)四周:深層神經網(wang)絡的關鍵(jian)概念 課后作業和代碼實(shi)踐(jian)
此分類用于記錄吳恩達深度學習課程的學習筆記。
課程相關信息鏈(lian)接如(ru)下:
- 原課程視頻鏈接:
- github課程資料,含課件與筆記:
- 課程配套練習(中英)與答案:
本篇為第(di)一課(ke)第(di)四周的課(ke)程練習部分(fen)的講解。
1.理論習題
同(tong)樣,這是(shi)本周理論部分(fen)的習題(ti)和相應(ying)解析,博主已經做好了(le)翻譯和解析工(gong)作。
這里單(dan)獨選出一道題如(ru)下:
向量化允許您在L層神經網絡中計算前向傳播,而不需要在層(l = 1,2,…,L)上顯式的使用for-loop(或任何其他顯式迭代循環),正確嗎?
答案:錯誤
通過這道題,我們來總結一下:
向量化后的神經網絡,到底省去了哪些部分的顯式循環?又有哪些循環始終不能省去?
1.1 非向量化的神經網絡需要定義哪些循環?
在沒有向量化之前,前向傳播過程通常包含(han)以下顯式循(xun)環(huan):
| 循環類型 | 作用對象 | 描述 |
|---|---|---|
| 輪次循環(Epoch) | 整個訓練集反復訓練的次數 | 為了不斷優化參數,必須多次重復訓練整個訓練集,每一輪稱為一個 Epoch。 |
| 批次循環(Batch) | 每一個批次 | 若使用小批次梯度下降,則每輪訓練需要對所有批次依次執行前向和反向傳播。(一批次訓練所有樣本則不需此行) |
| 樣本循環 | 每個樣本 | 對訓練集中的每個樣本單獨計算前向傳播和損失(在未向量化時存在)。 |
| 層循環 | 每一層 | 逐層計算:當前層的輸出依賴上一層輸出,因此必須按順序執行,無法并行消除。 |
| 層內神經元循環 | 當前層的每個神經元 | 計算當前層每個神經元的加權和與激活(可被矩陣運算替代)。 |
| 特征循環 | 某個神經元內部所有輸入特征 | 對輸入特征逐一乘權重再求和,即實現一次點乘(可向量化消除)。 |
1.2 向量化的神經網絡省去了哪些循環?
向量化后,再次總結如下(xia):
| 循環類型 | 是否被消除 | 原因說明 |
|---|---|---|
| 輪次循環(Epoch) | 無法消除 | 每一輪訓練都需更新參數,是梯度下降優化的外層結構,與是否向量化無關。 |
| 批次循環(Batch) | 無法消除 | Mini-batch 是訓練策略的一部分,用于節省顯存與穩定梯度,無法用矩陣運算替代。(一批次訓練所有樣本則不需此行) |
| 樣本循環 | 被消除 | 向量化使得矩陣 ( \(X=[x^{(1)}, x^{(2)}, ...]\) ) 可以一次性處理所有樣本,無需逐個 for。 |
| 層循環 | 無法消除 | 每層的輸出依賴上一層結果,必須逐層計算,屬于模型結構依賴,無法并行消除,只是代碼中可不寫顯式 for。 |
| 層內神經元循環 | 被消除 | 矩陣乘法 ( \(Z = W A + b\) ) 可一次計算整層所有神經元的輸出,無需逐神經元循環。 |
| 特征循環 | 被消除 | 神經元加權求和(點乘)由底層線性代數庫以矩陣計算形式完成,無需手工遍歷每個特征。 |
這里要單獨說明一下層循環,層循環不同于輪次和批次,他不會直接使用顯示的 for 語法。
層循環的本質是輸入樣本的處理順序,我們只有在前一層得到結果后才可以進行下一層的計算,這樣嚴密的串行結構無法并行消除。
因此(ci),我(wo)們才說(shuo)(shuo)習題里(li)的說(shuo)(shuo)法是錯誤的。
1.3表格總結
| 循環類型 | 是否可向量化消除 |
|---|---|
| 輪次循環 | 不可消除 |
| 批次循環 | 不可消除 |
| 層循環 | 不可消除 |
| 樣本循環 | 可消除 |
| 層內神經元循環 | 可消除 |
| 特征循環 | 可消除 |
2.代碼實踐:多層神經網絡
同樣先粘貼整理課程習題的博主答案,博主依舊在不借助很多現在流行框架的情況下手動構建模型的各個部分,并手動實現傳播,計算過程。
如果希望更(geng)扎(zha)實地了解原理,更(geng)推薦跟隨這位博主的(de)內容(rong)一(yi)步步手動構建模(mo)型,這樣對之后框架的(de)使用(yong)也會(hui)更(geng)得心應手。
這次實操不同于之前直接使用框架構建,我們在本周的理論部分里了解了正向傳播和反向傳播的模塊化,再回看一下定義:

這次,我們按照理論梳理的內容,看看如何實現這兩個函數。
之(zhi)后(hou),再使用框架來看(kan)看(kan)多層神(shen)經網絡對(dui)(dui)損失,對(dui)(dui)最(zui)終準(zhun)確率(lv)的影響(xiang)。
2.1 手工實現傳播
1.正向傳播
import numpy as np
# 1.定義ReLU激活函數用于隱藏層
def ReLU(Z):
return np.maximum(0, Z),Z # 返回格式:(層輸出,加權和)
# 2.定義Sigmoid 激活函數用于輸出層
def sigmoid(Z):
return 1/(1+np.exp(-Z)),Z # 返回格式:(層輸出,加權和)
# 3.向前傳播函數,實現線性組合+激活函數
def forward(A_prev, W, b, activation):
"""
參數介紹:
A_prev : 上一層輸出 A^[L-1]
W : 當前層權重 W^[L]
b : 當前層偏置 b^[L]
activation : 本層使用的激活函數
"""
# 3.1 線性組合 Z = W*A_prev + b
Z = np.dot(W, A_prev) + b
# 3.2 激活函數
if activation == "sigmoid":
A, Z_cache = sigmoid(Z)
elif activation == "ReLU":
A, Z_cache = ReLU(Z)
else:
raise ValueError("未定義該激活函數")
# 3.3 保存中間值供 backward 使用
cache = (A_prev, W, b, Z_cache)
return A, cache
"""
返回:
A : 當前層輸出 A^[L]
cache : 緩存中間結果 (A_prev, W, b, Z),用于反向傳播
"""
2.反向傳播
import numpy as np
# 1. 激活函數的導數實現
def dReLU(dA, Z):
"""
ReLU 導數:
f(Z) = max(0, Z)
f'(Z) = 1, 若 Z > 0
0, 若 Z <= 0
"""
# dZ = dA*f'(Z)
dZ = np.array(dA, copy=True) # **確保修改dZ時不會影響到原始的dA
dZ[Z <= 0] = 0 # 只有 Z>0 時梯度能傳遞
return dZ
def dSigmoid(dA, Z):
"""
Sigmoid 導數:
A = σ(Z) = 1/(1 + e^-Z)
f'(Z) = A * (1 - A)
"""
A = 1 / (1 + np.exp(-Z))
# dZ = dA*f'(Z)
dZ = dA * A * (1 - A)
return dZ
# 2. 反向傳播函數(鏈式求導得到參數梯度)
def backward(dA, cache, activation):
"""
參數:
dA : 當前層損失對激活輸出 A^[L] 的梯度
cache : 前向傳播保存的中間結果 (A_prev, W, b, Z)
activation : 本層使用的激活函數
"""
# 2.1取出前向傳播緩存
A_prev, W, b, Z = cache
m = A_prev.shape[1] # 樣本數量 m
# 2.2 根據激活函數計算 dZ
if activation == "ReLU":
dZ = dReLU(dA, Z)
elif activation == "sigmoid":
dZ = dSigmoid(dA, Z)
else:
raise ValueError("未定義的激活函數")
# 2.3 線性部分梯度(公式對應):
# dW = 1/m * dZ * A_prev^T
# db = 1/m * ∑(dZ)
# dA_prev = W^T * dZ
dW = (1/m) * np.dot(dZ, A_prev.T)
db = (1/m) * np.sum(dZ, axis=1, keepdims=True)
dA_prev = np.dot(W.T, dZ)
return dA_prev, dW, db
"""
返回:
dA_prev : 傳遞給上一層的梯度
dW : 當前層權重的梯度
db : 當前層偏置的梯度
"""
這(zhe)樣,我們就實(shi)現了多(duo)層神(shen)經(jing)(jing)網絡(luo)層間的正向傳(chuan)播和反(fan)向傳(chuan)播函數,如有更(geng)多(duo)興趣可以去最(zui)開始(shi)推薦(jian)的博主的博客內,看看手工搭建整個神(shen)經(jing)(jing)網絡(luo)的過(guo)程(cheng)。
2.2 搭建多層神經網絡
我們在第三周的代碼實踐中搭建的淺層神經(jing)網絡(luo)的代碼如(ru)下:
class ShallowNeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten() #
# 隱藏層:輸入128*128*3維特征,輸出4個神經元
self.hidden = nn.Linear(128 * 128 * 3, 4)
# 隱藏層激活函數:ReLU,作用是加入非線性特征
self.ReLU = nn.ReLU()
# 輸出層:將隱藏層的 4 個輸出映射為 1 個加權和
self.output = nn.Linear(4, 1)
self.sigmoid = nn.Sigmoid()
# 前向傳播方法
def forward(self, x):
# 輸入 x 的維度為 [32, 3, 128, 128]
x = self.flatten(x)
# 展平后 x 的維度為 [32, 128 * 128 * 3]
x = self.hidden(x)
# 通過隱藏層線性變換后,x 的維度為 [32, 4]
x = self.ReLU(x)
# 經過 ReLU 激活后形狀不變,仍為 [32, 4]
x = self.output(x)
# 通過輸出層得到加權和,形狀變為 [32, 1]
x = self.sigmoid(x)
# 經過 sigmoid 激活后得到 0~1 的概率值,形狀仍為 [32, 1]
return x
現在,我們再按本周理論內容里的多層神經網絡修改一下,不改變其他部分:
經過三次代碼實踐,本周的內容里也總結了層級間維度變化的規律,想必對這部分的代碼(ma)內(nei)容已經(jing)很熟悉了。這次(ci)就(jiu)不再添加(jia)詳細注釋。
class ShallowNeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
# 隱藏層
self.hidden1 = nn.Linear(128 * 128 * 3, 5)
self.hidden2 = nn.Linear(5, 5)
self.hidden3 = nn.Linear(5, 3)
self.ReLU = nn.ReLU()
# 輸出層
self.output = nn.Linear(3, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.flatten(x)
x = self.hidden1(x)
x = self.ReLU(x)
x = self.hidden2(x)
x = self.ReLU(x)
x = self.hidden3(x)
x = self.ReLU(x)
x = self.output(x)
x = self.sigmoid(x)
return x
其構建的網絡結構為:

很容易就可以對應出(chu)來。
2.3 結果分析
從淺層神經網絡到多層神經網絡,網絡的規模再次擴大,帶來的效果是什么樣的呢?
我們還是先看結果:

不對。
不是哥們,多了兩層隱藏層,準確率沒怎么升,損失收斂速度還變慢了?
先別急,回憶一下,在第三周,我們對比的是邏輯回歸和淺層神經網絡,從結果上我們得到的是淺層神經網絡極大降低了損失。
當時為了控制變量,我們統一設置訓練輪次為10輪。
但有個容易忽略的前提——
當時的網絡結構比較簡單,10輪訓練足夠讓它收斂。
而現在我們提升到了多層神經網絡,模型容量更大、表達的函數更復雜,再加上學習率設置得不高,僅僅訓練10輪,根本不足以發揮出它的潛力。
我們可以把它想象成:一場賽跑,淺層網絡和多層網絡都跑10圈,淺層網絡更快,但它已經精疲力竭了,而后者剛剛結束熱身。
為了讓復雜的網絡得到更充分的訓練,現在我們把訓練輪次增加到100輪。
再次分別運行兩個網絡模型,來對比一下:

現(xian)在(zai),是不是就能發(fa)現(xian)差別了?
- 淺層神經網絡:即使訓練到100輪,準確率依舊徘徊在 60% 左右,最高也難突破。
- 多層神經網絡:在大約第30輪后準確率迅速提升,最高甚至接近 70%,之后也很少跌回 60% 以下。
這就是我們之前提到的模型的“想象力”。
網絡越深、參數越多,它越有能力去刻畫復雜的函數關系,但反過來,它也需要更多時間和數據去學習,否則就像一位天賦很高但沒受過訓練的選手,潛力被埋沒了。
這就(jiu)是多層(ceng)神經網絡的效果。
3.總結
自此,吳恩達老師的深度學習第一課的筆記內容就結束了。
最后簡單總結一下:
第一周,神經網絡簡介,從概念上簡單介紹了神經網絡。
第二周,用邏輯回歸這個單神經元網絡講解最簡單的神經網絡的傳播過程。
第三周,從邏輯回歸網絡擴展到淺層神經網絡,講解隱藏層的作用和傳播過程。
第四周,從淺層神經網絡擴展到多層神經網絡,補充更多細節和直觀效果。
總的來說,第一課還是比較基礎的內容。
之后的(de)(de)第二(er)課開始(shi)就會以神(shen)經網絡(luo)為(wei)基礎(chu),以實際(ji)網絡(luo)運行(xing)為(wei)引,介紹其中的(de)(de)各種問(wen)題和解(jie)決方(fang)案,技術,會更加偏實踐一些,也會更有“訓練AI”的(de)(de)感(gan)覺。
最后附一下(xia)這次的多層神經網絡(luo)完整代碼(ma):
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader, random_split
import matplotlib.pyplot as plt
from sklearn.metrics import accuracy_score
transform = transforms.Compose([
transforms.Resize((128, 128)),
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
dataset = datasets.ImageFolder(root='./cat_dog', transform=transform)
train_size = int(0.8 * len(dataset))
val_size = int(0.1 * len(dataset))
test_size = len(dataset) - train_size - val_size
train_dataset, val_dataset, test_dataset = random_split(dataset, [train_size, val_size, test_size])
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
class ShallowNeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
# 隱藏層
self.hidden1 = nn.Linear(128 * 128 * 3, 5)
self.hidden2 = nn.Linear(5, 5)
self.hidden3 = nn.Linear(5, 3)
self.ReLU = nn.ReLU()
# 輸出層
self.output = nn.Linear(3, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.flatten(x)
x = self.hidden1(x)
x = self.ReLU(x)
x = self.hidden2(x)
x = self.ReLU(x)
x = self.hidden3(x)
x = self.ReLU(x)
x = self.output(x)
x = self.sigmoid(x)
return x
model = ShallowNeuralNetwork()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
criterion = nn.BCELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
epochs =10
train_losses = []
val_accuracies = []
for epoch in range(epochs):
model.train()
epoch_train_loss = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device).float().unsqueeze(1)
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_train_loss += loss.item()
avg_train_loss = epoch_train_loss / len(train_loader)
train_losses.append(avg_train_loss)
model.
val_true, val_pred = [], []
with torch.no_grad():
for images, labels in val_loader:
images = images.to(device)
outputs = model(images)
preds = (outputs.cpu().numpy() > 0.5).astype(int).flatten()
val_pred.extend(preds)
val_true.extend(labels.numpy())
val_acc = accuracy_score(val_true, val_pred)
val_accuracies.append(val_acc)
print(f"輪次: [{epoch + 1}/{epochs}], 訓練損失: {avg_train_loss:.4f}, 驗證準確率: {val_acc:.4f}")
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.plot(train_losses, label='訓練損失')
plt.plot(val_accuracies, label='驗證準確率')
plt.title("訓練損失與驗證準確率隨輪次變化圖")
plt.xlabel("訓練輪次(Epoch)")
plt.ylabel("數值")
plt.legend()
plt.grid(True)
plt.show()
model.
y_true, y_pred = [], []
with torch.no_grad():
for images, labels in test_loader:
images = images.to(device)
outputs = model(images)
preds = (outputs.cpu().numpy() > 0.5).astype(int).flatten()
y_pred.extend(preds)
y_true.extend(labels.numpy())
acc = accuracy_score(y_true, y_pred)
print(f"測試準確率: {acc:.4f}")
