1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

マルチパーセプトロンはなぜ XOR を解けるのか— 1ニューロンで AND/OR、2層3ニューロンで XOR、そして多ユニットで境界はどれだけ複雑になる?

Last updated at Posted at 2025-11-07

TL;DR

  • 単一ニューロン(パーセプトロン)は入力空間を直線 1 本で二分する(線形分離)。AND/OR/NAND は可能だが XOR は不可能
  • XOR(x1 OR x2) AND NOT(x1 AND x2) と分解できるため、2層3ニューロン(隠れ:OR・NAND、出力:AND)で構成的に実現できる。
  • MLP(多層パーセプトロン)は非線形を重ね合わせて複雑な決定境界を学習できる。円環データの分類で直感を得る。

続編記事

半加算器と全加算器をニューラルネットで作ってみる — 2次元の決定領域で学ぶ論理回路と MLP

導入

ニューラルネットワークは「重み付き和+活性化」という単純な仕組みで動く一方、層(深さ)と非線形を重ねるだけで表現できる関数の種類が飛躍的に増えます。本記事では、単一ニューロン=直線分離という事実から出発し、XOR が単層では解けないこと、そして2層(合計3ニューロン)で解けることを図と最小コードで確かめます。最後に、線形では分けられない同心円データで MLP の表現力を体感します。


1. 単一ニューロンで AND / OR / NAND

単一ニューロン(パーセプトロン)は y = step(w1*x1 + w2*x2 + b) で出力します。境界は直線 w·x + b = 0。以下は重みとバイアスの具体例決定領域です。

  • AND : w=[1,1], b=-1.5
  • OR : w=[1,1], b=-0.5
  • NAND: w=[-1,-1], b=1.5

gate_and.png
gate_or.png
gate_nand.png

直感:バイアス(しきい値)を上げると判定が「厳しく」(AND)、下げると「緩く」(OR)なります。NAND は AND の符号反転です。


2. 単層では XOR を表現できない(線形分離不可)

XOR の真理値は (0,0)->0, (1,0)->1, (0,1)->1, (1,1)->0
1 本の直線ではこの 4 点を分け切れません。線形モデル(ロジスティック回帰)で挑戦すると、必ず取りこぼしが生じます。
xor_logreg.png


3. 2層3ニューロンなら XOR を「構成的」に実装できる

XOR は
XOR = OR AND NOT(AND)
と分解できます。隠れ層で OR と NANDを計算し、出力層で ANDをとれば完成です(合計 3 ニューロン)。
xor_manual.png

作れる(表現可能性)と学習で到達できる(最適化)は別問題。ここでは「作れる」ことを確認します。


4. 学習でも確認:MLP は XOR を解ける

同じ 4 点のデータに対して、1 隠れ層の MLP を学習させると、正しく分離できる決定領域が得られます。
xor_mlp.png


5. 多層 MLP の表現力:同心円データを分類

線形では絶対に分けられない同心円状のデータを、MLP が非線形の境界で分離します(make_circles、活性化 ReLU、隠れ 16-16)。
circles_mlp.png


付録コード:1セルで記事中の図をすべて再現(保存つき)

# %pip -q install scikit-learn
import numpy as np, matplotlib.pyplot as plt, os
from itertools import product
from sklearn.linear_model import LogisticRegression
from sklearn.neural_network import MLPClassifier
from sklearn.datasets import make_circles

np.random.seed(0); os.makedirs("fig", exist_ok=True)

def step(z): return (z >= 0).astype(int)
def grid2d(xmin=-0.5, xmax=1.5, ymin=-0.5, ymax=1.5, n=400):
    xx, yy = np.meshgrid(np.linspace(xmin, xmax, n), np.linspace(ymin, ymax, n))
    X = np.stack([xx.ravel(), yy.ravel()], axis=1); return xx, yy, X

def plot_region_single_neuron(w, b, title, save):
    xx, yy, X = grid2d(); Z = step(X @ w + b).reshape(xx.shape)
    plt.figure(figsize=(6,6)); plt.contourf(xx, yy, Z, levels=[-0.5,0.5,1.5], alpha=0.25)
    if abs(w[1])>1e-6:
        xs = np.linspace(-0.5, 1.5, 200); ys = -(w[0]*xs + b)/w[1]; plt.plot(xs, ys, lw=2)
    else: plt.axvline(-b/w[0], lw=2)
    P = np.array(list(product([0,1],[0,1]))); plt.scatter(P[:,0], P[:,1], c='k', s=60, edgecolors='white')
    for a,bv in P: plt.text(a+0.04,bv+0.04,f"({a},{bv})",fontsize=9)
    plt.xlim(-0.5,1.5); plt.ylim(-0.5,1.5); plt.xticks([0,1]); plt.yticks([0,1])
    plt.title(title); plt.xlabel("x1"); plt.ylabel("x2"); plt.grid(True, ls=":")
    plt.savefig(save, dpi=150, bbox_inches='tight'); plt.show()

# Gates
w_and, b_and   = np.array([1.0,1.0]), -1.5
w_or,  b_or    = np.array([1.0,1.0]), -0.5
w_nand, b_nand = np.array([-1.0,-1.0]), 1.5
plot_region_single_neuron(w_and,  b_and,  "Single neuron as AND",  "fig/gate_and.png")
plot_region_single_neuron(w_or,   b_or,   "Single neuron as OR",   "fig/gate_or.png")
plot_region_single_neuron(w_nand, b_nand, "Single neuron as NAND", "fig/gate_nand.png")

# XOR: logistic regression fails (single-layer)
X = np.array([[0,0],[0,1],[1,0],[1,1]]); y = np.array([0,1,1,0])
logr = LogisticRegression(C=1e6, solver='lbfgs').fit(X, y)
xx, yy, gridX = grid2d(); Z_logr = logr.predict(gridX).reshape(xx.shape)
plt.figure(figsize=(6,6)); plt.contourf(xx, yy, Z_logr, levels=[-0.5,0.5,1.5], alpha=0.25)
plt.scatter(X[:,0], X[:,1], c=y, s=80, edgecolors='white')
plt.title(f"Single-layer logistic regression on XOR (acc={logr.score(X,y):.2f})")
plt.xlabel("x1"); plt.ylabel("x2"); plt.grid(True, ls=":"); plt.savefig("fig/xor_logreg.png", dpi=150, bbox_inches='tight'); plt.show()

# XOR: manual two-layer (OR & NAND -> AND)
h1 = step(gridX @ w_or + b_or); h2 = step(gridX @ w_nand + b_nand)
Z_manual = step(np.c_[h1,h2] @ np.array([1.0,1.0]) - 1.5).reshape(xx.shape)
plt.figure(figsize=(8,6)); plt.contourf(xx, yy, Z_manual, levels=[-0.5,0.5,1.5], alpha=0.25)
plt.scatter(X[:,0], X[:,1], c=y, s=80, edgecolors='white')
plt.title("Two-layer (OR & NAND -> AND) implements XOR (manual weights)")
plt.xlabel("x1"); plt.ylabel("x2"); plt.grid(True, ls=":"); plt.savefig("fig/xor_manual.png", dpi=150, bbox_inches='tight'); plt.show()

# XOR: learn with 2-layer MLP (hidden=2)
mlp_xor = MLPClassifier(hidden_layer_sizes=(2,), activation='logistic',
                        solver='lbfgs', alpha=1e-4, random_state=0, max_iter=10000).fit(X, y)
Z_mlp = mlp_xor.predict(gridX).reshape(xx.shape)
plt.figure(figsize=(6,6)); plt.contourf(xx, yy, Z_mlp, levels=[-0.5,0.5,1.5], alpha=0.25)
plt.scatter(X[:,0], X[:,1], c=y, s=80, edgecolors='white')
plt.title(f"Two-layer MLP learns XOR (acc={mlp_xor.score(X,y):.2f})")
plt.xlabel("x1"); plt.ylabel("x2"); plt.grid(True, ls=":"); plt.savefig("fig/xor_mlp.png", dpi=150, bbox_inches='tight'); plt.show()

# Expressivity: nested circles
from sklearn.datasets import make_circles
Xc, yc = make_circles(n_samples=600, noise=0.04, factor=0.45, random_state=0)
mlp_circles = MLPClassifier(hidden_layer_sizes=(16,16), activation='relu',
                            solver='adam', random_state=0, max_iter=4000).fit(Xc, yc)
xx2, yy2 = np.meshgrid(np.linspace(Xc[:,0].min()-0.2, Xc[:,0].max()+0.2, 600),
                       np.linspace(Xc[:,1].min()-0.2, Xc[:,1].max()+0.2, 600))
Zc = mlp_circles.predict(np.c_[xx2.ravel(), yy2.ravel()]).reshape(xx2.shape)
plt.figure(figsize=(7,7)); plt.contourf(xx2, yy2, Zc, levels=[-0.5,0.5,1.5], alpha=0.3)
plt.scatter(Xc[:,0], Xc[:,1], c=yc, s=15, edgecolors='white', linewidths=0.2)
plt.title("MLP decision regions on nested circles"); plt.xlabel("x1"); plt.ylabel("x2"); plt.grid(True, ls=":")
plt.savefig("fig/circles_mlp.png", dpi=150, bbox_inches='tight'); plt.show()
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?