メインコンテンツへスキップ

6.1.3 ニューロンから多層パーセプトロンへ

ニューロンから MLP への構造図

この節の概要

ニューラルネットワークは、重み付きスコアを計算し、非線形の活性化を通し、その単位を層として積み重ねる、という単純な考えから始まります。

作るもの

この節では、小さな PyTorch 実験を実行します。

  • 人工ニューロンを手で計算する;
  • sigmoidReLU を比較する;
  • 小さな MLP で XOR を解く;
  • 1 つの線形層だけでは足りない理由を説明する。

中心となる流れは次です。

features -> weighted sum z -> activation a -> layer -> multilayer network

ニューロンの線形スコアと活性化ゲート図

最小限の歴史

パーセプトロンが人々を興奮させたのは、機械がデータからルールを学べることを示したからです。その後、XOR のような単純な非線形パターンを単層パーセプトロンが解けないことが明らかになりました。

この歴史から学ぶべきことは次です。

ニューロン自体は単純。表現力を生むのは、非線形活性化を持つ層の積み重ね。

XOR における単層パーセプトロンの限界図

セットアップ

python -m pip install -U torch

コードでは安定した PyTorch API を使います:torch.Tensornn.Modulenn.Sequentialnn.Linear、活性化関数、loss、optimizer です。

完全な実験を実行する

neuron_mlp_lab.py を作成します。

import torch
import torch.nn as nn


torch.manual_seed(42)

x = torch.tensor([[0.8, 0.3, 0.5]])
w = torch.tensor([[0.2], [-0.4], [0.6]])
b = torch.tensor([0.1])
z = x @ w + b
print("single_neuron")
print("z=", round(float(z.item()), 3))
print("sigmoid=", round(float(torch.sigmoid(z).item()), 3))
print("relu=", round(float(torch.relu(z).item()), 3))

xor_x = torch.tensor([[0., 0.], [0., 1.], [1., 0.], [1., 1.]])
xor_y = torch.tensor([[0.], [1.], [1.], [0.]])


class TinyMLP(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Linear(2, 4),
nn.Tanh(),
nn.Linear(4, 1),
nn.Sigmoid(),
)

def forward(self, x):
return self.net(x)


model = TinyMLP()
loss_fn = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.1)

for step in range(2000):
pred = model(xor_x)
loss = loss_fn(pred, xor_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()

with torch.no_grad():
prob = model(xor_x)
pred = (prob >= 0.5).float()
print("xor_mlp")
for row, p, y_hat in zip(xor_x.tolist(), prob.squeeze().tolist(), pred.squeeze().tolist()):
print(f"x={row} prob={p:.3f} pred={int(y_hat)}")
print("final_loss=", round(float(loss.item()), 4))

実行します。

python neuron_mlp_lab.py

期待される出力:

single_neuron
z= 0.44
sigmoid= 0.608
relu= 0.44
xor_mlp
x=[0.0, 0.0] prob=0.000 pred=0
x=[0.0, 1.0] prob=1.000 pred=1
x=[1.0, 0.0] prob=1.000 pred=1
x=[1.0, 1.0] prob=0.000 pred=0
final_loss= 0.0001

ニューロンと XOR 実験結果図

1 つのニューロンを読む

最初の部分は次を計算しています。

z = x @ w + b

出力では:

z= 0.44
sigmoid= 0.608
relu= 0.44

重み付きスコア z はまだ線形です。活性化関数が、信号をどう次へ渡すかを変えます。

活性化することよく使う場面
Sigmoid0-1 に押し込む二値分類の確率出力
Tanh-1 から 1 に押し込む小さなデモ、一部の系列モデル
ReLU正の値を残し、負の値を 0 にする隠れ層の一般的な既定選択

活性化関数が重要な理由

線形層だけを積み重ねても、全体としては 1 つの大きな線形層と等価です。非線形活性化があるから、層を重ねたネットワークは曲がった境界を表現できます。

そのため、この MLP は次を使います。

nn.Linear(2, 4),
nn.Tanh(),
nn.Linear(4, 1),
nn.Sigmoid(),

隠れ層の Tanh が非線形の表現力を与えます。最後の Sigmoid は、二値分類向けの確率らしい値に変換します。

XOR が古典的なテストである理由

XOR は 4 行だけです。

x1x2y
000
011
101
110

直線 1 本では、このラベルを分けられません。だから単層パーセプトロンは失敗します。小さな MLP が成功するのは、最終判断の前に中間の隠れ特徴を作れるからです。

よくあるトラブル

症状よくある原因修正
loss が下がらない学習率が高すぎる/低すぎる、loss の組み合わせが違うLR を下げ、出力活性化と loss の組み合わせを確認する
確率がすべて 0.5 付近モデルが学習していない長く訓練し、勾配を見て、hidden size を変える
output shape エラーtarget shape と prediction が違うこの二値例では target を [batch, 1] にする
nan が出る学習が不安定学習率を下げ、入力を確認する
訓練データは解けるが実データで弱い訓練データを記憶しているtrain/validation split と正則化を使う

練習

  1. 隠れユニットを 4 から 2 に変えてください。XOR は安定して学習できますか?
  2. nn.Tanh()nn.ReLU() に置き換えてください。結果は変わりますか?
  3. 200 step ごとに loss を表示し、学習曲線を見てください。
  4. 隠れ層の活性化関数を外し、なぜ弱くなるか説明してください。
  5. 隠れ層をもう 1 つ追加し、final loss を比較してください。

合格チェック

次を説明できれば、この節はクリアです。

  • ニューロンは x @ w + b を計算し、その後に活性化を適用する;
  • 活性化関数は非線形性を加える;
  • 単層パーセプトロンは XOR を解けない;
  • MLP は層を積み重ねて中間特徴を作る;
  • PyTorch モデルは通常 nn.Module、loss、optimizer、backward()step() を組み合わせる。