趣味のPython・深層学習

中級者のための実装集

Clean code オブジェクト指向PyTorch実装

はじめに

本記事では、Pythonの深層学習ライブラリであるPyTorchを用いて、深層学習モデルを実装する方法について説明します。ここでは、オブジェクト指向プログラミングの原則に従い、モジュール性と拡張性の高いコードを書くことを目指します。具体的には、以下のようなSOLID原則に基づいたクラス設計を行います。

  • 単一責任の原則 (Single Responsibility Principle)
  • オープン・クローズドの原則 (Open-Closed Principle)
  • インターフェイス分離の原則 (Interface Segregation Principle)

データ準備

まずは、学習に使用するデータセットを準備するクラスから始めましょう。ここでは、単一責任の原則に従い、データの準備とロードに特化したクラスを作成します。

import torch
from torch.utils.data import Dataset

class DummyDataset(Dataset):
    """ダミーデータセットを生成するクラス"""

    def __init__(self, num_samples, input_size, output_size):
        self.num_samples = num_samples
        self.input_size = input_size
        self.output_size = output_size
        self.X = torch.randn(num_samples, input_size)
        self.y = torch.randn(num_samples, output_size)

    def __len__(self):
        return self.num_samples

    def __getitem__(self, idx):
        return self.X[idx], self.y[idx]

DummyDatasetクラスは、PyTorchのDatasetクラスを継承しています。initメソッドでは、サンプル数、入力サイズ、出力サイズを受け取り、ダミーのデータセットを生成します。lenメソッドはデータセットの長さを返し、getitemメソッドは指定されたインデックスのサンプルを返します。

モデルの定義

次に、モデルクラスを定義します。ここでは、開放閉鎖の原則に従い、異なる種類のモデルを簡単に拡張できるようにします。

import torch.nn as nn

class NeuralNetwork(nn.Module):
    """単純な全結合ニューラルネットワーク"""

    def __init__(self, input_size, hidden_size, output_size):
        super(NeuralNetwork, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        out = self.fc1(x)
        out = self.relu(out)
        out = self.fc2(out)
        return out

class ConvolutionalNeuralNetwork(nn.Module):
    """畳み込みニューラルネットワーク"""

    def __init__(self, input_channels, output_channels, kernel_size):
        super(ConvolutionalNeuralNetwork, self).__init__()
        self.conv = nn.Conv2d(input_channels, output_channels, kernel_size)
        self.relu = nn.ReLU()
        self.pool = nn.MaxPool2d(2, 2)
        self.fc = nn.Linear(output_channels * 12 * 12, 10)

    def forward(self, x):
        out = self.conv(x)
        out = self.relu(out)
        out = self.pool(out)
        out = out.view(-1, 288)
        out = self.fc(out)
        return out

NeuralNetworkクラスは単純な全結合ニューラルネットワークを表し、ConvolutionalNeuralNetworkクラスは畳み込みニューラルネットワークを表しています。両クラスともnn.Moduleを継承しており、forwardメソッドで前向き計算を定義しています。 このように、異なる種類のモデルをそれぞれ別のクラスとして定義することで、開放閉鎖の原則に従っています。新しい種類のモデルを追加する場合は、単に新しいクラスを作成するだけで済みます。

学習とモデル評価

学習とモデル評価の機能は、インターフェイス分離の原則に従って別々のクラスに分離しています。

import torch.nn as nn

class Trainer:
    """モデルの学習を行うクラス"""

    def __init__(self, model, criterion, optimizer, device):
        self.model = model.to(device)
        self.criterion = criterion
        self.optimizer = optimizer
        self.device = device

    def train(self, dataloader, num_epochs):
        self.model.train()
        for epoch in range(num_epochs):
            running_loss = 0.0
            for inputs, labels in dataloader:
                inputs, labels = inputs.to(self.device), labels.to(self.device)

                self.optimizer.zero_grad()
                outputs = self.model(inputs)
                loss = self.criterion(outputs, labels)
                loss.backward()
                self.optimizer.step()

                running_loss += loss.item()

            epoch_loss = running_loss / len(dataloader)
            print(f'Epoch {epoch+1}, Loss: {epoch_loss:.4f}')

class Evaluator:
    """モデルの評価を行うクラス"""

    def __init__(self, model, criterion, device):
        self.model = model.to(device)
        self.criterion = criterion
        self.device = device

    def evaluate(self, dataloader):
        self.model.eval()
        running_loss = 0.0
        with torch.no_grad():
            for inputs, labels in dataloader:
                inputs, labels = inputs.to(self.device), labels.to(self.device)
                outputs = self.model(inputs)
                loss = self.criterion(outputs, labels)
                running_loss += loss.item()

        eval_loss = running_loss / len(dataloader)
        print(f'Evaluation Loss: {eval_loss:.4f}')

Trainerクラスは、モデルの学習を行います。initメソッドでは、モデル、損失関数、オプティマイザー、デバイス(CPU/GPU)を受け取ります。trainメソッドでは、指定された epochの数だけ学習を行い、各epochの損失値を出力します。 Evaluatorクラスは、モデルの評価を行います。initメソッドでは、モデル、損失関数、デバイスを受け取ります。evaluateメソッドでは、渡されたデータローダーを使ってモデルの評価損失を計算し、出力します。

ハイパーパラメータの管理

ハイパーパラメータの管理は、専用のクラスに集約して行います。これにより、グローバル変数を使うことなく、オブジェクト指向の原則に従ったコードを書くことができます。

import torch
import torch.nn as nn
from torch.utils.data import DataLoader

from dataset import DummyDataset
from models import NeuralNetwork, ConvolutionalNeuralNetwork
from trainers import Trainer, Evaluator

class Config:
    """ハイパーパラメータを管理するクラス"""

    def __init__(self, num_samples=1000, input_size=10, output_size=1, hidden_size=20, batch_size=32, num_epochs=100, lr=0.001, device='cpu'):
        self.num_samples = num_samples
        self.input_size = input_size
        self.output_size = output_size
        self.hidden_size = hidden_size
        self.batch_size = batch_size
        self.num_epochs = num_epochs
        self.lr = lr
        self.device = torch.device(device)

def main():
    # ハイパーパラメータの設定
    config = Config()

    # データセットの準備
    dataset = DummyDataset(config.num_samples, config.input_size, config.output_size)
    dataloader = DataLoader(dataset, batch_size=config.batch_size, shuffle=True)

    # モデルの定義
    model = NeuralNetwork(config.input_size, config.hidden_size, config.output_size)

    # 損失関数とオプティマイザーの定義
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=config.lr)

    # トレーナーとエバリュエーターの初期化
    trainer = Trainer(model, criterion, optimizer, config.device)
    evaluator = Evaluator(model, criterion, config.device)

    # 学習
    trainer.train(dataloader, config.num_epochs)

    # 評価
    evaluator.evaluate(dataloader)

if __name__ == '__main__':
    main()

Configクラスを新たに作成し、ハイパーパラメータを集約して管理するようにしました。このクラスのインスタンスを作成し、必要な箇所でアクセスすることで、グローバル変数を使う必要がなくなります。 メインスクリプトでは、Configクラスのインスタンスを作成し、その設定を使ってデータセット、モデル、損失関数、オプティマイザー、トレーナー、エバリュエーターを初期化しています。最後に、trainer.trainメソッドでモデルの学習を行い、evaluator.evaluateメソッドでモデルを評価しています。

まとめ

オブジェクト指向とSOLID原則に基づいた、PyTorchを使った深層学習の実装例を紹介しました。単一責任の原則、開放閉鎖の原則、インターフェイス分離の原則に従ってクラスを設計することで、モジュール性と拡張性の高いコードを書くことができます。また、ハイパーパラメータの管理をクラスに集約することで、グローバル変数を使わずにオブジェクト指向の原則に従うことができます。 実際のアプリケーションでは、データの前処理、ハイパーパラメータのチューニング、モデルの評価指標の設定など、さまざまな工夫が必要になります。しかし、ここで紹介した設計原則に従えば、保守性と拡張性の高い深層学習システムを構築することができるでしょう。