趣味のPython・深層学習

中級者のための実装集

【1dconv】一次元畳み込みCNNの実装

連続したデータを扱う時、1dCNNを実装したいときがあります。 今回はipynbファイルでデバッグを行いながら、実装する際に便利な関数をつけました。 中間層を取り出すおまけ付きです。

class Conv1d(nn.Module):
    def __init__(self, channel_1, channel_2, channel_3, kernel_size_1, kernel_size_2, kernel_size_3, debug=False):
        super(Conv1d, self).__init__()

        self.debug = debug
        self.conv1 = nn.Conv1d(1, channel_1, kernel_size=kernel_size_1, stride=2)
        self.bn1 = nn.BatchNorm1d(channel_1)
        self.relu = nn.ReLU()
        self.maxpool = nn.MaxPool1d(kernel_size=kernel_size_2, stride=2)
        self.conv2 = nn.Conv1d(channel_1, channel_2, kernel_size=kernel_size_2, stride=2)
        self.bn2 = nn.BatchNorm1d(channel_2)
        self.conv3 = nn.Conv1d(channel_2, channel_3, kernel_size=kernel_size_3, stride=1)
        self.gap = nn.AdaptiveAvgPool1d(15)
        self.fc = nn.Linear(30, 1)

    def forward(self, x):
        self.intermediate_outputs = []

        x = self.conv1(x)
        self.print_debug(x, 'conv1')

        x = self.bn1(x)
        self.print_debug(x, 'bn1')

        x = self.relu(x)
        self.print_debug(x, 'relu')

        x = self.maxpool(x)
        self.print_debug(x, 'maxpool')

        x = self.conv2(x)
        self.print_debug(x, 'conv2')

        x = self.bn2(x)
        self.print_debug(x, 'bn2')

        x = self.relu(x)
        self.print_debug(x, 'relu')

        x = self.maxpool(x)
        self.print_debug(x, 'maxpool')

        x = self.conv3(x)
        self.print_debug(x, 'conv3')

        x = self.gap(x)
        self.print_debug(x, 'gap')

        x = x.view(x.size(0), -1)
        self.print_debug(x, 'view')

        self.intermediate_outputs.append(x.clone().detach())
        self.print_debug(self.intermediate_outputs[0], '中間層')

        x = self.fc(x)
        self.print_debug(x, 'fc')

        return x

    def get_intermediate_outputs(self):
        return self.intermediate_outputs

    def print_debug(self, data, message):
        if self.debug:
            print(message, data.shape

下記は実際のテスト使用例になります。 チャンネル数やストライドなどの引数はタスクに合わせてチューニングして下さい。

# モデルのインスタンス化
model = Conv1d(channel_1=16, channel_2=32, channel_3=64, kernel_size_1=3, kernel_size_2=2, kernel_size_3=3, debug=True)

# ダミーの入力データの生成
dummy_input = torch.randn(1, 1, 128)  # サイズ (バッチサイズ, チャネル数, シーケンス長)

# モデルの概要を表示
summary(model, input_size=(1, 128))

# ダミーの入力データをモデルに渡して出力を得る
output = model(dummy_input)

# 中間層の出力を取得
intermediate_outputs = model.get_intermediate_outputs()

# 結果の表示
print("モデルの出力サイズ:", output.shape)
print("中間層の出力サイズ:", intermediate_outputs[0].shape)

全人類が技術報告書をMarkdownで書くべき10000の理由

技術報告書は全てMarkdownで書くべきです。 間違っても表計算ソフトを使って書くべきではありません。 本記事では特に重要なポイントをピックアップします。

  • 文章の構造化Markdownはシンプルでわかりやすい文法を提供し、文章の階層構造を簡単に表現できます。これにより、報告書全体を効果的に構造化できます。

  • 可読性と編集の容易性Markdownはプレーンテキストで書かれるため、可読性が高く、修正や編集が容易です。コードの挿入やリンクの追加も簡単に行えます。

  • バージョン管理の利便性Markdownはテキストベースのフォーマットであり、バージョン管理ツール(例: Git)との互換性が優れています。変更履歴の管理がスムーズに行えます。

  • クロスプラットフォーム対応Markdownはどんなプラットフォームでも一貫して表示されるため、異なる環境やデバイスでの共有が容易です。

  • HTMLへの変換が容易MarkdownはHTMLに変換しやすく、Webページやプレゼンテーションへの変換がスムーズに行えます。

  • シンプルで学習コストが低いMarkdownの文法はシンプルであり、学習コストが低いため、技術者だけでなく非技術者にも扱いやすいです。

  • 表の作成が簡単Markdownは表の作成が直感的であり、データを整理しやすいため、技術報告書において数値や情報を効果的に示すのに適しています。

  • カスタマイズ可能なスタイルMarkdownは柔軟なスタイリングが可能で、テキストのフォーマットや見た目を自由に調整できます。

  • プレゼンテーションへの利用が可能Markdownはスライド形式に変換でき、技術報告書をプレゼンテーションとしても利用できます。

  • オープンな標準仕様Markdownはオープンな標準仕様であり、広くサポートされています。これにより、将来的な互換性の心配が少なくなります。

将来的に社内文書をLLMで学習させる際に、軽量かつ構造化された文書は重宝されます。エクセルにしがみついて非効率な書類を作り続ける組織は淘汰されるべきです。

Pythonディスクリプタの基本: カスタム属性の作成と活用

はじめに

Pythonにおいて、ディスクリプタは強力なオブジェクトの属性を制御するための仕組みです。この記事では、ディスクリプタの基本的な概念と、実際の利用例について詳しく解説します。サンプルコードを交えながら、どのようにディスクリプタを定義し、なぜそれが役立つのかを理解しましょう。

ディスクリプタとは?

ディスクリプタは、Pythonオブジェクトの属性アクセスを制御するための特殊なメカニズムです。通常、ディスクリプタgetsetdeleteメソッドのうち、少なくとも一つを実装したオブジェクトです。これにより、属性の取得、設定、削除時にカスタムの挙動を定義できます。

ディスクリプタの基本的な実装

サンプルコードを通じて、シンプルなディスクリプタの実装を学びましょう。

class SimpleDescriptor:
    def __get__(self, instance, owner):
        print("Getting the value")
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print("Setting the value")
        instance.__dict__[self.name] = value

class MyClass:
    def __init__(self):
        self._value = None

    my_attribute = SimpleDescriptor()

この例では、SimpleDescriptorクラスがディスクリプタとして機能し、MyClass内でmy_attributeとして使用されます。getsetメソッドを実装し、それぞれ属性の取得と設定時にメッセージを表示しています。

プロパティファクトリを使用したディスクリプタの生成

プロパティファクトリを使うと、ディスクリプタの定義がより簡潔になります。次のサンプルコードでは、プロパティファクトリを使用して、幅と高さを持つサイズクラスを作成します。

def property_factory(name):
    def getter(instance):
        return instance.__dict__[name]

    def setter(instance, value):
        if value < 0:
            raise ValueError('Value must be > 0')
        instance.__dict__[name] = value

    return property(getter, setter)

class Size:
    width = property_factory('width')
    height = property_factory('height')

このアプローチを用いることで、コードを再利用して簡潔なプロパティを生成できます。

実用例: ディスクリプタを使ったプロパティの制御

最後に、ディスクリプタを使ってプロパティの制御を行う実用的な例を見てみましょう。以下のコードでは、温度を表すクラスを作成し、温度が絶対零度より低い場合は無効とするディスクリプタを使用しています。

class TemperatureDescriptor:
    def __get__(self, instance, owner):
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if value < -273.15:
            raise ValueError('Temperature cannot be below absolute zero.')
        instance.__dict__[self.name] = value

class Thermometer:
    temperature = TemperatureDescriptor()

この例では、TemperatureDescriptorが温度の値を制御し、Thermometerクラス内でディスクリプタを使って温度を扱っています。

まとめ

ディスクリプタPythonの強力な機能であり、カスタムの属性制御や再利用可能なプロパティの作成に役立ちます。この記事では基本的なディスクリプタの実装方法を学び、実用例を通じてその活用法を理解しました。これを活かして、より柔軟で効果的なクラスの設計に挑戦してみてください。

深層学習内部の行列の内積を行うクラス(Matmul)の実装

はじめに

行列の乗算(Matrix Multiplication)は、深層学習などで頻繁に利用される基本的な演算です。その中でも、逆伝播(Backpropagation)における勾配計算の一環として行われることがあります。この記事では、NumPyを用いて行列の乗算を行うMatMulクラスに焦点を当て、その役割や逆伝播の概念について解説します。 ※この記事のコードは書籍「ゼロから作るDeep learning2」を参考にしております。

実装内容

import numpy as np

class MatMul:
    def __init__(self, W: np.ndarray) -> None:
        # 重み行列 W を使用して MatMul インスタンスを初期化します。
        self.params: list[np.ndarray] = [W]
        self.grads: list[np.ndarray] = [np.zeros_like(W)]
        self.x: np.ndarray | None = None

    def forward(self, x: np.ndarray) -> np.ndarray:
        # 行列の乗算の順伝播を実行します。
        W, = self.params
        out: np.ndarray = np.dot(x, W)
        self.x = x
        return out

    def backward(self, dout: np.ndarray) -> np.ndarray:
        # 勾配を計算するための逆伝播を実行します。
        W, = self.params
        dx: np.ndarray = np.dot(dout, W.T)
        dW: np.ndarray = np.dot(self.x.T, dout)
        
        # 勾配を更新します。grads[0] は dW を格納しています。
        self.grads[0][...] = dW
        return dx

初期化部分

def __init__(self, W: np.ndarray) -> None:
    # 重み行列 W を使用して MatMul インスタンスを初期化します。
    self.params: list[np.ndarray] = [W]
    self.grads: list[np.ndarray] = [np.zeros_like(W)]
    self.x: np.ndarray | None = None

MatMulクラスは、行列の乗算を担当するためのインスタンスを初期化します。重要な属性には、重み行列 W、その勾配 dW、および順伝播時の入力 x があります。これらは逆伝播で使用されます。

順伝播部分

def forward(self, x: np.ndarray) -> np.ndarray:
    # 行列の乗算の順伝播を実行します。
    W, = self.params
    out: np.ndarray = np.dot(x, W)
    self.x = x
    return out

forward メソッドは、行列の乗算の順伝播を行います。入力行列 x と重み行列 W の積を計算し、その結果を返します。また、self.x に順伝播時の入力を保存しておくことで、後の逆伝播で使用します。

逆伝播部分

def backward(self, dout: np.ndarray) -> np.ndarray:
    # 勾配を計算するための逆伝播を実行します。
    W, = self.params
    dx: np.ndarray = np.dot(dout, W.T)
    dW: np.ndarray = np.dot(self.x.T, dout)
    
    # 勾配を更新します。grads[0] は dW を格納しています。
    self.grads[0][...] = dW
    return dx

backward メソッドは逆伝播を担当し、連鎖律を用いて入力に関する勾配 dx とパラメータに関する勾配 dW を計算します。これらの勾配は後の層への逆伝播やパラメータの更新に使用されます。self.grads[0] にはパラメータ W に関する勾配が格納され、これがモデルの学習において重要な役割を果たします。

__iter__, __next__によるイテレータの実装

1. iter メソッドの役割

iter メソッドはクラスがイテレータとして振る舞うための特殊メソッドです。このメソッドを実装することで、クラスのオブジェクトを iter() 関数で呼び出せるようになります。

class MyIterator:
    def __iter__(self):
        return self

iter メソッドでは、通常 self を返し、これによりオブジェクト自体がイテレータであることが示されます。これによって for ループなどでオブジェクトを直接イテレートできるようになります。

2. next メソッドの役割

next メソッドはイテレータが次の要素を返すための特殊メソッドです。このメソッドを実装することで、next() 関数や for ループでイテレーションが進められます。

class MyIterator:
    def __next__(self):
        # 次の要素があれば返す
        # なければ StopIteration を発生させる
        if ...:
            return ...
        else:
            raise StopIteration

next メソッド内で次の要素があればその値を返し、なければ StopIteration を発生させることでイテレーションを終了させます。

3. イテレータの実装例

class CountdownIterator:
    def __init__(self, start: int) -> None:
        self.start: int = start

    def __iter__(self) -> 'CountdownIterator':
        return self

    def __next__(self) -> int:
        if self.start > 0:
            result: int = self.start
            self.start -= 1
            return result
        else:
            raise StopIteration

# CountdownIteratorのインスタンスを作成
countdown_iterator: CountdownIterator = CountdownIterator(5)

# イテレーションを通じて値を取得
for number in countdown_iterator:
    print(number)

この例では、CountdownIterator クラスが iternext メソッドを実装しています。これにより、オブジェクトがイテレータとして機能し、for ループなどで使用できるようになります。

ジェネレータ関連(yield, __init__, __next__)の初心者向け解説

ジェネレータの定義

ジェネレータはイテレータを作成するための特別な関数です。(ジェネレータによって作成されたイテレータをジェネレータイテレータとも呼びます。)

yieldによるジェネレータ実装

from typing import Generator

def simple_generator() -> Generator[int, None, None]:
    yield 1
    yield 2
    yield 3

Generator[int, None, None]: ジェネレータの型アノテーションです。

特殊メソッドでのジェネレータの実装

ジェネレータは iternext メソッドを特殊メソッドとして持っています。これらを実装することで、ジェネレータオブジェクトを作成できます:

from typing import Generator

class CountdownGenerator:
    def __init__(self, start: int) -> None:
        self.start = start

    def __iter__(self) -> Generator[int, None, None]:
        return self

    def __next__(self) -> int:
        if self.start > 0:
            result = self.start
            self.start -= 1
            return result
        else:
            raise StopIteration

nn.Sequentialの挙動を実装して理解する。

この記事では、nn.Sequentialの基本的な概念から始め、自作のCustomSequentialクラスを通してその挙動を解説します。

1. nn.Sequentialとは?

nn.Sequentialは、PyTorchでネットワークを構築するためのシンプルで便利なツールです。これは順番にモジュールを適用することができ、モデルの構築を簡略化します。まず、基本的な使い方を見てみましょう。

import torch.nn as nn

# nn.Sequentialを使った例
model = nn.Sequential(
    nn.Linear(10, 20),
    nn.ReLU(),
    nn.Linear(20, 5)
)

上記の例では、3つの層が順番に適用されるモデルが構築されています。

2. CustomSequentialクラスの作成

まずは、nn.Sequentialと同様の動作を持つ簡単なカスタムクラスCustomSequentialを作成しましょう。このクラスでは、与えられたモジュールを順番に適用するforwardメソッドを持っています。

class CustomSequential(nn.Module):
    _modules: dict  # 子モジュールを格納する辞書

    def __init__(self, *args: nn.Module):
        super(CustomSequential, self).__init__()
        
        self._modules = {}

        # argsに渡された層を順に追加
        for idx, layer in enumerate(args):
            self.add_module(f'layer_{idx}', layer)

    def add_module(self, name: str, module: nn.Module) -> None:
        # 子モジュールを追加
        if not isinstance(module, nn.Module):
            raise ValueError(f"{module} is not a Module")
        self._modules[name] = module

    def children(self) -> torch.nn.ModuleDict:
        # 子モジュールを返す
        for name, module in self._modules.items():
            yield module

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # 子モジュールを順に適用
        for layer in self.children():
            x = layer(x)
        return x

このクラスはnn.Sequentialと同じように使用でき、カスタムモジュールを順番に適用します。 add_moduleやchildrenなどのメソッドはnn.Module継承時に使えますが、ここでは分かりやすく簡単に実装しています。

3. カスタムクラスの利用

さっそく、先ほど作成したCustomSequentialクラスを使用してモデルを構築し、入力データに適用してみましょう。

# カスタムSequentialを使った例
model = CustomSequential(
    nn.Linear(10, 20),
    nn.ReLU(),
    nn.Linear(20, 5)
)

# 入力データの例
input_data = torch.randn((32, 10))

# フォワードパスの実行
output = model(input_data)

これにより、nn.Sequentialと同じようにカスタムモデルが動作します。CustomSequentialクラス内部では、forwardメソッドで子モジュールを順番に適用しています。

4. 挙動の理解

nn.Sequentialは各モジュールを順番に適用し、その出力を次のモジュールの入力として渡します。これにより、層を簡潔に積み重ねることができ、ネットワークの構築が容易になります。