3  メッセージパッシングとポリモーフィズムの関係性

Author

Ryo Nakagami

Published

2025-11-19

Modified

2025-11-19

NoteTL;DRs
  • クラス: 変数とサブルーチン(処理)をひとまとめにして独立性の高い部品を作る仕組み
  • 継承: コードの重複を排除する仕組み
  • ポリモーフィズム: 汎用性の高い部品を作る仕組み

メッセージパッシング

Definition 3.1 メッセージパッシング

  • オブジェクトに対して「メソッド呼び出し(=メッセージ)」を送ること
  • メッセージを送られた側が何をするかは,基本的にメッセージを送られた側(=メソッドを呼び出された型)の判断事項
model.predict(X)

というメソッド呼び出しは

  • 「predict」というメッセージを model に送る
  • model がそのメッセージにどう応えるかは model 側の実装に依存

相手が具体的にどのクラスのインスタンスであるかは意識せず,メッセージを送られたオブジェクトがメッセージに応えられるかを重要視する考え方という感じです.

ポリモーフィズム

Definition 3.2 ポリモーフィズム

  • 類似したクラスに対するメッセージの送り方を共通にする仕組み
  • オブジェクト間で統一されたインターフェース(同じ名称のメソッド)を有するが,そのメソッドの動作はオブジェクトごとに異なるという実装になる

典型的なポリモーフィズムの一つとして,継承ベースのポリモーフィズムがあります.継承ベースでは基底クラスを継承した派生クラスが共通インターフェースを実装します.

Example 3.1 (統計パッケージと継承ベースのポリモーフィズム)

まず推定量の抽象クラスとなる基底クラスを考えます

from abc import ABC, abstractmethod

class BaseEstimator(ABC):
    """Estimator Common Interface"""

    @abstractmethod
    def fit(self, X, y):
        pass

    @abstractmethod
    def predict(self, X):
        pass

これは統計推定量クラスの共通API(fit, predict)を強制するための抽象クラスになります.インターフェースを統一することで Pipeline,複数モデルの比較,グリッドサーチといった処理を共通の呼び出し方法で実現できるようになるメリットがあります.

次に,この基底クラスを継承する形で LinearRegressionKernelRegression の2つの推定量クラスを実装します..

import numpy as np
from sklearn.metrics.pairwise import rbf_kernel

# -----------------------------
# OLS Regression
# -----------------------------
class LinearRegression(BaseEstimator):

    def fit(self, X, y):
        # add constant
        X_ = np.c_[np.ones(X.shape[0]), X]
        
        # OLS regression
        self.coef_ = np.linalg.pinv(X_.T @ X_) @ X_.T @ y
        return self

    def predict(self, X):
        X_ = np.c_[np.ones(X.shape[0]), X]
        return X_ @ self.coef_

# -----------------------------
# Kernel Regression
# -----------------------------
class KernelRegression(BaseEstimator):

    def __init__(self, gamma=1.0):
        self.gamma = gamma

    def fit(self, X, y):
        """
        K(x, y) = exp(-gamma ||x-y||^2)
        """
        self.X_train = X
        self.y_train = y
        
        # use gaussian kernel
        self.K = rbf_kernel(X, X, gamma=self.gamma)
        
        #  Moore-Penrose pseudoinverse to solve the least squares problem. 
        self.alpha_ = np.linalg.pinv(self.K) @ y
        return self

    def predict(self, X):
        K_test = rbf_kernel(X, self.X_train, gamma=self.gamma)
        return K_test @ self.alpha_

上記の統一APIを活用して予測を行います

# ダミーデータ
X = np.random.randn(100, 2)
y = 2*X[:,0] - X[:,1] + np.random.randn(100)*0.1

models = [LinearRegression(), KernelRegression(gamma=0.5)]

for model in models:
    model.fit(X, y)
    print(model.predict(X[:3]))
[1.26715646 1.93175836 1.1192046 ]
[1.27131283 2.14333344 0.96539307]

継承によらないポリモーフィズム

Pythonは継承によるポリモーフィズムも利用できますが,型に依存しない振る舞いベースのポリモーフィズムも利用できます. このスタイルをダックタイピングと呼びます.

Definition 3.3 ダックタイピング

  • 「もしもそれがアヒルのように歩き,アヒルのように鳴くのなら,それはアヒルに違いない」
  • オブジェクトの型や継承関係をチェックするのではなく,必要なメソッドが実装されているかどうかだけに注目するプログラミングスタイル
  • インタフェース(振る舞い)を満たしていれば,そのクラスが何を継承していても,していなくても構わない

Example 3.2 (fitpredict を持つなら何でも推定量として扱う)

def evaluate_estimator(estimator, X_train, y_train, X_test, y_test):
    
    # Don't check whether estimator object inherit a specific class 
    estimator.fit(X_train, y_train)
    y_pred = estimator.predict(X_test)

    # Return MSE
    return ((y_pred - y_test) ** 2).mean()

fitpredict を持つなら何でも推定量として扱っています.なにかしらの BaseEstimator を継承しているかどうかは問いません.