『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

任意関数の近似関数を生成するクラス

Fit_funcクラス

前回記事では、入力変数 $x$ と目標データ $y$ の関係を基底関数 $\phi_k(x)$ の線形結合
 \[f(x)=a_0\phi_0(x)+a_1\phi_1(x)+a_2\phi_2(x)+\cdots +a_{M-1}\phi_{M-1}(x)+a_M\]
によって近似するようなモデル(線形基底関数モデル)について学びました。今後、色々な基底関数を選びながら精度の検証を繰り返すことを考えると、任意の関数リストを与えて(平均2乗誤差を最小にする)近似関数を得られるようなクラスを実装しておいたほうがよさそうです (≫ Python におけるクラスの基本事項についてはこちらを参照してください)。

Fit_funcクラスの実装

作成するクラスの概要は次のようになります。
 
・クラス名は Fit_func とします。
 (Python の慣例として、クラス名の先頭は大文字にします)
 
・インスタンスを生成するコンストラクタの引数には、入力データベクトル x と目標データベクトル y、および関数オブジェクトのシーケンスを渡します。
 
・入力ベクトル x, 目標ベクトル y および関数オブジェクトのシーケンスをデータ属性として付与します。
 
・以下のメソッドを実装します。
 Fit_func.pseudo_inverse(phi):疑似逆行列を計算
 Fit_func.params():パラメータベクトルを取得
 Fit_func.line(x):配列 x に対する近似関数値を取得
 Fit_func.mse():平均2乗誤差の値を取得
 Fit_func.sd():標準偏差の値を取得
 
関数オブジェクトのシーケンスとは、たとえば [np.sin, np.cos, np.log] のような 関数オブジェクト を格納したリストのことです(タプルでもかまいません)。ここから任意の要素を取り出して引数 x を渡すことで初めて値が計算されるようになっています。

# Fitting

# In[1]

import numpy as np
import matplotlib.pyplot as plt
np.set_printoptions(precision=3, suppress=True)

# Fit_funcクラスを定義
class Fit_func:
    def __init__(self, x, y, func):
        self.x = x
        self.y = y
        self.basis = func

    # 疑似逆行列関数
    def pseudo_inverse(self, phi):
        return np.linalg.inv(phi.T @ phi) @ phi.T

    # パラメータベクトル
    def params(self):
        a = len(self.basis) # 基底関数の数
        b = self.x.shape[0]  # 入力ベクトル行数
        Phi = np.zeros((b, a))  # b×aサイズの0行列を定義
        for k in range(a):  # 基底関数行列Φを作成
            Phi[:,k] = self.basis[k](self.x)
        pi_Phi = self.pseudo_inverse(Phi)  # Φの疑似逆行列
        p = pi_Phi @ self.y  # パラメータベクトルを計算
        return p

    # 近似関数
    def line(self, xv2):
        prm = self.params()  # params()メソッドで得たパラメータベクトル
        s = len(self.basis)  # 基底関数の数
        f = np.zeros(len(xv2))  # 要素が0の配列
        for k in range(s):  # xv2に対する近似関数値を計算
            f += prm[k] * self.basis[k](xv2)
        return f

    # 平均2乗誤差
    def mse(self):
        delta = self.y - self.line(self.x)
        return np.mean(delta ** 2)

    # 標準偏差
    def sd(self):
        s = np.sqrt(self.mse())
        return s

これで Fit_func クラスを実装完了です。

Fit_funcクラスのインスタンスを生成

前回と同じ基底関数 $x^2,\ x,\ 1$ を用意して同じ結果を得られるかを確認しておきましょう。すなわち 2次関数
 \[f(x)=ax^2+bx+c\]
を近似曲線として採用し、パラメータベクトル $(a,b,c)$ を決定します。

# In[2]

# 入力データベクトル
x = np.array([35, 16, 22, 43, 5,
              66, 20, 13, 52, 1,
              39, 62, 45, 33, 8,
              28, 71, 24, 18, 3])

# 目標データベクトル
y = np.array([85.19, 58.93, 64.27, 68.91, 21.27,
              68.88, 60.07, 55.18, 88.08, 8.89,
              82.31, 81.18, 80.76, 78.98, 37.55,
              75.9, 72.39, 69.51, 62.04, 12.47])

# 基底関数を準備
def phi_0(x):
    return x ** 2

def phi_1(x):
    return x

def phi_2(x):
    return 1

# 基底関数のリスト
basis = [phi_0, phi_1, phi_2]

# Fit_funcオブジェクトを作成
z = Fit_func(x, y, basis)

# params()メソッドで最適化されたパラメータを取得
print("パラメータベクトル: {}".format(z.params()))

# mse()メソッドで平均2乗誤差を取得
mse = np.round(z.mse(), 3)

# sd()メソッドで標準偏差を取得
sd = np.round(z.sd(), 3)

print("平均2乗誤差(MSE): {}".format(mse))
print("標準偏差(SD): {}".format(sd))

# Figureを作成
fig = plt.figure(figsize = (8, 6))

# FigureにAxesを1つ追加
ax = fig.add_subplot(111)

# Axesのタイトルを設定
ax.set_title("Age vs. Weight", fontsize = 16)

# 目盛線を表示
ax.grid()

# 軸範囲を設定
ax.set_xlim([0, 70])
ax.set_ylim([0, 100])

# 軸ラベルを設定
ax.set_xlabel("Age", fontsize = 14)
ax.set_ylabel("Weight [kg]", fontsize = 14)

# 身長と体重データの散布図
ax.scatter(x, y, color = "blue")

# line()メソッドを用いて近似曲線を描画
x2 = np.linspace(0, 70, 100)
y2 = z.line(x2)
ax.plot(x2, y2, color = "red")

plt.show()
# パラメータベクトル: [-0.034  3.228 10.083]
# 平均2乗誤差(MSE): 32.227
# 標準偏差(SD): 5.677

線形基底関数モデル 年齢と体重の多項式近似

 

コメント

  1. HNaito より:

    In[1] プログラムの def params(self) が少しややこしく感じられました。以下のようにΦの形状をaxb→bxaに変更すれば、前回の記事の (12) 式の通りに基底関数行列を作成できてわかりやすいかもしれません。

    def params(self):
    a = len(self.basis) # 基底関数の数
     b = self.x.shape[0] # 入力ベクトル行数
     Phi = np.zeros((b, a)) # b×aサイズの0行列を定義 ←
     for k in range(a): # 基底関数行列Φを作成
      Phi[:, k] = self.basis[k](self.x) # ←
     # Phi = Phi.T ←
     pi_Phi = pseudo_inverse(Phi) # Φの疑似逆行列
     p = pi_Phi @ self.y # パラメータベクトルを計算
     return p

    • あとりえこばと より:

      確かにその通りですね!
      コードがすっきりしました。ありがとうございます。

      • HNaito より:

        In[1] プログラムのコメントも、# Φの転置行列を作成 → # 基底関数行列Φを作成ということになると思います。

  2. HNaito より:

    現在の Fit_func クラス は pseudo_inverse( ) をセットにして使わなければならないので、Fit_func クラスの中にメソッドとして pseudo_inverse( ) を入れてしまってはどうでしょうか。

    • あとりえこばと より:

      言われてみれば、確かにそうですね。
      pseudo_inverse()をメソッドにしておきました。

  3. あとりえこばと より:

    ChatGPT に、本記事の In[1] からコメントアウト文を取り除いたコードを見せて、内容を理解できるか試してみました。

    [ChatGPT] このPythonコードは、線形回帰に関連するクラス Fit_func を定義しています。Fit_func クラスには、次のメソッドが含まれています:

    1. __init__(self, x, y, func): コンストラクタメソッドで、入力データ x と対応するターゲット値 y、および基底関数のリスト func を受け取ります。

    2. pseudo_inverse(self, phi): 擬似逆行列を計算するメソッドです。線形代数の擬似逆行列を用いて、パラメータを計算する際に使用されます。

    3. params(self): パラメータを計算するメソッドです。基底関数の適用によって得られる特徴行列 Phi を作成し、擬似逆行列を使用してパラメータ p を計算します。

    4. line(self, xv2): 与えられた xv2 の値に対して、学習済みのパラメータを使用して予測値を計算するメソッドです。

    5. mse(self): 平均二乗誤差(Mean Squared Error)を計算するメソッドです。学習済みモデルの予測値と実際のターゲット値の差を二乗し、その平均を取ります。
     
    6. sd(self): 平均二乗誤差の平方根(Standard Deviation)を計算するメソッドです。これはモデルの予測精度を示す指標となります。

    このクラスを使用することで、基底関数を用いた線形回帰を行うことができます。基底関数として、任意の非線形関数を使用できるため、より複雑なデータに対しても適用することが可能です。

    [あとりこばと] … マジですか … 完璧に解読しました。「基底関数として任意の非線形関数を使用できる」って … ちゃんとコードの本質を理解しています。なんか、怖くなってきましたよ。