CNN實作Kaggle貓狗影像辨識(Pytorch)

閱讀時間約 27 分鐘
最近剛好修了Pytorch相關的課,在Kaggle上也丟了個比賽,想說就來分享一下Pytorch的入門實戰,我會實作一個最入門的用CNN辨識貓狗的程式,但關於CNN理論的部分我不會提到太多,有興趣就麻煩再去搜尋了~

CNN是什麼?

先簡單介紹一下CNN,CNN的全名是(Convolutional Neural Network),中文是卷積神經網路,是機器學習中的深度學習的一種,也是目前應用於影像辨識非常熱門的一種模型。

資料集準備

我這次使用Kaggle的貓狗資料集,可以先下載下來,總共有三個檔案分別是訓練集train(包含貓狗各12500張圖片),以及測試集test(12500張未分類的圖片),和sample_submission。由於小弟太窮沒錢買GPU且為了節省時間,我先把貓和狗各挑了100張圖片,並分別放到自建的dog和cat資料夾內。
來源:Pixabay

程式實作

載入需要的Library

import torch
import torch.nn as nn
from torchvision import datasets ,models,transforms
from pathlib import Path
from matplotlib import pyplot as plt
import numpy as np
import torch.nn.functional as F
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from torch.nn import Linear, ReLU, CrossEntropyLoss, Conv2d, MaxPool2d, Module
from torch.optim import Adam
import pandas as pd
import os
from os import listdir
from tqdm import tqdm_notebook as tqdm
from PIL import Image

準備資料、設定超參數

Path_train填入自己的資料夾路徑,我的train裡面有dog和cat的資料夾,分別有各100張圖,並設定Batch和Learning Rate,transforms函數可以將圖片轉成(224,224)的像素,同時將圖片轉成Pytorch能讀取的tensor格式。
PATH_train="...../train"
TRAIN =Path(PATH_train)
#Batch:每批丟入多少張圖片
batch_size = 8
#Learning Rate:學習率
LR = 0.0001
transforms = transforms.Compose([transforms.Resize((224,224)), transforms.ToTensor()]

切分訓練驗證集

用ImageFolder讀取檔案並套入前面transforms的轉換函數,ImageFolder會把圖片根據資料夾給予label,可以用class_to_idx查詢貓和狗分別對應的label,print出來的結果會像這樣。
{‘cat’: 0, ‘dog’: 1}
注意ImageFolder必須在資料夾內有子資料夾才可使用,所以我先分別把貓和狗的圖放進cat和dog的資料夾。
train_data = datasets.ImageFolder(TRAIN, transform=transforms)
#print(train_data.class_to_idx)
#切分70%當作訓練集、30%當作驗證集
train_size = int(0.7 * len(train_data))
valid_size = len(train_data) - train_size
train_data, valid_data = torch.utils.data.random_split(train_data, [train_size, valid_size])
#Dataloader可以用Batch的方式訓練
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size,shuffle=True)
valid_loader = torch.utils.data.DataLoader(valid_data, batch_size=batch_size,shuffle=True)

建立CNN的架構

這邊要定義自己的CNN架構,我用最簡單的範例,基本上CNN最主要就是ConvolutionalMaxpool兩種層所組成,Relu是激發函數,然後最後要用線性層輸出預測結果,因為貓狗是兩個種類,所以Linear後面的參數就是2,輸出結果如[0.487,0.9527],index為1的狗比較大,代表預測結果為狗。
最後也可以加一層Softmax讓兩者機率加起來為1如[0.7,0.3])
Pytorch最少要定義兩個function,一個是__init__,用來建立你forward需要用到哪些層,另一個是forward,也就是定義路徑要怎麼走,不需另外定義Backward,Pytorch會自動幫你設定Back-propagation的路徑。
至於參數的設定我這邊簡單講一下
self.cnn1=nn.Conv2d(3,16,kernal_size=5,stride=1)
3代表input的channel,因為圖片是RGB所以是3,16代表output的channel,這邊我用了16個hidden node所以為16,kernel_size是5*5的filter。
self.maxpool1 = nn.MaxPool2d(kernel_size=2)
這裡的kernel_size代表2*2的格子取最大的一格,會將8*8縮成4*4。
self.fc = nn.Linear(8 * 50 * 50, 2)
至於線性層為何是(8*50*50,2),根據下面這個公式算出下一層的shape,如原圖是(3,224,224)經過cnn1後,(224–5+1)/(1+1)=110,因此maxpool1的input就變成(16,110,110),而Maxpool1的kernal_size為2,因此output就變成(16,55,55),以此類推最後的Shape就變成(8*50*50,2)。
(weight-kernel+1)/stride+1 無條件進位
class CNN_Model(nn.Module):
#列出需要哪些層
def __init__(self):
super(CNN_Model, self).__init__()
# Convolution 1 , input_shape=(3,224,224)
self.cnn1 = nn.Conv2d(3, 16, kernel_size=5, stride=1)
self.relu1 = nn.ReLU(inplace=True)
# Max pool 1
self.maxpool1 = nn.MaxPool2d(kernel_size=2)
# Convolution 2
self.cnn2 = nn.Conv2d(16,8, kernel_size=11, stride=1)
self.relu2 = nn.ReLU(inplace=True)
# Max pool 2
self.maxpool2 = nn.MaxPool2d(kernel_size=2)
# Fully connected 1 ,#input_shape=(8*50*50)
self.fc = nn.Linear(8 * 50 * 50, 2)
#列出forward的路徑,將init列出的層代入
def forward(self, x):
out = self.cnn1(x)
out = self.relu1(out)
out = self.maxpool1(out)
out = self.cnn2(out)
out = self.relu2(out)
out = self.maxpool2(out)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out

定義訓練過程、計算Loss、Accuracy

Tdqm訓練過程圖
這邊先將訓練、驗證模組化,傳入的函數包含model(要使用的模型)、n_epochs(迭代次數)、train_loader、valid_loader(訓練、驗證集)、optimizer(優化器)、Criterion(損失函數)。
1.train_loss和valid loss是算出每個batch的平均loss
2.tqdm可以很好的跟data製作出進度條(如上圖)
3.model.eval()會關閉batchnorm、dropout,雖這範例沒有,但一般都會用到
4.output.data.max用來輸出較大的index如[0.487,0.9527],則輸出1 P
5.Validation階段不需做BP,所以少了幾步
def train(model,n_epochs,train_loader,valid_loader,optimizer,criterion):
train_acc_his,valid_acc_his=[],[]
train_losses_his,valid_losses_his=[],[]
for epoch in range(1, n_epochs+1):
# keep track of training and validation loss
train_loss,valid_loss = 0.0,0.0
train_losses,valid_losses=[],[]
train_correct,val_correct,train_total,val_total=0,0,0,0
train_pred,train_target=torch.zeros(8,1),torch.zeros(8,1)
val_pred,val_target=torch.zeros(8,1),torch.zeros(8,1)
count=0
count2=0
print('running epoch: {}'.format(epoch))
###################
# train the model #
###################
model.train()
for data, target in tqdm(train_loader):
# move tensors to GPU if CUDA is available
if train_on_gpu:
data, target = data.cuda(), target.cuda()
# forward pass: compute predicted outputs by passing inputs to the model
output = model(data)
# calculate the batch loss
loss = criterion(output, target)
#calculate accuracy
pred = output.data.max(dim = 1, keepdim = True)[1]
train_correct += np.sum(np.squeeze(pred.eq(target.data.view_as(pred))).cpu().numpy())
train_total += data.size(0)
# backward pass: compute gradient of the loss with respect to model parameters
loss.backward()
# perform a single optimization step (parameter update)
optimizer.step()
# update training loss
train_losses.append(loss.item()*data.size(0))
# clear the gradients of all optimized variables
optimizer.zero_grad()
if count==0:
train_pred=pred
train_target=target.data.view_as(pred)
count=count+1
else:
train_pred=torch.cat((train_pred,pred), 0)
train_target=torch.cat((train_target,target.data.view_as(pred)), 0)
train_pred=train_pred.cpu().view(-1).numpy().tolist()
train_target=train_target.cpu().view(-1).numpy().tolist()
######################    
# validate the model #
######################
model.eval()
for data, target in tqdm(valid_loader):
# move tensors to GPU if CUDA is available
if train_on_gpu:
data, target = data.cuda(), target.cuda()
# forward pass: compute predicted outputs by passing inputs to the model
output = model(data)
# calculate the batch loss
loss =criterion(output, target)
#calculate accuracy
pred = output.data.max(dim = 1, keepdim = True)[1]
val_correct += np.sum(np.squeeze(pred.eq(target.data.view_as(pred))).cpu().numpy())
val_total += data.size(0)
valid_losses.append(loss.item()*data.size(0))
if count2==0:
val_pred=pred
val_target=target.data.view_as(pred)
count2=count+1
else:
val_pred=torch.cat((val_pred,pred), 0)
val_target=torch.cat((val_target,target.data.view_as(pred)), 0)
val_pred=val_pred.cpu().view(-1).numpy().tolist()
val_target=val_target.cpu().view(-1).numpy().tolist()

# calculate average losses
train_loss=np.average(train_losses)
valid_loss=np.average(valid_losses)

# calculate average accuracy
train_acc=train_correct/train_total
valid_acc=val_correct/val_total
train_acc_his.append(train_acc)
valid_acc_his.append(valid_acc)
train_losses_his.append(train_loss)
valid_losses_his.append(valid_loss)
# print training/validation statistics 
print('\tTraining Loss: {:.6f} \tValidation Loss: {:.6f}'.format(
train_loss, valid_loss))
print('\tTraining Accuracy: {:.6f} \tValidation Accuracy: {:.6f}'.format(
train_acc, valid_acc))
return train_acc_his,valid_acc_his,train_losses_his,valid_losses_his,model

開始訓練囉~

首先初始化CNN_Model(),使用最常用的Adam作為Optimizer,由於是分類問題Loss Function選用CrossEntropy,代入函數即可以開始訓練囉!!
model1=CNN_Model()
n_epochs = 10
optimizer1 = torch.optim.Adam(model1.parameters(), lr=LR)
criterion = CrossEntropyLoss()
train_acc_his,valid_acc_his,train_losses_his,valid_losses_his,model1=train(model1,n_epochs,train_loader,valid_loader,optimizer1,criterion)

訓練結果、儲存Model

訓練結果圖
這邊就可以把剛剛訓練完的結果留下來,並畫成loss和accuracy(如上圖),檢驗訓練的狀況,由於訓練非常耗時間,可以用torch.save的函數把訓練好的model保存下來,之後就可以直接load進來用。
由於是做範例示範,所以用了很簡單的架構及非常少的data,所以從上面的圖可發現training和validation差非常遠,有非常嚴重的Overfitting的問題,因此之後可以再去進行調整。
plt.figure(figsize=(15,10))
plt.subplot(221)
plt.plot(train_losses_his, 'bo', label = 'training loss')
plt.plot(valid_losses_his, 'r', label = 'validation loss')
plt.title("Simple CNN Loss")
plt.legend(loc='upper left')
plt.subplot(222)
plt.plot(train_acc_his, 'bo', label = 'trainingaccuracy')
plt.plot(valid_acc_his, 'r', label = 'validation accuracy')
plt.title("Simple CNN Accuracy")
plt.legend(loc='upper left')
plt.show()
torch.save(model1, "....../Dogcat_resnet18")
#model1 = torch.load('..../Dogcat_resnet18')
接下來不斷調整找出最好的參數、架構即可,但注意調整太多次,Validation的效果可能會變差,也容易對Validation Set有Overfitting的問題,而影響模型對Test的泛用性,因此前面data_loader也用了shuffle的函數,把每次的batch洗散,切分資料集也沒使用random_state固定切分的資料。

用自己的CNN參加Kaggle競賽

由於網路上通常到上一步就結束了,但相信不少人也會想知道自己的模型到底好不好,所以我這邊會分享一下如何用自己設計的CNN參加Kaggle競賽。
由於Kaggle給的test集檔案為1.jpg,2.jpg…..因此先將檔案以PIL的格式讀取,在套入transforms的轉換。model的input是batch的方式讀取,因此會有4個維度(8,3,224,224),由於我們是一張一張讀取,所以model只有3個維度(3,224,224),因此在用unsqueeze(0)在0的index上加一個維度即變成(1,3,224,224),就可以預測了。
PS由於Dataloader輸出順序不固定,所以提交部分就另外寫了一個function
def test_submit(model,n_img,path): 
model.eval()
pred_label=[]
for i in tqdm(range(1,n_img+1)):
path = image_path + str(i) +'.jpg'
img = Image.open(path).convert('RGB')
img = transforms(img)
img = img.unsqueeze(0)
with torch.no_grad():
output=model(img)
pred = output.data.max(dim = 1, keepdim = True)[1]
pred_label.append(int(pred))
return pred_label
最後把參數帶入,輸出成csv,提交到Kaggle的網站上,大功就告成啦!!
image_path =’.../test/’
n_img=12500
pred_label=test_submit(model1,n_img,image_path)
submit = pd.read_csv('.../sample_submission.csv')
submit['label'] = pred_label
submit['label'] = submit['label'].astype(int)
submit.to_csv('..../submit_dogcat.csv', index= False)
由於小弟我才疏學淺且第一次寫技術分享相關文章,可能有很多錯誤和不周延的地方,再請各位大神指教、糾正了,感謝大家~
若覺得有幫助可以追蹤我、按喜歡、收藏,我就會寫出更多相關文章,謝謝你~
若還有其他想問的或希望我介紹的,可以用FB私訊或在下面回應,我會盡我所能回答
你可能還會想看:
為什麼會看到廣告
1會員
37內容數
大學科系選擇技巧、高中升學考試經驗分享
留言0
查看全部
發表第一個留言支持創作者!
你可能也想看
卷積神經網路(CNN)在影像辨識中的應用卷積神經網路(CNN)是一種專門用於影像相關應用的神經網路。本文介紹了CNN在影像辨識中的應用,包括圖片的組成、Receptive Field、Parameter Sharing、以及Pooling等技術。通過本文,讀者將瞭解CNN在影像辨識領域的優勢和運作原理。
Thumbnail
avatar
dab戴伯
2024-05-02
[新北旅遊]CNN票選為台灣八大祕境,季節性特殊風光老梅石槽、形成美麗的藍綠白穿梭色彩景觀 台灣各地有許多難得一見到地理景觀現。這篇介紹老梅石槽。老梅石槽是全台灣唯一少見的特殊景觀,更是萬年才形成地理景觀。綠油油的一片又一片海藻與海水及白沙相呼應、形成美麗的藍綠白穿梭色彩風光,這裡是季節性特殊地景。 老梅石槽相關資訊:: ​地址: 新北市石門區老梅里東海海岸線 ​電話: 02
Thumbnail
avatar
bravejim
2024-05-02
[機器學習]CNN學習MNIST 手寫英文字母資料,用網頁展現成果_Streamlit Web應用程式篇前言 上一篇討論到如何訓練出模型,此篇將說明Streamlit建立的簡單Web應用程式的解說 Streamlit網頁App_貓狗辨識 連結 程式碼Github連結 [機器學習]CNN學習MNIST 手寫英文字母資料,用網頁展現成果_模型訓練篇 如何連動github與stramlit可以參考
Thumbnail
avatar
螃蟹_crab
2024-01-06
[機器學習]CNN學習MNIST 手寫英文字母資料,用網頁展現成果_模型訓練篇streamlit與github連動程式庫,呈現即時預測手寫英文字母 整理了一下,先前學的機器學習利用Colab來訓練出能辨識手寫A~Z英文字母的模型,使用的模型是CNN(Convolutional Neural Network,CNN)模型 訓練好的模型,當然是要拿來應用,成果呈現
Thumbnail
avatar
螃蟹_crab
2024-01-06
[新北美食]全台灣首台無障礙餐車,新巨輪行動號餐車,吃美食與作公益結合餐食​ 餐車在許多市集與活動時常會看到,餐車對比店面具有快速出餐、便利速度等優點。新巨輪服務協會的餐車更是一個很特別餐車,新巨輪服務協會首創全台灣無障礙餐車,由障礙者擔任餐車大廚,致力讓全台灣吃到最有公益價值的餐車美食。 新巨輪服務協會餐車相關資訊:: ​地址: 新北市土城區承天路43巷10號
Thumbnail
avatar
bravejim
2023-09-30
[台北美食]嚭香平民餐食,菜色有鐵板燒、便當、台式熱炒,熱湯與冷飲隨你喝到飽​ 嚭香便宜好吃、又吃得飽的平民餐店,隨便點一個餐食,只要你是正常人都會吃飽這家店。這家店每到用餐時間總是來店家吃飯很多人。平民餐食這家絕對是很棒店家。 嚭香相關資訊:: ​地址: 台北市中正區八德路一段82巷9弄9號 ​營業時間: AM10:30-PM20:30 (店休日:周日)
Thumbnail
avatar
bravejim
2023-09-23
《藉由CNN俄羅斯「傭兵」(mercenary)標題,深入學習commerce、commercial、commercia藉由CNN時是新聞標題及歐美制度的歷史背景,深入學習mercenary(圖利的) 、commerce(商業;貿易)、commercial(電視、廣播)商業廣告)、commercialize(使商業化,使商品化)、merchant(商人)、merchandise(商品;貨物)六個同字根的英文生字用法。
Thumbnail
avatar
Jenny Hsu
2023-07-03
月子餐食譜跟買菜計畫,在家坐月子沒有想像中那麼難!月子餐每日基本營養分配 中藥補湯(湯800-1000cc、肉類蛋白質) 肉類料理2道(蛋白質)午餐跟晚餐各一 蔬菜料理2道(纖維)午餐跟晚餐各一 常備菜3-4道(纖維、蛋白質) 主食1-2種(澱粉熱量) 水果2-3種(維他命) 中藥茶1種(800-1000cc) 飲料1種(800-1000cc)
Thumbnail
avatar
希維亞實驗廚房
2023-07-02
《閱讀CNN商業新聞,增廣宏觀見聞及適時調整自己職場方向》閱讀CNN商業新聞,增廣自己跨領域學習商用英文外,也讓自己培養(國際)宏觀觀點,進行個人的微觀理財及職涯規劃。
Thumbnail
avatar
Jenny Hsu
2023-06-09
葉郎時光機:1991年1月17日美軍沙漠風暴行動造就了CNN的新聞盛世#葉郎時光機 [ 1991年1月17日:美軍沙漠風暴行動造就了CNN的新聞盛世 ] 才剛剛寫信向員工告解並稱會努力喚回員工信任的 CNN 執行長 Chris Licht ,不到24小時內就傳出已經向母公司 Warner Bros. Discovery 執行長 David Zaslav 提出辭呈,結束
Thumbnail
avatar
葉郎
2023-06-09