直交関数系#

最小二乗法#

関数の足し合わせによる関数の近似を理解するために 最小二乗法 を紹介する.最小二乗法では,データを与えられそのデータをよく表現する関数を求める 関数近似 を行う.関数近似によってデータの背後に潜む関係性を関数として捉えることができれば,データ間の対応関係や未知のデータに対する予測が可能となる.

例えば,以下のように何らかのデータを取得したとしよう.プログラムとしては,\(y=2x+1\)の関係が背後に潜むノイズの乗ったデータを擬似的に生成している.matplotlibを使って生成したデータ(青色の点)とその背後に潜む関係性(赤色の直線)をプロットしている.

import numpy as np
from sympy import *
import matplotlib.pyplot as plt

N = 30
x_data = np.linspace(0, 1, N) # xの値
y_gt = 2 * x_data + 1 # y=2x+1の真の値
y_data = 2 * x_data + 1 + 0.1 * np.random.randn(len(x_data)) # 一定のノイズがのった観測データ

plt.scatter(x_data, y_data)
plt.plot(x_data, y_gt, c='r')
Intel MKL WARNING: Support of Intel(R) Streaming SIMD Extensions 4.2 (Intel(R) SSE4.2) enabled only processors has been deprecated. Intel oneAPI Math Kernel Library 2025.0 will require Intel(R) Advanced Vector Extensions (Intel(R) AVX) instructions.
Intel MKL WARNING: Support of Intel(R) Streaming SIMD Extensions 4.2 (Intel(R) SSE4.2) enabled only processors has been deprecated. Intel oneAPI Math Kernel Library 2025.0 will require Intel(R) Advanced Vector Extensions (Intel(R) AVX) instructions.
[<matplotlib.lines.Line2D at 0x1245a0880>]
../../_images/8df578424e9f23f2844e905ab0fd6e8965d6e3fb8efee6ee718dd2ee0f72b6e8.png

関数近似の目的は,\(x\) を入力値,\(y\)\(x\) に対応する目標値としたとき,\(N\) 個のデータ \(\{(x_i,y_i)\}_{i=1}^N\) からデータの背後に潜む赤色の関係性を求めることである.しかしながら,利用できるデータはここでは x_datay_data のみであり,関係性に関する情報(関数の形など)は未知である.

そこで最小二乗法では,\(N\) 個のデータ \(\{(x_i,y_i)\}_{i=1}^N\) を与えられ,\(n\)個のの関数の値 \(\phi_{k}(x)\) とその係数 \(c_k\) との定数倍の和で表現される関数を考える(\(k=1,...,n\)

\[ y_{\alpha} \approx \sum_{k=1}^{n} c_{k} \phi_{k}\left(x_{\alpha}\right), \alpha=1, \ldots, N \]

この関数が成立するような係数 \(c_1,...,c_n\) を求めることがゴールであり,これらの係数は以下の最小化問題から求めることができる.

\[ J=\frac{1}{2} \sum_{\alpha=1}^{N}\left(y_{\alpha}-\sum_{k=1}^{n} c_{k} \phi_{k}\left(x_{\alpha}\right)\right)^{2} \rightarrow \min \]

線型結合

前述の定数倍の和で表される第一式は係数 \(c\) と関数 \(\phi\) の値の足し合わせ(線型結合)になっていることが以下のように展開するとわかる.

\[\begin{split} \begin{align} y_{\alpha} &\approx \sum_{k=1}^{n} c_{k} \phi_{k}\left(x_{\alpha}\right) \\ &=c_{1} \phi_{1}\left(x_{\alpha}\right) + c_{2} \phi_{2}\left(x_{\alpha}\right) + \cdots \end{align} \end{split}\]

上記の例のように直線で近似することを考えると,\(\{\phi_1, \phi_2, c_1, c_2\}=\{1,x,b,a\}\) とすれば,上記の式は,\(y_{\alpha} \approx ax_{\alpha}+b\) となり,直線の方程式 \(y=ax+b\) の係数 \(a,b\) をデータから求める問題になっていることがわかる.さらに,\(\{\phi_1, \phi_2, \phi_3, c_1, c_2, c_3\}=\{1,x,x^2, c, b,a\}\) とすれば,二次関数 \(y_{\alpha} \approx ax_{\alpha}^{2}+bx_{\alpha} + c\) で関数を近似することとなる.

ここまで考えると,\(\{\phi_1, \phi_2, \phi_3\}=\{1,\cos{x},\sin{x}\}, \{c_1, c_2, c_3\}=\{a_0, a_1, b_1\}\) とすれば,\(y_{\alpha} \approx a_0 + a_1 \cos{x_{\alpha}} + b_1 \sin{x_{\alpha}}\) となり,最小二乗法からフーリエ級数展開の式が書けそうなのがイメージできるかと思う.

では,係数 \(c_k\) を求める最小化問題を深掘りする.二つ目のシグマを展開してみると,

\[ \begin{align} J&=\frac{1}{2} \sum_{\alpha=1}^{N}\left(y_{\alpha}-\left ( c_{1} \phi_{1}\left(x_{\alpha}\right) + c_{2} \phi_{2}\left(x_{\alpha}\right) + \cdots \right )\right)^{2} \end{align} \]

となり,係数 \(c\) をもつ関数 \(\phi\) の線形結合の式と \(y_{\alpha}\) との差の計算をしていることがわかる.そして残ったシグマも展開してみると,

\[\begin{split} \begin{align} J= &\frac{1}{2} \left ( \left(y_{1}-\left ( c_{1} \phi_{1}\left(x_{1}\right) + c_{2} \phi_{2}\left(x_{1}\right) + \cdots \right )\right)^{2} \right. \\ & \left. + \left(y_{2}-\left ( c_{1} \phi_{1}\left(x_{2}\right) + c_{2} \phi_{2}\left(x_{2}\right) + \cdots \right )\right)^{2} + \cdots \right ) \end{align} \end{split}\]

となり,すべてのデータの差を足していることがわかる.つまりはデータを表現する関数 \(\phi\) は固定で \(c\) の値を変化させて関数と実際に観測されたデータの値との差(=\(J\))をできるだけ小さくする問題を考えているのである.そして,\(J\)を小さくする\(c\)を求める方法はこれまで最小値の問題を解いてきた時と同じように関数の傾きを考え,傾きが\(0\)となる点を求めれば良い.ただし,今回は \(J\) は多変数関数なので偏微分を考える必要がある.

\(J\)\(c_i\) で偏微分したときの偏導関数を求める(慣れるまではシグマを展開して偏微分すると良い).

\[ \frac{\partial J}{\partial c_i}= \sum_{\alpha=1}^{N}\left(y_{\alpha}-\sum_{k=1}^{n} c_{k} \phi_{k}\left(x_{\alpha}\right)\right)\left ( - \phi_{i} \left(x_{\alpha}\right) \right ) \]

そして,全ての係数 \(c_1,...,c_n\) の偏導関数について以下の方程式を解く.

\[ \frac{\partial J}{\partial c_1}=0, \cdots, \frac{\partial J}{\partial c_n}=0 \]

この連立方程式を解くために 正規方程式 と呼ばれる行列で表現される形に書き換える.まず求めたい \(c\) について偏導関数を整理する.

\[\begin{split} \begin{align} \frac{\partial J}{\partial c_i} &= \sum_{\alpha=1}^{N}\left(y_{\alpha}-\sum_{k=1}^{n} c_{k} \phi_{k}\left(x_{\alpha}\right)\right)\left ( - \phi_{i} \left(x_{\alpha}\right) \right ) \\ &=- \sum_{\alpha=1}^{N} y_{\alpha} \phi_{i} \left(x_{\alpha}\right) + \sum_{k=1}^{n} \left ( \sum_{\alpha=1}^{N} \phi_{k}\left(x_{\alpha}\right) \phi_{i} \left(x_{\alpha}\right) \right ) c_{k} \end{align} \end{split}\]

ここで,\( \partial J / \partial c_i = 0 \) より,

\[ \sum_{k=1}^{n} \left ( \sum_{\alpha=1}^{N} \phi_{k}\left(x_{\alpha}\right) \phi_{i} \left(x_{\alpha}\right) \right ) c_{k} = \sum_{\alpha=1}^{N} y_{\alpha} \phi_{i} \left(x_{\alpha}\right) \]

となる.これをすべての係数で並べてみると,

\[\begin{split} \left\{\begin{matrix} \sum_{k=1}^{n} \left ( \sum_{\alpha=1}^{N} \phi_{k}\left(x_{\alpha}\right) \phi_{1} \left(x_{\alpha}\right) \right ) c_{k} = \sum_{\alpha=1}^{N} y_{\alpha} \phi_{1} \left(x_{\alpha}\right)\\ \sum_{k=1}^{n} \left ( \sum_{\alpha=1}^{N} \phi_{k}\left(x_{\alpha}\right) \phi_{2} \left(x_{\alpha}\right) \right ) c_{k} = \sum_{\alpha=1}^{N} y_{\alpha} \phi_{2} \left(x_{\alpha}\right)\\ \vdots \\ \sum_{k=1}^{n} \left ( \sum_{\alpha=1}^{N} \phi_{k}\left(x_{\alpha}\right) \phi_{n} \left(x_{\alpha}\right) \right ) c_{k} = \sum_{\alpha=1}^{N} y_{\alpha} \phi_{n} \left(x_{\alpha}\right)\\ \end{matrix}\right. \end{split}\]

となり,以下のように行列の形で書き直すことができる.

\[\begin{split} \begin{pmatrix} \sum_{\alpha=1}^{N} \phi_{1}\left(x_{\alpha}\right) \phi_{1}\left(x_{\alpha}\right) & \sum_{\alpha=1}^{N} \phi_{1}\left(x_{\alpha}\right) \phi_{2}\left(x_{\alpha}\right) & \cdots & \sum_{\alpha=1}^{N} \phi_{1}\left(x_{\alpha}\right) \phi_{n}\left(x_{\alpha}\right) \\ \sum_{\alpha=1}^{N} \phi_{2}\left(x_{\alpha}\right) \phi_{1}\left(x_{\alpha}\right) & \sum_{\alpha=1}^{N} \phi_{2}\left(x_{\alpha}\right) \phi_{2}\left(x_{\alpha}\right) & \cdots & \sum_{\alpha=1}^{N} \phi_{2}\left(x_{\alpha}\right) \phi_{n}\left(x_{\alpha}\right) \\ \vdots & \vdots & \ddots & \vdots \\ \sum_{\alpha=1}^{N} \phi_{n}\left(x_{\alpha}\right) \phi_{1}\left(x_{\alpha}\right) & \sum_{\alpha=1}^{N} \phi_{n}\left(x_{\alpha}\right) \phi_{2}\left(x_{\alpha}\right) & \cdots & \sum_{\alpha=1}^{N} \phi_{n}\left(x_{\alpha}\right) \phi_{n}\left(x_{\alpha}\right) \end{pmatrix} \begin{pmatrix} c_1\\ c_2\\ \vdots\\ c_n \end{pmatrix} = \begin{pmatrix} \sum_{\alpha=1}^{N} \phi_{1}\left(x_{\alpha}\right) y_{\alpha} \\ \sum_{\alpha=1}^{N} \phi_{2}\left(x_{\alpha}\right) y_{\alpha} \\ \vdots \\ \sum_{\alpha=1}^{N} \phi_{n}\left(x_{\alpha}\right) y_{\alpha} \end{pmatrix} \end{split}\]

この方程式を解けば係数 \(c\) を求めることができ,最小二乗法による関数近似を実現できる.

行列への変換

2つ以上のシグマを含む偏導関数を行列の形式へ変換するのはやや慣れが必要かもしれない.初めのうちは以下のようにシグマを展開して考えてみると良い(\(c_1\)の偏導関数の展開).

\[ \begin{align}\begin{aligned}c_{1} \sum_{\alpha=1}^{N} \phi_{1}\left(x_{\alpha}\right) \phi_{1} \left(x_{\alpha}\right) + c_{2} \sum_{\alpha=1}^{N} \phi_{2}\left(x_{\alpha}\right) \phi_{1} \left(x_{\alpha}\right) + \cdots + c_{n} \sum_{\alpha=1}^{N} \phi_{n}\left(x_{\alpha}\right) \phi_{1} \left(x_{\alpha}\right)\\= \sum_{\alpha=1}^{N} y_{\alpha} \phi_{1} \left(x_{\alpha}\right)\end{aligned}\end{align} \]

正規方程式の行列計算で偏導関数が表現できていることが確認できると思う.

Pythonによる実装#

では,先ほど作成した x_datay_data から最小二乗法を実装してみる.今回は以下の線型結合で表される式,

\[ y_{\alpha} \approx \sum_{k=1}^{n} c_{k} \phi_{k}\left(x_{\alpha}\right), \alpha=1, \ldots, N \]

について,前述したように,\(\{\phi_1, \phi_2, c_1, c_2\}=\{1,x,b,a\}\) として一次関数

\[y_{\alpha} \approx ax_{\alpha}+b\]

を考える.このとき \(N\) はデータ数.つまり x_data.shape[0]としたときの値である.まずは \(N\) を確認する.

x_data.shape[0]
30

x_data を作成したときの N と一致していることを確認されたい.続いて,式をsympyで定義していく.

a = symbols('a')
b = symbols('b')

x = symbols('x')
y = symbols('y') # y_data が格納される正解用の y
y_ = symbols('y_') # 近似後の y_alpha が格納される y_

y_ = a * x + b # 関数を定義
y_
\[\displaystyle a x + b\]

続いて,\(J\) を定義する.numpy表記のように書けるのでプログラムは簡単に書ける

J = (y - (a * x + b)) ** 2 / 2
J
\[\displaystyle \frac{\left(- a x - b + y\right)^{2}}{2}\]

今回,係数は \(a\)\(b\)である.これらのパラメータに対する偏導関数\(\frac{\partial J}{\partial a}, \frac{\partial J}{\partial b}\)を定義する.

from sympy import diff
J_a = diff(J, a)
J_a
\[\displaystyle - x \left(- a x - b + y\right)\]
J_b = diff(J, b)
J_b
\[\displaystyle a x + b - y\]

得られた偏導関数について,各データを代入して方程式を得る.具体的には次の式の計算を各係数\(a,b\)について行っている.

\[ \sum_{\alpha=1}^{N} y_{\alpha} \phi_{i} \left(x_{\alpha}\right) + \sum_{k=1}^{n} \left ( \sum_{\alpha=1}^{N} \phi_{k}\left(x_{\alpha}\right) \phi_{i} \left(x_{\alpha}\right) \right ) c_{k} \]
sum_a = sum([J_a.subs([(x, _x), (y, _y)]) for _x, _y in zip(x_data, y_data)])
sum_a
\[\displaystyle 10.1724137931034 a + 15.0 b - 35.0288444949234\]
sum_b = sum([J_b.subs([(x, _x), (y, _y)]) for _x, _y in zip(x_data, y_data)])
sum_b
\[\displaystyle 15.0 a + 30 b - 59.0225862837622\]

得られた連立方程式を solve を使って解いて係数\(a,b\)を求める.行っている計算は正規方程式を解いているのと同じである.

from sympy import solve
coef = solve([sum_a, sum_b], [a, b])
coef
{a: 2.06463211920293, b: 0.935103483190611}

得られた係数\(a,b\)が実際の値に近いことがわかる.実際にプロットもしてみる

y_pred = coef[a] * x_data + coef[b] 

plt.scatter(x_data, y_data)
plt.plot(x_data, y_gt, c='r')
plt.plot(x_data, y_pred, c='g')
[<matplotlib.lines.Line2D at 0x12491d0d0>]
../../_images/cd48624c8350553954481f5411c74bfbe708e2e44057b39562c25948ab97e190.png

直交関数系#

正規方程式からパラメータ \(c_k\) を求めることができたが,関数 \(\phi\) を自身以外との積が \(0\) となるように関数の集合 \(\{ \phi_{i}(x) \}^{n}_{i=1}\) を設計するとさらに計算が簡単になる.この自身以外との積が \(0\) となるような性質を 直交性 と言い,以下で定義される.

\[ \sum_{\alpha=1}^{N} \phi_{i}\left(x_{\alpha}\right) \phi_{j}\left(x_{\alpha}\right) = 0, \qquad i \neq j \]

そして,直交性を持つ関数の集合を 直交関数系 と言う.

直交関数系で関数 \(\phi\) を設計したときの正規方程式を考えると,自身以外との積が \(0\) となるので正規方程式の非対角成分が \(0\) となるのは明らかである.

\[\begin{split} \begin{pmatrix} \sum_{\alpha=1}^{N} \phi_{1}\left(x_{\alpha}\right) \phi_{1}\left(x_{\alpha}\right) & & & \\ & \sum_{\alpha=1}^{N} \phi_{2}\left(x_{\alpha}\right) \phi_{2}\left(x_{\alpha}\right) & & \\ & & \ddots & \\ & & & \sum_{\alpha=1}^{N} \phi_{n}\left(x_{\alpha}\right) \phi_{n}\left(x_{\alpha}\right) \end{pmatrix} \begin{pmatrix} c_1\\ c_2\\ \vdots\\ c_n \end{pmatrix} = \begin{pmatrix} \sum_{\alpha=1}^{N} \phi_{1}\left(x_{\alpha}\right) y_{\alpha} \\ \sum_{\alpha=1}^{N} \phi_{2}\left(x_{\alpha}\right) y_{\alpha} \\ \vdots \\ \sum_{\alpha=1}^{N} \phi_{n}\left(x_{\alpha}\right) y_{\alpha} \end{pmatrix} \end{split}\]

つまりは各成分 \(c_i\) は以下の式から容易に計算できる.

\[ c_i = \frac{\sum_{\alpha=1}^{N} \phi_{i} \left(x_{\alpha}\right) y_{\alpha}}{\sum_{\alpha=1}^{N} \phi_{i}\left(x_{\alpha}\right)^2 }, \qquad i=1,...,n \]

ベクトルの直交#

正規方程式は自身以外との積が \(0\) となるように関数を設計することで簡単に解くことができたが,近似対象が\(N\) 個の離散データ \(\{(x_i,y_i)\}_{i=1}^N\) ではなくベクトルや関数そのものであったときどのように最小二乗法や直交性を考えるべきかをここで紹介する.

そもそも 直交する という表現はこれまでベクトルで登場したかと思う.具体的には,\(m\)次元のベクトル \(\mathbf{a}, \mathbf{b}\) の内積が \(0\) であるときこれらのベクトルは直交していると習ったはずだ.

\[ \left (\mathbf{a}, \mathbf{b} \right ) = \sum_{i=1}^m a_i b_i = a_1 b_1 + \cdots + a_m b_m = 0 \]

そして,ベクトルが直交するとは二つのベクトルが成す角が\(90°\)であった.このベクトルの直交性は,ベクトルを最小二乗法で近似する場合に直接利用することもできる.

以下にベクトルの最小二乗法についてまとめた.クリックすると確認できる.

関数の直交#

離散データとベクトルで直交を定義し,正規方程式からの最小二乗法を導出したのと同様に関数に対しても直交を定義することができる.

区間\([a,b]\)で関数\(f(x), g(x)\)が以下のとき関数が直交するという.

\[ \int_{a}^{b}\ f(x) g(x) dx= 0 \]

そして,関数の集合 \(\{\phi_i(x)\}^n_{i=0}\) が互いに直交するとき,区間 \([a,b]\) 上の直交関数系であるという.

\[ \int_{a}^{b}\ \phi_{i}\left(x\right) \phi_{j}\left(x\right) dx= 0, \qquad i \neq j \]

この関数の直交性を利用してベクトルと同様に関数の最小二乗法を考えることもできる.

以下に関数の最小二乗法についてまとめた.クリックすると確認できる.