# Lesson 12 | 神經網路概論1
## 第一節:人工神經網路(1)
* 這堂課程,我們要介紹第三次人工智能爆發在演算法上的突破:
* 人工神經網路(Artificial neural network)。
* 人工神經網路是參考大腦神經元運作所設計出來的演算法。大腦是神經系統的一部分,而他是由多個神經元互相結合而成的。
* (下圖為Ramón y Cajal在1905年所畫下的神經細胞樣貌)

* 神經細胞的構造如下,神經元有接收區、觸發區、傳導區和輸出區。

* 神經細胞傳導訊息的過程是透過電位的變化,在接收區會接收到一個電位改變的訊號,再交給突觸來處理。透過樹突(dendrite)能接收上一個神經元的訊息,而有些會在接收訊息後產生抑制性作用,有些會產生興奮性作用,然後這些訊號再透過神經元整合,之後再透過軸突(axon)將訊號傳導出去。

* 我們根據這樣的生物學知識,開始來用電腦模擬一個簡單的神經元。
## 第一節:人工神經網路(2)
* 第一代的人工神經網路是1958年由Frank Rosenblatt所發展的感知機(Perceptron),此時還稱不上是神經「網路」,他的結構如下:

* 讓我們用數學語言來描述他:
\begin{align}
\mbox{weighted sum} & = w_{0} + w_{1}x_1 + w_{2}x_2 + \dots \\
\hat{y} & = step(\mbox{weighted sum})
\end{align}
* 仔細一看,他跟線性回歸實在是很像,差別在output經過了一個轉換。
## 第一節:人工神經網路(3)
* 顯然大腦的構成不是僅僅使用一個神經元,既然回歸的構造與神經元很像,所以我們試著把多個回歸結合在一起吧!

* 我們要怎麼在Python裡面實作人工神經網路呢?
* 在Python裡面常用的深度學習框架有: [PyTorch](https://pytorch.org/)與[TensorFlow](https://www.tensorflow.org/?hl=zh-tw)等。
* 在這堂課,我們將實作Pytorch,請按[這裡](https://pytorch.org/get-started/locally/)觀看如何安裝。

```
pip3 install torch torchvision torchaudio
```
## 第一節:人工神經網路(4)
* 訓練人工網路,我們要依序把下面兩個部份準備好:
* 資料:對於不同的預測函數需要指定不同的結構,你只要記住你想要用的部分即可
* 模型結構:負責定義預測函數
* 後面我們依序學習如何編寫這2個部分,並了解怎樣把2個部分結合在一起運算。
## 第一節:人工神經網路(5)
* 讓我們使用真實資料來進行實驗吧!請至[這裡](https://linchin.ndmctsgh.edu.tw/data/ECG_train.csv)下載範例資料
```python=
import pandas as pd
data = pd.read_csv('ECG_train.csv')
data
```
* 這份資料共包含了5000人,應用目標是希望透過心電圖的參數去預測幾個重要的指標,包含了:
* AMI:這是個類別變項描述心肌梗塞的狀態,包含STEMI、NSTEMI及not-AMI
* K:這是一個連續變項描述鉀離子的濃度
* LVD:這是一個二元類別變項:1代表left ventricular dysfunction,0則代表正常
* time與death:這組變項描述病患隔多久後死亡與否,這用來做存活分析之用
* 除了性別(GENDER)和年齡(AGE)外,心電圖的重要參數包含了8個連續變項特徵(Rate、PR、QRSd、QT、QTc、Axes_P、Axes_QRS、Axes_T)以及31個二元類別變項描述相對應的rhythm。
* rhythm依序為:abnormal T wave、atrial fibrillation、atrial flutter、atrial premature complex、complete AV block、complete left bundle branch block、complete right bundle branch block、first degree AV block、incomplete left bundle branch block、incomplete right bundle branch block、ischemia/infarction、junctional rhythm、left anterior fascicular block、left atrial enlargement、left axis deviation、left posterior fascicular block、left ventricular hypertrophy、low QRS voltage、pacemaker rhythm、prolonged QT interval、right atrial enlargement、right ventricular hypertrophy、second degree AV block、sinus bradycardia、sinus pause、sinus rhythm、sinus tachycardia、supraventricular tachycardia、ventricular premature complex、ventricular tachycardia、Wolff-Parkinson-White syndrome
* 在大部分的狀態下,我們會使用後面的幾個變項去預測前面的4組變項。
## 第一節:人工神經網路(6)
* 本週讓我們直接使用真實資料來進行實驗吧!我們就以二元分類預測LVD為例。
```python=
import pandas as pd
data = pd.read_csv('ECG_train.csv')
x_var = ['PR','QRSd', 'QT', 'QTc', 'Axes_P', 'Axes_QRS', 'Axes_T', 'rhythm.1',
'rhythm.2', 'rhythm.3', 'rhythm.4', 'rhythm.5', 'rhythm.6', 'rhythm.7',
'rhythm.8', 'rhythm.9', 'rhythm.10', 'rhythm.11', 'rhythm.12',
'rhythm.13', 'rhythm.14', 'rhythm.15', 'rhythm.16', 'rhythm.17',
'rhythm.18', 'rhythm.19', 'rhythm.20', 'rhythm.21', 'rhythm.22',
'rhythm.23', 'rhythm.24', 'rhythm.25', 'rhythm.26', 'rhythm.27',
'rhythm.28', 'rhythm.29', 'rhythm.30', 'rhythm.31']
y_var = ['LVD']
# Clean
data = data[y_var + x_var].dropna()
print(data.shape)
data
```
* 需要特別注意的是,對於資料科學實驗的流程,我們通常會把樣本分為3個部分,分別是:
* 訓練組(Training set, Development set):負責用來建構一個預測模型,我們之前學的三大流程就是用在這上面的,樣本可以隨意調整
* 驗證組(Validation set, Tuning set):不參與模型訓練,但會用來指導模型訓練,以及建構重要的資訊所用,樣本可以隨意調整
* 測試組(Testing set, Hold-out set, Validation set):最終模型會在上面運行一次(只能一次),已確定最終的準確度,原則上樣本選取必須符合未來使用條件
* 實務上,訓練組跟驗證組在不選擇模型的前提下可以合併,但測試組是必須的。讓我們來先整理資料:
```python=
import random
import pandas as pd
import numpy as np
data = pd.read_csv('ECG_train.csv')
x_var = ['PR','QRSd', 'QT', 'QTc', 'Axes_P', 'Axes_QRS', 'Axes_T', 'rhythm.1',
'rhythm.2', 'rhythm.3', 'rhythm.4', 'rhythm.5', 'rhythm.6', 'rhythm.7',
'rhythm.8', 'rhythm.9', 'rhythm.10', 'rhythm.11', 'rhythm.12',
'rhythm.13', 'rhythm.14', 'rhythm.15', 'rhythm.16', 'rhythm.17',
'rhythm.18', 'rhythm.19', 'rhythm.20', 'rhythm.21', 'rhythm.22',
'rhythm.23', 'rhythm.24', 'rhythm.25', 'rhythm.26', 'rhythm.27',
'rhythm.28', 'rhythm.29', 'rhythm.30', 'rhythm.31']
y_var = ['LVD']
# Clean
data = data[y_var + x_var].dropna()
# Split Dataset
train_idx = np.array(random.sample(range(1677), 1200))
train_data = data[np.isin(range(1677), train_idx) == True]
test_data = data[np.isin(range(1677), train_idx) == False]
train_x = train_data[x_var]
train_y = train_data[y_var]
test_x = test_data[x_var]
test_y = test_data[y_var]
```
## 第一節:人工神經網路(7)
* 首先,我們需要準備資料,在```PyTorch```裡,我們需要將資料轉成Pytorch特有的資料格式:
```python=
import torch
from torch.utils.data import DataLoader, TensorDataset
# 將 numpy 數組轉換為 Tensor 對象
x_tensor = torch.tensor(np.array(train_x)).to(torch.float32)
y_tensor = torch.tensor(np.array(train_y).reshape(-1, 1)).to(torch.float32)
# 將 Tensor 對象轉換為 TensorDataset 對象
dataset = TensorDataset(x_tensor, y_tensor)
```
* 接著,就可以用```PyTorch```提供的類別來讀取樣本。
```python=
# Create data loaders.
train_dataloader = DataLoader(dataset, batch_size = 32)
for X, y in train_dataloader:
print(f"Shape of X [N, C, H, W]: {X.shape} {X.dtype}")
print(f"Shape of y: {y.shape} {y.dtype}")
break
```
## 第一節:人工神經網路(8)

* 在神經網絡的部分,我們可以用這個方式搭建:
```python=
from torch import nn
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.linear_relu_stack = nn.Sequential(
nn.Linear(38, 50),
nn.ReLU(),
nn.Linear(50, 1),
)
self.final_pred = nn.Sigmoid()
def forward(self, x):
main_network = self.linear_relu_stack(x)
logits = self.final_pred(main_network)
return logits
model = NeuralNetwork().to("cpu")
print(model)
```
## 第一節:人工神經網路(9)
* 損失函數與優化器的部分可以這樣搭建:
```python=
loss_fn = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr = 5e-3)
```
* 搭配上訓練函式,就可以訓練所建構的神經網路:
```python
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
for batch, (X, y) in enumerate(dataloader):
X, y = X.to('cpu'), y.to('cpu')
# Compute prediction error
pred = model(X)
loss = loss_fn(pred, y)
# Backpropagation
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0:
loss, current = loss.item(), (batch + 1) * len(X)
print('loss: ', round(loss, 4), '[', current, '/', size, ']')
```
* 開始訓練你的第一個神經網絡!使用範例如下:
```python=
epochs = 20
model.train() # 記得要將模型宣告成訓練模式。
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)
print("Done!")
```
## 第一節:人工神經網路(10)
* 讓我們來看看模型的準確度吧!預測的函式有點不同:
```python=
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
model.eval() # 測試模式這個函式是重點
test_loss, correct = 0, 0
with torch.no_grad():
for X, y in dataloader:
X, y = X.to('cpu'), y.to('cpu')
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += ((pred > 0.5) == (y == 1)).type(torch.float).sum().item()
test_loss /= num_batches
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
```
* 預測的時候一樣要把測試資料準備好:
```python=
x_tensor = torch.tensor(np.array(test_x)).to(torch.float32)
y_tensor = torch.tensor(np.array(test_y).reshape(-1, 1)).to(torch.float32)
dataset = TensorDataset(x_tensor, y_tensor)
test_dataloader = DataLoader(dataset, batch_size=32)
test(test_dataloader, model, loss_fn)
```
* 覺得準嗎?同學們可以試著用上一堂課的機器學習模型進行比較。記得要固定訓練集以及測試集。
## 第二節:使用經典的神經網路(1)
* 了解神經網路的後,為了加速我們學習的進程,我們先學會「借用」別人訓練過的神經網路:
* 史丹佛大學的李飛飛從2007年創辦ImageNet,收集大量帶有標註信息的圖片數據供電腦視覺模型訓練,至今為止這個資料庫已有上百萬張圖片

* 這項經典的研究提供了一個大型資料庫,供研究者檢驗他的模型有多強。


## 第二節:使用經典的神經網路(2)
* 我們已經知道了在李飛飛的努力下,從2010年開始舉辦ImageNet Large Scale Visual Recognition Challenge (ILSVRC),這項比賽提供了巨量的數據以及一個客觀平台比較各種演算法在圖像識別任務中的表現。
* 下面這張圖是每一年的冠軍演算法,我們可以看到在2012年以前,這項比賽大多是由SVM、隨機森林等方法獲得冠軍,但自2012年以來卷積神經網路就席捲了ILSVRC之後所有的冠軍。

## 第二節:使用經典的神經網路(2)
* 讓我們在網路上隨便找一張圖試試看經典模型的威力

* 我們可以用Pytorch所提供已經訓練好的神經網路,程式碼範例如下:
```python=
from torchvision.models import resnet50, ResNet50_Weights
# Step 1: Initialize model with the best available weights
weights = ResNet50_Weights.DEFAULT
model = resnet50(weights=weights)
model.eval()
# Step 2: Initialize the inference transforms
preprocess = weights.transforms()
```
* 接著,我們直接讓模型預測我們的輸入圖片的類別為何。
```python=
import numpy as np
from torchvision.io import read_image
img = read_image("L12-9.png")
batch = preprocess(img).unsqueeze(0)
prediction = model(batch).squeeze(0).softmax(0)
```
* 顯示出前三名的類別,你覺得準嗎?
```python=
for i in sorted(prediction, reverse=True)[0:3]:
class_id = np.where(prediction == i)[0][0]
score = prediction[class_id].item()
category_name = weights.meta["categories"][class_id]
print(f"{category_name}: {100 * score:.1f}%")
```
## 總結
* 本周從人工神經網路的基礎開始介紹,也讓同學們訓練了以感知機所組成的人工智慧。
* 雖然本堂課並沒有介紹人工智慧的訓練細節,但想必同學們已經體會第三代人工智慧的強大之處。