# 2024-09-20 DL - 主題:**Keras** - 放映影片:`Video_2024-08-14_115653._類神經網路_03.mp4` 播放完畢 - 搭配講義:[**DL Lecture 01 (How to construct an NN - Part)**](https://drive.google.com/file/d/1ugcciON1F1hrOS72I_7BOiqn_6VIxsGe/view?usp=drive_link) - 分工 - 主持人: @Ot4v6SRLSZ2amlJNMb6Ynw - 教學組 - 領讀人: @sin-iu-ho - 編譯組: @sin-iu-ho @munin - 紀錄組 - 文字組: @munin - 影音組: @as854398 (音質太差,不錄影) ## 課程內容筆記 - 目的:搭建輸入為 3,隱藏層 1 層,輸出為 1 的類神經網路。 ![image](https://hackmd.io/_uploads/ry6ZNNiTC.png =200x) - 整體類神經網路的學習方式 ![image](https://hackmd.io/_uploads/BkwUX4s6R.png =600x) - 損失函數幫助估計誤差,而優化函數用來調整權重,最終使損失最小化。 - 損失估計 (Loss Estimation):損失函數是用來衡量模型預測值與實際值之間誤差的函數。在訓練模型時,我們希望通過優化過程將這個誤差最小化。 - 權重調整 (Weight Adjustment) :這部分涉及模型在損失函數基礎上進行參數更新,以最小化損失函數,達到優化目標。 - 準備輸入資料 - 搭建第一層隱藏層 ```python= Model.add(Dense(128, activation = 'relu', input_dim = 3)) ``` - 建立第一個隱藏層的同時,也要將輸入層設定好 - `input_dim = 3` 代表 3 個輸入 - 但也可以將輸入層另外拉出設定(下方示範) - 輸出層 ```python= Model.add(Dense(1, activation = 'sigmoid')) ``` - 輸出層,只輸出 1 個 - 因為此處是解決二元分類的問題(輸出的資料 y 只有 0 和 1),所以 `activation` (激活函式)僅能選擇 `sigmoid` - 查看模型搭建的狀況 ```python= Model.summary() ``` - 此處模型的參數量 3x128+128=512 為可以訓練的參數 - 損失估計(Loss function) ```python= Model.compile(optimizer = 'rmsprop', loss = 'binary_crossentropy', metrics = ['accuracy']) ``` - `optimizer`:優化器設定 - `loss`:損失函數,(老師說)這不是人類可以了解ㄉ - `metrics`:評比(表現評量) - `accuracy`:正確率 - 權重調整(SGD+BP) ```python= Model.fit(X,y,epochs = 1000, batch_size = 3, verbose = 1) ``` - 利用前述的 `optimizer`(優化器)和 `loss`(損失函數),會根據損失函數計算出的誤差來調整每個神經元之間的權重。具體來說,模型會通過 `反向傳播(backpropagation)` 來計算每一個神經元對總損失的貢獻,並根據這些貢獻使用優化器來更新神經元間的權重,這樣模型就能逐步減小預測錯誤。 - 訓練過程中,將訓練資料放入神經網路中,模型會計算出預測結果,與實際結果相比較後得出損失值,接著根據優化器更新神經元的權重,使模型在後續的訓練中表現得更好。 :::spoiler 什麼是反向傳播(backpropagation)? 反向傳播(backpropagation)是一種優化神經網路的技術,用來更新網路中每個神經元的權重。它的目的是根據模型的預測誤差來調整這些權重,從而使預測結果更接近實際值。 具體來說,反向傳播過程分為以下幾個步驟: 1. **前向傳播(Forward Propagation)**:將輸入資料放入神經網路,經過隱藏層的計算,最終得到輸出結果(模型的預測值)。 2. **計算損失**:將網路輸出的預測值與實際的目標值進行比較,使用損失函數來計算誤差(損失值),表示模型預測與實際值的差距。 3. **反向傳播誤差**:從輸出層開始,反向計算每個神經元對損失的影響。這個過程通過鏈式法則(chain rule),逐層向前追溯,計算每個神經元的偏導數(即權重的梯度),這樣模型知道該如何調整權重來減小誤差。 4. **更新權重**:使用優化器(如梯度下降法),根據計算出的梯度來更新每個神經元的權重,使預測誤差逐漸減小。這是通過調整網路的參數(權重和偏差)來實現的。 反向傳播的核心思想就是讓模型逐步學習如何減少誤差,從而在給定的訓練資料上進行更準確的預測。 ::: - `batch_size`(批次大小) - 在訓練模型時,`batch_size` 定義了每次更新權重時,所使用的樣本數量。 - 例如,若 `batch_size` 設為 3,則模型會從資料集中選擇 3 筆資料,進行一次前向傳播、計算損失、再進行反向傳播來更新權重。 - 在這個案例中,若資料集有 8 筆資料,最多只能有 8 個資料點,因此可以選擇批次大小不超過 8。 - `epochs`(迭代次數) - 算是一種 iteration ,涉及計算 Loss、metrics,表示完整訓練資料集被送入模型進行前向傳播和反向傳播的次數。 - 每個 `epoch` 包含若干次的前向與反向計算,直到遍歷完所有訓練資料。 - 每個 `epoch` 是一個完整的「回合」,並不等於一次計算,而是所有資料都經過一次模型訓練的過程。 - `verbose`(顯示訓練狀況) - 控制訓練過程中的輸出訊息,顯示訓練進度和結果,預設為1(auto)。 - 設為 `0` 則不顯示任何信息 - 設為 `1`,會以`進度條`顯示每個 `epoch` 的詳細訓練信息 例如: Epoch 1/2 186219/186219 [==============================] - 85s 455us/step - loss: 0.5815 - acc: 0.7728 - val_loss: 0.4917 - val_acc: 0.8029 - 設為`2`會以純數字$\frac{n}{totalEpochs}$形式顯示每個epoch訓練信息,而不顯示進度條。 例如: Epoch 1/2- 88s - loss: 0.5746 - acc: 0.7753 - val_loss: 0.4816 - val_acc: 0.8075 - 訓練循環(training loop):compile() & fit() - 預測 - 通常採用 predict() 或 evaluate() 方法 ```python= import numpy as np X_test = np.array([[0,0,0]]) Model.predict(X_test) Model.evaluate(X,y) ``` - `predict(X_test)`:因為是預測 0 或 1 的二元分類問題,所以這邊會得出 0-1 之間的浮點數 - `evaluate(X,y)`:顯示模型對資料集的表現如何,計算 Loss function 與 metrics - 把輸入層從第一層隱藏層放出來的方式,指定輸入層的 shape ```python= from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Input # Model = Sequential() Model.add(Input(shape=(3,))) # Model.add(Dense(128, activation = 'relu')) Model.add(Dense(1, activation = 'sigmoid')) ``` - 課後討論速記 - 資料集英文大小寫的差別 - 向量:一維結構,使用小寫字母表示。 - 矩陣:二維結構,使用大寫字母表示,像一個「面」。 - 張量:三維或更高維度結構,通常也是大寫字母表示。 - 解決回歸問題的有哪些損失函數?[MAE](https://hackmd.io/@learnai2024/mldl-encyclopedia#Mean-square-error)、[RMSE](https://hackmd.io/@learnai2024/mldl-encyclopedia#RMSE) - Keras 中的 Sequential API 和 Functional API 的差異 - Sequential API(序列式 API) - 此處老師使用的案例是 Sequential API ,當模型的結構是簡單的逐層堆疊,且每一層的輸入來自上一層的輸出,這種「一層一層」的線性/序列式結構適合用 Sequential API 來實現。 - 應用場景如:影像分類、回歸任務等。 - Functional API(函數式 API) - 當模型的結構較為複雜,涉及到多輸入、多輸出、共享層或需要自定義拓撲結構時,可以使用 Functional API。 - 應用場景如:多模態的輸入(處理影像和文本的模型)、多輸出模型等。 - Model 是 Sequential 的實例,Model 之後所接的方法,都是 Sequential 的方法,例如 add()、compile()、fit() 等。 - 資料集通常分為 Training Data、Evaluation Data、Testing Data(對訓練模型來說的新資料) - 什麼是 overfitting 與 generalization? - [Overfitting](https://hackmd.io/@learnai2024/mldl-encyclopedia#Overfitting)(過擬合):使用有看過的資料(訓練集)表現很好,但使用沒看過的資料(測試集或其他沒使用過的資料)表現很差。 - [Generalization](https://hackmd.io/@learnai2024/mldl-encyclopedia#Generalization)(泛化能力):使用有遇過(訓練集)或沒遇過(測試集)的資料皆表現很好。 - `input_dim` 和 `input_shape` 都是在定義神經網路的輸入層時常見的參數 - `input_dim` 是維度,而 `input_shape` 是形狀。 - `input_dim` :用來指定輸入的維度(即向量的維度數量) - 適用情境:通常用在一維資料(如序列或特徵向量)上。這個參數多出現在像是 `Dense` 層這類的一維資料輸入。 - 定義:`input_dim = 3` 表示輸入是一個一維向量,該向量的長度是 `3`,這代表這個模型的每個樣本有 `3` 個特徵值。 - Example:`input_dim=3` 表示輸入是一個一維向量 `[x1, x2, x3]`,所以輸入的形狀其實是 `(3,)`。 - `input_shape`:用來定義輸入資料的形狀(包括各維度的大小),需要傳遞一個 tuple。 - 適用情境:適合用於多維資料(如二維圖片、三維視頻)或一維資料。它可以在任何層(如卷積層、LSTM層等)中使用來定義輸入的完整形狀。 - 定義:`input_shape = (3, )` 表示輸入是一個一維向量,它的形狀是 `(3,)`。這樣的寫法明確指出了輸入的形狀,不僅是維度,也指明了該維度的具體大小。 - Example:如果你有一個 28x28 的灰階圖片,你會用 `input_shape=(28, 28, 1)`(三維資料)。 - 為什麼二元分類的問題,隱藏層的 activation 是用 `ReLU` 函式,而輸出層是用 `Sigmoid`? - [ReLU](https://hackmd.io/@learnai2024/mldl-encyclopedia#ReLU) ![image](https://hackmd.io/_uploads/rkI37Vsa0.png =400x) - [sigmoid](https://hackmd.io/@learnai2024/mldl-encyclopedia#Sigmoid-function) ![image](https://hackmd.io/_uploads/HkSt7VjTA.png =400x) - 避免梯度消失(下回讀書會討論) - 引入非線性(nonlinearity)。 - 當所有層都是線性組合時,無論堆疊多少層,最後的結果還是可以通過一個線性函數來表示,這樣就無法有效學習和擬合非線性關係。 - 啟動函數(如 ReLU、Sigmoid、Tanh 等)能夠將輸入轉換成非線性的輸出,使神經網路能夠擬合複雜的非線性資料,從而增強模型的表現能力。 - 以買iphone來舉例sigmoid : 會買的情況為1,不會買的情況為0,在這sigmoid中算是一種機率分布,我們可以設定一個斷點/門檻;當其超過50%直接強制變成1(會買),小於50%則是直接變成0(不會買),但為了顧及非線性因此會使用sigmoid函數。(這裡聽起來有可能是指除了sigmoid函數也可能會有其他函數也可以用買iphone來舉例) @sin-iu-ho 麻煩確認一下 :::spoiler 參考資料 {#ref} - 同學提供 in Chat Room 1. ReLU 的使用時機: 隱藏層中的首選激活函數:ReLU 是目前神經網絡中最常用的激活函數,尤其是在深度網絡的隱藏層中,因為它計算高效,並且能有效解決梯度消失問題。 適合大多數任務的隱藏層:無論是圖像分類、自然語言處理還是其他任務,只要是隱藏層,ReLU 通常是首選激活函數,因為它能使模型學習到更深層的特徵。 大數據集和深層神經網絡:ReLU 能夠快速收斂,特別適合於大規模數據集和深層網絡。 2. Sigmoid 的使用時機: 輸出層適合二分類問題:Sigmoid 常用於二分類問題的輸出層,因為它可以將輸出值壓縮到 [ 0 , 1 ] [0,1] 之間,解釋為概率。當模型需要預測某個樣本屬於某個類別的概率時,Sigmoid 是理想選擇。 適合概率預測:當你需要將輸出值解釋為概率(如二分類或多標籤分類),Sigmoid 是非常合適的。 ::: ## 問題與討論 ==陸續新增中== :::spoiler 我們今天所搭建的神經網路適用哪一種機器學習任務? 二元分類(binary classification) ::: :::spoiler 我們用了 Keras 的哪一種 API 來搭建神經網路? - [x] Sequential:線性或序列式的網路架構。(講義 p.28) - [ ] Functional ::: :::spoiler 我們用了 [`Sequential`](https://www.tensorflow.org/api_docs/python/tf/keras/Sequential) 類別的哪些方法? - [x] `add` - [x] `summary` - [x] `compile` - [x] `fit` - [x] `evaluate` - [x] `predict` - [ ] `save` - [ ] `load_weights` ::: :::spoiler 如何搭建一個含有 128 個神經元的密集層(全連接層)? 使用 [`tf.keras.layers.Dense`](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dense) 假設使用 ReLU 作為啟動函數。 ```python= from tensorflow.keras.layers import Dense model.add(Dense(128, activation='relu')) ``` 如果這是第一層,還需要指定輸入的形狀。 - 輸入層可以在第一層定義時一併指定 ```python= Model.add(Dense(128, activation='relu', input_dim =3 )) ``` - 也可以在第一個隱藏層之前單獨指定 ```python= Model = Sequential() Model.add(Input(shape=(3,))) ``` ::: :::spoiler 在範例中,哪一層使用了哪一種啟動函數?為什麼? ```python= Model = Sequential() Model.add(Input(shape=(3,))) Model.add(Dense(128,activation = 'relu', input_shape = (3,) )) Model.add(Dense(1, activation = 'sigmoid')) ``` 在第一個隱藏層是使用 ReLU,在輸出層是使用 sigmoid,理由詳見 [參考資料](#ref) ::: :::spoiler 我們用哪一種損失函數?為什麼? 二元交叉熵(binary cross entropy),因為是二元分類任務。 ::: :::spoiler `fit` 方法中使用了哪些參數?它們分別代表什麼? - [x] `epoch` - [x] `batch_size` - [x] `verbose` ::: ## 延伸閱讀 - input_dim:在 TensorFlow/Keras 2.x 中,input_dim 是一個早期 Keras API 中的參數,並不是 TensorFlow 2.x 的標準參數。在 TensorFlow 2.x 中,`input_dim` 這個參數實際上已經被 `input_shape` 取代。它不再作為顯式參數出現在現代的 TensorFlow 源代碼中。如果你寫了 model.add(Dense(64, input_dim=100)) 這種語法,它實際上是通過 input_shape=(100,) 的方式來處理的。原因是 Keras 的 Dense 層會在初始化的時候,將這些額外的參數(如 input_dim)視為 input_shape 的簡化形式來自動轉換。那麼問題來了,哪裡的代碼處理了這種自動轉換?事實上,input_dim 參數的處理是發生在 Dense 層的基類 Layer 中,而不是 Dense 層本身。它是通過 Keras 的 Input 或 build 方法來自動解析的。雖然 input_dim 作為早期 API 的遺留參數,已經在新的 Keras API 中被 input_shape 取代,但它仍然可以兼容,因為內部會自動轉換。 [SourceCode](https://github.com/keras-team/keras/blob/v3.3.3/keras/src/layers/layer.py#L59-L1448 ) 以下為Dense的父層Layer其中源代碼,實際上由Layer這個類別處理input_dim這個參數 ``` python input_dim_arg = kwargs.pop("input_dim", None) if input_dim_arg is not None: input_shape_arg = (input_dim_arg,) ``` - 反向傳播: {%youtube ibJpTrp5mcE%} ## 其他議題 1. [**期初成員問卷**](https://forms.gle/gMWuL2W5uonh1tJN9) - 2024-09-27 以前開放作答 2. 讀書會分工 - 職位安排(待下週確認) - 主持人:主持會議流程,協調工作事項分配 - 教學組 - 領讀人:講課、導讀書籍,或針對影片帶領問題討論 - 編譯組:將紀錄組的文字紀錄整合講義內容,在 HackMD 上編寫讀書會筆記 - 紀錄組 - 文字組:速記課程內容;收集聊天室話題 - 影音組:控制課程影片播放;(如有需要)側錄讀書會過程 - ==讀書會參與者輪流擔任不同職位,以促進參與度==。 3. 音質太差的配套措施 - [ ] **方案 A**:使用講義搭配 Colab 程式碼,由領讀人主講 - [ ] **方案 B**:由領讀人(學務組)事先看完影片,再到讀書會上主講;此方案需要領讀人(學務組)大量付出心血,需提高誘因。 4. 新增分享會/讀書會 - 面試經驗分享:羽喬發起,上課時間預定為 2024-09-25 19:00~:20:00 - 數學基礎讀書會: @sin-iu-ho 發起,上課時間預定為 - 2024-09-24 10:00--12:00 - 2024-09-26 19:00--21:00