【機械学習】多層パーセプトロンについて
今回から何回かに分けて、機械学習におけるニューラルネットワークのアーキテクチャについて書いていきたいと思います。ブログのネタが切れたわけではありません
はじめに
このシリーズ(になるといいな)では、ニューラルネットワークの構成要素についてある程度知っている人を対象にしています。といっても、私自身のメインの分野ではないため、間違っていることもあるかもしれません。間違いがあれば、Twitter等で指摘していただけると助かります。
多層パーセプトロンとは
ニューラルネットワーク(以下NN)の基本的なモデルの一つに、MLP(Multi-Layer Perceptron)があります。MLPは、入力層(Input Layer)、1つ以上の隠れ層(Hidden Layer)、出力層(Output Layer)で構成されます。隠れ層を複数持つことが「多層」の由来です。
各層では、前の層のニューロンと次の層のニューロンがすべて接続されています。この仕組みを全結合(Fully Connected)といい、全結合を行う層を全結合層(Fully Connected Layer / Dense Layer)と呼びます。つまり、ある層の各ニューロンは、前の層の出力をすべて受け取って計算します。
MNISTを分類するMLPの構成は、次のようになります。
入力画像 (1 x 28 x 28)
|
Flatten (784)
|
Linear (784 -> 512) -> ReLU
|
Linear (512 -> 256) -> ReLU
|
Linear (256 -> 10)
|
各数字のスコア (0~9)
.code-block-filename {
--code-filename-fontsize: calc(0.96em * 0.9);
display: table;
max-width: 100%;
background: var(--accent-color-darker);
color: var(--accent-color-text);
font-size: var(--code-filename-fontsize);
line-height: 1;
padding-top: calc(var(--code-filename-fontsize) * 0.5);
padding-left: calc(var(--code-filename-fontsize) * 0.7);
padding-right: calc(var(--code-filename-fontsize) * 0.7);
padding-bottom: calc(var(--code-filename-fontsize) * 0.7);
border-radius: calc(var(--code-filename-fontsize) * 0.5)
calc(var(--code-filename-fontsize) * 0.5) 0 0;
}
.code-block-content {
padding-top: 0px;
}
入力を x、重みを W、バイアスを b とすると、全結合層の線形変換とReLUを組み合わせた計算は次のようになります。
yj
=
ReLU(
∑
i=1
n
Wji
xi
+
bj
)
ここでxiは入力のi番目の値、Wjiは入力iから出力jへの重みです。iを1からnまで足し合わせているため、出力jはすべての入力を使って計算されます。
この計算のあとに活性化関数を適用することで、単純な線形変換だけでは表現できない複雑な関係を学習できます。隠れ層ではReLU、Sigmoid、tanhなどが使われますが、現在はReLUがよく使われます。ReLUは、入力が0未満の場合は0にし、0以上の場合は入力をそのまま出力する関数です。
ReLU(z)
=
max(0,z)
活性化関数については、次回以降に専用の記事で扱う予定です。
この例では、入力画像を784個の値に変換してから全結合層へ入力します。最終層の10個の出力は、それぞれ数字の0~9に対応するスコアです。最もスコアの大きい番号を予測結果として選びます。
PyTorchでMLPを作ってみる
今回は、PyTorchを使って、MNISTデータセットの数字を分類するMLPを作ってみます。
import torch
import torch.nn as nn
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.fc1 = nn.Linear(28*28, 512)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(512, 256)
self.relu2 = nn.ReLU()
self.fc3 = nn.Linear(256, 10)
def forward(self, x):
x = self.flatten(x)
x = self.fc1(x)
x = self.relu1(x)
x = self.fc2(x)
x = self.relu2(x)
x = self.fc3(x)
return x
.code-block-filename {
--code-filename-fontsize: calc(0.96em * 0.9);
display: table;
max-width: 100%;
background: var(--accent-color-darker);
color: var(--accent-color-text);
font-size: var(--code-filename-fontsize);
line-height: 1;
padding-top: calc(var(--code-filename-fontsize) * 0.5);
padding-left: calc(var(--code-filename-fontsize) * 0.7);
padding-right: calc(var(--code-filename-fontsize) * 0.7);
padding-bottom: calc(var(--code-filename-fontsize) * 0.7);
border-radius: calc(var(--code-filename-fontsize) * 0.5)
calc(var(--code-filename-fontsize) * 0.5) 0 0;
}
.code-block-content {
padding-top: 0px;
}
今回は3層MLPを作成しました。入力層は28x28の画像を受け取り、隠れ層は512ニューロンと256ニューロン、出力層は10ニューロン(0~9の数字)となっています。また、隠れ層の活性化関数にはReLUを使用しています。
学習コードなどは省略します。すべてコードはこちらにあります。
nn.ModuleはPyTorchのニューラルネットワークの基本クラスであり、ニューラルネットワークを定義する際に継承して使用します。forwardメソッドは、入力データがネットワークを通過する際の計算を定義します。
nn.Flatten()は、入力データを1次元に変換するための層です。MNISTの画像は28x28の2次元配列ですが、全結合層に入力するためには784個の値が並ぶ1次元のベクトルに変換する必要があります。
nn.Linear()は、全結合層を定義するためのクラスです。引数には、入力の次元数と出力の次元数を指定します。例えばnn.Linear(28*28, 512)は、784個の入力を受け取り、512個の値を出力します。
このコードでは、最後の層のあとにSoftmaxを置いていません。CrossEntropyLossを損失関数に使う場合、損失関数の内部でSoftmaxに相当する処理が行われるためです。推論時に確率として確認したい場合は、出力に対してtorch.softmaxを適用します。
このようにPyTorchを使うことで、簡単にMLPを構築することができます。
隠れ層はnn.Linearとnn.ReLUの組み合わせを増やすことで追加できます。しかし、層やニューロンを無闇に増やしても性能が向上するとは限りません。学習が頭打ちになったり、過学習の原因になったりするので注意が必要です。入力データを1次元に変換するため、画像の位置関係を活かしにくいという弱点もあります。そのため、画像認識ではCNNなど別のアーキテクチャが使われることもあります。