さまざまなライブラリによる深層学習の実装例のご紹介

この記事は約39分で読めます。

深層学習(ディープラーニング)は、近年急速に発展してきた技術の一つです。
Chainer、PyTorch、TensorFlowは、これらの深層学習モデルを実装し、効率的に学習するための主要なフレームワークです。

この記事では、Chainer、PyTorch、TensorFlowを使用した簡単なCNNの実装について紹介します。
簡単な場合ということで、今回はMNISTの分類モデルを各フレームワークを使って作ってみます。
いずれも簡単な例から試しながらコードでやっていることを理解すると、実務など別の問題も書きやすくなりますし、他の種類の深層学習への参入もグッと楽になります。

各フレームワークの実装コードを例示しますので、皆さんの学習の参考になれば幸いです。

データの準備

前述の通り、今回の記事で使うMNISTデータセットをダウンロードしておきます。

import urllib
from sklearn.datasets import fetch_mldata

try:
    mnist = fetch_mldata('MNIST original')
except urllib.error.HTTPError as ex:
    from six.moves import urllib
    from scipy.io import loadmat
    import os
    print('Could not download MNIST data from mldata.org, trying alternative...')
    mnist_alternative_url = 'https://github.com/amplab/datascience-sp14/raw/master/lab7/mldata/mnist-original.mat'
    mnist_path = './mnist-original.mat'
    response = urllib.request.urlopen(mnist_alternative_url)
    with open(mnist_path, 'wb') as f:
        content = response.read()
        f.write(content)
    mnist_raw = loadmat(mnist_path)
    mnist = {
        "data": mnist_raw['data'].T,
        "target": mnist_raw['label'][0],
        "COL_NAMES": ['label', 'data'],
        "DESCR": 'mldata.org dataset: mnist-original',
    }
    print('Success!')

mnist['data'] = mnist['data'].astype(np.float32).reshape(len(mnist['data']), 28, 28, 1) # image data
mnist['data'] /= 255
mnist['target'] = mnist['target'].astype(np.int32) # label data
mnist['data'].shape, mnist['target'].shape # ((70000, 28, 28, 1), (70000,))

ChainerによるCNNの実装

Chainerの場合は、Classifierクラス・Trainerクラスを使った書き方もでき、とても便利です。
本記事では、これらのクラスを使わない、生の書き方とクラスを使った書き方の両方を記そうと思います。

まずはClassifierクラス・Trainerクラスを使わない場合の書き方が以下の通りです。

import datetime
import numpy as np
import matplotlib
import matplotlib.pylab as plt
import seaborn as sns
from sklearn import model_selection, metrics
import chainer
import chainer.functions as F
import chainer.links as L
from chainer.training import extensions

# train data size : validation data size= 8 : 2
train_x, valid_x, train_y, valid_y = model_selection.train_test_split(mnist['data'], mnist['target'], test_size=0.2)

# モデルクラス定義

class CNN(chainer.Chain):
    
    def __init__(self):
        super(CNN, self).__init__(            
            conv1 = L.Convolution2D(None, 20, 5),
            conv2 = L.Convolution2D(20, 50, 5),
            l1 = L.Linear(800, 500),
            l2 = L.Linear(500, 500),
            l3 = L.Linear(500, 10, initialW=np.zeros((10, 500), dtype=np.float32))
        )
 
    def __call__(self, x, t=None, train=False):
        
        x = chainer.Variable(x)
        if train:
            t = chainer.Variable(t)
            
        h = F.max_pooling_2d(F.relu(self.conv1(x)), 2)
        h = F.max_pooling_2d(F.relu(self.conv2(h)), 2)
        h = F.relu(self.l1(h))
        h = F.relu(self.l2(h))
        y = F.softmax(self.l3(h), axis=1)
        
        if train:
            loss, accuracy = F.softmax_cross_entropy(y, t), F.accuracy(y, t)
            return loss, accuracy
        else:
            return np.argmax(y.data, axis=1)
 
    def reset(self):
        
        self.zerograds() # 勾配の初期化

# モデルとオプティマイザの準備

gpu = 0

model = CNN()
optimizer = chainer.optimizers.Adam()
optimizer.setup(model)

if gpu >= 0:
    
    chainer.cuda.get_device(gpu).use()
    model.to_gpu(gpu)
    
    train_x = chainer.cuda.to_gpu(train_x)
    train_y = chainer.cuda.to_gpu(train_y)

# 学習

epoch_num = 5
batch_size = 1000
N = train_x.shape[0]

st = datetime.datetime.now()
for epoch in range(epoch_num):
    
    # mini-batch
    perm = np.random.permutation(N)
    total_loss = 0
    total_accuracy = 0
    
    for i in range(0, N, batch_size):
        
        x = train_x[perm[i:i+batch_size]]
        t = train_y[perm[i:i+batch_size]]
        
        model.reset()
        loss, accuracy = model(x=x, t=t, train=True)
        loss.backward()
        loss.unchain_backward()
        total_loss += loss.data
        total_accuracy += accuracy.data
        optimizer.update()
        
    ed = datetime.datetime.now()
    print("epoch:\t{}\ttotal loss:\t{}\tmean accuracy:\t{}\ttime:\t{}".format(epoch+1, total_loss, total_accuracy/(N/batch_size), ed-st))
    st = datetime.datetime.now()
epoch:	1	total loss:	103.07084	mean accuracy:	0.6566249	time:	0:00:00.707081
epoch:	2	total loss:	94.620056	mean accuracy:	0.7714464	time:	0:00:00.673655
epoch:	3	total loss:	93.4981	mean accuracy:	0.7904821	time:	0:00:00.678233
epoch:	4	total loss:	85.87946	mean accuracy:	0.9309107	time:	0:00:00.706425
epoch:	5	total loss:	83.2471	mean accuracy:	0.9762682	time:	0:00:00.683051

混合行列を確認した結果がこちらです。

model.to_cpu()

preds = model(valid_x, train=False)
    
cm = metrics.confusion_matrix(preds, valid_y)

plt.figure(figsize=(10,8))
sns.heatmap(cm, annot=True, fmt='d')
plt.show()

何枚か予測させてみた結果がこちらです。

indices = np.random.choice(len(valid_x), 30)

for i, idx in enumerate(indices):
    
    if i%10 == 0:
        fig, axs = plt.subplots(ncols=10, figsize=(15,1))
    
    x = valid_x[idx]
    y = valid_y[idx]
    
    x_img = x.reshape(28, 28)
    x = x[np.newaxis]
    
    pred = model(x, train=False)[0]

    axs[i%10].imshow(x_img, cmap='gray')
    title = 'y: {}'.format(y) + '\n' + 'pred: {}'.format(pred)
    axs[i%10].set_title(title)
    axs[i%10].axis('off')

plt.show()

Classifierクラス・Trainerクラスを用いると以下のように書けます。
モデルクラスが順伝播の役割だけにシンプル化されていて、学習部分もとてもスッキリ書けます。

import numpy as np
import matplotlib
import matplotlib.pylab as plt
import seaborn as sns
from sklearn import model_selection, metrics
import chainer
import chainer.functions as F
import chainer.links as L
from chainer.training import extensions

# train data size : validation data size= 8 : 2
train_x, valid_x, train_y, valid_y = model_selection.train_test_split(mnist['data'], mnist['target'], test_size=0.2)

train_dataset = chainer.datasets.tuple_dataset.TupleDataset(train_x, train_y)
valid_dataset = chainer.datasets.tuple_dataset.TupleDataset(valid_x, valid_y)

# モデルクラス定義

class CNN(chainer.Chain):
    
    def __init__(self):
        super(CNN, self).__init__(
            conv1 = L.Convolution2D(1, 20, ksize=5),
            conv2 = L.Convolution2D(20, 50, ksize=5),
            fc1 = L.Linear(None, 500),
            fc2 = L.Linear(500, 500),
            fc3 = L.Linear(500, 10)
        )
    
    def __call__(self, x):
        
        h = F.max_pooling_2d(F.relu(self.conv1(x)), 2)
        h = F.max_pooling_2d(F.relu(self.conv2(h)), 2)
        h = self.fc1(h)
        h = self.fc2(h)
        y = self.fc3(h)
        return y

# モデルとオプティマイザの準備

gpu = 0

model = L.Classifier(CNN())
optimizer = chainer.optimizers.Adam()
optimizer.setup(model)

if gpu >= 0:
    
    chainer.cuda.get_device(gpu).use()
    model.to_gpu(gpu)

# 学習

epoch_num = 10
batch_size = 1000

train_iter = chainer.iterators.SerialIterator(train_dataset, batch_size)
test_iter = chainer.iterators.SerialIterator(valid_dataset, batch_size, repeat=False, shuffle=False)
updater = chainer.training.StandardUpdater(train_iter, optimizer, device=gpu)
trainer = chainer.training.Trainer(updater, (epoch_num, 'epoch'), out='result')
trainer.extend(extensions.Evaluator(test_iter, model, device=gpu))
trainer.extend(extensions.LogReport(trigger=(1, 'epoch')))
trainer.extend(extensions.LogReport())
trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'validation/main/loss', 'main/accuracy', 'validation/main/accuracy', 'elapsed_time']))
trainer.run()
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.410563    0.106101              0.873232       0.968071                  13.8441       
2           0.0775864   0.0748249             0.976143       0.976572                  14.5781       
3           0.0493338   0.0472499             0.985          0.985857                  15.3067       
4           0.0381874   0.0457631             0.987768       0.986286                  16.0328       
5           0.0298464   0.0450175             0.990696       0.987071                  16.7709       
6           0.0265287   0.0429706             0.991482       0.986786                  17.513        
7           0.0200595   0.0417418             0.993822       0.986857                  18.2376       
8           0.0140155   0.0386183             0.995768       0.988571                  18.9775       
9           0.0122673   0.0397485             0.996197       0.989143                  19.7192       
10          0.0107453   0.0427487             0.996589       0.988429                  20.4545 

混合行列を確認した結果がこちらです。

model.to_cpu()

with chainer.using_config('train', False):
    
    preds = F.argmax(F.softmax(model.predictor(valid_x), axis=1), axis=1).data.squeeze()
    
cm = metrics.confusion_matrix(preds, valid_y)

plt.figure(figsize=(10,8))
sns.heatmap(cm, annot=True, fmt='d')
plt.show()

何枚か予測させてみた結果がこちらです。

indices = np.random.choice(len(valid_x), 30)

for i, idx in enumerate(indices):
    
    if i%10 == 0:
        fig, axs = plt.subplots(ncols=10, figsize=(15,1))
    
    x = valid_x[idx]
    y = valid_y[idx]
    
    x_img = x.reshape(28, 28)
    x = x[np.newaxis]
    
    with chainer.using_config('train', False):
    
        pred = F.argmax(F.softmax(model.predictor(x), axis=1), axis=1).data.squeeze()
        
        axs[i%10].imshow(x_img, cmap='gray')
        title = 'y: {}'.format(y) + '\n' + 'pred: {}'.format(pred)
        axs[i%10].set_title(title)
        axs[i%10].axis('off')

plt.show()

PyTorchによるCNNの実装

PyTorchの場合は以下の通りになります。
PyTorchはChainerにインスパイヤされて開発されているので、Chainerの生の書き方ととても似ています。

import sys
import numpy as np
import matplotlib.pylab as plt
import seaborn as sns
from sklearn import model_selection, metrics
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import datasets, transforms
from torch.autograd import Variable
from tqdm import tqdm

gpu = 0
device = torch.device("cuda:{}".format(gpu) if torch.cuda.is_available() else "cpu")

# train data size : validation data size= 8 : 2
train_x, valid_x, train_y, valid_y = model_selection.train_test_split(mnist['data'], mnist['target'], test_size=0.2)

# DataLoader化
batch_size = 1000
train = torch.utils.data.TensorDataset(torch.from_numpy(train_x), torch.from_numpy(train_y))
train_loader = torch.utils.data.DataLoader(train, batch_size=batch_size, shuffle=True)
valid = torch.utils.data.TensorDataset(torch.from_numpy(valid_x), torch.from_numpy(valid_y))
valid_loader = torch.utils.data.DataLoader(valid, batch_size=batch_size)

# モデルクラス定義

class CNN(nn.Module):
    
    def __init__(self):
        super(CNN, self).__init__()
        
        # 画像を畳み込みを行うまで
        self.head = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=20, kernel_size=(5, 5), stride=1, padding=0),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(in_channels=20, out_channels=50, kernel_size=(5, 5), stride=1, padding=0),
            nn.MaxPool2d(kernel_size=2)
        )
        
        # 畳み込みで得られたベクトルを出力層に順伝播させるまで
        self.tail = nn.Sequential(
            nn.Linear(50*4*4, 500),
            nn.ReLU(),
            nn.Linear(500, 500),
            nn.ReLU(),
            nn.Linear(500, 10)
        )
    
    def __call__(self, x):
        
        h = self.head(x)
        h = h.view(-1, 50*4*4)
        y = self.tail(h)
        return y

# モデルとオプティマイザの準備

model = CNN()

if torch.cuda.is_available():
    model.to(device)
    
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters())

# 学習

def forward(data_loader, train=True):
    
    running_loss = 0
    correct = 0
    total = 0
    
    for batch_idx, (xs, ys) in enumerate(data_loader):
        
        if torch.cuda.is_available():
            xs, ys = xs.to(device), ys.to(device).long()
            
        preds = model(xs)
        
        if train:
            optimizer.zero_grad()
            
            loss = criterion(preds, ys)
            running_loss += loss.item()
            
            loss.backward()
            optimizer.step()
            
        else:
            loss = criterion(preds, ys)
            running_loss += loss.item()
            
        _, pred_labels = torch.max(preds.data, 1)
        correct += (pred_labels == ys.data).sum().item()
        total += ys.size(0)

    loss = running_loss / len(data_loader)
    acc = correct / total
    
    return loss, acc

epoch_num = 5

for epoch in tqdm(range(epoch_num), file=sys.stdout):
    
    train_loss, train_acc = forward(train_loader)
    val_loss, val_acc = forward(valid_loader, train=False)
    
    if (epoch+1) % 1 == 0:
        tqdm.write('epoch:\t{}\ttrain loss:\t{:.5f}\ttrain acc:\t{:.5f}\tval loss:\t{:.5f}\tval acc:\t{:.5f}'.format(
            epoch+1, train_loss, train_acc, val_loss, val_acc)
        )
epoch:	1	train loss:	0.62843	train acc:	0.82121	val loss:	0.18639	val acc:	0.94286
epoch:	2	train loss:	0.13478	train acc:	0.95946	val loss:	0.08982	val acc:	0.97221
epoch:	3	train loss:	0.07862	train acc:	0.97600	val loss:	0.06848	val acc:	0.97850
epoch:	4	train loss:	0.05616	train acc:	0.98261	val loss:	0.06634	val acc:	0.97800
epoch:	5	train loss:	0.04141	train acc:	0.98739	val loss:	0.04751	val acc:	0.98507
100%|██████████| 5/5 [00:06<00:00,  1.25s/it]

混合行列を確認した結果がこちらです。

model.cpu()

loader = torch.utils.data.DataLoader(valid, batch_size=len(valid_x))

for i, (x, y) in enumerate(loader):
    
    preds = model(x)
    _, pred_labels = torch.max(preds.data, 1)

cm = metrics.confusion_matrix(pred_labels, valid_y)

plt.figure(figsize=(10,8))
sns.heatmap(cm, annot=True, fmt='d')
plt.show()

何枚か予測させてみた結果がこちらです。

indices = np.random.choice(len(valid_x), 30)

for i, idx in enumerate(indices):
    
    if i%10 == 0:
        fig, axs = plt.subplots(ncols=10, figsize=(15,1))
    
    x = valid_x[idx]
    y = valid_y[idx]
    
    x_img = x.reshape(28, 28)
    x = x[np.newaxis]
    x = torch.from_numpy(x)
    
    pred = model(x)
    pred = pred.data.numpy()
    p = np.argmax(pred, axis=1)[0]

    axs[i%10].imshow(x_img, cmap='gray')
    title = 'y: {}'.format(y) + '\n' + 'pred: {}'.format(p)
    axs[i%10].set_title(title)
    axs[i%10].axis('off')

plt.show()

TensorFlowによるCNNの実装

次にTensorFlowによる書き方です。
TensorFlowも自分でネットワークを作る書き方と、kerasを使ってある程度スッキリとした書き方があるので、今回の記事では両方のパターンを作ってみます。

自身でネットワークを定義する書き方は以下の通りです。

import sys
import numpy as np
import matplotlib
import matplotlib.pylab as plt
import seaborn as sns
from sklearn import model_selection, metrics
import tensorflow as tf
from tqdm import tqdm

# train data size : validation data size= 8 : 2
train_x, valid_x, train_y, valid_y = model_selection.train_test_split(mnist['data'], mnist['target'], test_size=0.2)

# ラベルはone-hotベクトルに変換する
train_y = np.eye(np.max(train_y)+1)[train_y]
valid_y = np.eye(np.max(valid_y)+1)[valid_y]

# ネットワークの定義

# プレースホルダー
x_ = tf.placeholder(tf.float32, shape=(None, 28, 28, 1))
y_ = tf.placeholder(tf.float32, shape=(None, 10))

# 畳み込み層1
conv1_features = 20 # 畳み込み層1の出力次元数
max_pool_size1 = 2 # 畳み込み層1のマックスプーリングサイズ
conv1_w = tf.Variable(tf.truncated_normal([5, 5, 1, conv1_features], stddev=0.1), dtype=tf.float32) # 畳み込み層1の重み
conv1_b = tf.Variable(tf.constant(0.1, shape=[conv1_features]), dtype=tf.float32) # 畳み込み層1のバイアス
conv1_c2 = tf.nn.conv2d(x_, conv1_w, strides=[1, 1, 1, 1], padding="SAME") # 畳み込み層1-畳み込み
conv1_relu = tf.nn.relu(conv1_c2+conv1_b) # 畳み込み層1-ReLU
conv1_mp = tf.nn.max_pool(conv1_relu, ksize=[1, max_pool_size1, max_pool_size1, 1], strides=[1, max_pool_size1, max_pool_size1, 1], padding="SAME") # 畳み込み層1-マックスプーリング

# 畳み込み層2
conv2_features = 50 # 畳み込み層2の出力次元数
max_pool_size2 = 2 # 畳み込み層2のマックスプーリングのサイズ
conv2_w = tf.Variable(tf.truncated_normal([5, 5, conv1_features, conv2_features], stddev=0.1), dtype=tf.float32) # 畳み込み層2の重み
conv2_b = tf.Variable(tf.constant(0.1, shape=[conv2_features]), dtype=tf.float32) # 畳み込み層2のバイアス
conv2_c2 = tf.nn.conv2d(conv1_mp, conv2_w, strides=[1, 1, 1, 1], padding="SAME") # 畳み込み層2-畳み込み
conv2_relu = tf.nn.relu(conv2_c2+conv2_b) # 畳み込み層2-ReLU
conv2_mp = tf.nn.max_pool(conv2_relu, ksize=[1, max_pool_size2, max_pool_size2, 1], strides=[1, max_pool_size2, max_pool_size2, 1], padding="SAME") # 畳み込み層2-マックスプーリング

# 全結合層1
result_w = x_.shape[1] // (max_pool_size1*max_pool_size2)
result_h = x_.shape[2] // (max_pool_size1*max_pool_size2)
fc_input_size = result_w * result_h * conv2_features # 畳み込んだ結果、全結合層に入力する次元数
fc_features = 500 # 全結合層の出力次元数(隠れ層の次元数)
s = conv2_mp.get_shape().as_list() # [None, result_w, result_h, conv2_features]
conv_result = tf.reshape(conv2_mp, [-1, s[1]*s[2]*s[3]]) # 畳み込みの結果を1*N層に変換
fc1_w = tf.Variable(tf.truncated_normal([fc_input_size.value, fc_features], stddev=0.1), dtype=tf.float32) # 重み
fc1_b = tf.Variable(tf.constant(0.1, shape=[fc_features]), dtype=tf.float32) # バイアス
fc1 = tf.nn.relu(tf.matmul(conv_result, fc1_w)+fc1_b) # 全結合層1

# 全結合層2
fc2_w = tf.Variable(tf.truncated_normal([fc_features, fc_features], stddev=0.1), dtype=tf.float32) # 重み
fc2_b = tf.Variable(tf.constant(0.1, shape=[fc_features]), dtype=tf.float32) # バイアス
fc2 = tf.nn.relu(tf.matmul(fc1, fc2_w)+fc2_b) # 全結合層2

# 全結合層3
fc3_w = tf.Variable(tf.truncated_normal([fc_features, 10], stddev=0.1), dtype=tf.float32) # 重み
fc3_b = tf.Variable(tf.constant(0.1, shape=[10]), dtype=tf.float32) # バイアス
pred = tf.nn.softmax(tf.matmul(fc2, fc3_w)+fc3_b)
#y = tf.nn.softmax(tf.matmul(x, W) + b)

# クロスエントロピー誤差
#cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=y, labels=y_))
cross_entropy = -tf.reduce_sum(y_*tf.log(pred))

# 勾配法
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

# 正解率の計算
correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 学習

epoch_num = 5
batch_size = 1000
train_size = len(train_x)

sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

for epoch in tqdm(range(epoch_num), file=sys.stdout):
    perm = np.random.permutation(train_size)
    total_loss = 0
    for i in range(0, train_size, batch_size):
        batch_x = train_x[perm[i:i+batch_size]]
        batch_y = train_y[perm[i:i+batch_size]]
        train_step.run(feed_dict={x_: batch_x, y_: batch_y})
        total_loss += cross_entropy.eval(feed_dict={x_:batch_x, y_: batch_y})
    if (epoch+1)%1 == 0:
        train_accuracy = accuracy.eval(feed_dict={x_: train_x, y_: train_y})
        test_accuracy = accuracy.eval(feed_dict={x_: valid_x, y_: valid_y})
        tqdm.write('epoch:\t{}\tloss:\t{}\taccuracy:\t{}\ttest accuracy:\t{}'.format(epoch+1, total_loss, train_accuracy, test_accuracy))
epoch:	1	loss:	87826.048828125	accuracy:	0.833392858505249	test accuracy:	0.8343571424484253
epoch:	2	loss:	22228.538848876953	accuracy:	0.9141964316368103	test accuracy:	0.9131428599357605
epoch:	3	loss:	13720.878723144531	accuracy:	0.9390714168548584	test accuracy:	0.9347142577171326
epoch:	4	loss:	10438.166870117188	accuracy:	0.9506964087486267	test accuracy:	0.9468571543693542
epoch:	5	loss:	8523.885612487793	accuracy:	0.960107147693634	test accuracy:	0.9558571577072144
100%|██████████| 5/5 [00:08<00:00,  1.69s/it]

混合行列を確認した結果がこちらです。

preds = np.argmax(pred.eval(feed_dict={x_: valid_x}), axis=1)

cm = metrics.confusion_matrix(preds, np.argmax(valid_y, axis=1))

plt.figure(figsize=(10,8))
sns.heatmap(cm, annot=True, fmt='d')
plt.show()

何枚か予測させてみた結果がこちらです。

indices = np.random.choice(len(valid_x), 30)

for i, idx in enumerate(indices):
    
    if i%10 == 0:
        fig, axs = plt.subplots(ncols=10, figsize=(15,1))
    
    x = valid_x[idx]
    y = np.argmax(valid_y, axis=1)[idx]
    
    x_img = x.reshape(28, 28)
    x = x[np.newaxis]
    
    p = np.argmax(pred.eval(feed_dict={x_: x}), axis=1)[0]

    axs[i%10].imshow(x_img, cmap='gray')
    title = 'y: {}'.format(y) + '\n' + 'pred: {}'.format(p)
    axs[i%10].set_title(title)
    axs[i%10].axis('off')

plt.show()

次にkerasを使った書き方が以下の通りです。
だいぶスッキリとして書けます。

import sys
import numpy as np
import matplotlib
import matplotlib.pylab as plt
import seaborn as sns
from sklearn import model_selection, metrics
import tensorflow as tf
from tqdm import tqdm

# train data size : validation data size= 8 : 2
train_x, valid_x, train_y, valid_y = model_selection.train_test_split(mnist['data'], mnist['target'], test_size=0.2)

# ラベルはone-hotベクトルに変換する
train_y = np.eye(np.max(train_y)+1)[train_y]
valid_y = np.eye(np.max(valid_y)+1)[valid_y]

epoch_num = 1
batch_size = 1000

model = tf.keras.models.Sequential([
    tf.keras.layers.Conv2D(20, (5, 5), activation=tf.nn.relu),
    tf.keras.layers.Conv2D(50, (5, 5), activation=tf.nn.relu),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(500, activation=tf.nn.relu),
    tf.keras.layers.Dense(500, activation=tf.nn.relu),
    tf.keras.layers.Dense(10, activation=tf.nn.softmax),
])

model.compile(
    optimizer=tf.train.AdamOptimizer(),
    loss=tf.keras.losses.categorical_crossentropy,
    metrics=[tf.keras.metrics.categorical_accuracy]
)

model.fit(train_x, train_y, epochs=epoch_num, batch_size=batch_size, validation_data=(valid_x, valid_y))
Train on 56000 samples, validate on 14000 samples
Epoch 1/1
56000/56000 [==============================] - 2s 29us/step - loss: 0.4044 - categorical_accuracy: 0.8761 - val_loss: 0.0894 - val_categorical_accuracy: 0.9734

混合行列を確認した結果がこちらです。

preds = np.argmax(model.predict(valid_x), axis=1)

cm = metrics.confusion_matrix(preds, np.argmax(valid_y, axis=1))

plt.figure(figsize=(10,8))
sns.heatmap(cm, annot=True, fmt='d')
plt.show()

何枚か予測させてみた結果がこちらです。

indices = np.random.choice(len(valid_x), 30)

for i, idx in enumerate(indices):
    
    if i%10 == 0:
        fig, axs = plt.subplots(ncols=10, figsize=(15,1))
    
    x = valid_x[idx]
    y = np.argmax(valid_y, axis=1)[idx]
    
    x_img = x.reshape(28, 28)
    x = x[np.newaxis]
    
    p = np.argmax(model.predict(x), axis=1)[0]

    axs[i%10].imshow(x_img, cmap='gray')
    title = 'y: {}'.format(y) + '\n' + 'pred: {}'.format(p)
    axs[i%10].set_title(title)
    axs[i%10].axis('off')

plt.show()

まとめ

この記事では、Chainer、PyTorch、TensorFlowを使った簡単なCNNの実装方法について学びました。

Chainerでは、ネットワークをクラスとして定義し、各層を関数として組み立てる方法を紹介しました。
特にClassifierクラスやTrainerクラスなどの便利な関数によるシンプルな記法により、柔軟でカスタマイズ性の高いモデル構築が可能であるという特長があります。

PyTorchでは、Chainerと同様にクラスを使用してCNNモデルを定義する手法を解説しました。

そしてTensorFlowを使った場合は、ネットワークを自ら定義する方法と、KerasのAPIを使用してシンプルに構築する方法を学びました。
Kerasの高い抽象化レベルにより、簡潔かつ効率的な記述が可能であり、深層学習初心者にもおすすめです。

深層学習の世界は広く、さらに高度なモデルやテクニックが存在しますが、これらの基本的な知識を踏まえることで、より洗練されたモデルを追求していくことができるでしょう。
引き続き学習を続け、さまざまな応用にチャレンジしてみてください。