建議大家可以實踐下,程式碼都很詳細,有不清楚的地方評論區見~
二、AlexNet
在imagenet上的影象分類challenge上大神Alex提出的alexnet網路結構模型贏得了2012屆的冠軍,振奮人心,利用CNN實現了圖片分類,別人用傳統的機器學習演算法調參跳到半死也就那樣,Alex利用CNN精度遠超傳統的網路。
1. conv1階段DFD(data flow diagram):
第一層輸入資料為原始的227
227
3的影象,這個影象被11
11
3的卷積核進行卷積運算,卷積核對原始影象的每次卷積都生成一個新的畫素。卷積核沿原始影象的x軸方向和y軸方向兩個方向移動,移動的步長是4個畫素。因此,卷積核在移動的過程中會生成(227-11)/4+1=55個畫素(227個畫素減去11,正好是54,即生成54個畫素,再加上被減去的11也對應生成一個畫素),行和列的55
55個畫素形成對原始影象卷積之後的畫素層。共有96個卷積核,會生成55
55
96個卷積後的畫素層。96個卷積核分成2組,每組48個卷積核。對應生成2組55
55
48的卷積後的畫素層資料。這些畫素層經過relu1單元的處理,生成啟用畫素層,尺寸仍為2組55
55*48的畫素層資料。
這些畫素層經過pool運算(池化運算)的處理,池化運算的尺度為3
3,運算的步長為2,則池化後圖像的尺寸為(55-3)/2+1=27。 即池化後像素的規模為27
27
96;然後經過歸一化處理,歸一化運算的尺度為5
5;第一卷積層運算結束後形成的畫素層的規模為27
27
96。分別對應96個卷積核所運算形成。這96層畫素層分為2組,每組48個畫素層,每組在一個獨立的GPU上進行運算。
反向傳播時,每個卷積核對應一個偏差值。即第一層的96個卷積核對應上層輸入的96個偏差值。
2. conv2階段DFD(data flow diagram):
第二層輸入資料為第一層輸出的27
27
96的畫素層,為便於後續處理,每幅畫素層的左右兩邊和上下兩邊都要填充2個畫素;27x27x96的畫素資料分成27x27x48的兩組畫素資料,兩組資料分別再兩個不同的GPU中進行運算。每組畫素資料被5x5x48的卷積核進行卷積運算,卷積核對每組資料的每次卷積都生成一個新的畫素。卷積核沿原始影象的x軸方向和y軸方向兩個方向移動,移動的步長是1個畫素。因此,卷積核在移動的過程中會生成(27-5+2x2)/1+1=27個畫素。(27個畫素減去5,正好是22,在加上上下、左右各填充的2個畫素,即生成26個畫素,再加上被減去的5也對應生成一個畫素),行和列的27x27個畫素形成對原始影象卷積之後的畫素層。共有256個5x5x48卷積核;這256個卷積核分成兩組,每組針對一個GPU中的27x27x48的畫素進行卷積運算。會生成兩組27x27x128個卷積後的畫素層。這些畫素層經過relu2單元的處理,生成啟用畫素層,尺寸仍為兩組27x27x128的畫素層。
這些畫素層經過pool運算(池化運算)的處理,池化運算的尺度為3x3,運算的步長為2,則池化後圖像的尺寸為(57-3)/2+1=13。 即池化後像素的規模為2組13x13x128的畫素層;然後經過歸一化處理,歸一化運算的尺度為5*5;第二卷積層運算結束後形成的畫素層的規模為2組13x13x128的畫素層。分別對應2組128個卷積核所運算形成。每組在一個GPU上進行運算。即共256個卷積核,共2個GPU進行運算。
反向傳播時,每個卷積核對應一個偏差值。即第一層的96個卷積核對應上層輸入的256個偏差值。
3. conv3階段DFD(data flow diagram):
第三層輸入資料為第二層輸出的2組13
13
128的畫素層;為便於後續處理,每幅畫素層的左右兩邊和上下兩邊都要填充1個畫素;2組畫素層資料都被送至2個不同的GPU中進行運算。每個GPU中都有192個卷積核,每個卷積核的尺寸是3
3
256。因此,每個GPU中的卷積核都能對2組13
13
128的畫素層的所有資料進行卷積運算。卷積核對每組資料的每次卷積都生成一個新的畫素。卷積核沿畫素層資料的x軸方向和y軸方向兩個方向移動,移動的步長是1個畫素。因此,運算後的卷積核的尺寸為(13-3+1
2)/1+1=13(13個畫素減去3,正好是10,在加上上下、左右各填充的1個畫素,即生成12個畫素,再加上被減去的3也對應生成一個畫素),每個GPU中共13
13
192個卷積核。2個GPU中共13
13
384個卷積後的畫素層。這些畫素層經過relu3單元的處理,生成啟用畫素層,尺寸仍為2組13
13
192畫素層,共13
13*384個畫素層。
4. conv4階段DFD(data flow diagram):
第四層輸入資料為第三層輸出的2組13
13
192的畫素層;為便於後續處理,每幅畫素層的左右兩邊和上下兩邊都要填充1個畫素;2組畫素層資料都被送至2個不同的GPU中進行運算。每個GPU中都有192個卷積核,每個卷積核的尺寸是3
3
192。因此,每個GPU中的卷積核能對1組13
13
192的畫素層的資料進行卷積運算。卷積核對每組資料的每次卷積都生成一個新的畫素。卷積核沿畫素層資料的x軸方向和y軸方向兩個方向移動,移動的步長是1個畫素。因此,運算後的卷積核的尺寸為(13-3+1
2)/1+1=13(13個畫素減去3,正好是10,在加上上下、左右各填充的1個畫素,即生成12個畫素,再加上被減去的3也對應生成一個畫素),每個GPU中共13
13
192個卷積核。2個GPU中共13
13
384個卷積後的畫素層。這些畫素層經過relu4單元的處理,生成啟用畫素層,尺寸仍為2組13
13
192畫素層,共13
13*384個畫素層。
5. conv5階段DFD(data flow diagram):
第五層輸入資料為第四層輸出的2組13
13
192的畫素層;為便於後續處理,每幅畫素層的左右兩邊和上下兩邊都要填充1個畫素;2組畫素層資料都被送至2個不同的GPU中進行運算。每個GPU中都有128個卷積核,每個卷積核的尺寸是3
3
192。因此,每個GPU中的卷積核能對1組13
13
192的畫素層的資料進行卷積運算。卷積核對每組資料的每次卷積都生成一個新的畫素。卷積核沿畫素層資料的x軸方向和y軸方向兩個方向移動,移動的步長是1個畫素。因此,運算後的卷積核的尺寸為(13-3+1
2)/1+1=13(13個畫素減去3,正好是10,在加上上下、左右各填充的1個畫素,即生成12個畫素,再加上被減去的3也對應生成一個畫素),每個GPU中共13
13
128個卷積核。2個GPU中共13
13
256個卷積後的畫素層。這些畫素層經過relu5單元的處理,生成啟用畫素層,尺寸仍為2組13
13
128畫素層,共13
13*256個畫素層。
2組13
13
128畫素層分別在2個不同GPU中進行池化(pool)運算處理。池化運算的尺度為3
3,運算的步長為2,則池化後圖像的尺寸為(13-3)/2+1=6。 即池化後像素的規模為兩組6
6
128的畫素層資料,共6
6*256規模的畫素層資料。
6. fc6階段DFD(data flow diagram):
第六層輸入資料的尺寸是6
6
256,採用6
6
256尺寸的濾波器對第六層的輸入資料進行卷積運算;每個6
6
256尺寸的濾波器對第六層的輸入資料進行卷積運算生成一個運算結果,透過一個神經元輸出這個運算結果;共有4096個6
6
256尺寸的濾波器對輸入資料進行卷積運算,透過4096個神經元輸出運算結果;這4096個運算結果透過relu啟用函式生成4096個值;並透過drop運算後輸出4096個本層的輸出結果值。
由於第六層的運算過程中,採用的濾波器的尺寸(6
6
256)與待處理的feature map的尺寸(6
6
256)相同,即濾波器中的每個係數只與feature map中的一個畫素值相乘;而其它卷積層中,每個濾波器的係數都會與多個feature map中畫素值相乘;因此,將第六層稱為全連線層。
第五層輸出的6
6
256規模的畫素層資料與第六層的4096個神經元進行全連線,然後經由relu6進行處理後生成4096個數據,再經過dropout6處理後輸出4096個數據。
7. fc7階段DFD(data flow diagram):
第六層輸出的4096個數據與第七層的4096個神經元進行全連線,然後經由relu7進行處理後生成4096個數據,再經過dropout7處理後輸出4096個數據。
8. fc8階段DFD(data flow diagram):
第七層輸出的4096個數據與第八層的1000個神經元進行全連線,經過訓練後輸出被訓練的數值。
Alexnet網路中各個層發揮的作用如下表所述:
實現程式碼
#model。py
import torch。nn as nn
import torch
classAlexNet(nn。Module):
def__init__(self, num_classes=1000, init_weights=False):
super(AlexNet, self)。__init__()
self。features = nn。Sequential( #打包
nn。Conv2d(3, 48, kernel_size=11, stride=4, padding=2), # input[3, 224, 224] output[48, 55, 55] 自動捨去小數點後
nn。ReLU(inplace=True), #inplace 可以載入更大模型
nn。MaxPool2d(kernel_size=3, stride=2), # output[48, 27, 27] kernel_num為原論文一半
nn。Conv2d(48, 128, kernel_size=5, padding=2), # output[128, 27, 27]
nn。ReLU(inplace=True),
nn。MaxPool2d(kernel_size=3, stride=2), # output[128, 13, 13]
nn。Conv2d(128, 192, kernel_size=3, padding=1), # output[192, 13, 13]
nn。ReLU(inplace=True),
nn。Conv2d(192, 192, kernel_size=3, padding=1), # output[192, 13, 13]
nn。ReLU(inplace=True),
nn。Conv2d(192, 128, kernel_size=3, padding=1), # output[128, 13, 13]
nn。ReLU(inplace=True),
nn。MaxPool2d(kernel_size=3, stride=2), # output[128, 6, 6]
)
self。classifier = nn。Sequential(
nn。Dropout(p=0。5),
#全連結
nn。Linear(128 * 6 * 6, 2048),
nn。ReLU(inplace=True),
nn。Dropout(p=0。5),
nn。Linear(2048, 2048),
nn。ReLU(inplace=True),
nn。Linear(2048, num_classes),
)
if init_weights:
self。_initialize_weights()
defforward(self, x):
x = self。features(x)
x = torch。flatten(x, start_dim=1) #展平 或者view()
x = self。classifier(x)
return x
def_initialize_weights(self):
for m in self。modules():
if isinstance(m, nn。Conv2d):
nn。init。kaiming_normal_(m。weight, mode=‘fan_out’, nonlinearity=‘relu’) #何教授方法
if m。bias isnotNone:
nn。init。constant_(m。bias, 0)
elif isinstance(m, nn。Linear):
nn。init。normal_(m。weight, 0, 0。01) #正態分佈賦值
nn。init。constant_(m。bias, 0)
下載資料集
DATA_URL = ‘http://download。tensorflow。org/example_images/flower_photos。tgz’
下載完後執行下面指令碼,將資料集進行分類
#spile_data。py
import os
from shutil import copy
import random
defmkfile(file):
ifnot os。path。exists(file):
os。makedirs(file)
file = ‘flower_data/flower_photos’
flower_class = [cla for cla in os。listdir(file) if“。txt”notin cla]
mkfile(‘flower_data/train’)
for cla in flower_class:
mkfile(‘flower_data/train/’+cla)
mkfile(‘flower_data/val’)
for cla in flower_class:
mkfile(‘flower_data/val/’+cla)
split_rate = 0。1
for cla in flower_class:
cla_path = file + ‘/’ + cla + ‘/’
images = os。listdir(cla_path)
num = len(images)
eval_index = random。sample(images, k=int(num*split_rate))
for index, image in enumerate(images):
if image in eval_index:
image_path = cla_path + image
new_path = ‘flower_data/val/’ + cla
copy(image_path, new_path)
else:
image_path = cla_path + image
new_path = ‘flower_data/train/’ + cla
copy(image_path, new_path)
print(“\r[{}] processing [{}/{}]”。format(cla, index+1, num), end=“”) # processing bar
print()
print(“processing done!”)
之後應該是這樣:
train。py
import torch
import torch。nn as nn
from torchvision import transforms, datasets, utils
import matplotlib。pyplot as plt
import numpy as np
import torch。optim as optim
from model import AlexNet
import os
import json
import time
#device : GPU or CPU
device = torch。device(“cuda:0”if torch。cuda。is_available() else“cpu”)
print(device)
#資料轉換
data_transform = {
“train”: transforms。Compose([transforms。RandomResizedCrop(224),
transforms。RandomHorizontalFlip(),
transforms。ToTensor(),
transforms。Normalize((0。5, 0。5, 0。5), (0。5, 0。5, 0。5))]),
“val”: transforms。Compose([transforms。Resize((224, 224)), # cannot 224, must (224, 224)
transforms。ToTensor(),
transforms。Normalize((0。5, 0。5, 0。5), (0。5, 0。5, 0。5))])}
#data_root = os。path。abspath(os。path。join(os。getcwd(), “。。/。。”)) # get data root path
data_root = os。getcwd()
image_path = data_root + “/flower_data/”# flower data set path
train_dataset = datasets。ImageFolder(root=image_path + “/train”,
transform=data_transform[“train”])
train_num = len(train_dataset)
# {‘daisy’:0, ‘dandelion’:1, ‘roses’:2, ‘sunflower’:3, ‘tulips’:4}
flower_list = train_dataset。class_to_idx
cla_dict = dict((val, key) for key, val in flower_list。items())
# write dict into json file
json_str = json。dumps(cla_dict, indent=4)
with open(‘class_indices。json’, ‘w’) as json_file:
json_file。write(json_str)
batch_size = 32
train_loader = torch。utils。data。DataLoader(train_dataset,
batch_size=batch_size, shuffle=True,
num_workers=0)
validate_dataset = datasets。ImageFolder(root=image_path + “/val”,
transform=data_transform[“val”])
val_num = len(validate_dataset)
validate_loader = torch。utils。data。DataLoader(validate_dataset,
batch_size=batch_size, shuffle=True,
num_workers=0)
test_data_iter = iter(validate_loader)
test_image, test_label = test_data_iter。next()
#print(test_image[0]。size(),type(test_image[0]))
#print(test_label[0],test_label[0]。item(),type(test_label[0]))
#顯示影象,之前需把validate_loader中batch_size改為4
# def imshow(img):
# img = img / 2 + 0。5 # unnormalize
# npimg = img。numpy()
# plt。imshow(np。transpose(npimg, (1, 2, 0)))
# plt。show()
#
# print(‘ ’。join(‘%5s’ % cla_dict[test_label[j]。item()] for j in range(4)))
# imshow(utils。make_grid(test_image))
net = AlexNet(num_classes=5, init_weights=True)
net。to(device)
#損失函式:這裡用交叉熵
loss_function = nn。CrossEntropyLoss()
#最佳化器 這裡用Adam
optimizer = optim。Adam(net。parameters(), lr=0。0002)
#訓練引數儲存路徑
save_path = ‘。/AlexNet。pth’
#訓練過程中最高準確率
best_acc = 0。0
#開始進行訓練和測試,訓練一輪,測試一輪
for epoch in range(10):
# train
net。train() #訓練過程中,使用之前定義網路中的dropout
running_loss = 0。0
t1 = time。perf_counter()
for step, data in enumerate(train_loader, start=0):
images, labels = data
optimizer。zero_grad()
outputs = net(images。to(device))
loss = loss_function(outputs, labels。to(device))
loss。backward()
optimizer。step()
# print statistics
running_loss += loss。item()
# print train process
rate = (step + 1) / len(train_loader)
a = “*” * int(rate * 50)
b = “。” * int((1 - rate) * 50)
print(“\rtrain loss: {:^3。0f}%[{}->{}]{:。3f}”。format(int(rate * 100), a, b, loss), end=“”)
print()
print(time。perf_counter()-t1)
# validate
net。eval() #測試過程中不需要dropout,使用所有的神經元
acc = 0。0# accumulate accurate number / epoch
with torch。no_grad():
for val_data in validate_loader:
val_images, val_labels = val_data
outputs = net(val_images。to(device))
predict_y = torch。max(outputs, dim=1)[1]
acc += (predict_y == val_labels。to(device))。sum()。item()
val_accurate = acc / val_num
if val_accurate > best_acc:
best_acc = val_accurate
torch。save(net。state_dict(), save_path)
print(‘[epoch %d] train_loss: %。3f test_accuracy: %。3f’ %
(epoch + 1, running_loss / step, val_accurate))
print(‘Finished Training’)
最後進行預測
predict。py
import torch
from model import AlexNet
from PIL import Image
from torchvision import transforms
import matplotlib。pyplot as plt
import json
data_transform = transforms。Compose(
[transforms。Resize((224, 224)),
transforms。ToTensor(),
transforms。Normalize((0。5, 0。5, 0。5), (0。5, 0。5, 0。5))])
# load image
img = Image。open(“。/sunflower。jpg”) #驗證太陽花
#img = Image。open(“。/roses。jpg”) #驗證玫瑰花
plt。imshow(img)
# [N, C, H, W]
img = data_transform(img)
# expand batch dimension
img = torch。unsqueeze(img, dim=0)
# read class_indict
try:
json_file = open(‘。/class_indices。json’, ‘r’)
class_indict = json。load(json_file)
except Exception as e:
print(e)
exit(-1)
# create model
model = AlexNet(num_classes=5)
# load model weights
model_weight_path = “。/AlexNet。pth”
model。load_state_dict(torch。load(model_weight_path))
model。eval()
with torch。no_grad():
# predict class
output = torch。squeeze(model(img))
predict = torch。softmax(output, dim=0)
predict_cla = torch。argmax(predict)。numpy()
print(class_indict[str(predict_cla)], predict[predict_cla]。item())
plt。show()