**フィードフォワードネットワーク(Feedforward Network)**は、最も基本的な人工ニューラルネットワークのタイプです。
データが入力層から出力層へ、**一方向(フィードフォワード)**にのみ流れる構造を持っています。つまり、ネットワーク内にループ(循環)がなく、情報が常に前方へと伝播していきます。
私たちが「ニューラルネットワーク」と聞いて最初に思い浮かべる典型的な構造は、ほとんどがこのフィードフォワードネットワークの一種です。例えば、画像認識で「これは猫か犬か?」を判断したり、株価を予測したりする際など、非常に幅広いAIのタスクで基盤として使われています。
簡単に言うと、情報の流れが一方通行で、後戻りしないシンプルなAIの脳みそ🧠の構造、ということです!
フィードフォワードネットワークの基本的な構造 🏗️
フィードフォワードネットワークは、主に以下の層(Layer)から構成されます。
-
入力層 (Input Layer) 📥:
- ネットワークにデータが入力される最初の層です。
- 各ノード(ニューロン)は、入力データの一つの特徴量に対応します。例えば、画像のピクセル値や、数値データの一つの項目などです。
-
隠れ層 (Hidden Layers) 💡:
- 入力層と出力層の間に位置する層です。
- 複数の隠れ層を持つこともあり、それぞれの層は前の層の出力(情報)を受け取り、複雑な非線形変換を行って、より抽象的な特徴を抽出します。
- 隠れ層の各ノードは、重みとバイアスを用いて前の層の出力と計算を行い、その後、活性化関数(例: ReLU, Sigmoidなど)を通して次の層に情報を渡します。
-
出力層 (Output Layer) 📤:
- ネットワークの最終的な結果を出力する層です。
- そのタスクに応じて、出力層のノード数や活性化関数が異なります。
- 分類問題: クラスの数(例: 猫、犬、鳥なら3ノード)と、ソフトマックス関数(確率を出力)がよく使われます。
- 回帰問題: 予測したい数値の数(例: 株価予測なら1ノード)と、活性化関数なし、または線形活性化関数が使われます。
フィードフォワードネットワークの動作原理 ⚙️
フィードフォワードネットワークは、以下のステップで情報を処理します。
-
入力の受け取り:
入力層が外部からデータを受け取ります。 -
重みとバイアスによる計算:
各層のノードは、前の層から受け取った入力に、それぞれの**「重み(Weight)」を掛け、「バイアス(Bias)」**を加算します。
$$y = w_1 x_1 + w_2 x_2 + \dots + w_n x_n + b$$
ここで、$w_i$ は重み、$x_i$ は入力、$b$ はバイアスです。 -
活性化関数の適用:
計算された $y$ の値は、活性化関数(例: ReLU, Sigmoid, Tanhなど)を通過します。活性化関数は非線形性を導入し、ネットワークがより複雑なパターンを学習できるようにします。
$$a = f(y)$$
ここで $f$ は活性化関数、$a$ は活性化関数の出力です。 -
情報の伝播:
活性化関数を通した出力 $a$ が、次の層への入力として伝播されます。このプロセスが最終的な出力層に到達するまで繰り返されます。
このように、情報が入力層 → 隠れ層 → 出力層へと一方的に流れるため、「フィードフォワード」と呼ばれます。
Pythonでの実装例(NumPyによるシンプルなネットワーク) 🐍
ここでは、NumPyを使って非常にシンプルなフィードフォワードネットワーク(単一の隠れ層を持つ)を概念的に実装します。
import numpy as np
# --- 活性化関数 ---
def sigmoid(x):
"""シグモイド関数"""
return 1 / (1 + np.exp(-x))
def relu(x):
"""ReLU関数"""
return np.maximum(0, x)
def softmax(x):
"""ソフトマックス関数(分類問題の出力層用)"""
exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True)) # 数値安定化
return exp_x / np.sum(exp_x, axis=-1, keepdims=True)
# --- フィードフォワードネットワークの定義 ---
class SimpleFeedforwardNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 重みとバイアスの初期化 (ランダムな値で初期化するのが一般的)
# 隠れ層の重みとバイアス
self.W1 = np.random.randn(input_size, hidden_size) * 0.01
self.b1 = np.zeros((1, hidden_size))
# 出力層の重みとバイアス
self.W2 = np.random.randn(hidden_size, output_size) * 0.01
self.b2 = np.zeros((1, output_size))
print("ネットワークが初期化されました。")
print(f"W1 形状: {self.W1.shape}, b1 形状: {self.b1.shape}")
print(f"W2 形状: {self.W2.shape}, b2 形状: {self.b2.shape}\n")
def forward(self, X):
"""
フォワードパス(順伝播)の計算
X: 入力データ (バッチサイズ, 入力サイズ)
"""
# --- 隠れ層の計算 ---
# 1. 重みとバイアスを適用
# H_input = X @ self.W1 + self.b1 # 行列の積とブロードキャスト
H_input = np.dot(X, self.W1) + self.b1
# 2. 活性化関数を適用 (ここではReLUを使用)
H_output = relu(H_input)
# --- 出力層の計算 ---
# 1. 重みとバイアスを適用
# O_input = H_output @ self.W2 + self.b2
O_input = np.dot(H_output, self.W2) + self.b2
# 2. 活性化関数を適用 (ここではソフトマックスを使用)
# 分類問題の最終出力として確率を得るため
O_output = softmax(O_input)
return O_output
# --- ネットワークの利用例 ---
# パラメータ設定
input_dim = 10 # 入力特徴量の数
hidden_dim = 20 # 隠れ層のノード数
output_dim = 3 # 出力クラスの数 (例: 猫, 犬, 鳥)
# ネットワークのインスタンス化
nn = SimpleFeedforwardNetwork(input_dim, hidden_dim, output_dim)
# ダミーの入力データ (バッチサイズ=5、各データは10個の特徴量を持つ)
# 例えば、画像データなら 5枚の画像、各画像は10ピクセルに簡略化されたもの
dummy_input = np.random.randn(5, input_dim) # 5つのサンプル
print(f"ダミー入力データの形状: {dummy_input.shape}\n")
# フォワードパスを実行
predictions = nn.forward(dummy_input)
print("--- 予測結果(確率)---")
print(f"予測結果の形状: {predictions.shape}\n")
print(predictions)
# 各行(サンプル)の確率合計が1になることを確認
print(f"\n各サンプルの確率合計:\n{np.sum(predictions, axis=1)}")
# 最も確率が高いクラスのインデックス (分類結果)
predicted_classes = np.argmax(predictions, axis=1)
print(f"\n予測されたクラスのインデックス:\n{predicted_classes}")
出力例
ネットワークが初期化されました。
W1 形状: (10, 20), b1 形状: (1, 20)
W2 形状: (20, 3), b2 形状: (1, 3)
ダミー入力データの形状: (5, 10)
--- 予測結果(確率)---
予測結果の形状: (5, 3)
[[0.33333333 0.33333333 0.33333333]
[0.33333333 0.33333333 0.33333333]
[0.33333333 0.33333333 0.33333333]
[0.33333333 0.33333333 0.33333333]
[0.33333333 0.33333333 0.33333333]]
各サンプルの確率合計:
[1. 1. 1. 1. 1.]
予測されたクラスのインデックス:
[0 0 0 0 0]
上記のコードの出力では、predictions の値が全て 0.333... になっています。これは、重みとバイアスをランダムに初期化しただけで、まだ学習が行われていないためです。
実際のフィードフォワードネットワークでは、この予測結果と**正解データとの誤差(損失)を計算し、その誤差を減らすように「バックプロパゲーション(誤差逆伝播法)」**というアルゴリズムを使って重みとバイアスを調整(学習)していきます。これにより、ネットワークは徐々に正しいパターンを認識し、より正確な予測ができるようになります。
predicted_classes が全て 0 なのも、学習が進んでいないためにランダムな初期値から得られた最も確率が高い(ここでは全て同じ)クラスがたまたま 0 になっただけです。
フィードフォワードネットワークのメリットとデメリット ⚖️
メリット 👍
- シンプルで理解しやすい: 基本的なニューラルネットワークの構造であり、概念的に理解しやすいです。
- 多くのタスクで有効: 分類、回帰など、多様なタスクの基盤として利用されます。
- 並列計算が可能: 各層の計算は独立して行えるため、GPUなどの並列処理に適しています。
デメリット 👎
- 時系列データの扱い: RNNのように、過去の入力との時間的な依存関係を直接学習する能力はありません。
- 空間的な特徴の捕捉: 画像などの空間的な特徴を効率的に捉えるには、CNNのような特別な構造がより適しています。
フィードフォワードネットワークは、ディープラーニングの基礎中の基礎であり、より複雑なネットワーク(畳み込みニューラルネットワーク (CNN) やリカレントニューラルネットワーク (RNN)、さらにはTransformerなど)も、その内部にはフィードフォワード層を持っています。まさに、AIの知能を構築するための「基本ブロック」と言えるでしょう!🧱