end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

deep learningにおけるhello worldのMLP (Multi Layer Perceptron) から、畳込みニューラルネットワーク(CNN : Convolutional Neural Network )におけるhello worldのAlexNetへ

MLP と AlexNet の違い

f:id:end0tknr:20191019082403p:plain

全結合のニューラルネットワーク (例: MLP)

3次元データも1次元に変換し、全結合層への入力データとする為、 3次元データが持つ形状を無視してしまう

畳込みニューラルネットワーク (例: AlexNet)

3次元データを受け、畳込み(≒フィルタ)演算し、3次元を出力する為、形状を保つ. また、プーリングとは縦横の空間を小さくする演算.

pytorch for pythonによる CIFAR10 に対する AlexNet 画像分類

pytorch for pythonによる CIFAR10 に対する画像分類 - end0tknr's kipple - 新web写経開発

上記urlにある先日のエントリはMLPによるものですが、 これをAlexNetで実装すると以下の通り。

#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import numpy as np
# from matplotlib import pyplot as plt
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plt

NUM_CLASSES = 10  # CIFAR10データは、10種類のdata
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
DATA_DIR = './data/'
IMG_SIZE_AND_CHANNEL = 0
TRAIN_NUM_EPOCHS = 30   # 学習回数
#TRAIN_NUM_EPOCHS = 50   # 学習回数


def main():
    (train_loader, test_loader) = get_train_test_data()
    print("train_loader: ", train_loader)
    print("test_loader: ", test_loader)

    net = AlexNet(NUM_CLASSES).to(DEVICE)
#    net = MLPNet().to(DEVICE)

    (criterion, optimizer) = get_net_criterion_optimizer(net)

    (train_loss_list, train_acc_list, val_loss_list, val_acc_list) = \
        train(train_loader, test_loader, net, criterion, optimizer)

    output_to_file(train_loss_list, train_acc_list, val_loss_list, val_acc_list)

    # torch.save(net.state_dict(), 'net.ckpt')

    # net2 = MLPNet().to(device)
    # net2.load_state_dict(torch.load('net.ckpt'))

    # net2.eval()
    # with torch.no_grad():
    #     total = 0
    #     test_acc = 0
    #     for images, labels in test_loader:
    #         images, labels = images.view(-1, 32*32*3).to(device), labels.to(device)
    #         outputs = net2(images)
    #         test_acc += (outputs.max(1)[1] == labels).sum().item()
    #         total += labels.size(0)
    #     print('精度: {} %'.format(100 * test_acc / total))


def get_train_test_data():
    # CIFAR10とは、ラベル付済の5万枚の訓練画像と1万枚のテスト画像のデータセットで
    # 以下で、STEP1) www.cs.toronto.edu から自動的にダウンロード & 解凍します
    #         STEP2) 画像と正解ラベルのペアを返却 が行われます
    train_dataset = torchvision.datasets.CIFAR10(root=DATA_DIR,
                                                 train=True,
                                                 transform=transforms.ToTensor(),
                                                 download=True)
    test_dataset = torchvision.datasets.CIFAR10(root=DATA_DIR,
                                                train=False,
                                                transform=transforms.ToTensor(),
                                                download=True)
    # 試しに1つの訓練用データの内容を見ると、3チャネル , 32x32size だと分かります
    image, label = train_dataset[0]
    # print( image.size() )
    # print(label)
    global IMG_SIZE_AND_CHANNEL
    IMG_SIZE_AND_CHANNEL = image.size()[0] * image.size()[1] * image.size()[2]

    # DataLoader() は、batch_size分だけ、画像と正解ラベルを返します
    train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                               batch_size=64,
                                               shuffle=True,
                                               num_workers=2)
    test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                              batch_size=64,
                                              shuffle=False,
                                              num_workers=2)
    return train_loader, test_loader
    

# MLP ネットワークの定義
# (多層パーセプトロン, 入力層,隠れ層,出力層が全結合である最も単純なdeep learning)
class MLPNet (nn.Module):
    def __init__(self):
        super(MLPNet, self).__init__()
        # 32x32size & 3チャネル, 隠れ層のunit数は、600
        self.fc1 = nn.Linear(IMG_SIZE_AND_CHANNEL, 600)
        self.fc2 = nn.Linear(600, 600)
        self.fc3 = nn.Linear(600, NUM_CLASSES)
        self.dropout1 = nn.Dropout2d(0.2)
        self.dropout2 = nn.Dropout2d(0.2)
            
    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = self.dropout1(x)
        x = F.relu(self.fc2(x))
        x = self.dropout2(x)
        return F.relu(self.fc3(x))


class AlexNet(nn.Module):
    def __init__(self, num_classes):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=5),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.classifier = nn.Linear(256, num_classes)

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x
    
def get_net_criterion_optimizer(net):

    criterion = nn.CrossEntropyLoss()
    # 以下のparameterの妥当性は理解していません
    optimizer = optim.SGD(net.parameters(),
                          lr=0.01,
                          momentum=0.9,
                          weight_decay=5e-4)
    return criterion, optimizer

def train(train_loader, test_loader, net, criterion, optimizer):
    #最後にlossとaccuracyのグラフを出力する為
    train_loss_list = []
    train_acc_list =  []
    val_loss_list =   []
    val_acc_list =    []

    for epoch in range(TRAIN_NUM_EPOCHS):
        #エポックごとに初期化
        train_loss = 0
        train_acc = 0
        val_loss = 0
        val_acc = 0
    
        net.train()  #訓練モードへ切り替え
        #ミニバッチで分割し読込み
        for i, (images, labels) in enumerate(train_loader):
            # AlexNetの場合、view()での1次元化変換を行わない
            images, labels = images.to(DEVICE), labels.to(DEVICE)
            # #viewで縦横32x32 & 3channelのimgを1次元に変換し、toでDEVICEに転送
            # images, labels = \
            #     images.view(-1,IMG_SIZE_AND_CHANNEL).to(DEVICE),labels.to(DEVICE)

            optimizer.zero_grad()               # 勾配をリセット
            outputs = net(images)               # 順伝播の計算
            loss = criterion(outputs, labels)   #lossの計算
            train_loss += loss.item()           #lossのミニバッチ分を溜め込む
            #accuracyをミニバッチ分を溜め込む
            #正解ラベル(labels)と予測値のtop1(outputs.max(1))が一致時、1が返る
            train_acc += (outputs.max(1)[1] == labels).sum().item()
            #逆伝播の計算
            loss.backward()
            #重みの更新
            optimizer.step()
        #平均lossと平均accuracyを計算
        avg_train_loss = train_loss / len(train_loader.dataset)
        avg_train_acc = train_acc / len(train_loader.dataset)
    

        net.eval()   #評価モードへ切り替え
        
        #評価するときに必要のない計算が走らないようtorch.no_gradを使用
        with torch.no_grad():
            for images, labels in test_loader:        
                # AlexNetの場合、view()での1次元化変換を行わない
                images, labels = images.to(DEVICE), labels.to(DEVICE)
#                images, labels = \
#                images.view(-1,IMG_SIZE_AND_CHANNEL).to(DEVICE),labels.to(DEVICE)
                outputs = net(images)
                loss = criterion(outputs, labels)
                val_loss += loss.item()
                val_acc += (outputs.max(1)[1] == labels).sum().item()
        avg_val_loss = val_loss / len(test_loader.dataset)
        avg_val_acc = val_acc / len(test_loader.dataset)
    

        # 訓練データのlossと検証データのlossとaccuracyをログ出力.
        print ("Epoch [{}/{}], Loss: {loss:.4f},val_loss: {val_loss:.4f},val_acc: {val_acc:.4f}"
               .format(epoch+1,
                       TRAIN_NUM_EPOCHS,
                       i+1,
                       loss=avg_train_loss,
                       val_loss=avg_val_loss, val_acc=avg_val_acc))

        train_loss_list.append(avg_train_loss)
        train_acc_list.append(avg_train_acc)
        val_loss_list.append(avg_val_loss)
        val_acc_list.append(avg_val_acc)

    return train_loss_list,train_acc_list,val_loss_list,val_acc_list

def output_to_file(train_loss_list,train_acc_list,val_loss_list,val_acc_list):

    plt.figure()
    plt.plot(range(TRAIN_NUM_EPOCHS),
             train_loss_list,
             color='blue',
             linestyle='-',
             label='train_loss')
    plt.plot(range(TRAIN_NUM_EPOCHS),
             val_loss_list,
             color='green',
             linestyle='--',
             label='val_loss')
    plt.ylim([0,0.04])
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.title('Training and validation loss')
    plt.grid()
    plt.savefig( 'test_4_1.png' )

    plt.figure()
    plt.plot(range(TRAIN_NUM_EPOCHS),
             train_acc_list,
             color='blue',
             linestyle='-',
             label='train_acc')
    plt.plot(range(TRAIN_NUM_EPOCHS),
             val_acc_list,
             color='green',
             linestyle='--',
             label='val_acc')
    plt.ylim([0,1])
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('acc')
    plt.title('Training and validation accuracy')
    plt.grid()
    plt.savefig( 'test_4_2.png' )

    
if __name__ == '__main__':
    main()

MLP , AlexNet の実行結果を比較すると、 以下のように CNNである AlexNet の精度が高いことが分かります。

MLP

f:id:end0tknr:20191019082442p:plain

CNN (AlexNet)

f:id:end0tknr:20191019082450p:plain