0
高校数学解説
文献あり

傾きを共有して切片が異なるデータ列の線形回帰

464
0

こんな感じに傾きが同じ複数のデータ列があるときに,傾きaと切片たちbiを最小二乗法で解析的に求めたいという問題です.

データ データ

研究で必要になって計算したのですがいろいろ計算をミスって手こずったので,せっかくなので残しておこうと思います.
ニッチなシチュエーションですが,アレニウスプロットのように両対数をとって傾きからべきを求めるような解析なんかには使えるかもしれません.

普通の最小二乗法

まず計算の参考にするため,「普通の」最小二乗法の計算を追ってみます.
こちらの計算は[1]の記事を参考にさせていただきました.

最小二乗法による線形回帰は,データが直線
y=ax+b
従っているところにノイズが乗っていると考えて,このノイズを最小化するa,bを求める問題です.
各データ点を(xi,yi)(i=1,,N)として式で表すと
minimizea, bi=1N(yiaxib)2
ここで「ノイズの大きさ」を「差の絶対値」などではなく「差の2乗」で表すのは正規分布の最尤推定との関連と,後述するように微分できた方が計算が簡単であるという都合によるものです.

さて,最小化すべき関数f(a,b)=i=1N(yiaxib)2は係数が1の2次関数を足し合わせたものなので下に凸です.
すなわち最小点は極小点と一致するためabで微分して0になる点が,求めたいa, bであることが分かります.

よって,a, bは,連立方程式
(1)f(a,b)a=i2xi(axi+byi)=0
(2)f(a,b)b=i2(axi+byi)=0
を解くと,(2)式を変形して,
aixi+Nbiyi=0
b=1Niyia1Nixi

ここで,1Ni=1Niiについての平均なのでと表記することにして,
b=yax
となります.これを(1)式に代入して整理すると,
i(axi2+bxixiyi)=0
a1Ni(x2)+(yax)1Nix1Ni(xy)=0
ax2+(yax)xxy=0
a(x2x2)(xyxy)=0
a=xyxyx2x2
b=x2yxxyx2x2
このようにして,a,bの値を求められました.

最小二乗法による線形回帰

a=xyxyx2x2,b=x2yxxyx2x2

ちなみに,誤差の大きさは正規分布の最尤推定問題を解くことでσ2=(ax+by)2が得られます.

傾きを共有して切片が異なるデータ

冒頭の図1のように,k番目の直線がy=ax+bkk=1,,Mのように傾きaは共通,切片bkkによって異なるという場合の最小二乗法を考えます.
k番目の直線のi番目のデータを(xk,i,yk,i)とすると,この問題は
minimizea, b1,,bMk=1Mi=1N(yk,iaxk,ibk)2
と定式化できます.
シグマが増えてややこしくなりましたが,最小化すべき関数g(a,b1,,bM)=ki(yk,iaxk,ibk)2は複数のkにまたがるような積の項はないため偏微分は簡単で,
(3)g(a,b1,,bM)a=ki2xk,i(axk,i+bkyk,i)=0
(4)g(a,b1,,bM)bk=i2(axk,i+bkyk,i)=0(k=1,,M)
M+1連立方程式となります.

(4)式は各kについて(2)式と同様に解けて,
bk=ykaxk
です.
これを(3)式に代入し整理すると,
ki(axk,i2+bkxk,ixk,iyk,i)=0
k(axk2+(ykaxk)xkxkyk)=0
ak(xk2xk2)k(xkykxkyk)=0
a=k(xkykxkyk)k(xk2xk2)

kについての平均1Mk=1Mk[]と表すことにすれば,
a=[xkykxkyk][xk2xk2]
となります.

kごとに線形回帰をおこなってaをもとめて平均をとる,という方法だと
a=[xkykxkykxk2xk2]
となるので少し式が違いますね.

結論:

傾きを共有する複数データ列による回帰

a=[xyxy][x2x2],bk=ykaxk=yk[xyxy][x2x2]xk(k=1,,M)

この場合も,同様に正規分布の最尤推定によりσ2=[(ax+by)2]が得られます.

Python での計算

文字式ばかりだとピンとこないので,実際に数値計算も行ってみます.
a=1.5,b1=0.5,b2=2.1,b3=4.3とし,適当に定めたxからy=ax+bσ2=0.5のガウスノイズを加えてデータとします.

プロット プロット

      import numpy as np
np.random.seed(42)

a = 1.5
b1 = 0.5
b2 = 2.1
b3 = 4.3
s = 0.5

x1 = [0.4, 2.0, 2.7, 3.6, 4.2]
x2 = [0.2, 1.1, 2.9, 3.6, 4.7]
x3 = [0.0, 2.0, 2.8, 3.2, 4.2]

y1 = [a*x+b1+np.random.normal(0,s) for x in x1]
y2 = [a*x+b2+np.random.normal(0,s) for x in x2]
y3 = [a*x+b3+np.random.normal(0,s) for x in x3]

X = np.array([x1,x2,x3])
Y = np.array([y1,y2,y3])
    

x,yの列をスタックしたものをX,Yとすると,大きさM×Nの行列となります.
単に+*で演算すると成分ごとの演算となり,,[]は第2成分,第1成分についての平均なので.mean(axis=1),.mean()(注:0-indexed)で得られるため,a,(bk)の推定値は以下のようにそれぞれ1行を求められます.

      a_hat = ((X*Y).mean(axis=1)-X.mean(axis=1)*Y.mean(axis=1)).mean() / ((X**2).mean(axis=1) - X.mean(axis=1)**2).mean()
# => 1.574510913222147
b_hat = Y.mean(axis=1) - a_hat*X.mean(axis=1)
# => array([0.07364212, 1.35999338, 4.42261194])
    

点が少ないのでブレがありますが,一応それらしい値が得られました.

ところでこの解析は,モデルとして階層的なものを最尤推定で求めているため,パラメータ推定として不適切です.
現に,「それっぽい値が出ている」ということしかわからず,推定値がどれくらいの分散を持っているのかすら求められていませんね.
本当はパラメータの分布をベイズ推定で求め,詳しく解析する必要があります.
が,計算が大変なのでそれはまた別の機会に……

参考文献

投稿日:2021923
OptHub AI Competition

この記事を高評価した人

高評価したユーザはいません

この記事に送られたバッジ

バッジはありません。
バッチを贈って投稿者を応援しよう

バッチを贈ると投稿者に現金やAmazonのギフトカードが還元されます。

投稿者

Y. Saki
Y. Saki
2
5240

コメント

他の人のコメント

コメントはありません。
読み込み中...
読み込み中
  1. 普通の最小二乗法
  2. 傾きを共有して切片が異なるデータ
  3. Python での計算
  4. 参考文献