---
tags: PyTorch
title: PyTorch 使用 vgg16 訓練 Stanford Dog Datasets
---
# PyTorch 使用 vgg16 訓練 Stanford Dog Datasets
## 實驗環境
<table>
<tr>
<td>Operating System</td>
<td>Ubuntu 20.04.03 LTS (Focal Fossa)</td>
</tr>
<tr>
<td>GPU</td>
<td>Nvidia RTX 3080</td>
</tr>
<tr>
<td>Nvidia Driver Version</td>
<td>470.74</td>
</tr>
<tr>
<td>Python Version</td>
<td>3.8.10</td>
</tr>
<tr>
<td>CUDA Version</td>
<td>11.1</td>
</tr>
<tr>
<td>cuDNN Version</td>
<td>8.2.0</td>
</tr>
</table>
## 先前準備
* 確認有安裝相關套件,e.g. PyTorch、numpy、pandas、...
可參考這篇: [連結](https://hackmd.io/@TienYi/python-venv)
* 自行下載 datasets,將 `Images` 資料夾的所有資料夾取出放在 `stanford_dog` 的資料夾: [下載連結](http://vision.stanford.edu/aditya86/ImageNetDogs/images.tar)
* 原始路徑:
```
|- images
|- Image
|- n02085620-Chihuahua
|- n02085782-Japanese_spaniel
‧
‧
‧
‧
‧
```
* 修改後路徑:
```
|- Stanford_dog
|- n02085620-Chihuahua
|- n02085782-Japanese_spaniel
‧
‧
‧
‧
‧
```
---
## 1. import 需要的 packages
```python=
import os, torch
import numpy as np
import torch.nn as nn
import matplotlib.pyplot as plt
from PIL import Image
import torchvision.transforms as transforms
import torchvision.models as models
from torch.utils.data import Dataset, DataLoader
from torchvision.datasets import ImageFolder
from torchvision import models
from torch import optim
from torchsummary import summary
import time
```
## 2. 確認一下使用 cuda 或是 cpu
```python=+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
```
## 3. Dataloader 實作
```python=+
# 實作一個可以讀取 stanford dog (mini) 的 Pytorch dataset
class DogDataset(Dataset):
def __init__(self, filenames, labels, transform):
self.filenames = filenames # 資料集的所有檔名
self.labels = labels # 影像的標籤
self.transform = transform # 影像的轉換方式
def __len__(self):
return len(self.filenames) # return DataSet 長度
def __getitem__(self, idx): # idx: Inedx of filenames
image = Image.open(self.filenames[idx]).convert('RGB')
image = self.transform(image) # Transform image
label = np.array(self.labels[idx])
return image, label # return 模型訓練所需的資訊
```
## 4. 定義 Normalize 以及 Transform 的參數
```python=+
normalize = transforms.Normalize(mean = [0.485, 0.456, 0.406], std = [0.229, 0.224, 0.225])
# Transformer
train_transformer = transforms.Compose([
transforms.Resize(256),
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
normalize
])
test_transformer = transforms.Compose([
transforms.Resize(224),
transforms.CenterCrop(224),
transforms.ToTensor(),
normalize
])
```
## 5. 將每一個類別以 8 : 2 的比例分割成 Training data 和 Testing data 傳至 dataloader
```python=+
def split_Train_Val_Data(data_dir):
dataset = ImageFolder(data_dir)
# 建立 20 類的 list
character = [[] for i in range(len(dataset.classes))]
# print(character)
# 將每一類的檔名依序存入相對應的 list
for x, y in dataset.samples:
character[y].append(x)
train_inputs, test_inputs = [], []
train_labels, test_labels = [], []
for i, data in enumerate(character): # 讀取每個類別中所有的檔名 (i: label, data: filename)
np.random.seed(42)
np.random.shuffle(data)
# -------------------------------------------
# 將每一類都以 8:2 的比例分成訓練資料和測試資料
# -------------------------------------------
num_sample_train = int(len(data) * 0.8)
num_sample_test = len(data) - num_sample_train
# print(str(i) + ': ' + str(len(data)) + ' | ' + str(num_sample_train) + ' | ' + str(num_sample_test))
for x in data[:num_sample_train] : # 前 80% 資料存進 training list
train_inputs.append(x)
train_labels.append(i)
for x in data[num_sample_train:] : # 後 20% 資料存進 testing list
test_inputs.append(x)
test_labels.append(i)
train_dataloader = DataLoader(DogDataset(train_inputs, train_labels, train_transformer),
batch_size = batch_size, shuffle = True)
test_dataloader = DataLoader(DogDataset(test_inputs, test_labels, test_transformer),
batch_size = batch_size, shuffle = False)
return train_dataloader, test_dataloader
```
## 6. 建立 CNN Model
```python=+
# 參數設定
batch_size = 32 # Batch Size
lr = 1e-3 # Learning Rate
epochs = 50 # epoch 次數
data_dir = 'stanford_dog' # 資料夾名稱
```
## 7. 利用 Pytorch 內建的 CNN model 來進行訓練且 torchsummary 來印出模型的架構資訊
```python=+
train_dataloader, test_dataloader = split_Train_Val_Data(data_dir)
C = models.vgg16(pretrained=True).to(device) # 使用內建的 model
optimizer_C = optim.SGD(C.parameters(), lr = lr) # 選擇你想用的 optimizer
summary(C, (3, 244, 244)) # 利用 torchsummary 的 summary package 印出模型資訊,input size: (3 * 224 * 224)
# Loss function
criterion = nn.CrossEntropyLoss() # 選擇想用的 loss function
```
```
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 64, 244, 244] 1,792
ReLU-2 [-1, 64, 244, 244] 0
Conv2d-3 [-1, 64, 244, 244] 36,928
ReLU-4 [-1, 64, 244, 244] 0
MaxPool2d-5 [-1, 64, 122, 122] 0
Conv2d-6 [-1, 128, 122, 122] 73,856
ReLU-7 [-1, 128, 122, 122] 0
Conv2d-8 [-1, 128, 122, 122] 147,584
ReLU-9 [-1, 128, 122, 122] 0
MaxPool2d-10 [-1, 128, 61, 61] 0
Conv2d-11 [-1, 256, 61, 61] 295,168
ReLU-12 [-1, 256, 61, 61] 0
Conv2d-13 [-1, 256, 61, 61] 590,080
ReLU-14 [-1, 256, 61, 61] 0
Conv2d-15 [-1, 256, 61, 61] 590,080
ReLU-16 [-1, 256, 61, 61] 0
MaxPool2d-17 [-1, 256, 30, 30] 0
Conv2d-18 [-1, 512, 30, 30] 1,180,160
ReLU-19 [-1, 512, 30, 30] 0
Conv2d-20 [-1, 512, 30, 30] 2,359,808
ReLU-21 [-1, 512, 30, 30] 0
Conv2d-22 [-1, 512, 30, 30] 2,359,808
ReLU-23 [-1, 512, 30, 30] 0
MaxPool2d-24 [-1, 512, 15, 15] 0
Conv2d-25 [-1, 512, 15, 15] 2,359,808
ReLU-26 [-1, 512, 15, 15] 0
Conv2d-27 [-1, 512, 15, 15] 2,359,808
ReLU-28 [-1, 512, 15, 15] 0
Conv2d-29 [-1, 512, 15, 15] 2,359,808
ReLU-30 [-1, 512, 15, 15] 0
MaxPool2d-31 [-1, 512, 7, 7] 0
AdaptiveAvgPool2d-32 [-1, 512, 7, 7] 0
Linear-33 [-1, 4096] 102,764,544
ReLU-34 [-1, 4096] 0
Dropout-35 [-1, 4096] 0
Linear-36 [-1, 4096] 16,781,312
ReLU-37 [-1, 4096] 0
Dropout-38 [-1, 4096] 0
Linear-39 [-1, 1000] 4,097,000
================================================================
Total params: 138,357,544
Trainable params: 138,357,544
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.68
Forward/backward pass size (MB): 258.51
Params size (MB): 527.79
Estimated Total Size (MB): 786.98
----------------------------------------------------------------
```
## 8. 儲存訓練資訊的 List
```python=+
loss_epoch_C = []
train_acc, test_acc = [], []
best_acc, best_auc = 0.0, 0.0
```
## 9. 實作模型訓練和測試模型效能
```python=+
if __name__ == '__main__':
for epoch in range(epochs):
start_time = time.time()
iter = 0
correct_train, total_train = 0, 0
correct_test, total_test = 0, 0
train_loss_C = 0.0
C.train() # 設定 train 或 eval
print('epoch: ' + str(epoch + 1) + ' / ' + str(epochs))
# ---------------------------
# Training Stage
# ---------------------------
for i, (x, label) in enumerate(train_dataloader) :
x, label = x.to(device), label.to(device)
optimizer_C.zero_grad() # 清空梯度
train_output = C(x) # 將訓練資料輸入至模型進行訓練 (Forward propagation)
train_loss = criterion(train_output, label) # 計算 loss
train_loss.backward() # 將 loss 反向傳播
optimizer_C.step() # 更新權重
# 計算訓練資料的準確度 (correct_train / total_train)
_, predicted = torch.max(train_output.data, 1) # 取出預測的 maximum
total_train += label.size(0)
correct_train += (predicted == label).sum()
train_loss_C += train_loss.item()
iter += 1
print('Training epoch: %d / loss_C: %.3f | acc: %.3f' % \
(epoch + 1, train_loss_C / iter, correct_train / total_train))
# --------------------------
# Testing Stage
# --------------------------
C.eval() # 設定 train 或 eval
for i, (x, label) in enumerate(test_dataloader) :
with torch.no_grad(): # 測試階段不需要求梯度
x, label = x.to(device), label.to(device)
test_output = C(x) # 將測試資料輸入至模型進行測試
test_loss = criterion(test_output, label) # 計算 loss
# 計算測試資料的準確度 (correct_test / total_test)
_, predicted = torch.max(test_output.data, 1)
total_test += label.size(0)
correct_test += (predicted == label).sum()
print('Testing acc: %.3f' % (correct_test / total_test))
train_acc.append(100 * (correct_train / total_train).cpu()) # training accuracy
test_acc.append(100 * (correct_test / total_test).cpu()) # testing accuracy
loss_epoch_C.append((train_loss_C / iter)) # loss
end_time = time.time()
print('Cost %.3f(secs)' % (end_time - start_time))
```
```
epoch: 1 / 50
Training epoch: 1 / loss_C: 5.587 | acc: 0.014
Testing acc: 0.020
Cost 122.854(secs)
epoch: 2 / 50
Training epoch: 2 / loss_C: 4.714 | acc: 0.040
Testing acc: 0.041
Cost 122.754(secs)
show more (open the raw output data in a text editor) ...
epoch: 50 / 50
Training epoch: 50 / loss_C: 1.147 | acc: 0.678
Testing acc: 0.691
Cost 121.660(secs)
```
## 10. 將每一個 epoch 的 Loss 以及 Training / Testing accuracy 紀錄下來並繪製成圖
```python=+
fig_dir = './fig/'
if not os.path.isdir(fig_dir):
os.makedirs(fig_dir)
plt.figure()
plt.plot(list(range(epochs)), loss_epoch_C) # plot your loss
plt.title('Training Loss')
plt.ylabel('loss'), plt.xlabel('epoch')
plt.legend(['loss_C'], loc = 'upper left')
plt.savefig(os.path.join(fig_dir, 'loss.png'))
plt.show()
plt.figure()
plt.plot(list(range(epochs)), train_acc) # plot your training accuracy
plt.plot(list(range(epochs)), test_acc) # plot your testing accuracy
plt.title('Training acc')
plt.ylabel('acc (%)'), plt.xlabel('epoch')
plt.legend(['training acc', 'testing acc'], loc = 'upper left')
plt.savefig(os.path.join(fig_dir, 'acc.png'))
plt.show()
```

