# CS231N (2017) Lec 06 課程筆記
###### tags: `CV`
## 🔷 Recap
### 📌 Loss func. (Lec 03), NN (Lec 04), CNN (Lec 05)
(1) 計算圖(computational graphs)
(2) 神經網路(neural network)
🔘 一種圖形(graph)結構
🔘 堆疊多個線性層,其中穿插非線性層(激勵函數)
(3) 卷積神經網路(convolutional neural network)
🔘 使用卷積層以保持空間結構(spatial structure)
🔘 激勵圖(activation map)是卷積層的輸出
🔘 激勵圖的產生方式:
在輸入的所有空間位置上滑動權重濾鏡,
並在對應窗格上,以輸入和濾鏡的點積作為輸出
🔘 每一層可以具有多個濾鏡,濾鏡的數量稱為深度(depth)
🔘 每一個濾鏡對應一個單獨的激勵圖
(4) 神經網路的學習方法
🔘 梯度下降法:
透過指定的最佳化方法,不斷地: 學習(更新)神經網路的參數(權重)
🔘 小批次隨機梯度下降法(Mini-Batch SGD)
Loop:
1. 「抽樣」一批資料
2. 透過「順向傳播」將資料逐層向前傳遞到整個計算圖(神經網路)
並得到: 損失(殘差)
3. 透過「反向傳播」計算梯度
4. 利用梯度去「更新」參數
## 🔷 Training Neural Network
### 📌 Overview
(1) 準備工作(One time setup)
🔘 激勵函數(activation function)
🔘 資料預處理(data preprocessing)
🔘 權重初始化(weight initialization)
🔘 正規化(regularization)
🔘 梯度檢查(gradient checking)
(2) 訓練動態(Training dynamics)
🔘 學習過程(babysitting the learning process)
🔘 參數更新(parameter updates)
🔘 超參數最佳化(hyperparameter optimization)
(3) 評估(Evaluation)
🔘 模型評估(model evaluation)
🔘 模型集成(model ensembles)
### 📌 激勵函數(activation function)
(1) Sigmoid
🔘 將數值範圍壓縮到 [0,1]
🔘 歷史上因為類似神經元飽和放電率而被頻繁使用
// 從根本不放電(0)到以假定的最大頻率(1)完全飽和放電
🔘 x = -10, 梯度 = 0
x = 0, 梯度 呈線性狀態(linear regime)
x = 10, 梯度 = 0
🔘 缺點
▫️缺點 1: 當 Sigmoid 的 input 為一個「絕對值很大的實數」
➡️ Sigmoid 的 output 處於「飽和」的情況
➡️ 可能扼殺梯度流(gradient flow)
🌟 參考作法-1: 資料預處理: 歸一化(Normalization) 或 標準化(Standardization)
🌟 參考作法-2: 改用 tanh 或 ReLU 等作為激勵函數
// 解釋:
// 當此函數的 input 為一個絕對值很大的正數或負數,
// 會得到接近 0 的 output,並作為上游梯度(upstream gradient)
// 接著,其下游梯度(位於更靠近輸入層的節點)因為鏈鎖率:
// 下游梯度 = 局部梯度 * 上游梯度,
// 使: 下游梯度 接近 0,
// 進而影響: 不同節點上的梯度開始連帶地出現很多零值,讓「梯度反向傳播」無法正常運算
//
// 補充-2:
// https://blog.csdn.net/weixin_44120025/article/details/114699890
// 若激勵函數採用 Sigmoid,在「初始化權重」時必須特別小心,以防「飽和」的情況發生
// 例: 如果「初始權重太大」,可能導致「飽和」,使網路將幾乎無法學習
▫️ 缺點 2: Sigmoid 的 output 不是以零為中心
➡️ 鋸齒形路徑(zig-zag path)
➡️ 低效的梯度更新
Q: 若神經元的輸入 X 恆為正,會有何影響?
A:
假設函數為 f ( Σ_i (w_i * x_i + b) )
權重 W 的梯度為: 上游梯度 * 局部梯度 = (∂L / ∂F) * (∂F / ∂W) = (∂L / ∂F) * X
---
因為: 輸入 X 恆為正
➡️ 反向傳播的梯度方向恆保持上游節點的梯度方向(∂L / ∂F)
➡️ 參數更新時,權重矩陣 W 中的所有權重值: 若非「全為正」則為「全為負」
➡️ 鋸齒形路徑(zig-zag path)
➡️ 低效的梯度更新(inefficient gradient update)
圖例: "📌 CS 231n lecture slides > p21"
// 補充-1:
// Hyper-Parameter Optimization: A Review of Algorithms and Applications
// (https://arxiv.org/pdf/2003.05689.pdf)
// ➡️ Related contents in page 14
// 補充-2:
// Downsides of the sigmiod activation and why you should center your inputs
// (https://rohanvarma.me/inputnormalization/)
▫️ 缺點 3: 自然指數(e)計算成本昂貴
(2) tanh
🔘 將數值範圍壓縮到 [-1,1]
🔘 output 以零為中心
🔘 缺點: 仍有飽和問題
// 當 input 為一個「絕對值很大的實數」可能扼殺梯度流(gradient flow)
//
// 補充:
// [Machine Learning] Tanh 函式介紹與程式實作
// https://clay-atlas.com/blog/2019/10/22/machine-learning-notes-tanh-function/
(3) ReLU(Rectifier Linear Unit, 整流線性單位函式)
🔘 計算 f = max ( 0, x )
換言之,激勵函數被簡單地設計為零閾值(threshold = 0)
🔘 在 "+" 區域: 沒有飽和問題
// 很大的優勢
🔘 計算效率很高
// 不像 sigmoid、 tanh 需要計算成本高的: 自然指數(e)
🔘 收斂速度比 sigmoid 和 tanh 更快
// 與 sigmoid、tanh 函數相比,
// 它被發現大大加速了隨機梯度下降的收斂(例: [Krizhevsky et al.] 的 6 倍)
// 有人認為這是由於它的線性、非飽和形式
🔘 在生物神經科學實驗上比 sigmoid 更合理
🔘 x = -10, 梯度 = 0
x = 0, 梯度 未定義
x = 10, 梯度 呈線性狀態(linear regime)
🔘 缺點:
▫️ output 不以零為中心
▫️ 潛在的 dead ReLU 問題:
🌟 因為 ReLU 有以下性質:
1. 在 零點(input: x = 0)的輸出未定義(實際上通常指定為 0)
2. 在 正半邊(input: x > 0)的輸出呈「線性」
➡️ 導數 = 1
➡️ 梯度反向傳播: 梯度 = 1
// 梯度不會越乘越小
➡️ 不用擔心 梯度消失 的問題
3. 在 負半邊(input: x < 0)的輸出都是 0
➡️ 導數 = 0
➡️ 梯度反向傳播: 梯度 = 0
// 意義: 此神經元不活躍(does not activated)
➡️ 影響
優: (1) 梯度不會越乘越小 >> 不用擔心 梯度消失 的問題
(2) 提供神經網路「稀疏性」
缺: 仍可能扼殺梯度流(kill the gradient flow)
> 參考: 前面 sigmoid 部分的說明
通常稱為「ReLU 神經元壞死問題」(Dying ReLU Problem)
即:
當 ReLU 神經元「大部分的輸入值為負數」
➡️ 在 [梯度反向傳播] 過程中: 計算出大量的零梯度
➡️ (1) 無法透過 [梯度反向傳播] 產生合理的梯度流(gradient flow)
(2) 無法更新參數(權重)
➡️ 神經網路停止學習
// 損失值(loss)不再隨訓練下降
注意: 「ReLU 神經元壞死問題」是在「訓練過程中」才會發生的問題
且「訓練初期」可能一切正常(不會有問題)
直到某個時間點: 神經網路因大量 ReLU 神經元(單元)壞死而無法繼續學習
// 補充:
// 「ReLU 神經元壞死問題」並不經常發生
// 因為: 優化器向網路提供各種輸入,並非所有輸入都在負範圍內
// 只要「輸入仍有一些正值」
// ➡️ 就仍有一些活躍神經元
// ➡️ 活躍神經元隨著梯度持續反向傳播,產生合理的梯度流
// ➡️ 神經網路可以持續學習(參數/權重)
// ---
// 參考:
// Dying ReLU Problem! - Keep your neural network alive...
// https://www.linkedin.com/pulse/dying-relu-problem-keep-your-neural-network-alive-snigdha-kakkar?trk=pulse-article
// 可能原因/例子:
// 1. 權重初始化問題
// "bad" weight initialization(i.e., W)
// ➡️ W * x + b 無法被觸發(i.e., (W * x + b) < threshold = 0)
// ➡️ 可能導致 ReLU 神經元壞死問題(i.e., 無法產生合理的梯度流)
// ---
// 例如: 將偏項(bias term)設定成一個絕對值很大的負數
// ➡️ 偏項: b 也會被考慮進 ReLU 函數的輸入(i.e., W * x + b)
// ➡️ W * x + b 無法被觸發(i.e., (W * x + b) < threshold = 0)
// ➡️ 可能導致 ReLU 神經元壞死問題
// ---
// 參考:
// Dying ReLU Problem! - Keep your neural network alive...
// https://www.linkedin.com/pulse/dying-relu-problem-keep-your-neural-network-alive-snigdha-kakkar?trk=pulse-article
//
// 2. 學習率太高
// 原因: 在 [更新權重] 時,「學習率太高」導致「權重更新量: | △W | 很大」
// ➡️ 權重 反覆地跳來跳去(難以收斂)
// ➡️ 在訓練過程中,
// 很多 ReLU 神經元因為被「資料流形」擊潰(gets knocked off of the data manifold)而壞死
//
// 結果: 多達 40% 的神經網路可能「壞死」
// (不活躍的神經元太多,難以從輸入的訓練資料集中學習)
//
// 改善方式: 設定適當的學習率
// ---
// 參考:
// (1) CS231N 課程筆記
// https://cs231n.github.io/neural-networks-1/
//
// (2) Data Science 論壇 / What is the "dying ReLU" problem in neural networks?
// https://datascience.stackexchange.com/questions/5706/what-is-the-dying-relu-problem-in-neural-networks
🌟 當在一個 2D 平面檢視資料分佈,會有以下不同種類的集合:
1. 資料雲(data cloud): 即「訓練資料」
2. 活躍的 ReLU
3. 壞死的 ReLU : 永不活躍(never activated) ➡️ 永不更新(never update)
🌟 權重初始化
偏項(bias term)通常設定為一個很小的正數,如: 0.01
🌟 試圖緩解 dead ReLU 的方法:
(1) [Arnekvist et al., 2020]
Arnekvist, I., Carvalho, J. F., Kragic, D., and Stork, J. A. (2020),
The effect of Target Normalization and Momentum on Dying ReLU
(2) [Lu et al., 2020]
Lu, L., Shin, Y., Su, Y., and Karniadakis, G. E. (2020),
Dying ReLU and Initialization: Theory and Numerical Examples
(3) [Matthew et al., 2022]
Matthew Frederick Ernst (2022),
Late Residual Neural Networks: An Approach to Combat the Dead Relu Problem
(4) ReLU 的變形: “Leaky ReLU”, “eLU”
(4) Leaky ReLU
🔘 論文:
[Mass et al., 2013]
Andrew L. Maas, Awni Y. Hannun, Andrew Y. Ng,
Rectifier Nonlinearities Improve Neural Network Acoustic Models
🔘 計算 f = max ( 0.01 * x, x )
負輸入區域的斜率不為 0,而是 0.01 * x
➡️ 解決 ReLU 惱人的 dead ReLU 問題的關鍵
🔘 繼承了 ReLU 的優點:
▫️ 計算效率很高
// 不像 sigmoid、 tanh 需要計算成本高的: 自然指數(e)
▫️ 收斂速度比 sigmoid 和 tanh 更快
// 與 sigmoid、tanh 函數相比,
// 加速了隨機梯度下降的收斂(例: [Krizhevsky et al.] 的 6 倍)
🔘 相較於 ReLU 更具優勢的改進:
▫️ 沒有飽和問題
// 即使在 "-" 區域(input: x < 0)也不會飽和
▫️ 解決了潛在的 dead ReLU 問題
(5) PReLU(Parametric ReLU, 參數線性整流)
🔘 論文:
[He et al., 2015]
Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun,
Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification
🔘 計算 f = max ( α * x, x )
負輸入區域的斜率不為 0,而是 α * x
相較 Leaky ReLU 直接指定 α = 0.01
改用未指定的參數: α 決定負輸入區域的斜率
🔘 繼承了 ReLU 的優點:
▫️ 計算效率很高
▫️ 收斂速度比 sigmoid 和 tanh 更快
🔘 相較於 ReLU 更具優勢的改進:
▫️ 沒有飽和問題
▫️ 解決了潛在的 dead ReLU 問題
🔘 相較於 Leaky ReLU 更具優勢的改進
▫️ 不同的神經元可以有不同的參數,因此更靈活(有彈性)
(6) ELU(Parametric ReLU, 參數線性整流)
🔘 論文:
[Clevert et al., 2015]
Djork-Arné Clevert, Thomas Unterthiner, Sepp Hochreiter,
Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs)
🔘 計算 f = { x if x > 0
α * exp(x) -1 if x <= 0 }
🔘 相較於 ReLU 更具優勢的改進:
▫️ 沒有飽和問題
▫️ 解決了潛在的 dead ReLU 問題
▫️ 更接近零均值(zero mean)輸出
🔘 相較於 Leaky ReLU 更具優勢的改進
▫️ 在 ReLU 的負飽和區(negative saturation regime)
對雜訊具有更高的強健性
🔘 缺點:
▫️ 計算成本較高
// 需要自然指數(e, exp())
(7) Maxout “Neuron”
🔘 論文:
[Goodfellow et al., 2013]
Ian J. Goodfellow, David Warde-Farley, Mehdi Mirza, Aaron Courville, Yoshua Bengio,
Maxout Networks
🔘 計算 max ( transpose(w_1) * x + b_1, transpose(w_2) * x + b_2 )
// 上述是一個上限值 k = 2 的特例
// 可以擴展為更多個 w_ij * x + b_ij 的形式
// 以「權重和資料的點積」的方式為模型帶來「非線性」
🔘 相較於 ReLU 更具優勢的改進:
▫️ 沒有飽和問題
▫️ 解決了潛在的 dead ReLU 問題
🔘 相較於 Leaky ReLU 更具優勢的改進
▫️ 廣義化的 ReLU 或 Leaky ReLU
// 例如,ReLU 可以作為 Maxout 的特例
// 即: max ( transpose(w_1) * x + b_1, transpose(w_2) * x + b_2
// 在 w_1 = 0, b_1 = 0 條件下,同義於 ReLU: max ( 0, w * x + b )
🔘 缺點:
▫️ 計算成本較高
// 每個神經元的參數數量加倍
(8) 小結: 實務上選擇激勵函數的方向
🔘 使用 ReLU 提供非線性
▫️ 要小心地設定「學習率」
▫️ 盡可能地監控: 神經網路中「壞死」神經元的比例
// Dead ReLU Problem
🔘 避免 Dead ReLU 問題的作法
▫️ 使用 Leaky ReLU/ELU/Maxout
// 沒必要的話不要用 Sigmoid
// 可試試 tanh,但它的效果預計會比 ReLU/Maxout 差
### 📌 資料預處理(data preprocessing)
🔘 處理資料
▫️ X
➡️ 原始資料(original data)
➡️ 假設 X 是一個資料矩陣,每個 例子/樣本 位於圖例中的每一列
▫️ X -= np.mean( X, axis=0 )
➡️ output: 以零為中心/零均值化的資料(zero-centric data)
➡️ 「機器學習」的普遍作法
➡️ 影像資料上用得比較多
// 原因: 避免 zig-zag path
// 回顧之前的討論
// 若所有輸入皆大於 0
// >> 會使: 權重 W 的梯度 皆正 或 皆負
// >> 梯度更新的方向呈「鋸齒形路徑」(zig-zag path)
// >> 低效的梯度更新
▫️ X / np.std( X, axis=0 )
➡️ output: 標準化的資料(normalized data)
➡️ 「機器學習」的普遍作法
➡️ 影像資料上用得比較少
// 與機器學習上「一般的/廣義的」資料相比
// 「影像資料」在每個位置上已經有
// 相對可比較的尺度和分佈(relatively comparable scale and distribution)
// 如: pixels
▫️ PCA
➡️ output: 去相關的資料(decorrelated data)
// 減少一個信號內的自相關或一組信號內的互相關
➡️ 「機器學習」的普遍作法
➡️ 影像資料上用得比較少
▫️ Whitening
➡️ output: XX 的資料(whitened data)
➡️ 「機器學習」的普遍作法
➡️ 影像資料上用得比較少
▫️ 小結: 對於影像資料的預處理,實務上: 零均值化(zero-centric)比較常被使用
// 補充:
// 一般會在訓練前執行一次,得到 [平均] 後儲存起來,在訓練集、測試集都要扣掉 [平均]
➡️ 例 1: AlexNet
預處理方式為: 減去「平均影像」(mean image)
// mean image = [32, 32, 3] array
➡️ 例 2: VGGNet
預處理方式為: 減去「每個頻道的平均」(per-channel mean)
// mean along each channel = 3 numbers
### 📌 權重初始化(weight initialization)
(1) Q: 假設 W = 0: 即所有權重初始值皆設定為 0,會發生什麼事?
A: 若 原始影像在每個空間位置的像素強度不同
則 輸入 x (向量)的每個元素(神經元的數值)各不相同
因為 W = 0
得 W * x + b = 0 * x + b = b
使「所有神經元的輸出值相同」,即: 每個神經元的輸出都是 b
進而使: 反向傳播時,所有權重的梯度皆相同
換言之,同一層的不同神經元: 都以相同數值做參數更新
而這不是期望看到的結果
// 補充:
// 一般會希望以「對稱性破壞」(symmetry breaking)的方式執行權重初始化
(2) First idea: 「小隨機數」(small random numbers)
🔘 隨機抽樣 - 條件:
➡️ 服從: 零均值的高斯分佈
➡️ 標準差為 1e-2(0.01)
🔘 適用性
➡️ 適用於小型網絡
➡️ 但用在更深的網絡會有問題
🔘 激勵函數的統計資料
➡️ 10 層網路,每層有 500 個神經元,使用 tanh 非線性
➡️ 按照以下方式進行權重初始化:
W = np.random.randn( D, H ) * 0.01
// D: width of input image
// H: num of neurons in the 1st hidden layer
🔘 方法
條件: (1) 使用 tanh 作為激勵函數 (2) W 乘以標準差項 0.01
➡️ 按照以下方式進行權重初始化:
W = np.random.randn( D, H ) * 0.01
🔘 結果: 不理想
所有激勵值(activations)都趨於 0
// W 趨於 0 >> W * x 趨於 0 >> 上游梯度趨於 0
// >> 下游梯度( = 局部梯度 * 上游梯度)越乘越小,且最終趨於 0
(3) Second idea: 「W = np.random.randn( D, H ) * 1 」(1 instead of 0.01)
🔘 方法
條件: (1) 使用 tanh 作為激勵函數 (2) W 乘以標準差項 1.0
➡️ 按照以下方式進行權重初始化:
W = np.random.randn( D, H ) * 1.0
🔘 結果: 不理想
所有激勵值(activations)若非 -1 則為 1
// 幾乎所有神經元都完全飽和
// 梯度最終趨於 0
(4) Xavier initialization [Xavier Glorot et al., 2010]
🔘 概念: 輸入變異 = 輸出變異
🔘 方法
條件: 使用 tanh 作為激勵函數
➡️ 按照以下方式進行權重初始化:
W = np.random.randn( fan_in, fan_out ) / np.sqrt( fan_in )
🔘 結果
優勢: 如果輸入的數值很小, 會將 權重 W 除以一個較小的數值
// 導入了: 類似 feature scaling 的概念
缺點 or 問題: 當改用 ReLU 作為 activation function 時,大約一半的 units 會 deactivated
// 一半的神經元數值都變成 "0"
(5) [He et al., 2015]
🔘 概念: 因為 Xavier initialization 在 ReLU 作為激勵函數下
會使大約一半的神經元變成 0
He 等人調整 初始化函數 的公式
即: 將 np.sqrt( fan_in ) 改為 np.sqrt( fan_in / 2 )
🔘 方法
條件: 使用 ReLU 作為激勵函數
➡️ 按照以下方式進行權重初始化:
W = np.random.randn( fan_in, fan_out ) / np.sqrt( fan_in / 2 )
(6) Related Works in "Weight Initialization"
// Serena Yeung: 權重初始化還是個活躍的研究主題 (said in 2017)
🔘 相關文獻
Understanding the difficulty of training deep feedforward neural networks
by Glorot and Bengio, 2010
Exact solutions to the nonlinear dynamics of learning in deep linear neural networks
by Saxe et al, 2013
Random walk initialization for training very deep feedforward networks
by Sussillo and Abbott, 2014
Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification
by He et al., 2015
Data-dependent Initializations of Convolutional Neural Networks
by Krähenbühl et al., 2015
All you need is a good init
by Mishkin and Matas, 2015
(7) Batch Normalization
🔘 概念: 希望得到 Unit Gaussian Activation
// 因此, 導入 Z-score Normalization, 對輸入特徵做處理
// 得到標準常態分佈(平均值為 0、標準差為 1)
🔘 文獻: [Ioffe and Szegedy, 2015]
🔘 方法: 對目前的 mini batch
➡️ (1) 對每個 dimension: 計算輸入特徵的: 平均數、標準差
➡️ (2) 接者, 做 Z-score Normalization
i.e. X ' = (X - mean(X)) / std(X)
➡️ (3) 再透過以下公式壓縮數值範圍:
i.e. Y = γ X ' + β
γ : 縮放因子
β : 移動因子
// 若希望得到 identity mapping
// 令 γ = std(X), β = mean(X) 即可
🔘 優勢:
➡️ 將分散的數據統一
➡️ 減緩 梯度消失 問題
➡️ 解決 Internal Covariate Shift 的問題
➡️ 加速收斂過程
🔘 補充:
➡️ Batch Normalization 通常被當作深度神經網路的一層
放在: 全連接層 (有時是卷積層) 之後
// 例如: FC >> BN >> tanh >> FC >> BN >> tanh
### 📌 學習過程(Babysitting the Learning Process)
(1) Steps for Learning Process
🔘 Step 1: Preprocess the data
➡️ original data >> zero-centered data >> normalized data
X -= mean(X) X /= std(X)
🔘 Step 2: Choose the architecture
🔘 Step 3: Do some checks
➡️ 確保 loss function 是合理的
🔘 Step 4: Start from regularization & Find learning rate
(2) 可能遇到的問題(train 不起來的可能原因)之一: 學習率
🔘 學習率太小 >> 梯度更新量(gradient update 不夠大)
>> loss 或 cost 幾乎沒有下降
// 講者在此以 1e-6 為例
🔘 學習率太大 >> loss 突破天際
// 講者在此以 1e6 為例
🔘 合理的學習率: 大約 1e-3 到 1e-5 之間
### 📌 超參數最佳化(Hyperparameter Optimization): Cross-Validation 策略
🔘 方法
Step 1: Training --- 使用 訓練資料集 訓練 模型
Step 2: Evaluating --- 使用 驗證資料集 評估 模型
🔘 細節
在各階段中, 採用 "由粗到細" 的 Cross-Validation
例如: 第一階段 => 只執行幾個步驟, 檢視參數的大致結果
第二階段 => 花費更長一些的時間, 做更精細的搜尋
小技巧: 為了避免求解器 (solver, 優化器) 爆炸
當: 持續出現 "current cost > 3 * original cost" 可以提早退出
🔘 Random Search vs. Grid Search
文獻: Random Search for Hyper-Parameter Optimization
[Bergstra and Bengio, 2012]
🔘 超參數
// 調整超參數的目的在得到更好的 "損失函數"
➡️ 神經網路架構 (neural network architecture)
➡️ 學習率 (learning rate)
=> decay schedule, update type
➡️ 正規化 (regularization)
=> L2 regularization, Dropout strength
🔘 訓練效果判讀
➡️ 幾種 loss functions

➡️ training accuracy 和 validation accuracy 的差異
兩者差異太大,意味者 model 可能 overfitting
兩者沒有太大差異,可以增加模型的 capacity
➡️ 印出訓練過程中:權重更新量 與 權重 的比值
理想的數值接近 0.001
此數值僅作為參考,重要的是不希望單次的權重更新量太大或太小
### 📌 CS 231n Lec 06
https://www.bilibili.com/video/BV1Dx411n7UE?p=6
// https://www.bilibili.com/video/BV1BK411S7ge?p=6
📌 CS 231n Lec 06 slides
http://cs231n.stanford.edu/slides/2017/cs231n_2017_lecture6.pdf
📌 CS 231n Lec 06 notes
https://cs231n.github.io//neural-networks-1/