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
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
確かにその通りですね!
コードがすっきりしました。
ありがとうございます。
In[1] プログラムのコメントも、# Φの転置行列を作成 → # 基底関数行列Φを作成
ということになると思います。
修正しておきました。
ありがとうございます。m(_ _)m
現在の Fit_func クラス は pseudo_inverse( ) をセットにして使わなければならないので、
Fit_func クラスの中にメソッドとして pseudo_inverse( ) を入れてしまってはどうでしょうか。
言われてみれば、確かにそうですね。
pseudo_inverse()をメソッドにしておきました。