fc2ブログ

Pytorchで「ひらがな」を分類する(2)

 前回の続きで、作成したプログラムと実行結果について、④から順を追って説明します。
  ④ モデル・損失関数・最適化関数の初期化
  ⑤ 学習実行と評価、結果保存
  ⑥ 検証実行

前回のプログラムの182行目(後半)以降を抜粋しました。以下、このプログラムの行番号で流れを説明します。
#------------------------------
# Initialize Model,
# optimizer and loss criteria
#------------------------------

device = "cpu"
if (torch.cuda.is_available()):
device = "cuda"
print('Training on', device)
model = Net(num_classes=len(classes)).to(device)

# Optimizer : Adam
optimizer = optim.Adam(model.parameters(), lr=0.001)

# Loss criteria : CrossEntropy
loss_criteria = nn.CrossEntropyLoss()


#-------
# Train
#-------

epoch_nums = []
training_loss = []
validation_loss = []
epochs = 5
for epoch in range(1, epochs + 1):
train_loss = train(model, device, train_loader, optimizer, epoch)
test_loss = test(model, device, test_loader)
epoch_nums.append(epoch)
training_loss.append(train_loss)
validation_loss.append(test_loss)

# training and validation graph
plt.plot(epoch_nums, training_loss)
plt.plot(epoch_nums, validation_loss)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['training', 'validation'], loc='upper right')
plt.show()

# Confusion matrix
from sklearn.metrics import confusion_matrix

# Set the model to evaluate mode
model.eval()

# Get predictions for the test data
truelabels = []
predictions = []
for data, target in test_loader:
for label in target.cpu().data.numpy():
truelabels.append(label)
for prediction in model.cpu()(data).data.numpy().argmax(1):
predictions.append(prediction)

# Plot the confusion matrix
plt.figure(figsize=(12, 12), dpi=80)
cm = confusion_matrix(truelabels, predictions)
plt.imshow(cm, interpolation="nearest", cmap=plt.cm.Blues)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes)
plt.yticks(tick_marks, classes)
plt.xlabel("Predicted Shape")
plt.ylabel("Actual Shape")
plt.show()

# Save the model
model_file = 'models/shape_classifier.pt'
torch.save(model.state_dict(), model_file)
del model

#------
# Test
#------

# test.csv : test image file list
with open('/Users/drbobt/Downloads/test.csv') as f:
l = f.readlines()
datano = len(l)

from random import randint

# Function to predict the class of an image
def predict_image(classifier, image):
# Set the classifer model to evaluation mode
classifier.eval()

# Apply the same transformations
transformation = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])

# Preprocess the image
image_tensor = transformation(image).float()

# Add an extra batch dimension since pytorch treats all inputs as batches
image_tensor = image_tensor.unsqueeze_(0)

# Turn the input into a Variable
input_features = Variable(image_tensor)

# Predict the class of the image
output = classifier(input_features)
index = output.data.numpy().argmax()
return index

# Create a new model class and load the saved weights
model = Net()
model.load_state_dict(torch.load(model_file))

for i in range(0, datano):
img_path = l[i]
img = cv2.imread(img_path[:-1])
# Call the predction function
index = predict_image(model, img)
# Output the result
print(str(l[i][:-1]) + ': ' + classes[index])
④ モデル・損失関数・最適化関数の初期化 : 1〜16行目
 参考プログラムと同様に、損失関数はCrossEntropy、最適化関数はAdamを使いました。これも最適か否かは未検証です。

⑤ 学習実行と評価、結果保存 : 19〜72行目
 学習が終わった時のTrain、Validationの平均損失、精度はTrainScore_210911.pngで、40行目の結果は以下の通りでした。Learning1_210911.png67行目のConfusion matrix結果はLearning2_210911.pngで「は行、ば行、ぱ行」あたりの精度が良くなさそうなことが分かりました。

⑥ 検証実行 : 74〜120行目
 73種類の文字からそれぞれ100画像の合計7、300画像を読み込み、それぞれその画像の文字がを判定して、答え合わせを行いました。各文字の正答率の結果は以下の通りです。result1_210911.png予測していたよりも正答率が良い結果でした。正答率が悪い文字のワースト10はこんな感じです。result2_210911.pngConfusion matrixで確認できたように「ば行、ぱ行」が苦手のようです。画像の質が悪く、「゛」「゜」が潰れているものも多く、区別が付かずそのまま学習したのでしょう。それを除外すれば、満足できる精度で分類できることが分かりました。

今度は別の種類のサンプル画像でPytorchのCNNを試してみようと思います。
スポンサーサイト



Pytorchで「ひらがな」を分類する(1)

 MNISTFashion MNISTは、私のようなAI初学者でも簡単にCNN(Convolutional Neural Network)を試せる題材として、ネット上にサンプルプログラムが多くあり、実際の処理の中身を知らなくても、実行がすぐにできます。ただ、中身が分かっていないので、別の画像にした場合、何も応用が利かずにエラーを吐いて動かないことが多いですね。私自身も例外でないので、今回、勉強も兼ねて、PytorchのCNNと「ひらがな」の画像データセットを用いて分類をしてみました。

 ひらがな文字画像はgithubからダウンロードして利用しました。ひらがな文字リスト1_2109117zipファイルをダウンロードし、解凍すると73個のフォルダに展開しひらがな文字リスト2_210911それぞれのフォルダに画像ファイルが入っていました。画像サイズは48 [pixel]×48 [pixel]です。ひらがな文字リスト3_210911各フォルダ(73文字分)から500画像をランダムに抽出し、400画像を学習データ、100画像をテストデータとしました。

 PytorchのCNNのプログラムはMicrosoftのmlーbasicsにあるものを参考にしました。作成したプログラムは以下の通りです。
# Hiragana classification
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.autograd import Variable

import numpy as np
import os
import matplotlib as mpl
import matplotlib.pyplot as plt
import cv2
mpl.rcParams['font.family']='AppleGothic'
%matplotlib inline

#------------
# Read Data
#------------

# The images folder
data_path = 'data/char/'

# Get the class names
classes = os.listdir(data_path)
classes.sort()
print(len(classes), 'classes:')
print(classes)

# Show the image
fig = plt.figure(figsize=(100, 150))
i = 0
for sub_dir in os.listdir(data_path):
i+=1
img_file = os.listdir(os.path.join(data_path,sub_dir))[0]
img_path = os.path.join(data_path, sub_dir, img_file)
img = cv2.imread(img_path)
a=fig.add_subplot(1, len(classes),i)
a.axis('off')
imgplot = plt.imshow(img)
a.set_title(img_file)
plt.show()

# Function to ingest data using training and test loaders
def load_dataset(data_path):
# Load all of the images
transformation = transforms.Compose([
# transform to tensors
transforms.ToTensor(),
# Normalize the pixel values (in R, G, and B channels)
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])

# Load all of the images, transforming them
full_dataset = torchvision.datasets.ImageFolder(
root=data_path,
transform=transformation
)

# Split into training (70% and testing (30%) datasets)
train_size = int(0.7 * len(full_dataset))
test_size = len(full_dataset) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(full_dataset, [train_size, test_size])

# Training data : 50-image batches
train_loader = torch.utils.data.DataLoader(
train_dataset,
batch_size=50,
num_workers=0,
shuffle=False
)

# Test data : 50-image batches
test_loader = torch.utils.data.DataLoader(
test_dataset,
batch_size=50,
num_workers=0,
shuffle=False
)

return train_loader, test_loader

# Dataloaders for test and training data
train_loader, test_loader = load_dataset(data_path)

#-----------------
# Model definition
#-----------------

# Create a neural net class
class Net(nn.Module):
def __init__(self, num_classes=73):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=12, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(in_channels=12, out_channels=12, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(in_channels=12, out_channels=24, kernel_size=3, padding=1),
nn.Dropout2d(p=0.2),
)
self.classifier = nn.Linear(in_features= 12*12*24, out_features=num_classes)

def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), -1)
x = self.classifier(x)
return F.log_softmax(x, dim=1)

def train(model, device, train_loader, optimizer, epoch):
# Set the model to training mode
model.train()
train_loss = 0

print("Epoch:", epoch)
# Process the images in batches
for batch_idx, (data, target) in enumerate(train_loader):
# Use the CPU or GPU as appropriate
data, target = data.to(device), target.to(device)

# Reset the optimizer
optimizer.zero_grad()

# Push the data forward through the model layers
output = model(data)

# Get the loss
loss = loss_criteria(output, target)

# Keep a running total
train_loss += loss.item()

# Backpropagate
loss.backward()
optimizer.step()

# Print metrics for every 10 batches
if batch_idx % 10 == 0:
print('Training set [{}/{} ({:.0f}%)] Loss: {:.6f}'.format(
batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))

# return average loss for the epoch
avg_loss = train_loss / (batch_idx+1)
print('Training set: Average loss: {:.6f}'.format(avg_loss))
return avg_loss

def test(model, device, test_loader):
# Switch the model to evaluation mode
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
batch_count = 0
for data, target in test_loader:
batch_count += 1
data, target = data.to(device), target.to(device)

# Get the predicted classes for this batch
output = model(data)

# Calculate the loss for this batch
test_loss += loss_criteria(output, target).item()

# Calculate the accuracy for this batch
_, predicted = torch.max(output.data, 1)
correct += torch.sum(target==predicted).item()

# Calculate the average loss and total accuracy for this epoch
avg_loss = test_loss/batch_count
print('Validation set: Average loss: {:.6f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
avg_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))

# return average loss for the epoch
return avg_loss


#------------------------------
# Initialize Model,
# optimizer and loss criteria
#------------------------------

device = "cpu"
if (torch.cuda.is_available()):
device = "cuda"
print('Training on', device)
model = Net(num_classes=len(classes)).to(device)

# Optimizer : Adam
optimizer = optim.Adam(model.parameters(), lr=0.001)

# Loss criteria : CrossEntropy
loss_criteria = nn.CrossEntropyLoss()


#-------
# Train
#-------

epoch_nums = []
training_loss = []
validation_loss = []
epochs = 5
for epoch in range(1, epochs + 1):
train_loss = train(model, device, train_loader, optimizer, epoch)
test_loss = test(model, device, test_loader)
epoch_nums.append(epoch)
training_loss.append(train_loss)
validation_loss.append(test_loss)

# training and validation graph
plt.plot(epoch_nums, training_loss)
plt.plot(epoch_nums, validation_loss)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['training', 'validation'], loc='upper right')
plt.show()

# Confusion matrix
from sklearn.metrics import confusion_matrix

# Set the model to evaluate mode
model.eval()

# Get predictions for the test data
truelabels = []
predictions = []
for data, target in test_loader:
for label in target.cpu().data.numpy():
truelabels.append(label)
for prediction in model.cpu()(data).data.numpy().argmax(1):
predictions.append(prediction)

# Plot the confusion matrix
plt.figure(figsize=(12, 12), dpi=80)
cm = confusion_matrix(truelabels, predictions)
plt.imshow(cm, interpolation="nearest", cmap=plt.cm.Blues)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes)
plt.yticks(tick_marks, classes)
plt.xlabel("Predicted Shape")
plt.ylabel("Actual Shape")
plt.show()

# Save the model
model_file = 'models/shape_classifier.pt'
torch.save(model.state_dict(), model_file)
del model

#------
# Test
#------

# test.csv : test image file list
with open('/Users/drbobt/Downloads/test.csv') as f:
l = f.readlines()
datano = len(l)

from random import randint

# Function to predict the class of an image
def predict_image(classifier, image):
# Set the classifer model to evaluation mode
classifier.eval()

# Apply the same transformations
transformation = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])

# Preprocess the image
image_tensor = transformation(image).float()

# Add an extra batch dimension since pytorch treats all inputs as batches
image_tensor = image_tensor.unsqueeze_(0)

# Turn the input into a Variable
input_features = Variable(image_tensor)

# Predict the class of the image
output = classifier(input_features)
index = output.data.numpy().argmax()
return index

# Create a new model class and load the saved weights
model = Net()
model.load_state_dict(torch.load(model_file))

for i in range(0, datano):
img_path = l[i]
img = cv2.imread(img_path[:-1])
# Call the predction function
index = predict_image(model, img)
# Output the result
print(str(l[i][:-1]) + ': ' + classes[index])

PytorchのCNNのプログラムの大まかな流れは以下の通りです。Pytorchのバージョンは1.8.0です。
 ① パッケージインポート
 ② データ読み込み(dataset設定、dataloader定義)
 ③ Model定義
 ④ モデル・損失関数・最適化関数の初期化
 ⑤ 学習実行と評価、結果保存
 ⑥ 検証実行

作成したプログラムと実行結果について、順を追って説明します。
① パッケージインポート
  2〜7行目の6個のパッケージは定番なものです。
② データ読み込み(dataset設定、dataloader定義) : 18〜85行目
  プログラムのあるフォルダに「/data/char/(73文字分のフォルダ)」を作成し、その各フォルダに学習用の文字画像を400枚入れました。この時、面倒でしたがフォルダ名は「U30xx」ではなく、ひらがなに振り直しました。28、29行目の結果は以下の通りです。文字情報1_21091143行目の各フォルダから1文字ずつ抽出した画像はこんな感じです。文字情報2_210911400枚の画像をtrain:test=7:3で分割し、バッチサイズは50枚で学習させました。
③ Model定義 : 87〜179行目
 参考プログラムと同様に、畳み込み層を3個、プーリング層を2個利用したモデルにしました。入出力チャンネル数、カーネル数、パディング数の設定が理解不十分でハマってしまいましたが、動く形にはできました。最適か否かは未検証です。

長くなりましたので、④以降は次回にしたいと思います。

ご訪問者数

(Since 24 July, 2016)

タグクラウド


プロフィール

Dr.BobT

Author: Dr.BobT
興味のおもむくままに生涯考え続けるエンジニアでありたい。

月別アーカイブ

メールフォーム

名前:
メール:
件名:
本文: