はじめに
この記事は限界開発鯖アドベントカレンダー1日目(12/1)の記事です。
遅刻しました。
ハロウィンネタは間に合いませんでした。
自己紹介
isso0424です。
goとtsを一生書いてるオタクです。
Twitter GitHub
きっかけ
ハロウィン、クリスマス、正月のような季節のイベント、オタクになった結果これにちなんだ推しのイラストを見ることで季節を感じるようになりました。PCくんにもこの感情を教えてやりたいと思い立ったので作りました。
実装
今回はpytorchを用いました。
ハロウィン、クリスマス、その他の3クラスの分別を行います。
AI触るの2年ぶりで全てを忘れてるので雑なのは許してください。
ディレクトリの構造はこんな感じです。
main.py
# データセット用のディレクトリ
data
- halloween
- christmas
- normal
データを読み込む
まず、データセットからデータを読み込みます
今回は3クラスそれぞれ60枚ずつ、合計180枚のデータを用います。
ちなみに遅刻の原因はこのデータを集めてたことです。
バッチサイズは16、学習用データと検証用データの比は7:3でいきます。
# 256x256の画像として読み込む
transform = transforms.Compose([transforms.Resize(256), transforms.ToTensor()])
dataset = ImageFolder("./data", transform)
sample_count = len(dataset)
train_count = int(sample_count * 0.7)
test_count = sample_count - train_count
train_dataset, test_dataset = random_split(dataset, [train_count, test_count])
batch_size = 16
train_dataloader = DataLoader(train_dataset, batch_size=batch_size)
test_dataloader = DataLoader(test_dataset, batch_size=batch_size)
ネットワークの定義
次に学習させるニューラルネットワークを定義していきます。
今回は以下の流れで通しています。(層の詳細には今回触れないので気になったら調べて見てください)
2次元畳み込み -> ReLU(活性化関数)へ通す -> プーリングでサイズ半減 -> もう1度繰り返す -> ベクトルへ変換 -> 全結合層*3 -> 3クラスへ
class NeuralNetwork(nn.Module):
def __init__(self):
super(NeuralNetwork, self).__init__()
self.flatten = nn.Flatten()
self.image_stack = nn.Sequential(
nn.Conv2d(3, 8, 3),
nn.ReLU(),
nn.MaxPool2d(2, 2),
nn.Conv2d(8, 16, 3),
nn.ReLU(),
nn.MaxPool2d(2, 2),
)
self.linear_relu_stack = nn.Sequential(
nn.Linear(2**6*31**2, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 3),
)
def forward(self, x):
x = self.image_stack(x)
x = self.flatten(x)
return self.linear_relu_stack(x
損失関数・最適化関数の選択
ネットワークの定義を行ったらネットワークをモデル化し、CPUに贈ります。
この際に損失関数と最適化関数の選択を行います。
損失関数は多クラス分類に向いているソフトマックス交差エントロピー誤差、最適化関数関数はAdamを利用します。
device = "cpu"
model = NeuralNetwork().to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters())
学習用関数の定義
データローダーに読み込んだデータを用いてモデルの学習を行います。
損失関数のデータを用いて最適化関数によりモデルの学習を勧めています。
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
model.train()
for batch, (x, y) in enumerate(dataloader):
x, y = x.to(device), y.to(device)
pred = model(x)
loss = loss_fn(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
loss, current = loss.item(), batch * len(x)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
検証用関数の定義
学習用関数で学習されたモデルに検証用のデータを入れることでモデルがどれぐらいの精度であるかを確かめます。
def test(dataloader, model, loss_fn):
size = len(dataloader)
num_batches = len(dataloader)
model.eval()
test_loss, correct = 0, 0
with torch.no_grad():
for x, y in dataloader:
x, y = x.to(device), y.to(device)
pred = model(x)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).sum().item()
test_loss /= num_batches
correct /= size * batch_size
print(f"Test Error \n Accuracy: {(100*correct):>0.1f}% Avg loss: {test_loss:>8f} \n")
学習用関数、検証用関数を回す
最後に、定義した関数を一定回数回すことでモデルの学習を行わせます。
epochs = 30
for t in range(epochs):
print(f"Epoch {t + 1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)
test(test_dataloader, model, loss_fn)
完成!
という訳でできたコードがこれです。
from pathlib import Path
from PIL import Image
import torch
import torchvision
from torch import nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, Dataset, random_split
from torchvision import transforms
from torchvision.datasets import ImageFolder
transform = transforms.Compose([transforms.Resize(256), transforms.ToTensor()])
dataset = ImageFolder("./pixiv", transform)
sample_count = len(dataset)
train_count = int(sample_count * 0.7)
test_count = sample_count - train_count
train_dataset, test_dataset = random_split(dataset, [train_count, test_count])
batch_size = 16
train_dataloader = DataLoader(train_dataset, batch_size=batch_size)
test_dataloader = DataLoader(test_dataset, batch_size=batch_size)
class NeuralNetwork(nn.Module):
def __init__(self):
super(NeuralNetwork, self).__init__()
self.flatten = nn.Flatten()
self.image_stack = nn.Sequential(
nn.Conv2d(3, 8, 3),
nn.ReLU(),
nn.MaxPool2d(2, 2),
nn.Conv2d(8, 16, 3),
nn.ReLU(),
nn.MaxPool2d(2, 2),
)
self.linear_relu_stack = nn.Sequential(
nn.Linear(2**6*31**2, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 3),
)
def forward(self, x):
x = self.image_stack(x)
x = self.flatten(x)
return self.linear_relu_stack(x)
device = "cpu"
model = NeuralNetwork().to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters())
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
model.train()
for batch, (x, y) in enumerate(dataloader):
x, y = x.to(device), y.to(device)
pred = model(x)
loss = loss_fn(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
loss, current = loss.item(), batch * len(x)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
def test(dataloader, model, loss_fn):
size = len(dataloader)
num_batches = len(dataloader)
model.eval()
test_loss, correct = 0, 0
with torch.no_grad():
for x, y in dataloader:
x, y = x.to(device), y.to(device)
pred = model(x)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).sum().item()
test_loss /= num_batches
correct /= size * batch_size
print(f"Test Error \n Accuracy: {(100*correct):>0.1f}% Avg loss: {test_loss:>8f} \n")
epochs = 30
for t in range(epochs):
print(f"Epoch {t + 1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)
test(test_dataloader, model, loss_fn)
print("Done!")
実行
実行はコマンドライン上で python main.py
で行えます
実行結果
Epoch 1
-------------------------------
loss: 1.089609 [ 0/ 133]
loss: 1.125430 [ 16/ 133]
loss: 1.057397 [ 32/ 133]
loss: 1.316751 [ 48/ 133]
loss: 1.466839 [ 64/ 133]
loss: 1.003903 [ 80/ 133]
loss: 1.409296 [ 96/ 133]
loss: 0.958725 [ 112/ 133]
loss: 1.126692 [ 40/ 133]
Test Error
Accuracy: 40.6% Avg loss: 1.113808
Epoch 2
-------------------------------
loss: 0.998735 [ 0/ 133]
loss: 0.971585 [ 16/ 133]
loss: 0.943458 [ 32/ 133]
loss: 0.857073 [ 48/ 133]
loss: 0.925722 [ 64/ 133]
loss: 0.927712 [ 80/ 133]
loss: 0.721942 [ 96/ 133]
loss: 0.766629 [ 112/ 133]
loss: 0.645847 [ 40/ 133]
Test Error
Accuracy: 43.8% Avg loss: 1.026091
Epoch 3
-------------------------------
loss: 0.641363 [ 0/ 133]
loss: 0.626481 [ 16/ 133]
loss: 0.982890 [ 32/ 133]
loss: 0.719489 [ 48/ 133]
loss: 0.644581 [ 64/ 133]
loss: 0.794168 [ 80/ 133]
loss: 0.522072 [ 96/ 133]
loss: 0.652429 [ 112/ 133]
loss: 0.318842 [ 40/ 133]
Test Error
Accuracy: 50.0% Avg loss: 0.944454
=====(省略)=====
Epoch 28
-------------------------------
loss: 0.002750 [ 0/ 133]
loss: 0.000667 [ 16/ 133]
loss: 0.002484 [ 32/ 133]
loss: 0.001230 [ 48/ 133]
loss: 0.076246 [ 64/ 133]
loss: 0.001946 [ 80/ 133]
loss: 0.044754 [ 96/ 133]
loss: 0.002072 [ 112/ 133]
loss: 0.000153 [ 40/ 133]
Test Error
Accuracy: 60.9% Avg loss: 1.523149
Epoch 29
-------------------------------
loss: 0.002148 [ 0/ 133]
loss: 0.000520 [ 16/ 133]
loss: 0.001764 [ 32/ 133]
loss: 0.000820 [ 48/ 133]
loss: 0.052940 [ 64/ 133]
loss: 0.001987 [ 80/ 133]
loss: 0.066464 [ 96/ 133]
loss: 0.001716 [ 112/ 133]
loss: 0.000110 [ 40/ 133]
Test Error
Accuracy: 59.4% Avg loss: 1.558621
Epoch 30
-------------------------------
loss: 0.001537 [ 0/ 133]
loss: 0.000431 [ 16/ 133]
loss: 0.001381 [ 32/ 133]
loss: 0.000679 [ 48/ 133]
loss: 0.055763 [ 64/ 133]
loss: 0.001720 [ 80/ 133]
loss: 0.058872 [ 96/ 133]
loss: 0.001472 [ 112/ 133]
loss: 0.000098 [ 40/ 133]
Test Error
Accuracy: 60.9% Avg loss: 1.584297
Done!
しっかり動いていますね!
めちゃめちゃ過学習になってそうですが適当に作ったにしては許容範囲ということにします。
最後に
久しぶりのディープラーニングだったのでだいぶ忘れてましたね...
許容範囲内とは言いましたが流石に正解率が低すぎるので学習用データ増やして再チャレンジしたいですね。
ぜひこの機械に皆さんもPyTorch、触ってみてください。