---
# System prepended metadata

title: LeNet 實作 Python
tags: [AI]

---

讓我們逐步解釋這段程式碼中的每個動作和參數的意思：

### 1. 匯入必要的模組：
```python
import numpy as np  # 相關運算
import time as t  # 用於計時
from math import floor, ceil  # floor: 往下取整數, ceil: 往上取整數
```
這段代碼匯入了 NumPy 模組來進行數學運算，匯入了 time 模組來計時，並從 math 模組中匯入了 `floor` 和 `ceil` 函數來進行整數的向下和向上取整。

### 2. 設置 NumPy 的輸出選項：
```python
np.set_printoptions(threshold=np.inf)  # 請勿無窮大的要素數
np.set_printoptions(suppress=True)  # 抑制顯示浮點數
```
這段代碼設置了 NumPy 的打印選項，第一行是為了在打印陣列時顯示所有元素（無限制），第二行是為了抑制顯示浮點數的科學計數法。

### 3. 定義 CNN 第一層的參數和架構：
```python
'''
Layer1
Layer: CNN
input:32x32x1
filter:5x5x6
stride:1
pad:0
output:28x28x6
activation: tanh
'''
```
這段註釋說明了第一層 CNN 的參數：
- 輸入：32x32x1，表示輸入圖像大小為32x32像素，1個通道（灰階圖像）。
- 卷積核：5x5大小，有6個卷積核。
- 步幅（stride）：1
- 填充（pad）：0
- 輸出：28x28x6，表示輸出特徵圖大小為28x28，6個特徵圖。
- 激活函數：tanh

### 4. 生成隨機的輸入特徵圖：
```python
### 32x32x1 image input feature map ###
fmap = np.random.randint(1, 256, size=(1, 32, 32), dtype=np.int32)  # 標準數數
print("\n-----------input feature map-----------\n")
print(fmap)
```
這段代碼生成了一個隨機的 32x32x1 的輸入特徵圖，其中像素值在1到255之間，並打印出來。

### 5. 定義卷積核：
```python
### 5x5x6 convolution kernel ###
filter = np.random.rand(6, 1, 5, 5)  # 標籤0,1用小數
stride = 1
pad = 0
```
這段代碼生成了6個5x5大小的卷積核，每個卷積核對應1個輸入通道（灰階圖像）。步幅設置為1，填充設置為0。

### 6. 計算卷積層的輸出特徵圖：
```python
### 28x28x6 output feature maps ###
ofmap = np.zeros((filter.shape[0], floor((fmap.shape[1] + 2 * pad - filter.shape[2]) / stride) + 1, floor((fmap.shape[2] + 2 * pad - filter.shape[3]) / stride) + 1))
```
這段代碼初始化了一個用來存儲卷積層輸出的28x28x6大小的特徵圖（全零初始化）。

### 7. 執行卷積運算：
```python
start = t.time()
print("\n-----------Start Layer 1 CNN computation-----------\n")
for depth in range(ofmap.shape[0]):  # depth=6,height=28,width=28,channel=1,i=5,j=5
    for height in range(ofmap.shape[1]):
        for width in range(ofmap.shape[2]):
            for channel in range(filter.shape[1]):
                for i in range(filter.shape[2]):
                    for j in range(filter.shape[3]):
                        ofmap[depth][height][width] += fmap[channel][i + height * stride][j + width * stride] * filter[depth][channel][i][j]
```
這段代碼逐個元素地計算卷積層輸出的特徵圖。遍歷每個輸出特徵圖的深度、高度和寬度，並對應地計算輸入特徵圖和卷積核的點積。

### 8. 激活函數：
```python
### activation function ###
for depth in range(ofmap.shape[0]):
    for height in range(ofmap.shape[1]):
        for width in range(ofmap.shape[2]):
            ofmap[depth][height][width] = 2 / (1 + np.exp(-2 * ofmap[depth][height][width])) - 1
ifmap = ofmap
print(ifmap)
```
這段代碼對卷積層的輸出應用雙曲正切（tanh）激活函數，並將結果存儲在 `ifmap` 中。

### 9. 定義池化層的參數和架構：
```python
'''
Layer2
Layer: pooling
input:28x28x6
filter:2x2x6
stride:2
pad:0
output:14x14x6
'''
```
這段註釋說明了池化層的參數：
- 輸入：28x28x6，表示輸入特徵圖大小為28x28，6個特徵圖。
- 池化核：2x2大小，有6個池化核。
- 步幅（stride）：2
- 填充（pad）：0
- 輸出：14x14x6，表示輸出特徵圖大小為14x14，6個特徵圖。

### 10. 定義池化核：
```python
### 2x2x6 ###
filter = np.ones((6, 1, 2, 2))
stride = 2
pad = 0
```
這段代碼生成了6個2x2大小的池化核，每個池化核對應1個輸入通道（灰階圖像）。步幅設置為2，填充設置為0。

### 11. 計算池化層的輸出特徵圖：
```python
### 14x14x6 ###
ofmap = np.zeros((filter.shape[0], ceil((ifmap.shape[1] + 2 * pad - filter.shape[2]) / stride) + 1, ceil((ifmap.shape[2] + 2 * pad - filter.shape[3]) / stride) + 1))
print("\n-----------Start Layer 2 pooling computation-----------\n")
for depth in range(ofmap.shape[0]):  # depth=6,height=14,width=14,channel=1,i=2,j=2
    for height in range(ofmap.shape[1]):
        for width in range(ofmap.shape[2]):
            for channel in range(filter.shape[1]):
                for i in range(filter.shape[2]):
                    for j in range(filter.shape[3]):
                        ofmap[depth][height][width] += ifmap[channel][i + height * stride][j + width * stride] * filter[depth][channel][i][j]
```
這段代碼初始化了一個用來存儲池化層輸出的14x14x6大小的特徵圖（全零初始化），並逐個元素地計算池化層的輸出特徵圖。遍歷每個輸出特徵圖的深度、高度和寬度，並對應地計算輸入特徵圖和池化核的點積。

### 12. 平均池化：
```python
# average pooling
ifmap = ofmap / (filter.shape[2] * filter.shape[3])
print(ofmap)
```
這段代碼對池化層的輸出進行平均池化，將每個池化區域的值除以池化核的大小，並將結果存儲在 `ifmap` 中。

這樣，每一步都詳細解釋了程式碼的作用和參數的意思，幫助理解 CNN 的基本運算過程。