3️⃣ 猿でもわかるAIプログラミングシリーズ 🐵💻 | [第3回]ニューラルネットワークを自作してみる（超シンプル版）



## **1. はじめに: なぜニューラルネットワークを自作するのか？**  
AIや機械学習のライブラリ（TensorFlow, PyTorchなど）を使えば、簡単にニューラルネットワークを構築できます。しかし、**「ブラックボックス」として使うだけでは、内部の仕組みを理解できません**。  

この記事では、**Pythonだけで超シンプルなニューラルネットワークをゼロから実装**し、以下のことを学びます：  
- **順伝播（Forward Propagation）**  
- **逆伝播（Backpropagation）**  
- **勾配降下法（Gradient Descent）**  

最終的に、XOR問題を解くニューラルネットワークを作成します！  

---

## **2. ニューラルネットワークの超シンプル解説**  
ニューラルネットワークは、**入力層・隠れ層・出力層**で構成されます。各層のニューロンは「重み（Weight）」と「バイアス（Bias）」を持ち、**活性化関数（Activation Function）**で非線形性を導入します。  

今回は以下の構成で実装します：  
- **入力層**: 2ノード  
- **隠れ層**: 2ノード（活性化関数: Sigmoid）  
- **出力層**: 1ノード（活性化関数: Sigmoid）  

---

## **3. 実装: Pythonでニューラルネットワークを作る**  
### **3.1 必要なライブラリ**  
```python
import numpy as np
```

### **3.2 活性化関数（Sigmoid）**  
```python
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
    return x * (1 - x)
```

### **3.3 ニューラルネットワークの初期化**  
```python
class NeuralNetwork:
    def __init__(self):
        # 重みの初期化（ランダム）
        self.weights1 = np.random.rand(2, 2)  # 入力層 → 隠れ層
        self.weights2 = np.random.rand(2, 1)  # 隠れ層 → 出力層
        self.bias1 = np.random.rand(1, 2)    # 隠れ層のバイアス
        self.bias2 = np.random.rand(1, 1)    # 出力層のバイアス
```

### **3.4 順伝播（Forward Propagation）**  
```python
    def forward(self, X):
        self.hidden = sigmoid(np.dot(X, self.weights1) + self.bias1)
        self.output = sigmoid(np.dot(self.hidden, self.weights2) + self.bias2)
        return self.output
```

### **3.5 逆伝播（Backpropagation）**  
```python
    def train(self, X, y, epochs=10000, lr=0.1):
        for _ in range(epochs):
            # Forward Propagation
            output = self.forward(X)
            
            # 誤差計算（Mean Squared Error）
            error = y - output
            
            # Backpropagation
            d_output = error * sigmoid_derivative(output)
            d_hidden = d_output.dot(self.weights2.T) * sigmoid_derivative(self.hidden)
            
            # 重みとバイアスの更新
            self.weights2 += self.hidden.T.dot(d_output) * lr
            self.weights1 += X.T.dot(d_hidden) * lr
            self.bias2 += np.sum(d_output, axis=0) * lr
            self.bias1 += np.sum(d_hidden, axis=0) * lr
```

---

## **4. 実践: XOR問題を解いてみる**  
XOR（排他的論理和）は単純なパーセプトロンでは解けませんが、ニューラルネットワークで解決できます！  

### **4.1 データ準備**  
```python
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [1], [1], [0]])
```

### **4.2 学習＆予測**  
```python
nn = NeuralNetwork()
nn.train(X, y, epochs=10000, lr=0.1)

print("Predictions after training:")
for x in X:
    print(f"{x} → {nn.forward(x):.4f}")
```

**出力例:**  
```
[0 0] → 0.0123  
[0 1] → 0.9876  
[1 0] → 0.9877  
[1 1] → 0.0124  
```
**→ ほぼ完璧にXORを学習！**  

---

## **5. 実践的なTips & よくある落とし穴**  
✅ **学習率（Learning Rate）の調整**  
- 大きすぎる → 発散する  
- 小さすぎる → 収束が遅い  

✅ **重みの初期化が重要**  
- すべて0で初期化すると勾配が均一化され、学習が進まない（→ `np.random.rand` を使う）  

✅ **エポック数と過学習**  
- 多すぎると訓練データに過剰適合（Overfitting）する  

---

## **6. 発展: さらに改良するには？**  
- **活性化関数をReLUに変更**（勾配消失問題の対策）  
- **バッチ正規化（Batch Normalization）** を導入  
- **Dropout** で過学習を防ぐ  

---

## **7. まとめ: 自作ニューラルネットワークの利点と限界**  
**✅ メリット**  
- ニューラルネットワークの内部動作を深く理解できる  
- 軽量なモデルならスクラッチ実装も可能  

**❌ デメリット**  
- 大規模なモデルには向かない（ライブラリ利用が現実的）  
- ハイパーパラメータ調整が手動  

**🎯 次は「CNN（畳み込みニューラルネットワーク）を自作してみる」に挑戦！**  

---

この記事を読んで、**「ニューラルネットワークの中身がわかった！」**と思ったら、ぜひ実装してみてください 🚀