讓我們逐步解釋這段程式碼中的每個動作和參數的意思:
### 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 的基本運算過程。