# fast.ai Lesson 5: Linear model and neural net from scratch

**URL:** <https://vip.studycamp.tw/t/fastai-lesson-5-linear-model-and-neural-net-from-scratch/6448>\
**Category:** fast.ai\
**Created:** [2023年九月12日 15:05 UTC](https://vip.studycamp.tw/t/fastai-lesson-5-linear-model-and-neural-net-from-scratch/6448 "2023-09-12T15:05:27Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![sky](https://vip.studycamp.tw/user_avatar/vip.studycamp.tw/sky/32/8098_2.png) [@sky](https://vip.studycamp.tw/u/sky)\
**Post date:** [2023年九月12日 15:05 UTC](https://vip.studycamp.tw/t/fastai-lesson-5-linear-model-and-neural-net-from-scratch/6448/1 "2023-09-12T15:05:27Z")

</div>

> Jeremy 老師：本節課我們將「從頭開始」構建和訓練深度學習模型，不使用任何預構建的架構、優化器或數據載入框架等。

假設現在我們不是在上課，而是面對一個真實的問題。

如何從頭到尾，自己逐步訓練模型：整理資料、根據資料建立模型、梯度下降計算、訓練模型、提高預測準確、使用 S 函數（sigmoid function）、產出結果（提交 Kaggle）。

本節所練習的題目是：[**鐵達尼號乘客存活率預測**](https://www.kaggle.com/competitions/titanic/)。

選擇這個題目是因為他的資料量很小，實作也簡單，很適合拿來當作從無到有創建模型的練習。

* * *

## [課程概要](https://forums.fast.ai/t/lesson-5-official-topic/96491)

### 老師推薦的預測筆記

> **[Exploring Survival on the Titanic](https://www.kaggle.com/code/mrisdal/exploring-survival-on-the-titanic)**
>
> Explore and run machine learning code with Kaggle Notebooks | Using data from Titanic - Machine Learning from Disaster

> **[Titanic WCG+XGBoost \[0.84688\]](https://www.kaggle.com/code/cdeotte/titanic-wcg-xgboost-0-84688/notebook)**
>
> Explore and run machine learning code with Kaggle Notebooks | Using data from multiple data sources

> **[Divide and Conquer \[0.82296\]](https://www.kaggle.com/code/pliptor/divide-and-conquer-0-82296)**
>
> Explore and run machine learning code with Kaggle Notebooks | Using data from Titanic - Machine Learning from Disaster

> **[Titanic using Name only \[0.81818\]](https://www.kaggle.com/code/cdeotte/titanic-using-name-only-0-81818/notebook)**
>
> Explore and run machine learning code with Kaggle Notebooks | Using data from multiple data sources

### 參考資料

> **[Lesson 5 official topic](https://forums.fast.ai/t/lesson-5-official-topic/96491)**
>
> This post is for topics related to lesson 5 of the course. This lesson is based partly on chapter 4 and chapter 9 of the book. This is a wiki post - feel free to edit to add links from the lesson or other useful info. \<\<\< Lesson 4｜Lesson 6 \>\>\> ...

* * *

## 整理資料

### 檢查資料正確性

一開始利用 Pandas 讀入 csv 檔，重點在 檢查資料中是否有 NaN。

Pandas 的 `isna()` 可以檢查各欄位的值是否有效，如果無效就會回傳 true。

> NaN: Not a Number 通常為無資料之意
> 
> isna(): is Not Available

```python
import torch, numpy as np, pandas as pd
np.set_printoptions(linewidth=140)
torch.set_printoptions(linewidth=140, sci_mode=False, edgeitems=7)
pd.set_option('display.width', 140)

df = pd.read_csv(path/'train.csv')

df.isna().sum()

```

輸出：

```python
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 177
SibSp 0
Parch 0
Ticket 0
Fare 0
Cabin 687
Embarked 2
dtype: int64

```

### 課外補充：mode()

> [**Pandas DataFrame mode() Method**](https://www.w3schools.com/python/pandas/ref_df_mode.asp): 列出最常見的值（眾數）

```python
import pandas as pd
data = [[1, 1, 2], [6, 4, 2], [4, 2, 1], [4, 2, 3]]
df = pd.DataFrame(data)

print(df)
print('==========')
print(df.mode())

```

輸出：

```python
   0 1 2
0 1 1 2
1 6 4 2
2 4 2 1
3 4 2 3
==========
   0 1 2
0 4 2 2

```

### 觀察、研究資料

我們可以看看，df.mode() 後的前幾筆資料：

```python
modes_test = df.mode()
modes_test

```

輸出（僅列出部分）：

| | PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | 1 | 0.0 | 3.0 | Abbing, Mr. Anthony | male | 24.0 | 0.0 | 0.0 | 1601 | 8.05 | B96 B98 | S |
| 1 | 2 | NaN | NaN | Abbott, Mr. Rossmore Edward | NaN | NaN | NaN | NaN | 347082 | NaN | C23 C25 C27 | NaN |
| 2 | 3 | NaN | NaN | Abbott, Mrs. Stanton (Rosa Hunt) | NaN | NaN | NaN | NaN | CA. 2343 | NaN | G6 | NaN |
| 3 | 4 | NaN | NaN | Abelson, Mr. Samuel | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |

列出第一筆資料：

```python
modes = df.mode().iloc[0]
modes

```

| | PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | 1 | 0.0 | 3.0 | Abbing, Mr. Anthony | male | 24.0 | 0.0 | 0.0 | 1601 | 8.05 | B96 B98 | S |

輸出：

```python
PassengerId 1
Survived 0.0
Pclass 3.0
Name Abbing, Mr. Anthony
Sex male
Age 24.0
SibSp 0.0
Parch 0.0
Ticket 1601
Fare 8.05
Cabin B96 B98
Embarked S
Name: 0, dtype: object

```

老師推薦閱讀的 iloc 文章：

> **[Pandas iloc and loc – quickly select data in DataFrames](https://www.shanelynn.ie/pandas-iloc-loc-select-rows-and-columns-dataframe/)**
>
> The iloc, loc and ix indexers for Python Pandas select rows and columns from DataFrames. Simple guide to find data by position, label & conditional statements.

### 使用眾數取代 NaN

> 提醒：因為 `inplace` 設為 True，所以會取代掉資料集中的內容。

```python
# 使用眾數取代 NaN
df.fillna(modes, inplace=True)

# 然後再看一次 NaN 的 count
df.isna().sum()

```

輸出：

```python
PassengerId 0
Survived 0
Pclass 0
Name 0
Sex 0
Age 0
SibSp 0
Parch 0
Ticket 0
Fare 0
Cabin 0
Embarked 0
dtype: int64

```

### 使用 describe 取得常用統計數據

對 DataFrame 中的資料集進行描述性統計，顯示統計數據，如平均值、標準差等。

```python
import numpy as np

df.describe(include=(np.number))

```

輸出：

| | PassengerId | Survived | Pclass | Age | SibSp | Parch | Fare |
| --- | --- | --- | --- | --- | --- | --- | --- |
| count | 891.000000 | 891.000000 | 891.000000 | 891.000000 | 891.000000 | 891.000000 | 891.000000 |
| mean | 446.000000 | 0.383838 | 2.308642 | 28.566970 | 0.523008 | 0.381594 | 32.204208 |
| std | 257.353842 | 0.486592 | 0.836071 | 13.199572 | 1.102743 | 0.806057 | 49.693429 |
| min | 1.000000 | 0.000000 | 1.000000 | 0.420000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 223.500000 | 0.000000 | 2.000000 | 22.000000 | 0.000000 | 0.000000 | 7.910400 |
| 50% | 446.000000 | 0.000000 | 3.000000 | 24.000000 | 0.000000 | 0.000000 | 14.454200 |
| 75% | 668.500000 | 1.000000 | 3.000000 | 35.000000 | 1.000000 | 0.000000 | 31.000000 |
| max | 891.000000 | 1.000000 | 3.000000 | 80.000000 | 8.000000 | 6.000000 | 512.329200 |

> 老師推薦去 google 學習什麼是 [**histogram tutorial**](https://www.google.com/search?q=histogram+tutorial&oq=histogram+tutorial)"。

### 將資料變成我們想要的樣子：數值型資料

> 💵 錢錢沒有不見，只是變成我們想要的樣子。
> 
> **將數值處理到我們希望的格式及範圍。**

利用長條圖來觀察資料特性： **長尾分佈** 。

```python
df['Fare'].hist();

```

> **長尾分佈是很常見的現象，處理方式也很簡單：取 log 值。**

> 因為 Fare 資料集中有 0，而 0 取 log 值會變無限大。與其特別針對 0 作處理，不如把資料集中所有值通通先+1 後，再取 log 值。

```python
df['LogFare'] = np.log(df['Fare']+1)

```

然後將取 log 值後的數據，再畫一次長條圖看看： **有點變形的鐘形分佈** 。這就是我們要的。

接著看看其他資料：Pclass。

```python
pclasses = sorted(df.Pclass.unique())
pclasses

# 輸出
[1, 2, 3]

```

### 將資料變成我們想要的樣子：非數值型資料

> **對於非數值型的資料，找方法用數字取代之。**

對 DataFrame 中的非數值列（例如，字符串列）進行描述性統計，顯示統計數據，如唯一值數量、最常見的值等。

```python
df.describe(include=[object])
# 比較一下前面數值型資料的語法：
# df.describe(include=(np.number))

```

輸出：

| | Name | Sex | Ticket | Cabin | Embarked |
| --- | --- | --- | --- | --- | --- |
| count | 891 | 891 | 891 | 891 | 891 |
| unique | 891 | 2 | 681 | 147 | 3 |
| top | Braund, Mr. Owen Harris | male | 347082 | B96 B98 | S |
| freq | 1 | 577 | 7 | 691 | 646 |

> 方法是建立新的列，將這些非數值型資料，轉成我們想要的數值型資料，並填入其值到新的列。
> 
> 這些值不是 1 就是 0，例如： `Sex` 是 `'male'` 時，其值就是 1，否則為 0。

~~使用獨熱編碼（One-Hot Encoding）~~ 將非數值列（`Sex` 、`Pclass` 、`Embarked` ）轉換為虛擬變數，使它們適合用於機器學習模型。這會為每個類別創建一個新的二元列。

Pandas 可以使用 `get_dummies` 自動創建這些列，這也會刪除原始列。

我們將創建虛擬變量 `Pclass` ，即使它是數字，數字 `1` 、`2` 和`3` 對應於一等艙、二等艙和三等艙 - 而不是有意義的乘法計數或度量。

我們還將為 `Sex` 和創建虛擬變量 `Embarked` ，因為我們希望將它們用作模型中的預測變量。

另一方面，`Cabin` 、`Name` 、 和 `Ticket` 有太多唯一值，因此為它們創建虛擬變量沒有意義。

```python
df = pd.get_dummies(df, columns=["Sex","Pclass","Embarked"])
df.columns

```

輸出：

```python
Index(['PassengerId', 'Survived', 'Name', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'LogFare', 'Sex_female', 'Sex_male',
       'Pclass_1', 'Pclass_2', 'Pclass_3', 'Embarked_C', 'Embarked_Q', 'Embarked_S'],
      dtype='object')

```

| 原參數(列) | 新參數(列) |
| --- | --- |
| `Sex` | ‘Sex\_female’, ‘Sex\_male’ |
| `Pclass` | ‘Pclass\_1’, ‘Pclass\_2’, ‘Pclass\_3’ |
| `Embarked` | ‘Embarked\_C’, ‘Embarked\_Q’, ‘Embarked\_S’ |

這些新的列轉換，用於將類別變數（例如性別、艙位等級、登船港口）轉換為虛擬變數，以便在機器學習模型中使用。

每個原始列都被轉換成一個或多個二進制列（不是 1 就是 0），其中每個可能的值都有一個相對應的列，用1表示存在，0表示不存在。

這種轉換有助於模型處理類別變數，因為模型通常需要數值輸入，無法處理非數值資料。

參考資料：

> **[獨熱](https://zh.wikipedia.org/zh-tw/%E7%8B%AC%E7%83%AD)**
>
> 獨熱（英語：One-hot）在數位電路和機器學習中被用來表示一種特殊的位元組或向量，該位元組或向量裏僅容許其中一位爲1，其他位都必須爲0。其被稱爲獨熱因爲其中只能有一個1，若情況相反，只有一個0，其餘爲1，則稱爲獨冷（One-cold）。在統計學中，虛擬變數代表了類似的概念。
> 獨熱碼常常被用來表示一個有限狀態機的狀態（英語：State (computer science)）。如果使用二進制或格雷碼來代表狀態，則需要用到解碼器才能得知該碼代表的狀態。使用獨熱碼來代表狀態的話，則不需要解碼器，因爲若第
>   
>     
>       
> n
>       
>     
> {\\displaystyle n}
>   
> 個位元爲1，就代表機器目前在第
>   
>     
>       
> n
>       
>     
> {\\displaystyle n}
>   
> 個狀態。 
> 一個有限狀態機的例子是由15個狀態構成的環狀計數器（英語：Ring counter）。使用獨熱編碼來實現此狀態機的話，可以將15個正反器串聯在一起，每個正反器的Q輸出接到下一個正反器的D輸入，而第一個正...

[https://axk51013.medium.com/不要再做one-hot-encoding-b5126d3f8a63](https://axk51013.medium.com/%E4%B8%8D%E8%A6%81%E5%86%8D%E5%81%9Aone-hot-encoding-b5126d3f8a63)

將上述新參數(列) 製作成 list，顯示前面幾行來觀察 值的改變。

```python
added_cols = ['Sex_male', 'Sex_female', 'Pclass_1', 'Pclass_2', 'Pclass_3', 'Embarked_C', 'Embarked_Q', 'Embarked_S']
df[added_cols].head()

```

| | Sex\_male | Sex\_female | Pclass\_1 | Pclass\_2 | Pclass\_3 | Embarked\_C | Embarked\_Q | Embarked\_S |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
| 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 |
| 2 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 |
| 3 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 |
| 4 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |

建一個名為 `t_dep` 的 PyTorch tensor（張量），其中包含了目標變數 `Survived` 的數據。

```python
from torch import tensor

t_dep = tensor(df.Survived)

```

建立一個包含特徵列名的列表，其中包括年齡（Age）、兄弟姐妹／配偶（SibSp）、父母／子女（Parch）和轉換後的票價（LogFare），以及剛剛新增的虛擬變數列。

```python
indep_cols = ['Age', 'SibSp', 'Parch', 'LogFare'] + added_cols

t_indep = tensor(df[indep_cols].values, dtype=torch.float)
t_indep

```

輸出：

```python
tensor([[22.0000, 1.0000, 0.0000, 2.1102, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 1.0000],
        [38.0000, 1.0000, 0.0000, 4.2806, 0.0000, 1.0000, 1.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000],
        [26.0000, 0.0000, 0.0000, 2.1889, 0.0000, 1.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 1.0000],
        [35.0000, 1.0000, 0.0000, 3.9908, 0.0000, 1.0000, 1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000],
        [35.0000, 0.0000, 0.0000, 2.2028, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 1.0000],
        [24.0000, 0.0000, 0.0000, 2.2469, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 1.0000, 0.0000],
        [54.0000, 0.0000, 0.0000, 3.9677, 1.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000],
        ...,
        [25.0000, 0.0000, 0.0000, 2.0857, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 1.0000],
        [39.0000, 0.0000, 5.0000, 3.4054, 0.0000, 1.0000, 0.0000, 0.0000, 1.0000, 0.0000, 1.0000, 0.0000],
        [27.0000, 0.0000, 0.0000, 2.6391, 1.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000],
        [19.0000, 0.0000, 0.0000, 3.4340, 0.0000, 1.0000, 1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000],
        [24.0000, 1.0000, 2.0000, 3.1966, 0.0000, 1.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 1.0000],
        [26.0000, 0.0000, 0.0000, 3.4340, 1.0000, 0.0000, 1.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000],
        [32.0000, 0.0000, 0.0000, 2.1691, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 1.0000, 0.0000]])

```

檢查一下資料集大小（891 x 12）

```python
t_indep.shape

# 輸出
torch.Size([891, 12])

```

* * *

## 根據資料建立模型

> 把資料集調整成我們想要的格式和數值後，接下來就是建立模型。

現在我們已經有了矩陣（ **自變量** 和 **因變量向量** ），我們可以計算我們的 **預測** 和 **損失** 。在本節中，我們將手動執行一個步驟，來 **計算每行數據的預測和損失** 。

### 第一個模型：線性模型

我們需要為 `t_indep` 中的每一列提供一個係數，在 `(-0.5,0.5)` 範圍內選擇隨機數。

```python
## 設置 PyTorch 的隨機種子，以確保結果的可重複性
torch.manual_seed(442)  

## 計算特徵數量，即獨立變數的數目（12）
n_coeff = t_indep.shape[1]  

## 創建一個包含隨機數的 PyTorch 張量，這些隨機數範圍在 -0.5 到 0.5 之間
## 這些數字將作為模型的權重。
coeffs = torch.rand(n_coeff)-0.5  

## 顯示查看結果
coeffs

```

輸出：

```python
tensor([-0.4629, 0.1386, 0.2409, -0.2262, -0.2632, -0.3147, 0.4876, 0.3136, 0.2799, -0.4392, 0.2103, 0.3625])

```

我們的預測將每行乘以係數並將它們相加來計算。

這裡我們不需要單獨的常數項（也稱為“偏差”或“截距”項），也不需要一列所有 `1` 來給出具有常數項的相同效果。

因為我們的虛擬變量已經覆蓋了整個數據集。例如：有一個 “男性” 列和一個 “女性”列，並且數據集中的每個人都恰好屬於其中一個。

因此，我們不需要單獨的截距項來覆蓋不屬於列的行。

線性模型的計算步驟：對 `t_indep` （獨立變數）和 `coeffs` （權重）進行逐元素相乘，產生一個預測值。

```python
t_indep*coeffs

```

輸出：

```python
tensor([[-10.1838, 0.1386, 0.0000, -0.4772, -0.2632, -0.0000, 0.0000, 0.0000, 0.2799, -0.0000, 0.0000, 0.3625],
        [-17.5902, 0.1386, 0.0000, -0.9681, -0.0000, -0.3147, 0.4876, 0.0000, 0.0000, -0.4392, 0.0000, 0.0000],
        [-12.0354, 0.0000, 0.0000, -0.4950, -0.0000, -0.3147, 0.0000, 0.0000, 0.2799, -0.0000, 0.0000, 0.3625],
        [-16.2015, 0.1386, 0.0000, -0.9025, -0.0000, -0.3147, 0.4876, 0.0000, 0.0000, -0.0000, 0.0000, 0.3625],
        [-16.2015, 0.0000, 0.0000, -0.4982, -0.2632, -0.0000, 0.0000, 0.0000, 0.2799, -0.0000, 0.0000, 0.3625],
        [-11.1096, 0.0000, 0.0000, -0.5081, -0.2632, -0.0000, 0.0000, 0.0000, 0.2799, -0.0000, 0.2103, 0.0000],
        [-24.9966, 0.0000, 0.0000, -0.8973, -0.2632, -0.0000, 0.4876, 0.0000, 0.0000, -0.0000, 0.0000, 0.3625],
        ...,
        [-11.5725, 0.0000, 0.0000, -0.4717, -0.2632, -0.0000, 0.0000, 0.0000, 0.2799, -0.0000, 0.0000, 0.3625],
        [-18.0531, 0.0000, 1.2045, -0.7701, -0.0000, -0.3147, 0.0000, 0.0000, 0.2799, -0.0000, 0.2103, 0.0000],
        [-12.4983, 0.0000, 0.0000, -0.5968, -0.2632, -0.0000, 0.0000, 0.3136, 0.0000, -0.0000, 0.0000, 0.3625],
        [-8.7951, 0.0000, 0.0000, -0.7766, -0.0000, -0.3147, 0.4876, 0.0000, 0.0000, -0.0000, 0.0000, 0.3625],
        [-11.1096, 0.1386, 0.4818, -0.7229, -0.0000, -0.3147, 0.0000, 0.0000, 0.2799, -0.0000, 0.0000, 0.3625],
        [-12.0354, 0.0000, 0.0000, -0.7766, -0.2632, -0.0000, 0.4876, 0.0000, 0.0000, -0.4392, 0.0000, 0.0000],
        [-14.8128, 0.0000, 0.0000, -0.4905, -0.2632, -0.0000, 0.0000, 0.0000, 0.2799, -0.0000, 0.2103, 0.0000]])

```

我把以上幾串資料，重新整理以下表格（僅放第一行），應該就可以一眼看清楚了。

- **t\_indep：精心整理過的乘客數值（僅放第一行）。**

- **coeffs： `(-0.5,0.5)` 範圍內的隨機數值。**

| | Age | SibSp | Parch | LogFare | Sex\_male | Sex\_female | Pclass\_1 | Pclass\_2 | Pclass\_3 | Embarked\_C | Embarked\_Q | Embarked\_S |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| t\_indep | 22.0000 | 1.0000 | 0.0000 | 2.1102 | 1.0000 | 0.0000 | 0.0000 | 0.0000 | 1.0000 | 0.0000 | 0.0000 | 1.0000 |
| coeffs | -0.4629 | 0.1386 | 0.2409 | -0.2262 | -0.2632 | -0.3147 | 0.4876 | 0.3136 | 0.2799 | -0.4392 | 0.2103 | 0.3625 |
| 相乘結果 | -10.1838 | 0.1386 | 0.0000 | -0.4772 | -0.2632 | -0.0000 | 0.0000 | 0.0000 | 0.2799 | -0.0000 | 0.0000 | 0.3625 |

檢視資料，很明顯 Age 的值與其他相比太大，需要處理。

我們將 Age 的值，改為除以最大值，這樣它的值，就會在 0~1 之間。（這樣才不會某一列因為值太高，而影響了其權重）

```python
vals,indices = t_indep.max(dim=0)
t_indep = t_indep / vals

```

然後再做一次。

```python
t_indep*coeffs

```

輸出：

```python
tensor([[-0.1273, 0.0173, 0.0000, -0.0765, -0.2632, -0.0000, 0.0000, 0.0000, 0.2799, -0.0000, 0.0000, 0.3625],
        [-0.2199, 0.0173, 0.0000, -0.1551, -0.0000, -0.3147, 0.4876, 0.0000, 0.0000, -0.4392, 0.0000, 0.0000],
        [-0.1504, 0.0000, 0.0000, -0.0793, -0.0000, -0.3147, 0.0000, 0.0000, 0.2799, -0.0000, 0.0000, 0.3625],
        [-0.2025, 0.0173, 0.0000, -0.1446, -0.0000, -0.3147, 0.4876, 0.0000, 0.0000, -0.0000, 0.0000, 0.3625],
        [-0.2025, 0.0000, 0.0000, -0.0798, -0.2632, -0.0000, 0.0000, 0.0000, 0.2799, -0.0000, 0.0000, 0.3625],
        [-0.1389, 0.0000, 0.0000, -0.0814, -0.2632, -0.0000, 0.0000, 0.0000, 0.2799, -0.0000, 0.2103, 0.0000],
        [-0.3125, 0.0000, 0.0000, -0.1438, -0.2632, -0.0000, 0.4876, 0.0000, 0.0000, -0.0000, 0.0000, 0.3625],
        ...,
        [-0.1447, 0.0000, 0.0000, -0.0756, -0.2632, -0.0000, 0.0000, 0.0000, 0.2799, -0.0000, 0.0000, 0.3625],
        [-0.2257, 0.0000, 0.2008, -0.1234, -0.0000, -0.3147, 0.0000, 0.0000, 0.2799, -0.0000, 0.2103, 0.0000],
        [-0.1562, 0.0000, 0.0000, -0.0956, -0.2632, -0.0000, 0.0000, 0.3136, 0.0000, -0.0000, 0.0000, 0.3625],
        [-0.1099, 0.0000, 0.0000, -0.1244, -0.0000, -0.3147, 0.4876, 0.0000, 0.0000, -0.0000, 0.0000, 0.3625],
        [-0.1389, 0.0173, 0.0803, -0.1158, -0.0000, -0.3147, 0.0000, 0.0000, 0.2799, -0.0000, 0.0000, 0.3625],
        [-0.1504, 0.0000, 0.0000, -0.1244, -0.2632, -0.0000, 0.4876, 0.0000, 0.0000, -0.4392, 0.0000, 0.0000],
        [-0.1852, 0.0000, 0.0000, -0.0786, -0.2632, -0.0000, 0.0000, 0.0000, 0.2799, -0.0000, 0.2103, 0.0000]])

```

> 提醒：不只是 Age 這樣做，而是 Age, SibSp, Parch & LogFare 全都這樣做。
> 
> 其他我們新建的 dummy 參數，不是 1 就是 0，除以最大值的事對他們沒影響。

老師特別提醒，這一行程式超級酷：`t_indep = t_indep / vals`

> 這就是矩陣除以向量。

這裡的技巧是我們利用 numpy 和 PyTorch（以及許多其他語言，一直追溯到 APL）中稱為 [**broadcasting**](https://numpy.org/doc/stable/user/basics.broadcasting.html) 的技術。

這就像矩陣的每一行都有一個單獨的向量副本一樣，因此它將矩陣的每一行除以向量。

在實務中，它並不製作任何副本，而是以高度優化的方式完成整個過程，充分利用現代 CPU（或 GPU，如果我們使用 GPU）。

**broadcasting** （廣播）是使程式碼簡潔、可維護且快速的最重要技術之一，非常值得學習和實踐。

現在，我們可以通過將乘積的行相加，從線性模型創建預測：

〉 計算預測值，將歸一化後的獨立變數 `t_indep` 與權重 `coeffs` 相乘， **然後對每一行的結果進行求和** ，得到最終的預測結果。

```python
preds = (t_indep*coeffs).sum(axis=1)

## 顯示前十筆資料看看
preds[:10]

```

輸出：

```python
tensor([0.1927, -0.6239, 0.0979, 0.2056, 0.0968, 0.0066, 0.1306, 0.3476, 0.1613, -0.6285])

```

這些預測不會有任何用處，因為我們的係數是隨機的。但我們可以利用它們做為 **梯度下降** 的起點。

為了進行梯度下降，我們需要一個 **損失函數** 。取 **行的平均誤差** （即預測與因變量之間的差的絕對值）通常是一種合理的方法。

> 損失函數的計算，用於評估模型的性能。
> 
> 計算預測值與實際目標值之間的絕對誤差的平均值，並將其存儲在 `loss` 變數中。

```python
loss = torch.abs(preds - t_dep).mean()
loss

```

輸出：

```python
tensor(0.5382)

```

> 將前面的推斷公式，整理成 function：
> 
> `calc_preds` ：計算預測值，接受權重 `coeffs` 和獨立變數 `indeps` 作為參數，並返回預測結果。
> 
> `calc_loss`：計算損失，接受權重 `coeffs` 、獨立變數 `indeps` 和目標變數 `deps` 作為參數，並返回平均絕對誤差（MAE）的值，這也是模型的損失函數。

```python
def calc_preds(coeffs, indeps): 
    return (indeps*coeffs).sum(axis=1)

def calc_loss(coeffs, indeps, deps): 
    return torch.abs(calc_preds(coeffs, indeps)-deps).mean()

```

* * *

## 實作梯度下降（ gradient descent）

啟用 `coeffs` 張量的梯度追蹤，使其能夠計算梯度。（請看輸出最後一個欄位）

```python
coeffs.requires_grad_()

```

輸出：

```python
tensor([-0.4629, 0.1386, 0.2409, -0.2262, -0.2632, -0.3147, 0.4876, 0.3136, 0.2799, -0.4392, 0.2103, 0.3625], requires_grad=True)

```

計算當前權重 `coeffs` （之前亂數產生的）下的模型損失，並將其存儲在 `loss` 變數中。

```python
loss = calc_loss(coeffs, t_indep, t_dep)
loss

```

輸出：（除了和前面相同的 0.5382 外，還有一個 grad\_fn function）

```python
tensor(0.5382, grad_fn=<MeanBackward0>)

```

使用 PyTorch `backward()` 計算梯度：

> backward()：根據損失函數的梯度，計算 `coeffs` 張量的梯度。
> 
> 這個步驟是反向傳播（Backpropagation）的一部分，用於計算模型權重的更新方向。

```python
loss.backward()

```

檢查 `coeffs` 張量的梯度，顯示當前權重下的梯度值。

```python
coeffs.grad

```

輸出：

```python
tensor([-0.0106, 0.0129, -0.0041, -0.0484, 0.2099, -0.2132, -0.1212, -0.0247, 0.1425, -0.1886, -0.0191, 0.2043])

```

每次我們呼叫 `backward()` 計算梯度，梯度都會被加到 `.grad` 屬性中。

例如：我們把上述三步驟再執行一次。

```python
loss = calc_loss(coeffs, t_indep, t_dep)
loss.backward()
coeffs.grad

```

輸出：就會發現所有的值都變兩倍。

```python
tensor([-0.0212, 0.0258, -0.0082, -0.0969, 0.4198, -0.4265, -0.2424, -0.0494, 0.2851, -0.3771, -0.0382, 0.4085])

```

所以我們每次呼叫後，必須先歸 0（`coeffs.grad.zero_()`）。

```python
loss = calc_loss(coeffs, t_indep, t_dep)
loss.backward()

___

## 進入一個不計算梯度的程式段落，這表示以下的操作不會影響模型權重的梯度計算。
with torch.no_grad():
    ## 使用梯度下降法更新權重 coeffs。這是一個常見的權重更新步驟。
    ## 將當前的權重減去學習率（0.1）乘以梯度的值，以向損失函數最小化的方向調整權重。
    coeffs.sub_(coeffs.grad * 0.1)

    ## 將 coeffs 張量的梯度歸零，以準備進行下一次梯度計算。
    coeffs.grad.zero_()

    ## 列印更新後的權重 coeffs 下的新損失，以利觀察
    print(calc_loss(coeffs, t_indep, t_dep))

```

輸出：

```python
tensor(0.4945) ## 原為 0.5382

```

> 程式補充：
> 
> 在 PyTorch 中，結尾是 `_` 的 method，表示計算後的值，會取代原有值。
> 
> `a.sub_(b)` 表示 `a` 減 `b` 。（然後取代原有值）
> 
> `a.zero_()` 同理，這表示將值歸 0。（然後取代原有值）

* * *

## 訓練模型

開始訓練模型前，要準備一組驗證集。

老方法，我們將原有資料集拆為兩份，一組訓練集、一組驗證集。（使用 fastai 庫中的 `RandomSplitter` 函數）

```python
from fastai.data.transforms import RandomSplitter

trn_split,val_split=RandomSplitter(seed=42)(df)

```

上述的訓練集和驗證集（trn\_split,val\_split），運用在實際資料中。

```python
## 從獨立變數 t_indep 中，拆份訓練集和驗證集
trn_indep,val_indep = t_indep[trn_split],t_indep[val_split]

## 從目標變數 t_dep 中，拆份訓練集和驗證集
trn_dep,val_dep = t_dep[trn_split],t_dep[val_split]

## 查看訓練集和驗證集的長度（樣本數量）各為多少
len(trn_indep),len(val_indep)

```

輸出：

```python
(713, 178)

```

> `update_coeffs` 用於根據梯度下降法更新權重 `coeffs` 。
> 
> 這個函數將權重減去學習率 `lr` 乘以梯度的值，然後將梯度歸零，以準備下一次梯度計算。

```python
def update_coeffs(coeffs, lr):
    coeffs.sub_(coeffs.grad * lr)
    coeffs.grad.zero_()

```

> `one_epoch` 代表一個訓練周期。
> 
> 在每個訓練周期內，計算訓練集上的損失，然後根據梯度下降法更新權重，最後列印出損失的值當參考。

```python
def one_epoch(coeffs, lr):
    loss = calc_loss(coeffs, trn_indep, trn_dep)
    loss.backward()
    with torch.no_grad(): update_coeffs(coeffs, lr)
    print(f"{loss:.3f}", end="; ")

```

> `init_coeffs` 用於初始化權重 `coeffs` 。
> 
> 它產生一個包含隨機數的 PyTorch 張量，並啟用梯度追蹤。

```python
def init_coeffs(): 
    return (torch.rand(n_coeff)-0.5).requires_grad_()

```

> `train_model` 訓練模型：接受訓練的周期數 `epochs` 和學習率 `lr` 作為參數。
> 
> 首先初始化權重 `coeffs` ，然後進行指定數量的訓練周期，每個周期使用 `one_epoch` 函數進行訓練和權重更新，最終返回訓練完成後的權重。

```python
def train_model(epochs=30, lr=0.01):
    torch.manual_seed(442)
    coeffs = init_coeffs()
    for i in range(epochs): 
        one_epoch(coeffs, lr=lr)
    return coeffs

```

實際來試試看吧！

```python
coeffs = train_model(18, lr=0.2)

```

輸出：感覺還不錯，做 18次，損失從 0.536 降到 0.289。

```python
0.536; 0.502; 0.477; 0.454; 0.431; 0.409; 0.388; 0.367; 0.349; 0.336; 0.330; 0.326; 0.329; 0.304; 0.314; 0.296; 0.300; 0.289; 

```

> `show_coeffs` 將權重和對應的特徵列名稱組合成一個字典，並返回該字典。
> 
> 該函數將權重的梯度追蹤關閉，以防止對其進行梯度計算。

```python
def show_coeffs(): 
    return dict(zip(indep_cols, coeffs.requires_grad_(False)))

show_coeffs()

```

輸出：

```python
{'Age': tensor(-0.2694),
 'SibSp': tensor(0.0901),
 'Parch': tensor(0.2359),
 'LogFare': tensor(0.0280),
 'Sex_male': tensor(-0.3990),
 'Sex_female': tensor(0.2345),
 'Pclass_1': tensor(0.7232),
 'Pclass_2': tensor(0.4112),
 'Pclass_3': tensor(0.3601),
 'Embarked_C': tensor(0.0955),
 'Embarked_Q': tensor(0.2395),
 'Embarked_S': tensor(0.2122)}

```

* * *

## 提高預測準確度

> Kaggle 競賽並不是通過絕對誤差（我們的損失函數）來評分，它是根據 **準確性** 評分，也就是我們正確預測存活的比例。

簡單的說，就是用驗證集來調整參數，提高預測存活的比例。

```python
preds = calc_preds(coeffs, val_indep)

```

當預測預測存活比率大於 0.5時（preds \> 0.5），表示預測結果為存活，反之則是死亡。

所以我們可以用驗證集資料來預測每個人是否存活，然後和實際結果作比對。

正確預測存活的比例，就是正確預測的總數，除以驗證集資料總數。

```python
results = val_dep.bool()==(preds>0.5)
results[:16]

```

輸出：

```python
tensor([True, True, True, True, True, True, True, True, True, True, False, False, False, True, True, False])

```

```python
results.float().mean()

```

輸出：

```python
tensor(0.7865)

```

寫個函數來專門計算預測正確比例。

```python
def acc(coeffs): 
    return (val_dep.bool()==(calc_preds(coeffs, val_indep)>0.5)).float().mean()

acc(coeffs)

```

輸出：

```python
tensor(0.7865)

```

* * *

## 使用 S 函數提高預測正確率

> S 函數（sigmoid function）

我們之前的預測，有個明顯的問題：對存活機率的預測，有些 `>1` ，也有些 `<0`，這顯然是要修正的。

顯示模型對前 28 個樣本的預測結果。

```python
preds[:28]

```

輸出：

```python
tensor([ 0.8160, 0.1295, -0.0148, 0.1831, 0.1520, 0.1350, 0.7279, 0.7754, 0.3222, 0.6740, 0.0753, 0.0389, 0.2216, 0.7631,
         0.0678, 0.3997, 0.3324, 0.8278, 0.1078, 0.7126, 0.1023, 0.3627, 0.9937, 0.8050, 0.1153, 0.1455, 0.8652, 0.3425])

```

我們使用 S 函數（sigmoid function）來解決這個問題。

> 繪製一個 S 形曲線圖，表示邏輯函數（Logistic Function），其公式為 1 / (1 + exp(-x))。
> 
> 這個函數通常用於二元分類問題中，將連續的輸入映射到 0 到 1 之間，用於計算概率。

以下函數將最小值設為零，最大值為一。

```python
import sympy

sympy.plot("1/(1+exp(-x))", xlim=(-5, 5));

```

> 本處繪圖略

因為 S 函數很常用，PyTorch 已經預先定義了該函數（`torch.sigmoid`）。

我們將之前的 `calc_preds` 改寫，使用 `torch.sigmoid` 將線性組合的結果映射到 0 到 1 之間。：

```python
def calc_preds(coeffs, indeps): 
    return torch.sigmoid((indeps*coeffs).sum(axis=1))

```

現在用這個更新後的函數來計算預測，訓練一個新模型：

> 重新訓練模型，但這次使用更高的學習率（lr=100）。看看使用不同學習率訓練模型會有什麼效果。

```python
coeffs = train_model(lr=100)

```

輸出：感覺 loss 更低

```python
0.510; 0.327; 0.294; 0.207; 0.201; 0.199; 0.198; 0.197; 0.196; 0.196; 0.196; 0.195; 0.195; 0.195; 0.195; 0.195; 0.195; 0.195; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 

```

使用剛剛的函數來計算預測正確比例：

```python
acc(coeffs)

```

輸出：也是比較好

```python
tensor(0.8258)

```

```python
show_coeffs()

```

輸出：蠻合理。老年人和男生存活機會低些，而頭等艙乘客存活機會高些。

```python
{'Age': tensor(-1.5061),
 'SibSp': tensor(-1.1575),
 'Parch': tensor(-0.4267),
 'LogFare': tensor(0.2543),
 'Sex_male': tensor(-10.3320),
 'Sex_female': tensor(8.4185),
 'Pclass_1': tensor(3.8389),
 'Pclass_2': tensor(2.1398),
 'Pclass_3': tensor(-6.2331),
 'Embarked_C': tensor(1.4771),
 'Embarked_Q': tensor(2.1168),
 'Embarked_S': tensor(-4.7958)}

```

* * *

## 產出結果（提交 Kaggle）

> 將資料填入一開始的 csv 檔，其中的專有欄位。

首先讀取 csv 檔。

```python
tst_df = pd.read_csv(path/'test.csv')

```

對測試集中的票價（`Fare`）列進行處理，將缺失值（NaN）填充為 `0`。

```python
tst_df['Fare'] = tst_df.Fare.fillna(0)

```

將訓練集執行的相同步驟，在測試集上執行來預處理數據：

```python
## 使用訓練數據集中的眾數 modes 對測試集中的缺失值進行填充。
## 這確保了測試數據的處理方式與訓練數據一致。
tst_df.fillna(modes, inplace=True)

## 對測試集中的票價（Fare）列進行對數轉換，並將結果存儲在新的 LogFare 列中
tst_df['LogFare'] = np.log(tst_df['Fare']+1)

## 使用獨熱編碼對測試集中的非數值列（Sex、Pclass、Embarked）進行轉換
tst_df = pd.get_dummies(tst_df, columns=["Sex","Pclass","Embarked"])

## 從測試數據中選擇獨立變數列並轉換為 PyTorch 張量
tst_indep = tensor(tst_df[indep_cols].values, dtype=torch.float)

## 對測試數據中的獨立變數進行歸一化處理
tst_indep = tst_indep / vals

```

使用訓練好的模型權重 `coeffs` 對測試數據進行預測，並將預測結果存儲在新的 `Survived` 列中。

預測值大於0.5的被視為生存（Survived=1），否則被視為未生存（Survived=0）。

```python
tst_df['Survived'] = (calc_preds(tst_indep, coeffs)>0.5).int()

```

選擇測試數據中的乘客ID（PassengerId）和預測的生存狀態（Survived）列，存在 `sub_df` 的 DataFrame 中。

```python
sub_df = tst_df[['PassengerId','Survived']]
sub_df.to_csv('sub.csv', index=False)

```

檢視前幾行的數據：

```python
!head sub.csv

```

輸出：

```python
PassengerId,Survived
892,0
893,0
894,0
895,0
896,0
897,0
898,1
899,0
900,1

```

在 Kaggle 中點擊 “保存版本”並等待筆記本運行時，會看到 `sub.csv` 出現在 “數據” 選項卡中。

點擊該文件將顯示 **提交** 按鈕，點擊即可提交參賽作品。

* * *

## 使用矩陣乘積

計算使用權重 `coeffs` 預測的結果。這是將驗證集的獨立變數 `val_indep` 逐元素與權重 `coeffs` 相乘，然後對每一行的結果進行求和，得到最終的預測值。

```python
(val_indep*coeffs).sum(axis=1)

```

輸出：

```python
tensor([ 12.3288, -14.8119, -15.4540, -13.1513, -13.3512, -13.6469, 3.6248, 5.3429, -22.0878, 3.1233, -21.8742, -15.6421, -21.5504,
          3.9393, -21.9190, -12.0010, -12.3775, 5.3550, -13.5880, -3.1015, -21.7237, -12.2081, 12.9767, 4.7427, -21.6525, -14.9135,
         -2.7433, -12.3210, -21.5886, 3.9387, 5.3890, -3.6196, -21.6296, -21.8454, 12.2159, -3.2275, -12.0289, 13.4560, -21.7230,
         -3.1366, -13.2462, -21.7230, -13.6831, 13.3092, -21.6477, -3.5868, -21.6854, -21.8316, -14.8158, -2.9386, -5.3103, -22.2384,
        -22.1097, -21.7466, -13.3780, -13.4909, -14.8119, -22.0690, -21.6666, -21.7818, -5.4439, -21.7407, -12.6551, -21.6671, 4.9238,
        -11.5777, -13.3323, -21.9638, -15.3030, 5.0243, -21.7614, 3.1820, -13.4721, -21.7170, -11.6066, -21.5737, -21.7230, -11.9652,
        -13.2382, -13.7599, -13.2170, 13.1347, -21.7049, -21.7268, 4.9207, -7.3198, -5.3081, 7.1065, 11.4948, -13.3135, -21.8723,
        -21.7230, 13.3603, -15.5670, 3.4105, -7.2857, -13.7197, 3.6909, 3.9763, -14.7227, -21.8268, 3.9387, -21.8743, -21.8367,
        -11.8518, -13.6712, -21.8299, 4.9440, -5.4471, -21.9666, 5.1333, -3.2187, -11.6008, 13.7920, -21.7230, 12.6369, -3.7268,
        -14.8119, -22.0637, 12.9468, -22.1610, -6.1827, -14.8119, -3.2838, -15.4540, -11.6950, -2.9926, -3.0110, -21.5664, -13.8268,
          7.3426, -21.8418, 5.0744, 5.2582, 13.3415, -21.6289, -13.9898, -21.8112, -7.3316, 5.2296, -13.4453, 12.7891, -22.1235,
        -14.9625, -3.4339, 6.3089, -21.9839, 3.1968, 7.2400, 2.8558, -3.1187, 3.7965, 5.4667, -15.1101, -15.0597, -22.9391,
        -21.7230, -3.0346, -13.5206, -21.7011, 13.4425, -7.2690, -21.8335, -12.0582, 13.0489, 6.7993, 5.2160, 5.0794, -12.6957,
        -12.1838, -3.0873, -21.6070, 7.0744, -21.7170, -22.1001, 6.8159, -11.6002, -21.6310])

```

這是使用矩陣乘法符號 `@` 來計算預測的另一種方式。

它表示將 `val_indep` 矩陣與 `coeffs` 張量相乘，會得到相同的預測結果。

```python
val_indep@coeffs

```

重新定義 `calc_preds` 函數，這次使用矩陣乘法。

它計算模型的預測結果，並將結果通過 sigmoid 函數進行轉換。

```python
def calc_preds(coeffs, indeps): 
    return torch.sigmoid(indeps@coeffs)

```

重新定義了 `init_coeffs` 函數，這次初始化權重 `coeffs` 為一個列向量，而不是行向量。

每個權重值都乘以 0.1，並啟用梯度追蹤。

為了進行矩陣-矩陣乘積（我們將在下一節中需要），我們需要轉換`coeffs` 為列向量（即具有單列的矩陣），我們可以通過將第二個參數傳遞`1` 給 來實現`torch.rand()` ，表示我們希望我們的係數只有一列：

```python
def init_coeffs(): return (torch.rand(n_coeff, 1)*0.1).requires_grad_()

```

將因變量轉換為列向量，我們可以通過使用特殊值 索引列維度來完成此操作，`None` 該值告訴 PyTorch 在該位置添加新維度：

將訓練集和驗證集的目標變數轉換為列向量，以適應一些後續的矩陣運算。這樣做是為了確保矩陣乘法的維度相容性。

```python
trn_dep = trn_dep[:,None]
val_dep = val_dep[:,None]

```

```python
coeffs = train_model(lr=100)

```

輸出：

```python
0.512; 0.323; 0.290; 0.205; 0.200; 0.198; 0.197; 0.197; 0.196; 0.196; 0.196; 0.195; 0.195; 0.195; 0.195; 0.195; 0.195; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 0.194; 

```

```python
acc(coeffs)

```

輸出：

```python
tensor(0.8258) ## 一樣的數字

```

* * *

## 神經網絡

現在我們已經有了實現神經網絡所需的東西。

首先，我們需要為每一層創建係數。

第一組係數使用 `n_coeff` 當作輸入，並創建輸出 `n_hidden`。

我們可以選擇任何 `n_hidden`：數字愈高彈性愈大，但速度會變慢、更難訓練。

所以我們需要一個大小為 `n_coeff` 乘 `n_hidden` 的矩陣。

把係數除以 `n_hidden` ，這樣當我們在下一層中求和時，可以得到和開始時同樣幅度的數字。

然後我們的第二層，使用 `n_hidden` 當作輸入，並創建單一輸出。所以我們需要一個`n_hidden` 乘 `1` 的矩陣。

第二層還需要添加一個常數項。

> 本函數用於初始化神經網絡的權重。它初始化三個部分：第一層權重 `layer1` 、第二層權重 `layer2` ，以及一個常數 `const` 。所有這些都啟用了梯度追蹤。
> 
> 還有一個參數 `n_hidden` ，表示隱藏層的大小。

```python
def init_coeffs(n_hidden=20):
    layer1 = (torch.rand(n_coeff, n_hidden)-0.5)/n_hidden
    layer2 = torch.rand(n_hidden, 1)-0.3
    const = torch.rand(1)[0]
    return layer1.requires_grad_(),layer2.requires_grad_(),const.requires_grad_()

```

```python
import torch.nn.functional as F

## 參數：接受三個部分的權重 coeffs（l1、l2 和 const）和獨立變數 indeps。
def calc_preds(coeffs, indeps):
    ## 首先將獨立變數 indeps 通過第一層權重 l1 進行線性變換，然後使用 ReLU 激活函數。
    l1,l2,const = coeffs
    res = F.relu(indeps@l1)

    ## 接著將結果通過第二層權重 l2 進行線性變換，再加上常數 const。
    res = res@l2 + const

    ## 最後，使用 sigmoid 函數將結果映射到 0 到 1 之間，以獲得最終的預測結果。
    return torch.sigmoid(res)

```

用於更新神經網絡權重的函數

它接受權重 `coeffs` 和學習率 `lr` ，然後遍歷所有的權重層，使用梯度下降法來更新權重。

```python
def update_coeffs(coeffs, lr):
    for layer in coeffs:
        layer.sub_(layer.grad * lr)
        layer.grad.zero_()

```

使用學習率 1.4 重新訓練神經網絡模型。

這是用來調整模型的超參數，以觀察不同學習率下的模型效能。

```python
coeffs = train_model(lr=1.4)

```

輸出：

```python
0.543; 0.532; 0.520; 0.505; 0.487; 0.466; 0.439; 0.407; 0.373; 0.343; 0.319; 0.301; 0.286; 0.274; 0.264; 0.256; 0.250; 0.245; 0.240; 0.237; 0.234; 0.231; 0.229; 0.227; 0.226; 0.224; 0.223; 0.222; 0.221; 0.220; 

```

再次使用不同的學習率（lr=20）重新訓練模型，以進行進一步的超參數調整。

```python
coeffs = train_model(lr=20)

```

輸出：

```python
0.543; 0.400; 0.260; 0.390; 0.221; 0.211; 0.197; 0.195; 0.193; 0.193; 0.193; 0.193; 0.193; 0.193; 0.193; 0.193; 0.193; 0.192; 0.192; 0.192; 0.192; 0.192; 0.192; 0.192; 0.192; 0.192; 0.192; 0.192; 0.192; 0.192; 

```

```python
acc(coeffs)

```

輸出：

```python
tensor(0.8258)

```

結論：本例中，神經網絡並沒有比線性模型更好。

這並不奇怪，這個數據集非常小而且非常簡單，並不是我們期望看到神經網絡擅長的事情。

此外，我們的驗證集太小，無法可靠地看到很大的準確性差異。

但關鍵是我們現在確切地知道真正的神經網絡是什麼樣子！

* * *

## 深度學習

上一節中的神經網絡僅使用一個隱藏層，因此它不算“深度”學習。但是我們可以使用同樣的技術，添加更多的矩陣乘法來使我們的神經網絡變得更深。

首先，我們需要為每一層創建額外的係數：

重新定義了 `init_coeffs` 函數，這次允許配置多個隱藏層。

在這個函數中，你可以設定 `hiddens` 列表，其中包含每個隱藏層的大小。

函數會自動計算出各層的大小，然後初始化相應的權重矩陣和常數。這個函數的返回值包括多個層的權重和常數。

```python
def init_coeffs():
    hiddens = [10, 10] # <-- set this to the size of each hidden layer you want
    sizes = [n_coeff] + hiddens + [1]
    n = len(sizes)
    layers = [(torch.rand(sizes[i], sizes[i+1])-0.3)/sizes[i+1]*4 for i in range(n-1)]
    consts = [(torch.rand(1)[0]-0.5)*0.1 for i in range(n-1)]
    for l in layers+consts: l.requires_grad_()
    return layers,consts

```

> 重新定義 calc\_preds 函數，這次可以處理多層神經網絡。
> 
> 函數接受 `coeffs` ，其中包含了多個層的權重和常數。
> 
> 它遍歷每一層，進行線性變換和激活操作（使用 ReLU 函數），最後使用 sigmoid 函數映射到0到1之間，獲得最終的預測結果。

這裡有很多混亂的常量來使隨機數處於正確的範圍內。

訓練模型時，你會發現這些初始化的最微小的變化，都可能導致我們的模型根本無法訓練！

這是深度學習在早期未能取得太大進展的一個關鍵原因：為我們的係數找到一個好的起點是非常挑剔的。

如今，我們有辦法解決這個問題，之後將在其他筆記本中了解這些方法。

```python
import torch.nn.functional as F

def calc_preds(coeffs, indeps):
    layers,consts = coeffs
    n = len(layers)
    res = indeps
    for i,l in enumerate(layers):
        res = res@l + consts[i]
        if i!=n-1: res = F.relu(res)
    return torch.sigmoid(res)

```

重新定義 `update_coeffs` 函數，這次可以處理多層神經網絡。

函數接受 `coeffs` ，其中包含了多個層的權重和常數。

它遍歷每一層的權重和常數，使用梯度下降法來更新它們。

```python
def update_coeffs(coeffs, lr):
    layers,consts = coeffs
    for layer in layers+consts:
        layer.sub_(layer.grad * lr)
        layer.grad.zero_()

```

```python
coeffs = train_model(lr=4)

```

輸出：

```python
0.521; 0.483; 0.427; 0.379; 0.379; 0.379; 0.379; 0.378; 0.378; 0.378; 0.378; 0.378; 0.378; 0.378; 0.378; 0.378; 0.377; 0.376; 0.371; 0.333; 0.239; 0.224; 0.208; 0.204; 0.203; 0.203; 0.207; 0.197; 0.196; 0.195; 

```

```python
acc(coeffs)

```

輸出：

```python
tensor(0.8258)

```

* * *

## 結語

我們已經成功地從頭開始創建了一個真正的深度學習模型，並對其進行了訓練，使其在該任務上獲得了超過 80% 的準確率。

研究和工業中使用的 “真正的” 深度學習模型看起來與此非常相似。事實上，如果查看任何深度學習模型的原始碼，你會發現基本步驟是相同的​​。

實際模型與我們以上實作的最大區別是：

- 如何進行初始化和標準化，以確保模型每次都能正確訓練

- 正則化（避免過度擬合）

- 以該問題領域的知識，修改神經網絡

- 對較小的批次，而不是整個數據集，進行梯度下降。

* * *

## 觀看課程影片

[![](https://img.youtube.com/vi/_rXzeWq4C6w/maxresdefault.jpg "Lesson 5: Practical Deep Learning for Coders 2022") ](https://www.youtube.com/watch?v=_rXzeWq4C6w)
