⛵
CNN(畳み込みニューラルネットワーク)
CNNとは
CNN(畳み込みニューラルネットワーク)は,ディープラーニングの一種であり,特に画像や音声などのデータから特徴を自動的に抽出して解析するために用いられます.従来のニューラルネットワークとは異なり,CNNは空間的な情報(例えば画像内のピクセルの位置関係)を活用することができます.これにより,画像認識や物体検出などのタスクで高い精度を実現します.記事の後半にはPythonによる実装例も紹介しています.
CNNは主に以下の層構成をもち,CNNならではの特徴としては「畳み込み層」と「プーリング層」が存在します.
- 畳み込み層
- プーリング層
- 全結合層
畳み込み層
畳み込み層では,「特徴マップ」を生成することを目標に,入力データ全体にフィルタ(カーネル)を作用させます.フィルタの重みと入力データの対応する部分を要素ごとに乗算し,その結果を合計するという操作を入力データ全体に対してスライドさせながらおこいます.
畳み込み演算の数式
-
:入力データ(画像など)X -
:フィルタ(カーネル)K -
:出力の特徴マップS -
:出力位置(i,j) -
:フィルタ内の位置(m,n)
「畳み込み演算」に関する用語
- カーネルサイズ:フィルタの高さと幅
- フィルタ数:各畳み込み層で使用するフィルタの数
- ストライド(Stride):フィルタを適用する際の移動ステップの大きさ.ストライドが大きいと出力サイズが小さくなります.
- パディング(Padding):入力データの周囲にゼロなどの値を追加して,出力サイズを調整します.
畳み込み層の出力サイズ
プーリング層
プーリング層は,特徴マップの空間的なサイズを縮小し,(1)計算効率を向上させること,(2)過学習を防ぐこと,を理由に使用されます.この処理自体に数学的な意味があるというよりは,サイズを縮小することで後の作業を効果的にするという印象です.
プーリングの種類
最大値プーリング(Max Pooling):プーリングウィンドウ内の最大値を取得します.
平均値プーリング(Average Pooling):プーリングウィンドウ内の平均値を計算します.
CNNの具体的な流れ
- 畳み込み層:入力データにフィルタを適用し,初期的な特徴を抽出します.
- 活性化関数:非線形性を導入するために,ReLUなどの活性化関数を適用します.
- プーリング層:特徴マップの空間的なサイズを縮小し,重要な情報を凝縮します.
- これらのステップを繰り返す:必要な深さまで繰り返し,より高次の特徴を学習します.
実装例
以下では,PyTorchを使用してシンプルなCNNを実装し,MNISTデータセットで手書き数字の分類を行う例を示します.
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
# データの前処理と読み込み
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
# 訓練データとテストデータのダウンロードと準備
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=1000, shuffle=False)
# CNNモデルの定義
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
# 畳み込み層1:入力チャンネル1、出力チャンネル32、カーネルサイズ3
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
# 畳み込み層2:入力チャンネル32、出力チャンネル64、カーネルサイズ3
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
# プーリング層:最大値プーリング
self.pool = nn.MaxPool2d(2, 2)
# 全結合層:入力特徴数7*7*64、出力特徴数128
self.fc1 = nn.Linear(7 * 7 * 64, 128)
# 出力層:入力特徴数128、出力特徴数10(クラス数)
self.fc2 = nn.Linear(128, 10)
# 活性化関数:ReLU
self.relu = nn.ReLU()
def forward(self, x):
# 畳み込み層1 + ReLU + プーリング
x = self.pool(self.relu(self.conv1(x)))
# 畳み込み層2 + ReLU + プーリング
x = self.pool(self.relu(self.conv2(x)))
# フラット化
x = x.view(-1, 7 * 7 * 64)
# 全結合層1 + ReLU
x = self.relu(self.fc1(x))
# 出力層
x = self.fc2(x)
return x
# デバイスの設定(GPUが利用可能ならGPUを使用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = SimpleCNN().to(device)
# 損失関数と最適化アルゴリズムの定義
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# モデルの訓練
num_epochs = 5
for epoch in range(num_epochs):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
# 勾配の初期化
optimizer.zero_grad()
# 順伝播
output = model(data)
# 損失の計算
loss = criterion(output, target)
# 逆伝播
loss.backward()
# パラメータの更新
optimizer.step()
# ログの出力
if batch_idx % 100 == 0:
print(f'Epoch: {epoch+1} [{batch_idx * len(data)}/{len(train_loader.dataset)}] Loss: {loss.item():.6f}')
# モデルの評価
model.eval()
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
# 最も高い確率を持つクラスを選択
pred = output.argmax(dim=1, keepdim=True)
# 正解数を累計
correct += pred.eq(target.view_as(pred)).sum().item()
print(f'Test Accuracy: {correct}/{len(test_loader.dataset)} ({100. * correct / len(test_loader.dataset):.2f}%)')
「畳み込み演算」って良い響きですよね,,,
LiFe is LiKe a BoAt
Discussion