Kaggle - Digit Recognizer(3) - 手書きの数字認識

手書きの数字認識問題Digit Recognizerの3回目の記事になります。

今回は畳み込みニューラルネットワーク(Convolutional Neural Networks)の処理を実装していきます。

CNNは画像全体にわたって、特徴の一致件数を計算することによって、フィルタをかける手法です。この時に実行する演算が「畳み込み」で、畳み込みニューラルネットワークの名称はここから取られています。

画像や動画認識に広く使われているモデルです。

ライブラリのインポート

必要なライブラリをインポートします。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
from tqdm import tqdm
from sklearn.model_selection import train_test_split
import seaborn as sns
import warnings
warnings.filterwarnings("ignore")

import torch
import torch.nn as nn
from torch.autograd import Variable
import torchvision

CSVファイル読み込み・正解ラベルの準備

CSVファイルを読み込み、正解ラベルのデータを分けます。

正解ラベルはNumpyのint32型に変換します。

[ソース]

1
2
3
4
data = pd.read_csv('../input/digit-recognizer/train.csv', dtype = np.float32)
labels = data.pop('label').astype('int32')

data.head() # let's see first five rows

[結果]


変換した正解ラベルを表示します。

[ソース]

1
labels.head() # after converting labels to `int32`

[結果]


次に0~255のピクセルデータを0~1に変換します。(正規化)

正解ラベルと0~1に変換したデータはNumpyの配列型にしておきます。

また、訓練データと評価データに分けます。(4:1の割合)

[ソース]

1
2
3
4
5
data = data.to_numpy() / 255.0 # converting to numpy and normalizing between 0 and 1
labels = labels.to_numpy()

x_train, x_val, y_train, y_val = train_test_split(data, labels, test_size=0.2)
print(f'x_train.shape: {x_train.shape}, x_val.shape: {x_val.shape}')

[結果]

ピクセルデータをイメージ化

手書き数字のピクセルデータをイメージ化して表示します。

[ソース]

1
2
3
4
5
6
7
8
plt.figure(figsize=(12, 10))
for i in range(16):
plt.subplot(4, 4,i+1)
plt.xticks([])
plt.yticks([])
plt.imshow(x_train[i].reshape(28,28), cmap=plt.cm.binary)
plt.xlabel(y_train[i])
plt.show()

[結果]

PyTorchデータセットの準備

NumpyのデータをPyTorchのデータセットに変換します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
x_train_tensor = torch.from_numpy(x_train)
y_train_tensor = torch.from_numpy(y_train).type(torch.LongTensor)

x_val_tensor = torch.from_numpy(x_val)
y_val_tensor = torch.from_numpy(y_val).type(torch.LongTensor)

print(f'x_train_tensor.dtype: {x_train_tensor.dtype}, y_train_tensor.dtype: {y_train_tensor.dtype}')

train_data = torch.utils.data.TensorDataset(x_train_tensor, y_train_tensor)
val_data = torch.utils.data.TensorDataset(x_val_tensor, y_val_tensor)

train_loader = torch.utils.data.DataLoader(dataset=train_data, batch_size=32, shuffle=True)
val_loader = torch.utils.data.DataLoader(dataset=val_data, batch_size=1, shuffle=False)

[結果]


GPUが使用できる場合は、GPU使用するように設定します。GPUがない場合はCPUを利用します。

[ソース]

1
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

CNNモデルの定義

CNNモデルを定義します。

損失関数(交差エントロピー)と最適化アルゴリズム(SGD Optimizer)もあわせて定義します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
learning_rate = 0.001
num_epochs = 10
num_classes = 10

# Convolutional neural network (two convolutional layers)
class ConvNet(nn.Module):
def __init__(self, num_classes=10):
super(ConvNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(16),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))

self.layer2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))

self.fc = nn.Linear(7 * 7 * 32, num_classes)

def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
return out

model = ConvNet(num_classes).to(device) # CNNモデルの生成

criterion = nn.CrossEntropyLoss() # 交差エントロピーの生成
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # SGD Optimizer(確率的勾配降下法)の生成

学習と評価

CNNモデルを使って学習と評価を行います。

Kaggleのノートブック環境で実行しましたが30分以上かかりましたので気長にお待ちください。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
iter_num = 0
loss_list = []
iteration_list = []
accuracy_list = []

# Train the model()
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader): # (訓練データごとにループ)
images = images.view(32, 1, 28, 28).to(device)
labels = labels.to(device)

# Forward pass(順伝搬:初期の入力を層ごとに処理して出力に向けて送ること)
outputs = model(images)
loss = criterion(outputs, labels)

# Backward and optimize(最適化、確率的勾配降下法)
optimizer.zero_grad()
loss.backward()
optimizer.step()

iter_num += 1

if iter_num % 50 == 0:
# Calculate Accuracy(正解率の算出)
correct = 0
total = 0
# Iterate through val dataset(評価データごとにループ)
for images, labels in val_loader:
test = images.view(1, 1, 28, 28).to(device)
labels = labels.to(device)
# Forward propagation(順伝搬:初期の入力を層ごとに処理して出力に向けて送ること)
outputs = model(test)

# Get predictions from the maximum value(予測値の取得)
predicted = torch.max(outputs.data, 1)[1]

# Total number of labels(正解ラベルの総数)
total += len(labels)

correct += (predicted == labels).sum()

accuracy = 100 * correct / float(total)

# store loss and iteration(損失・正解率を保持)
loss_list.append(loss.data)
iteration_list.append(iter_num)
accuracy_list.append(accuracy)
if iter_num % 500 == 0:
# Print Loss(損失と正解率を表示)
print('Iteration: {}, Loss: {:.4f}, Accuracy: {:.4f} %'.format(iter_num, loss.data, accuracy))

[結果]

最終的な正解率は94.98%になりました。


学習済みモデルを使って最終評価を行います。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Evaluation(評価)
correct = 0
total = 0
# Iterate through val dataset(データごとにループ)
for images, labels in val_loader:
test = images.view(1, 1, 28, 28).to(device)
labels = labels.to(device)
# Forward propagation(順伝搬:初期の入力を層ごとに処理して出力に向けて送ること)
outputs = model(test)

# Get predictions from the maximum value(予測値の取得)
predicted = torch.max(outputs.data, 1)[1]

# Total number of labels(正解ラベルの総数)
total += len(labels)

correct += (predicted == labels).sum()

accuracy = 100 * correct / float(total)
print(accuracy)

[結果]

同じく正解率は94.98%です。

グラフ化

損失と正解率の推移をグラフ化します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
# Visualize loss
plt.plot(iteration_list,loss_list)
plt.xlabel("Num. of Iters.")
plt.ylabel("Loss")
plt.title("Loss vs Num. of Iters.")
plt.show()

# Visualize Accuracy
plt.plot(iteration_list,accuracy_list)
plt.xlabel("Num. of Iters.")
plt.ylabel("Accuracy")
plt.title("Accuracyvs Num. of Iters.")
plt.show()

[結果]

前回記事(ロジスティック回帰モデル)と比べると損失はより早く収束しているように見えます。

また正解率に関しては収束のペースはあまり変わってないようですが、最終正解率は85%前後から95%前後と10%ほど精度が上がっています。