u++の備忘録

PyTorchでFizzBuzz

本記事では、PyTorchのMLP(Multilayer perceptron)を用いて、FizzBuzzを解いてみます。

条件設定は、以前に話題になった下記のサイトに従います。


学習データとテストデータ

  • 学習データとして N = 101~10000のFizzBuzzの系列を与える
  • テストデータは、N = 1~100

X, yの形式

Xとしては、Nを2進数的に変換した値を利用します。

def binary_encode(i, num_digits):
    return np.array([i >> d & 1 for d in range(num_digits)])
for i in range(1, 10):
    print(binary_encode(i, 10))
[1 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0]
[1 1 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0]
[1 0 1 0 0 0 0 0 0 0]
[0 1 1 0 0 0 0 0 0 0]
[1 1 1 0 0 0 0 0 0 0]
[0 0 0 1 0 0 0 0 0 0]
[1 0 0 1 0 0 0 0 0 0]

yは、0~3の4つのいずれかを取る4値分類です。

def fizz_buzz_encode(i):
    if   i % 15 == 0: return 3
    elif i % 5  == 0: return 2
    elif i % 3  == 0: return 1
    else: return 0

def fizz_buzz(i, prediction):
    return [str(i), "fizz", "buzz", "fizzbuzz"][prediction]

これらX, yのペアを学習させ、テストデータで精度を確かめます。

学習・検証データの準備

ライブラリ

import torch
from torch import nn, optim
from torch.utils.data import (Dataset, 
                              DataLoader,
                              TensorDataset)
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'IPAPGothic'
%matplotlib inline

torch.manual_seed(1)    # reproducible
from fastprogress import master_bar, progress_bar

検証データの分割

学習データ923件のうち、最初の100件は検証データとして分割しておきます。

NUM_DIGITS = 10
NUM_HIDDEN = 100
BATCH_SIZE = 128
X = np.array([binary_encode(i, NUM_DIGITS) for i in range(101, 2 ** NUM_DIGITS)])
y = np.array([fizz_buzz_encode(i) for i in range(101, 2 ** NUM_DIGITS)])

X_train = X[100:]
y_train = y[100:]
X_valid = X[:100]
y_valid = y[:100]

torch用に変換

X_train = torch.tensor(X_train, dtype=torch.float32)
X_valid = torch.tensor(X_valid, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.int64)
y_valid = torch.tensor(y_valid, dtype=torch.int64)

モデルの定義

net = nn.Sequential(
    nn.Linear(10, 100),
    nn.ReLU(),
    nn.BatchNorm1d(100),
    nn.Linear(100, 4),
)

loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr = 0.05)

ds = TensorDataset(X_train, y_train)
loader = DataLoader(ds, batch_size=32, shuffle=True)

学習

train_losses = []
test_losses = []

for _ in progress_bar(range(600)):
    running_loss = 0.0

    net.train()
    
    for i, (xx, yy) in enumerate(loader):
        y_pred = net(xx)
        loss = loss_fn(y_pred, yy)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        running_loss += loss.item()

    train_losses.append(running_loss / i)
    
    net.eval()
    y_pred = net(X_valid)
    test_loss = loss_fn(y_pred, y_valid)
    test_losses.append(test_loss.item())

f:id:upura:20190121201207p:plain

テストデータに適用

いよいよ、学習させたモデルをテストデータに適用します。

numbers = np.arange(1, 101)
X_test = np.array([binary_encode(i, NUM_DIGITS) for i in range(1, 101)])
X_test = torch.tensor(X_test, dtype=torch.float32)

net.eval()
_, y_pred = torch.max(net(X_test), 1)

output = np.vectorize(fizz_buzz)(numbers, y_pred)
print(output)

f:id:upura:20190121201143p:plain

ある程度うまくいっているように見えます。正解率を出してみたところ、95%となりました。

from sklearn.metrics import accuracy_score

y_true = np.array([fizz_buzz_encode(i) for i in range(1, 101)])
print(accuracy_score(y_true, y_pred))
0.94999999999999996

おわりに

本記事では、PyTorchのMLP(Multilayer perceptron)を用いて、FizzBuzzを解いてみました。

非常な単純なMLPで、ある程度の精度が達成できたのは驚きです。なお、後ほど余裕があれば記事化しますが、LightGBMでは全く精度が出ませんでした。

実装はGitHubで公開しています。

github.com