ボルツマン分類器: 熱統計力学に着想を得た超高速クラス分類器

機械学習の世界では、日々新しいアルゴリズムや興味深い論文が発表されています。そんな中、最近「Boltzmann Classifier: A Thermodynamic-Inspired Approach to Supervised Learning」という、熱統計力学のボルツマン分布に着想を得た新しいクラス分類器に関する論文が公開されているのを見つけました。

このボルツマン分類器は、入力と各クラスの中心点との間のエネルギーベースの距離から確率的にクラスを予測するという、シンプルながらもユニークなアプローチを取っています。特に、従来の複雑なモデルとは異なり、繰り返し最適化やバックプロパゲーションを必要としないため計算効率が良く、解釈もしやすいという特徴があるようです。

そこで、早速論文を参考にPythonで実装し、その性能を試してみました。今回は、このボルツマン分類器の基本的な考え方から、具体的な実装方法、そして実際にデータセットを用いた評価結果までを順を追ってご紹介します。

ボルツマン分類器とは

ボルツマン分類器は、その名の通り、物理学の熱統計力学で重要な役割を果たす「ボルツマン分布」という考え方に着想を得て開発されたクラス分類アルゴリズムです。聞き慣れない言葉かもしれませんが、このボルツマン分布の基本的なアイデアは「ある状態のエネルギーが低いほど、その状態は実現しやすい(確率が高い)」というものです。例えば、高いところにあるボールが自然に低いところに転がり落ちるように、系はエネルギーが低い安定した状態を取りやすい、というイメージです。

ボルツマン分類器は、この「エネルギーが低いほど確率が高い」という直感的な原理をクラス分類に応用します。具体的には、分類したい新しいデータ(入力サンプル)と、あらかじめ学習しておいた各クラスの「中心的な位置(クラス中心点)」との「距離」を一種の「エネルギー」として捉えます。そして、ある入力サンプルが特定のクラスの中心点に「近い」(つまりエネルギーが低い)ほど、そのクラスに属する確率が高いと判断します。

では、もう少し具体的に、ボルツマン分類器がどのように学習し、予測を行うのか見ていきましょう。

学習フェーズ-各クラスの「中心点」を見つける

まず、学習データを使って、各クラスがデータ空間のどのあたりに位置しているのか、その「中心点(平均特徴ベクトルやセントロイドとも呼ばれます)」を計算します。例えば、犬と猫を分類したい場合、訓練データに含まれるたくさんの犬の画像の特徴(耳の形、鼻の長さなど数値化されたもの)の平均値を「犬クラスの中心点」、同様に猫の画像の特徴の平均値を「猫クラスの中心点」として保持します。これは非常にシンプルな処理で、複雑な計算は必要ありません。

予測フェーズ-エネルギーを計算し、確率を求める

新しいデータが入力されると、ボルツマン分類器は以下のステップで各クラスに属する確率を計算します。

  1. エネルギーの計算:
    入力されたサンプルと、学習フェーズで計算しておいた各クラスの中心点との間で「エネルギー」を計算します。この「エネルギー」は、サンプルとクラス中心点との「距離」や「非類似度」のようなものだと考えてください。論文で提案されている方法では、L1ノルム(マンハッタン距離とも呼ばれます)を使って、各特徴量の差の絶対値を合計したものをエネルギーとしています。 数式で表すと、入力サンプル \(x\) がクラス \(c\) に属するときのエネルギー \(E_c(x)\) は以下のように計算されます。
    $$
    E_c(x) = \sum_{i=1}^{n} |x_i – \mu_{ic}|
    $$
    ここで、\(x_i\) は入力サンプルの \(i\) 番目の特徴量の値、\(\mu_{ic}\) はクラス \(c\) の中心点の \(i\) 番目の特徴量の値、\(n\) は特徴量の総数を表します。この式は、「入力サンプルの各特徴量と、クラス \(c\) の中心点の対応する特徴量との差の絶対値を計算し、それらを全て足し合わせる」という意味です。このエネルギー \(E_c(x)\) の値が大きいほど、入力サンプルはクラス \(c\) の中心から遠い(似ていない)ことになります。
  2. 確率の計算:
    次に、計算したエネルギー \(E_c(x)\) を使って、入力サンプル \(x\) が各クラス \(c\) に属する確率 \(P(c|x)\) をボルツマン分布の形式で計算します。
    $$
    P(c|x) = \frac{e^{-E_c(x) / (kT)}}{\sum_{j} e^{-E_j(x) / (kT)}}
    $$
    少し複雑に見えるかもしれませんが、分解して見てみましょう。
    • \(e\) はネイピア数(約2.718)です。
    • 分子の \(e^{-E_c(x) / (kT)}\) の部分は、エネルギー \(E_c(x)\) が小さいほど大きな値を取ります(マイナスがついているため)。つまり、クラス中心に近いほど、この値は大きくなります。
    • \(k\) と \(T\) はそれぞれ「スケーリング係数(ボルツマン定数に似た役割)」と「温度パラメータ」と呼ばれる調整可能なパラメータです。\(kT\) の積が大きいと、エネルギーが高い状態(つまりクラス中心から遠い状態)でも比較的確率が割り当てられやすくなり、確率分布が「なだらか」になります。逆に \(kT\) が小さいと、エネルギーが低い状態に確率が集中し、分布が「シャープ」になります。これにより、分類の確信度合いを調整できます。
    • 分母の \(\sum_{j} e^{-E_j(x) / (kT)}\) は、全てのクラス \(j\) について分子と同じ計算をしたものの総和です。これは、全てのクラスの確率を合計すると1になるように正規化するためのものです(確率の合計は常に1でなければなりません)。

このようにして計算された確率 \(P(c|x)\) が最も高いクラスを、最終的な予測結果とします。

ボルツマン分類器の特徴

  • シンプルで直感的: 計算のロジックが比較的単純で、何を行っているのか理解しやすいです。
  • 高速: 複雑な繰り返し計算や最適化処理を必要としないため、学習も予測も高速に実行できます。
  • 確率的な出力: 各クラスに属する確率が得られるため、予測の確信度を知ることができます。
  • 特徴量のスケーリングが重要: エネルギー計算は各特徴量のスケールに影響を受けるため、事前にMinMaxScalerなどを用いて全ての特徴量を例えば0から1の範囲に揃えるといった前処理が推奨されます。これを行わないと、スケールの大きな特徴量だけがエネルギー計算に強く影響してしまう可能性があります。

論文によれば、このボルツマン分類器は、いくつかのデータセットにおいて、ロジスティック回帰やk-最近傍法といった標準的な機械学習モデルと比較しても競争力のある精度を達成すると報告されています。

ボルツマン分類器の実装

ここでは、ボルツマン分類器をPythonのクラスとして実装する方法をご紹介します。機械学習ライブラリ scikit-learn の規約に沿って実装することで、他の scikit-learn のツール(例えば、モデル評価やハイパーパラメータ調整の機能)と簡単に連携できるようになります。具体的には、BaseEstimatorクラスとClassifierMixinクラスを継承して作成します。

# boltzmann_classifier.py

from __future__ import annotations

import numpy as np
from sklearn.base import BaseEstimator, ClassifierMixin
from sklearn.metrics import accuracy_score


class BoltzmannClassifier(BaseEstimator, ClassifierMixin):
    """
    ボルツマン分類器

    ボルツマン分布に着想を得たシンプルで解釈しやすい確率的分類器です。
    クラス確率は入力特徴量とクラス中心点との間のエネルギーベースの距離から計算されます。

    パラメータ
    ----------
    k : float, デフォルト=1.0
        ボルツマン定数に類似するスケーリング係数。

    T : float, デフォルト=1.0
        確率分布の柔らかさを制御する温度パラメータ。

    属性
    ----------
    classes_ : ndarray, 形状 (n_classes,)
        `fit`の実行中に確認されたクラスラベル。

    class_means_ : dict
        クラスラベルから平均特徴ベクトル(中心点)へのマッピング。
    """

    def __init__(self, k: float = 1.0, temperature: float = 1.0) -> None:
        """
        ボルツマン分類器を初期化します。

        パラメータ
        ----------
        k : float, デフォルト=1.0
            ボルツマン定数に類似するスケーリング係数。

        T : float, デフォルト=1.0
            確率分布の柔らかさを制御する温度パラメータ。
        """
        self.k = k
        self.T = temperature

    def fit(self, x: np.ndarray, y: np.ndarray) -> BoltzmannClassifier:
        """
        各クラスの平均ベクトルを計算します。

        パラメータ
        ----------
        x : array-like, 形状 (n_samples, n_features)
            訓練入力サンプル。NumPy配列が使用可能です。

        y : array-like, 形状 (n_samples,)
            目標ラベル。

        戻り値
        -------
        self : BoltzmannClassifier
            学習済みの分類器。
        """
        self.classes_ = np.unique(y)
        self.class_means_ = {cls: x[y == cls].mean(axis=0) for cls in self.classes_}
        return self

    def _energy(self, x_sample: np.ndarray, mean_vector: np.ndarray) -> float:
        """
        L1ノルムを使用してサンプルとクラス平均に対するエネルギーを計算します。

        パラメータ
        ----------
        x_sample : ndarray, 形状 (n_features,)
            入力サンプルベクトル。

        mean_vector : ndarray, 形状 (n_features,)
            クラスの平均ベクトル(中心点)。

        戻り値
        -------
        float
            サンプルとクラス平均の間の距離を表すエネルギー値。
        """
        return np.sum(np.abs(x_sample - mean_vector))

    def predict_proba(self, x: np.ndarray) -> np.ndarray:
        """
        各入力サンプルに対するクラス確率を計算します。

        パラメータ
        ----------
        x : array-like, 形状 (n_samples, n_features)
            入力サンプル。NumPy配列が使用可能です。

        戻り値
        -------
        proba : ndarray, 形状 (n_samples, n_classes)
            各サンプルに対するクラスごとの確率分布。
        """
        proba = []
        for x_sample in x:
            energies = [
                np.exp(-self._energy(x_sample, self.class_means_[cls]) / (self.k * self.T)) for cls in self.classes_
            ]
            total_energy_exp = np.sum(energies)
            if total_energy_exp == 0:
                num_classes = len(self.classes_)
                proba.append([1.0 / num_classes for _ in energies])
            else:
                proba.append([e / total_energy_exp for e in energies])
        return np.array(proba)

    def predict(self, x: np.ndarray) -> np.ndarray:
        """
        Xのサンプルに対するクラスラベルを予測します。

        パラメータ
        ----------
        X : array-like, 形状 (n_samples, n_features)
            入力サンプル。NumPy配列が使用可能です。

        戻り値
        -------
        labels : ndarray, 形状 (n_samples,)
            予測されたクラスラベル。
        """
        probas = self.predict_proba(x)
        indices = np.argmax(probas, axis=1)
        return self.classes_[indices]

    def score(self, x: np.ndarray, y: np.ndarray) -> float:
        """
        与えられたテストデータとラベルに対するモデルの精度(Accuracy)を計算します。

        パラメータ
        ----------
        x : array-like, 形状 (n_samples, n_features)
            テスト用サンプル。NumPy配列が使用可能です。

        y : array-like, 形状 (n_samples,)
            テスト用の真のラベル。

        戻り値
        -------
        score : float
            モデルの精度(正確に分類されたサンプルの割合)。
        """
        accuracy = accuracy_score(y, self.predict(x))
        return float(accuracy)

コードのポイント解説

  • __init__(self, k: float = 1.0, temperature: float = 1.0):
    分類器の初期化メソッドです。ここで、ボルツマン分類器の挙動を調整するための2つの重要なパラメータ、スケーリング係数 k と温度 temperature (論文中の \(T\) に相当) を設定します。デフォルト値はどちらも1.0としています。
  • fit(self, x: np.ndarray, y: np.ndarray):
    このメソッドが学習処理に相当します。やっていることは非常にシンプルで、与えられた訓練データ x とそのラベル y から、まずユニークなクラスラベルを self.classes_ に保存します。次に、クラスごとにデータをグループ化し、各クラスに属するサンプルの特徴量の平均値を計算します。この平均特徴ベクトルが、そのクラスの「中心点(セントロイド)」となり、self.class_means_ という辞書にクラスラベルをキーとして保存されます。
  • _energy(self, x_sample: np.ndarray, mean_vector: np.ndarray):
    このプライベートメソッドは、ある入力サンプル x_sample と、特定のクラスの平均ベクトル mean_vector との間の「エネルギー」を計算します。ここでは論文で提案されている通り、L1ノルム(各特徴量ごとの差の絶対値の合計)を用いています。
  • predict_proba(self, x: np.ndarray):
    新しい入力データ x(複数のサンプルを含むことができます)に対して、各サンプルがそれぞれのクラスに属する確率を計算します。
    内部では、まず各サンプル x_sample について、fit で学習した全てのクラス中心点 self.class_means_[cls] とのエネルギーを _energy メソッドを使って計算します。
    その後、これらのエネルギー値を使って、前のセクションで説明したボルツマン分布の確率計算式 \(P(c|x) = \frac{e^{-E_c(x) / (kT)}}{\sum_{j} e^{-E_j(x) / (kT)}}\) に従って各クラスの確率を算出します。
    分母にあたる total_exp_energy が0になった場合(非常に稀ですが、エネルギーが極端に大きい場合に発生しうる)のフォールバックとして、全てのクラスに均等な確率を割り当てる処理も入れています。
  • predict(self, x: np.ndarray):
    最終的なクラスラベルを予測するメソッドです。predict_proba で各クラスに属する確率を計算した後、最も確率の高いクラスを選択し、そのクラスラベルを返します。これは分類器の標準的な動作ですね。

このように、ボルツマン分類器の実装は、その背後にある理論と同様に比較的シンプルです。特に、複雑な最適化ループや微分計算などが不要なため、コードも直感的で、計算も高速に行える点が特徴です。

ボルツマン分類器の評価

ここでは、代表的なデータセットを使って、その分類精度を評価してみましょう。

使用データセットと評価の準備

評価には、scikit-learnに付属している load_breast_cancer データセットを使用します。これは乳がんの診断データセットで、良性か悪性かを分類する二値分類問題です。論文でも、このBreast Cancer Wisconsinデータセットが評価に使用されています。

ボルツマン分類器は特徴量のスケールに影響を受けるため、前処理として全ての特徴量を0から1の範囲に正規化する MinMaxScaler を適用します。これは論文でも推奨されている手順です。scikit-learn の Pipeline を使うと、このスケーリング処理と分類器の学習・予測をスムーズに連携させることができます。

評価用コード

以下に、データセットの読み込み、訓練データとテストデータへの分割、パイプラインの構築、学習、そして精度評価までを行うPythonコードを示します。分類器のパラメータ ktemperature は、ひとまずデフォルト値の 1.0 を使用します。

# main.py

from sklearn.datasets import load_breast_cancer
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler

from boltzmann_classifier import BoltzmannClassifier


def main() -> None:
    # データセットの読み込み
    x, y = load_breast_cancer(return_X_y=True)

    # 訓練データとテストデータに分割
    x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=123)

    # パイプラインの構築
    pipeline = Pipeline(
        [
            ("scaler", MinMaxScaler()),  # ボルツマン分類器の入力は0~1にすることが前提
            ("clf", BoltzmannClassifier(k=1.0, temperature=1.0)),
        ]
    )
    pipeline.fit(x_train, y_train)
    print("Accuracy (Train Data): ", pipeline.score(x_train, y_train))
    # => Accuracy (Train Data):  0.9295774647887324

    # 評価
    y_pred = pipeline.predict(x_test)
    print("Accuracy (Test Data): ", accuracy_score(y_test, y_pred))
    # => Accuracy (Test Data):  0.958041958041958


if __name__ == "__main__":
    main()

実行結果と考察

上記のコードを実行すると、例えば以下のような結果が得られます(random_stateの値などによって多少変動する可能性があります)。

Accuracy (Train Data): 0.9295774647887324
Accuracy (Test Data): 0.958041958041958

この結果を見ると、テストデータにおいて約95.8%の正解率を達成しています。論文では、Breast Cancer Wisconsinデータセットに対してボルツマン分類器が平均95%の精度を達成したと報告されていますので、今回の我々の実装と評価結果は、論文の報告とよく整合していると言えます。これは、提案されているボルツマン分類器のアルゴリズムが、シンプルながらもこの種のタスクに対して有効であることを示唆しています。

今回はパラメータ ktemperature にデフォルト値の1.0を使用しましたが、これらのハイパーパラメータを調整することで、さらに精度が向上する可能性も考えられます。例えば、グリッドサーチなどの手法を用いて最適なパラメータの組み合わせを探求するのも面白いかもしれません。

おわりに

本記事では、熱統計力学のボルツマン分布に着想を得た新しいクラス分類器「ボルツマン分類器」について、その基本的な考え方からPythonによる実装、そして実際のデータセットを用いた評価までをご紹介しました。この分類器の最大の魅力は、そのアルゴリズムのシンプルさと、それに伴う計算効率の良さにあると言えます。

学習フェーズでは各クラスの平均的な特徴(中心点)を記憶するだけであり、予測フェーズでは入力と各中心点との「エネルギー」を計算し、ボルツマン分布の式に基づいて確率を求めるという、直感的で理解しやすい処理フローでした。実際に試してみた結果、標準的なデータセットに対しても十分な分類性能を示し、論文の報告とも整合する結果が得られました。

このボルツマン分類器の軽量さと計算の速さは、いくつかの興味深い応用可能性を示唆しています。例えば、計算資源が限られているIoTデバイスやエッジコンピューティング環境において、リアルタイムでのクラス分類タスクに活用できるかもしれません。センサーから得られるデータストリームをその場で高速に処理し、異常検知や状態監視などを行うシナリオも考えられます。

また、そのシンプルさと高速性から、アンサンブル学習のベース推定器(base estimator)として利用するのも面白い試みかもしれません。異なるパラメータ設定や特徴量のサブセットで学習させた多数のボルツマン分類器を組み合わせることで、個々の分類器は単純でも、全体としてよりロバストで高精度なモデルを構築できる可能性があります。特に、学習コストが低いため、多くのバリエーションを試すことが容易です。

もちろん、今回は基本的な実装と評価に留まりましたが、論文で示唆されているように、エネルギー計算に用いる距離尺度を変更したり、パラメータ k や T の最適化をさらに追求したりすることで、性能向上の余地も残されています。

ボルツマン分類器は、物理学の原理から着想を得るというアプローチが非常にユニークであり、今後の機械学習分野における新たなアルゴリズム開発のヒントを与えてくれるかもしれません。

More Information

  • arXiv:2505.06753, Muhamed Amin, Bernard R. Brooks, 「Boltzmann Classifier: A Thermodynamic-Inspired Approach to Supervised Learning」, https://arxiv.org/abs/2505.06753