ローリングコンバットピッチなう!

AIとか仮想化とかペーパークラフトとか

【PyTorch始めました】MNISTで学習させたニューラルネットワークに自分の手書き数字を認識させてみるリターンズ(畳込みニューラルネットワークとドロップアウトの効果)

[technology]以前にやった自分の手書き数字をMNISTを学習させたネットワークで認識すると認識率が悪かったのは過学習のせいかもしれない

ちょっと長いタイトルですみません。

以前にchainerで作ったMNIST分類用のニューラルネットワークに自分の手書き数字(といってもお絵かきツールでマウス描き)を認識させるというエントリで、認識率が50〜60%で、これをデータ不足と判断し、自分で書いた文字を追加学習させる事で精度を上げるという実験をしました。
rc30-popo.hatenablog.com

この時、50個ほどの自作データをMNISTの学習データの一部と置き換える事で85%くらいの認識率を得たのですが、1つ前のエントリ、
rc30-popo.hatenablog.com
でFashion MNISTを使ったネットワークのチューニングを行った結果として、以前にやった自分の手書き文字の認識精度が悪い理由の一つは学習データ不足だけではなく、過学習による汎化性能不足もあるのでは?と今更ながらに思いました。(以前のものは全結合のみ、ドロップアウト無し)

ちなみに自分で書いた手書き数字は以下の様なもので100x100のキャンバス上にマウスで0〜9の文字を書いたものです。グレースケールのpngに保存し、0を書いたファイルなら0n.pngの様にファイル名先頭に0が付く様にしてラベリングしています。背景色が白、前景色が黒で、MNISTのデータと反転しているので、MNISTを学習させたネットワークに分類させる時は白黒反転をさせる必要があります。

で、前回のエントリで作成したネットワークを使って同じ自作手書き文字をMNISTだけを学習した結果を使い分類させてみました。
比較として以前に使った全結合ネットワークでも同じ事を試してみました。PyTorchへの移植がめんどくさかったので、以前使ったコードはChainerのままです。


1)以前に使った全結合ネットワーク(chainer)

# DNN class
class DNN(Chain):
    def __init__(self):
        super(DNN, self).__init__(
            l1 = L.Linear(784,100),
            l2 = L.Linear(100,100),
            l3 = L.Linear(100,10)
        )
    def forward(self,x):
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        h3 = self.l3(h2)
        return h3

2)今回使ったCNN+ドロップアウト(pytorch)

# DNN class
class DNN(nn.Module):
    def __init__(self):
        super(DNN,self).__init__()
        self.cv1 = nn.Conv2d(1,32,3)
        self.cv2 = nn.Conv2d(32,64,3)
        self.l1 = nn.Linear(64 * 5 * 5,500)
        self.l2 = nn.Linear(500,10)
    def forward(self,x,train=True):
        c = F.dropout(F.max_pool2d(F.relu(self.cv1(x)),2),training = train)
        c = F.dropout(F.max_pool2d(F.relu(self.cv2(c)),2),training = train)
        # Chainerと異なり、全結合層に入力する前にshape変換が必要
        c = c.view(-1,64 * 5 * 5)
        h = F.relu(self.l1(c))
        h = F.dropout(h,training = train)
        h = self.l2(h)
        return h

2つのネットワークを30エポック回して、自作データ50本を認識させた結果です。

1)以前に使った全結合ネットワーク(chainer) 2)今回使ったCNN+ドロップアウト(pytorch)

MNISTのテストデータ10000個の認識率: 97.73%

自作データ50個の認識率: 52%

MNISTのテストデータ10000個の認識率: 99.33%

自作データ50個の認識率: 82%

以前は自分で書いたデータを1)の全結合ネットワークの学習データに食わせて85%程度の認識率でしたが、今回はMNISTのデータの学習のみで自分の手書き文字の認識率を82%まで上げる事が出来ました。学習データの不足は事実としても、過学習の影響もかなりあった様です。CNNと学習時にドロップアウトを使う事で汎化性能をかなり上げられる事が判ります。

1)に使ったコード:Chainer

# -*- Coding: utf-8 -*-
# OS
import os
# OpenCV
import cv2

# Numpy
import numpy as np
# Chainer
import chainer
import chainer.links as L
import chainer.functions as F
from chainer import Chain,optimizers,Variable
from chainer import serializers
import matplotlib.pyplot as plt

train, test = chainer.datasets.get_mnist()

x_train = []
x_test  = []
t_train = []
t_test  = []

for line in train:
    x_train.append(line[0])
    t_train.append(int(line[-1]))

for line in test:
    x_test.append(line[0])
    t_test.append(int(line[-1]))

x_train = np.array(x_train,dtype=np.float32)
x_test = np.array(x_test,dtype=np.float32)
t_train = np.array(t_train,dtype=np.int32)
t_test = np.array(t_test,dtype=np.int32)

# DNN class
class DNN(Chain):
    def __init__(self):
        super(DNN, self).__init__(
            l1 = L.Linear(784,100),
            l2 = L.Linear(100,100),
            l3 = L.Linear(100,10)
        )
    def forward(self,x):
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        h3 = self.l3(h2)
        return h3

# Create DNN class instance
model = DNN()

# Set optimizer
optimizer = optimizers.Adam()
optimizer.setup(model)

# Number of epochs
n_epoch = 30

# batch size
batch_size = 1000

# Execute Training

for epoch in range(n_epoch):
    sum_loss = 0
    perm = np.random.permutation(60000)
    for i in range(0, 60000, batch_size):
        x = Variable(x_train[perm[i:i+batch_size]])
        t = Variable(t_train[perm[i:i+batch_size]])
        y = model.forward(x)
        model.cleargrads()
        loss = F.softmax_cross_entropy(y, t)
        loss.backward()
        optimizer.update()
        sum_loss += loss.data*batch_size

    print("epoch: {}, mean loss: {}".format(epoch, sum_loss/60000))

# Save Result
#serializers.save_npz("mnist_model2.npz", model)
# Execute Test
cnt = 0
for i in range(10000):
    x = np.array([x_test[i]], dtype=np.float32)
    t = t_test[i]
    y = model.forward(x)
    y = np.argmax(y.data[0])
    if t == y:
        cnt += 1

# Display Result
print("accuracy: {}".format(cnt/(10000)))

# Execute Test
# 自分の手書き文字の分類試験。./mydata/下にpngファイルを配置
mydata_dir = './mydata/'

mydata_files = os.listdir(mydata_dir)
mydata_files.sort()

testcnt = 0
testok  = 0
for test_file in mydata_files:
    if test_file.endswith('.png'):
        testcnt += 1
        x_test_data = []
        label = int(test_file[0])
        image = cv2.imread(mydata_dir + test_file)
        image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
        image_resized = cv2.resize(image,(28,28),interpolation=cv2.INTER_AREA)
        image_gs = image_resized.flatten()
        x_test_data.append(image_gs)

        x_test = np.array([image_gs],dtype=np.float32)
        x_test /= 255

        x = np.array([x_test[0]],dtype=np.float32)
        y = model.forward(x)
        res = np.argmax(y.data[0])
        if res == label:
            testok += 1
            test_res_str = 'OK'
        else:
            test_res_str = 'NG'

        print(' test file = %s ,label = %d,test result = %d,%s' % (test_file,label,res,test_res_str))

# Display result
print('OK %d,NG %d,Total %d' % (testok,(testcnt - testok),testcnt))
print('accuracy: {}'.format(testok/testcnt))

2)に使ったコード: PyTorch

# -*- Coding: utf-8 -*-

# OS
import os
# OpenCV
import cv2
# Numpy
import numpy as np
# Chainer
# ★Chainerのdatasetsからmnistを引っ張るためchainerは残す
import chainer
# ★Pytorchをインポート
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

print('MNIST Deep Learning sample by PyTorch')
print(' PyTorch Version: ',torch.__version__)

# GPU availability check
if torch.cuda.is_available():
    device = 'cuda'
else:
    device = 'cpu'
print(' Device: ',device)

train, test = chainer.datasets.get_mnist()
#train, test = chainer.datasets.get_fashion_mnist()

x_train = []
x_test  = []
t_train = []
t_test  = []

for line in train:
    x_train.append(line[0])
    t_train.append(int(line[-1]))

for line in test:
    x_test.append(line[0])
    t_test.append(int(line[-1]))

x_train = np.array(x_train,dtype=np.float32)
x_test = np.array(x_test,dtype=np.float32)
# ★PyTorchでは分類クラスはintではなくlong
t_train = np.array(t_train,dtype=np.int64)
t_test = np.array(t_test,dtype=np.int64)

x_train = x_train.reshape(len(x_train),1,28,28)
x_test = x_test.reshape(len(t_test),1,28,28)

        
# DNN class
class DNN(nn.Module):
    def __init__(self):
        super(DNN,self).__init__()
        self.cv1 = nn.Conv2d(1,32,3)
        self.cv2 = nn.Conv2d(32,64,3)
        self.l1 = nn.Linear(64 * 5 * 5,500)
        self.l2 = nn.Linear(500,10)
    def forward(self,x,train=True):
        c = F.dropout(F.max_pool2d(F.relu(self.cv1(x)),2),training = train)
        c = F.dropout(F.max_pool2d(F.relu(self.cv2(c)),2),training = train)
        # Chainerと異なり、全結合層に入力する前にshape変換が必要
        c = c.view(-1,64 * 5 * 5)
        h = F.relu(self.l1(c))
        h = F.dropout(h,training = train)
        h = self.l2(h)
        return h

# Create DNN class instance
model = DNN().to(device)

# Set optimizer
# ★optimizerをセット
optimizer = optim.Adam(model.parameters())

# Number of epochs
n_epoch = 30

# batch size
batch_size = 1000

# predict batch size
predict_batch_size = 1000 

# Execute Training
for epoch in range(n_epoch):
    sum_loss = 0
    perm = np.random.permutation(60000)
    for i in range(0, 60000, batch_size):
# ★x_trainとy_trainをpytorchのTensorに変換
#   ここで変換は効率悪い気もするけど..元のコードベースを崩さないため
        x = torch.from_numpy(x_train[perm[i:i+batch_size]]).to(device)
        t = torch.from_numpy(t_train[perm[i:i+batch_size]]).to(device)
        y = model.forward(x,train=True)
# ★cleargrads()はzero_grad()に
        model.zero_grad()
# ★softmax_cross_entropy()→cross_entropy()
        loss = F.cross_entropy(y,t)
        loss.backward()
#  ★optimizer.update()はoptimizer.step()に
        optimizer.step()
        sum_loss += loss.data*batch_size

    print("epoch: {}, mean loss: {}".format(epoch, sum_loss/60000))

# Execute Test
cnt = 0


# ★テストデータを纏めて推論
# GPUのメモリに入りきらない場合があるので、推論もミニバッチで実施
for si in range(0,10000,predict_batch_size):
    x = torch.from_numpy(x_test[si:si+predict_batch_size]).to(device)
    y = model.forward(x,train=False)
# ★照合用に推論結果yをCPUに戻し、numpyの行列に変換
    y = y.cpu().detach().numpy()
# ★1点づつ答え合わせ
    for i in range(predict_batch_size):
        t = t_test[si + i]
        yi = np.argmax(y[i])
        if t == yi:
            cnt += 1

# Display Result
print("accuracy: {}".format(cnt/(10000)))

# Start test
mydata_dir = './mydata/'

mydata_files = os.listdir(mydata_dir)
mydata_files.sort()

testcnt = 0
testok  = 0
for test_file in mydata_files:
    if test_file.endswith('.png'):
        testcnt += 1
        x_test_data = []
        label = int(test_file[0])
        image = cv2.imread(mydata_dir + test_file)
        image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
        image_resized = cv2.resize(image,(28,28),interpolation=cv2.INTER_AREA)
        image_gs = image_resized.flatten()
        x_test_data.append(image_gs)

        x_test = np.array([image_gs],dtype=np.float32)
        x_test /= 255

        x = torch.from_numpy(np.array([x_test[0]],dtype=np.float32).reshape(1,1,28,28)).to(device)
        y = model.forward(x,train=False)
        y = y.cpu().detach().numpy()
        res = np.argmax(y[0])
        if res == label:
            testok += 1
            test_res_str = 'OK'
        else:
            test_res_str = 'NG'

        print(' test file = %s ,label = %d,test result = %d,%s' % (test_file,label,res,test_res_str))
#        print(y[0])

# Display result
print('OK %d,NG %d,Total %d' % (testok,(testcnt - testok),testcnt))
print('accuracy: {}'.format(testok/testcnt))