まままのテックブログ

機械学習、統計、信号処理のまとめと仕事の役に立つツール紹介をします。

Pytorchでパーセプトロンを実装してみた

人間のニューロン活動を模したモデルであるパーセプトロンの実装をしてみました。

ニューラルネットについて

機械学習は推定値と観測値の誤差を更新していくことで モデルを構築する。その時の誤差を目的関数といい、通常 MSE や交差エントロピー誤差関数が使われる。この誤差を最小にするようにパラメータを更新していく。このパラメーターの更新には全てのデータセットを入力する必要があるがそれは現実的ではないので確率的勾配降下法などを用いて計算負荷を抑える工夫をする。

パーセプトロン

受け取った電気の量がしきい値を超えると発火するモデル。

入力信号を\boldsymbol{x}、重みを\boldsymbol{w}、活性化関数をf、出力信号を\boldsymbol{y}とすると、

\boldsymbol{y} = f(\boldsymbol{w}^T \boldsymbol{x})

条件

二値分類する時はシグモイド関数で他クラス分類するときは softmax 関数を使う。

コード

import numpy as np
from sklearn import datasets
from sklearn.utils import shuffle
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from sklearn.utils import shuffle
from sklearn.model_selection import train_test_split

class SP(nn.Module):
  def __init__(self, input_dim, output_dim):
    super().__init__()
    self.classifier = nn.Sequential(
        nn.Linear(in_features=input_dim, out_features=output_dim),
        nn.Sigmoid()
    )
  
  def forward(self, x):
    out = self.classifier(x)
    return out

まずモデルのクラスをつくる。今回のシングルパーセプトロンのクラスの入力には入力の次元、出力の次元を与えている。 nn.seqentialで入力の計算器と活性化関数を順々に定義することができる。 forward関数はcall関数として扱われる。 入力をxとして順伝播をする。

"""
1. データの準備
"""
device = torch.device("cuda")
d = 2
N = 20
mean = 5.
x1 = torch.randn(N, d) + torch.tensor([0, 0])
x2 = torch.randn(N, d) + torch.tensor([mean, mean])
t1 = torch.zeros(N)
t2 = torch.ones(N)
x = torch.cat((x1, x2), axis=0)
t = torch.cat((t1, t2), axis=0)
x_train, x_test, t_train, t_test = train_test_split(x, t, test_size=0.2)

pytorchでは GUI を使う際にコードで認識する。device変数に入力する。 入力2、出力1のパーセプトロンを用いて 0周辺の値と5周辺の値を線形分離する。 データを まとめてそれを訓練データとテストデータに分ける。テストサイズを0.2にすることで80%が訓練データとなる。

"""
2. モデルの構築
"""
model = SP(2, 1).to(device)
"""

モデルインスタンスを生成。

"""
3. モデルの学習
"""
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
def compute_loss(t, y):
  return criterion(y, t)

def train_step(x, t):
  model.train()
  y_pred = model(x)
  loss = compute_loss(t, y_pred)
  optimizer.zero_grad() #勾配の初期化
  loss.backward() #勾配をすべてのノードに伝播
  optimizer.step() #勾配からパラメータを更新する
  return loss

誤差関数目的関数のインスタンスを作る、そして最適化の手法のインスタンスを作る。 この時 SGD を使う時はモデルのパラメータを初期化する 処理が必要で、 LRはラーニングレートでハイパーパラメータである。 訓練時は訓練モードにする。その後誤差を計算。そして勾配を全て初期化した後に 勾配を全てのノードに伝播する。そして勾配からパラメーターを更新し、ロスを出力する

epochs = 20
batch_size = 10
n_batches = x_train.shape[0] // batch_size

for epoch in range(epochs):
  train_loss = 0.
  x_, t_ = shuffle(x_train, t_train)
  x_ = torch.tensor(x_).to(device)
  t_ = torch.tensor(t_).to(device)

  for n_batch in range(n_batches):
    start = n_batch * batch_size
    end = start + batch_size
    loss = train_step(x_[start:end], t_[start:end])
    train_loss += loss.item()
  
  print("epoch: {}, loss: {:,.3}".format(epoch+1, train_loss))

ミニバッチ法を使用する。エポック数を20、バッチサイズを10として 訓練データを10個のバッチに分ける。訓練データと正解データをシャッフルしそれらをデバイスに入力する。バッチごとに誤差関数を出力しそれを足し合わせていく。

"""
4. モデルの評価
"""
def test_step(x, t):
  x = torch.tensor(x).to(device)
  t = torch.tensor(t).to(device)
  model.eval()
  preds = model(x)
  loss = compute_loss(t, preds)
  return loss, preds

loss, preds = test_step(x_test, t_test)
test_loss = loss.item()
preds = preds.data.cpu().numpy() > 0.5
test_acc = accuracy_score(t_test, preds)
print("test_loss: {:.3f}, test_acc: {:.3f}".format(test_loss, test_acc))

モデルを評価バージョンモードにする。正解度を出力する。

結果

1次直線で分類できている。 パーセプトロンはデータ群を直線で分類できる。

f:id:mamama039356:20210111231933p:plain

所感

pytorchと tensorflow で誤差を計算する引数の順番が逆なので気をつける。値を小数にしてnumpyは使わない。