博客 / 詳情

返回

吳恩達深度學習課程二: 改善深層神經網絡 第三週:超參數調整,批量標準化和編程框架(五)框架演示

此分類用於記錄吳恩達深度學習課程的學習筆記。
課程相關信息鏈接如下:

  1. 原課程視頻鏈接:[雙語字幕]吳恩達深度學習deeplearning.ai
  2. github課程資料,含課件與筆記:吳恩達深度學習教學資料
  3. 課程配套練習(中英)與答案:吳恩達深度學習課後習題與答案

本篇為第二課的第三週內容,3.11的內容,也是本週理論部分的最後一篇。


本週為第二課的第三週內容,你會發現這周的題目很長,實際上,作為第二課的最後一週內容,這一週是對基礎部分的最後補充。
在整個第一課和第二課部分,我們會了解到最基本的全連接神經網絡的基本結構和一個完整的模型訓練,驗證的各個部分。
之後幾課就會進行更多的實踐和進階內容介紹,從“通用”走向“特化”。
總的來説這周的難度不高,但也有需要理解的內容,我仍會在相對較難理解的部分增加更多基礎和例子,以及最後的“人話版”總結。

本篇的內容關於框架高效性的演示,本課程這裏開始正式引入深度學習的編程框架TensorFlow,並沒有理解上的難度,只需要瞭解相應的語法即可。

1. Tensorflow的高效性

此前的我們的實踐主要使用 PyTorch,PyTorch 與 TensorFlow 是目前最主流的兩個深度學習框架,使用它們的開發者幾乎佔到了整個生態的絕大多數。二者雖然起初有一些明顯的差別,但隨着一代代更新,也越發的相似,並不存在明顯的優劣之分。

要提前説明的是,課程中對Tensorflow的演示只限於反向傳播部分,用來展示框架的高效性。我們先進行復現,之後再進行一些拓展。

1.1 Tensorflow1.x 的反向傳播

這部分我們就復現一下課程的內容。
假設現在有一個損失函數如下:

\[J(W) = W^2-10W+25 \]

你可能已經知道這個函數最小時 \(W=5\) 了,現在我們看看通過 Tensorflow 來最小化這個損失函數的過程。
首先還是導庫,這段語法我們在課程一第二週的代碼實踐裏已經介紹過了,就不再多提了。

import numpy as np
import tensorflow as tf

然後來看第一步:初始化用於優化和反向更新的變量。

# 1.定義用於反向傳播,可以更新的變量
w = tf.Variable(0,dtype = tf.float32)
# 0:初值 
# dtype = tf.float32 : 數據類型為32位浮點數
# 你可以直接把它理解為c語言裏的 float w =0; 只是在tf裏強調為進行反向傳播和優化的變量。

然後我們把剛剛的損失函數代碼化,這裏要説明的一點是,如果你看過原視頻,視頻裏還用了另一種方法來表示,但那種較為繁瑣,在實際編碼中很少在定義函數中使用,就不展示了。

# 2.定義損失函數
cost = w**2 - 10w + 25

繼續,調用梯度下降法來最小化損失函數:

# 3.進行反向傳播
train = tf.train.GradientDescentOptimizer(0.01).minimize(cost)
# GradientDescentOptimizer 直譯:梯度下降優化器,實際上,這個方法在更新中已經被移除。
# 0.01 :學習率
# minimize(cost) :類方法,最小化損失函數

現在,我們就可以看看如何開始訓練,這裏就是tf 的一些固定設置。
要強調一點的是,這種“啓動會話”的語法是tensorflow1.x的語法,在2.x的版本中有了更便捷的語法。
但有趣的是這種語法並沒有被淘汰,1.x擁有較強的兼容性和穩定性,2.x帶來的重構會讓很多使用1.x的項目出現bug,因此使用1.x仍是很多人的選擇。

# 4.初始化和創建會話
init = tf.global_variables_initializer() # 初始化所有 TensorFlow 變量,固定語法。
session = tf.Session() # 創建一個 TensorFlow 會話,用於執行計算,固定語法。
session.run(init) # 執行初始化操作,初始化圖中的變量,固定語法。
print(session.run(w)) # 打印變量 w的值(在初始化之後,它應該是 0.0)。

最後開始訓練,打印訓練完的參數:

# 5.訓練
for i in range(1000):
    session.run(train) #進行一千次訓練
print(session.run(w))# 打印訓練完的w

我們給出完整代碼

import numpy as np
import tensorflow as tf

w = tf.Variable(0,dtype = tf.float32)
cost = w**2 - 10*w + 25
train = tf.train.GradientDescentOptimizer(0.01).minimize(cost)

init = tf.global_variables_initializer()
session = tf.Session()
session.run(init)
print(session.run(w))

for i in range(1000):
    session.run(train) 
print(session.run(w))

現在,如果像課程裏一樣使用 tensorflow1.x來運行,你就可以看到w從0到無限接近5的訓練效果
就像課程裏這樣:
Pasted image 20251120112008

我們沒有定義反向傳播,沒有定義更新公式,沒有定義求導方法,這一切都由框架幫我們封裝在我們調用的函數裏,這就是框架的高效性所在。

1.2 Tensorflow2.x 的反向傳播

無論如何,2.x在2019年就已經發布了,雖然1.x仍佔有一定份額,但與時俱進仍然是必要的,我們來看看上面的代碼在2.x版本里是什麼樣的。

import tensorflow as tf
# 1. 定義變量 w,通過“.”自動識別為32位浮點型
w = tf.Variable(0.) 

# 2.優化器的API替換: GradientDescentOptimizer → SGD
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)

# 3.沒有會話機制,可以直接打印參數
print(w)
# numpy()是w的數值部分
print(w.numpy())

# 3. 訓練循環
for i in range(200):
    # GradientTape 是 TF2 的自動求導工具,
    # 相當於一個“梯度記錄器”:所有在 tape 下發生的計算
    # 都會被記錄,用來事後求梯度。
    with tf.GradientTape() as tape:
        # 每次循環都重新計算損失,GradientTape 會追蹤計算過程
        cost = w**2 - 10*w + 25
    # 自動求梯度
    # 這一步在 TF1.x 中被封裝在minimize(cost)裏。
    grads = tape.gradient(cost, [w])
    # 使用優化器(SGD)根據梯度更新 w
    # TF1.x 中 session.run(train) 就在內部完成了這一步。
    optimizer.apply_gradients(zip(grads, [w]))

# 4.打印最終 w
print(w.numpy())

其結果如下:
Pasted image 20251120161131

可以看到同樣幾乎完成了cost的最小化。

很明顯,TF的1.x和2.x有了很多API和機制上的不同,這帶來了一定程度的“陌生感”。
但無論如何,這種很龐大的,完整的框架都需要一定的學習時間,有一個熟練使用的過程。

2. pytorch 和 Tensorflow

上兩部分只是用了一個簡單的cost來演示框架在反向傳播中的高效性,現在我們已經瞭解了這一點,我們可以通過調用框架裏的模塊和方法來簡便的完成複雜網絡的構建。
現在,我們再對比一下框架間的不同。
還是用我們一直在每週的代碼實踐部分更新的pytorch框架中的網絡結構:

class NeuralNetwork(nn.Module):  
    def __init__(self):  
        super().__init__()  
        self.flatten = nn.Flatten()  
        self.hidden1 = nn.Linear(128 * 128 * 3, 1024)  
        self.hidden2 = nn.Linear(1024, 512)  
        self.hidden3 = nn.Linear(512, 128)  
        self.hidden4 = nn.Linear(128, 32)  
        self.hidden5 = nn.Linear(32, 8)  
        self.hidden6 = nn.Linear(8, 3)  
        self.relu = nn.ReLU()  
        # 輸出層  
        self.output = nn.Linear(3, 1)  
        self.sigmoid = nn.Sigmoid()  
        # Xavier初始化輸出層  
        init.xavier_uniform_(self.output.weight)  
  
    def forward(self, x):  
        x = self.flatten(x)  
        x = self.relu(self.hidden1(x))  
        x = self.relu(self.hidden2(x))  
        x = self.relu(self.hidden3(x))  
        x = self.relu(self.hidden4(x))  
        x = self.relu(self.hidden5(x))  
        x = self.relu(self.hidden6(x))  
        x = self.sigmoid(self.output(x))  
        return x

如果你看過之前每週的代碼實踐部分,想必對這段代碼並不陌生。
如果你沒有這方面的基礎也不用擔心,這只是使用另一個框架構建的網絡結構。
而用Tensorflow來構建這個網絡是這樣,你會發現二者很相似

import tensorflow as tf
from tensorflow.keras import layers, models

class NeuralNetwork(tf.keras.Model):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = layers.Flatten()
        #1.網絡層設置,少了“接水管”過程,會自動適配上一層輸出大小。
        #2.激活函數改為包含在層參數裏。
        self.hidden1 = layers.Dense(1024, activation='relu')
        self.hidden2 = layers.Dense(512, activation='relu')
        self.hidden3 = layers.Dense(128, activation='relu')
        self.hidden4 = layers.Dense(32, activation='relu')
        self.hidden5 = layers.Dense(8, activation='relu')
        self.hidden6 = layers.Dense(3, activation='relu')
        self.output = layers.Dense(1, activation='sigmoid')
        
    def call(self, x):
        x = self.flatten(x)
        x = self.hidden1(x)
        x = self.hidden2(x)
        x = self.hidden3(x)
        x = self.hidden4(x)
        x = self.hidden5(x)
        x = self.hidden6(x)
        x = self.output(x)
        return x

model = NeuralNetwork()

# 3.初始化
# 對於 ReLU激活函數,TensorFlow默認使用 He 初始化
# 對於 sigmoid(或者其他飽和激活函數),TensorFlow 默認使用Xavier初始化
initializer = tf.initializers.GlorotUniform()
model.output.kernel_initializer = initializer

在大體結構相似下二者仍有各自的不同之處,因為框架的龐大,本篇展示的只是冰山一角。
總的來説,框架的本質還是使用別人封裝好的代碼,在瞭解,學習的基礎上,使用什麼框架,框架的什麼版本都還是應以適合自己為主。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.