EEIC前期実験i2 第2日
本日のメニュー
- 機械学習におけるデータの取り扱い
- 線形回帰
- 単回帰
- 重回帰
- 基底関数回帰
はじめに
2日目は「教師あり学習」における基本的な問題である回帰問題を扱う(当初計画より若干変更)。教師あり学習は入力と出力の対応関係を求める問題設定である。教師あり学習で解くべきタスクは回帰と分類と大きく分かれているが、基本的にこの問題構造は変わらない。そのためスムーズな学習のためにまずは回帰を取り扱っていく。また本日は「線形」と冠した回帰を扱う。最も簡単な例から出発して、次元の拡大、基底関数の導入等を進め、基本的には線形なモデルを維持したまま、モデルを複雑にしていく。
機械学習におけるデータの取り扱い
1日目の最後にいくつか機械学習の基本概念を述べたが、機械学習は"Learning from examples"の実現である。データが与えられた時に何らかの規則や法則を獲得していく。獲得した規則や法則を活用して、次にデータの入力部分が与えられた時に、未知である部分を推定し、出力する。前半の獲得部分を学習フェーズ、後半部分を評価フェーズ(または推論フェーズ)といったりする。この時、重要な事は学習フェーズにおいて、評価フェーズにおける一切の情報は知らないという前提である。学習を行っている以上、暗黙的には「学習フェーズで用いたデータの性質は、評価フェーズのデータも持っている」という暗黙的な前提はあるものの、例えば「評価ではこのデータを入力に使う」や「評価データのうち、ここは正解出力である」ということは学習の時点では知り得ない。このことは一見すると当たり前に思える。ある講義の勉強を一生懸命したとして、(似たような過去問は知っているかもしれないが)実際の期末試験の問題は(タイムリープでもしていない限り)勉強時点では知らないはずである。
ところが、データセットが与えられて機械学習の問題を解きましょうとなった時、特に初学者はこの前提を忘却することがある。シミュレーションデータやトイデータの場合、簡便性のため全てのデータが一括で提供されることも多い。タスクとして機械学習を行う場合、上述の大前提に立ち、一番最初にデータの分割を行う。データサンプルのうち、学習フェーズに使うサブセットのことを訓練データ(training data)、評価フェーズで用いるサブセットのことをテストデータ(test data)と呼ぶ。一度分割したら、少なくともこの分割設定においては学習フェーズでテストデータに触れることはない。学習データのみを用いて規則や法則を獲得していく。
訓練データをさらに細かく分けることもある。これは主に規則や法則に潜んでいる、決定が難しい要因(例えばどの程度複雑なモデルを仮定するか、何回ぐらい反復学習するのか)といった情報を訓練データから決定する際に、訓練データをさらに2つのサブセットに分ける考え方である。このときモデルそのものを学習するためのデータをそのまま訓練データと呼ぶが、上記の情報を決定する際に用いるデータのことを検証データ(validation data)と呼ぶ。なおこちらの分割については以降であらためて触れる。
Test-time training (TTT)
上記の大前提に矛盾するようであるが、Test-time trainingという手法が近年注目されている。training という言葉が入っているため、評価フェーズと学習フェーズを混在しているように(少なくとも筆者のような古い人間は)感じてしまったりもするが、こちらは実際の利用時に少数の事例から学習済みモデルを適応する概念(ドメイン適応)に近いといえる。大規模言語モデル(LLM)の文脈で用いられ、LLMの実活用において注目されている手法である。興味がある人は機械学習の前提を頭におきつつ調査してみるとよい。
データリーク
上記の「テストデータは訓練には使わない」ということはとても当たり前のように思えるが、実際に研究を進めていく上では意識せずに行ってしまあうことがあり注意が必要である。テストデータを訓練に使ってしまうことをデータリーク(データの漏洩)と呼んだりする。
研究を進める際、公開データセットで手法を評価することは多々ある。例えば1000クラス画像認識問題(画像が与えられて、それがイヌなのかネコなのか、・・・を当てる。このクラスが1000個ある)を考えよう。公開データセットを用いることで、手法の開発者は自分の手法の性能を計算することが出来、他の手法と比較することが出来る。このような場合、テストデータも当然ながら公開されており、好きに触れることが出来る(そうでなくては性能を評価できない)。このときに、例えば「テストデータの中には変な外れ値画像がある。この画像の影響を除けば精度があがる。なので、うまいことこの外れ値を無視できるように、手法の閾値を0.56にしよう」といったことをした場合、これはデータリークになる。なのでそのようなことはしていはいけない。テストデータは、あくまで、手法が完成して最後の1回にだけ使い、その結果を論文に書く、それだけである。「閾値0.6のときテストデータでの性能が0.4だった。閾値0.8のときテストデータの性能が0.3だった。なので閾値0.6を採用する」ということもデータリークである。なぜなら極端にいえばそれを繰り返せば極めて高い性能を達成できてしまうが、それはテストデータに過学習しているだけだからである(そのような閾値調整は検証データ(validation data)で行うべき)。あくまでテストデータは最後の1回使うだけ、というのが正しい姿である。
ここでの問題は、他の対抗論文がどうみてもテストデータでのデータリークを行っている、、、ということが往々にしてあることである。近年のAI研究の世界は精度競争が起こってしまっており、そのようにルールを無視した論文がトップ会議にたくさん通ってしまっていることもまた事実である。みなさんは、そのようなルール無視のやり方に染まることなく、データリークをしないようにしてほしい。実際、データリークをしてまで精度を上げても、それは実世界では使いものにならない、論文のための論文という手法になってしまう(未知のデータに対して有効かわからないので)
ちなみに、Kaggleといった技術コンペサービスでは、リーダーボードといって、手法をシステムに提出するとシステム側で評価が行われてウェブサイトに通知されるような仕組みが整備されている。そのようにして、最終テストデータを盗み見するデータリークを防いでいるのだ。
線形回帰
線形回帰とは
線形回帰は、入力変数 \(x\) と出力変数 \(y\) の関係を、入力の線形結合として表現する手法である。
最も単純な場合、入力が \(D\) 次元ベクトル
であるとき、出力 \(y\) は
と表される。
ベクトル表記を用いると、
となる。
ここで、
- \(w_0\) は切片(バイアス)
- \(\mathbf{w}\) は重みベクトル
である。
線形回帰では、観測データから重み \(\mathbf{w}\) を推定し、入力と出力の関係をモデル化する。
1次元入力1次元出力(単回帰)
さらに拡張していく前に、まずは \(D=1\) の場合、入力 \(\mathbf{x} \in \mathbb{R}^1\)、出力 \(\mathbf{y} \in \mathbb{R}^1\) の 状況から考えてみよう。\(\mathbb{R}^{D}\) は \(D\)次元ベクトル空間を表すものとする。以降簡単のため、添字 \(d\) をつけた場合はベクトルの次元を、添字 \(n\) をつけた場合はサンプルの番号を表すとして、適宜用いる。
1次元の場合は添字が煩雑になるので、次元を省略し\(n\)番目のサンプル値を\(x_n\) で表す。\(x\)と\(y\) の間に
の関係があることが想定される。
このとき、\(N\) 個のデータからなるセット \(\{(x_1,y_1),(x_2,y_2),\ldots,(x_N,y_N)\}\)が与えられて傾き \(w_1\) と 切片 \(w_0\) を求める問題となる。これを統計分野的用語で単回帰と呼ぶ。これは線形回帰問題の最も単純な例である。
最小二乗法による重み推定
各データ点に対して、モデルによる予測値
を考える。
観測値と予測値の差
を残差(residual)という。
残差が小さいほどモデルはデータをよく説明していると考えられる。
残差の総和
を最小化しようとしても、正の誤差と負の誤差が打ち消し合う可能性がある。
そこで、残差を二乗した量を用いる。
この量を二乗和誤差と呼ぶ。冒頭に係数\(1/2\) を含むのはこの後の最適化処理と関係する。なおこの値にはサンプル数\(N\)の影響があるが、最適化のための係数を除いた上で\(N\)でこれを割って1サンプルあたりの平均的な誤差として表したものを平均二乗誤差(mean square error; MSE)と呼ぶ。
最小二乗法では、二乗和誤差が最も小さくなる \(w_0\) と \(w_1\) を求める。結果的に平均二乗誤差を最小にするのでMMSE(Minimum Mean Square Error)と言ったりもする。
さて誤差関数
をそれぞれのパラメータで偏微分する。
まず切片について微分すると、
となる。
同様に傾きについて微分すると、
となる。二乗和誤差の係数に\(1/2\) がついているのは、ここで偏微分で現れる係数と相殺するためであり、理論的に誤差を設計する際によく用いるトリックである。
最小値ではこれらがゼロになるため、
を満たす。
これらを(単回帰における)正規方程式という。
解の導出
1本目の式から
が得られる。
ここで
は標本平均である。
これを2本目の式へ代入すると、
が得られる。
さらに、
によって切片を求められる。
単回帰演習
1日目の最後に出力したデータを用いて、単回帰に最小二乗法によるパラメータ推定を行ってみる。以下の点に注意しながら実装していく。
- データは201点あるが、ランダムに40点抽出しこれを評価データとして分ける。すなわち\(N = 201 - 40\) となる。
- 上述の正規方程式から得られる更新式において、\(N\)サンプルの総和が式で登場する。これについてNumpy(に限らず数値計算系の言語全般)においてfor文を用いずに実装することを考える。要素積、内積、ブロードキャスト、統計関数などを利用してとにかくfor文を使わないことが重要である。これは主に速度面による。
- 抽出したテストデータに対して、推定した単回帰の結果とのMMSEを評価してみる。
演習(単回帰演習1)
1日目の最後に生成したデータを読み込み、単回帰パラメータを推定するプログラムを実装せよ。
- matplotlibなどを活用したデータ点および直線の描画も、下記を参考にしつつリファレンスを調査して実装する。
- 上述の通り適宜、訓練データと評価データを分けること。評価データを用いた評価も実施すること。
- 実は上記の更新式はさらに式変形可能である。式変形に基づいて何パターンか実装してみること。
matplotlib は、Pythonにおけるグラフ描画を担うライブラリの一つである。仮にdata.npy というファイルで保存した場合を仮定して(適宜読み替えること)、以下のようにプロットできる
matplotlibの使用法については、適宜、友人や生成AIに尋ねたり、公式ページ を確認しながら、描画を洗練させていくと良い。上記はシンプルに使っただけの状態である。
演習(単回帰演習2)
ソフトウェア2で用いたdata.csvについて、講義で用いた勾配法ではなく、上記と同様に単回帰としてパラメータ推定を実装せよ。
- data.csvの読み取りには
np.loadtxtが利用できる。pandasをここでの読み込みで使用するのは若干オーバースペックなので用いる必要はない。 - この演習に限りテストデータは富士山のみでよい。
なぜ二乗誤差を最小化するのか?
線形回帰では、予測値と観測値の差を二乗した
を最小化する。
一見すると「なぜ二乗するのか」は自明ではない。しかし、この選択には確率的な意味付けがある。
まず、観測データが
によって生成されると仮定する。
ここで \(\varepsilon_n\) は観測ノイズであり、
という平均0、分散 \(\sigma^2\) の正規分布に従うとする。
この仮定のもとでは、入力 \(x_n\) が与えられたときの出力 \(y_n\) の条件付き確率分布は
となる。
独立な観測データが \(N\) 個あるとすると、全データが観測される確率(尤度)は
で与えられる。
尤度の積は扱いにくいため、通常は対数を取った対数尤度を考える。
この式のうち、
は回帰係数 \(w_0,w_1\) に依存しない定数である。
したがって、対数尤度を最大化することは
を最小化することと同値になる。
つまり最小二乗法は、
「観測誤差が正規分布に従う」という仮定の下で最も尤もらしい回帰直線を求める方法
と解釈できる。
このように、二乗誤差は単なる計算上の都合ではなく、確率モデルから自然に導かれる評価関数である。
損失関数とノイズモデルの関係
最小二乗法が導かれたのは、観測誤差が正規分布に従うと仮定したためである。
実は、異なる確率分布を仮定すると異なる損失関数が現れる。
例えば、誤差がラプラス分布
に従うと仮定すると、対数尤度最大化は
の最小化と等価になる。
つまり、
- 正規分布 → 二乗誤差(L2損失)
- ラプラス分布 → 絶対値誤差(L1損失)
が対応している。
このことから、損失関数は単なる計算上の選択ではなく、「観測データがどのようなノイズを含むか」という仮定を反映していると解釈できる。
多次元入力1次元出力(重回帰)
さて、\(D > 1\)、すなわち多次元の場合に拡張してみよう。実際には出力が複数の要因によって決まることは多い。現実の問題にはよくある設定である。
例えば住宅価格を予測する場合、
- 面積
- 築年数
- 最寄り駅までの距離
など複数の特徴量を利用したいといった具合である。
ベクトル表現
最初に述べた通り、重みベクトル
を導入すると、
と書ける。
さらに切片をコンパクトに表すため、入力データを拡張し
と定義すれば、
と表せる。
最小二乗法
単回帰の場合と同様に、観測データ
が与えられたとする。
予測値
を用いると、二乗和誤差は
となる。
単回帰と同様に、この値を最小にする重みを求める。
行列表現
入力データをまとめて
と書く。
これを設計行列(design matrix)という。
また、
とすると、
となる。ここで重みベクトルはすでに切片を含んでいるとして、チルダを省略している。
二乗和誤差は
と表せる。
正規方程式
誤差関数を微分すると、
が得られる。
単回帰の場合に限らず、この方程式も正規方程式という。
さらに
が可逆であれば、
となる。
これは単回帰の場合に導出した解を、多次元の場合へ一般化したものである。
グラム行列の可逆性
上記について、\(X^\top X\) の可逆性が条件として描かれている。まずこの行列はグラム行列と呼ばれる。この可逆性は特徴量の次元数\(D\)とデータサンプル数\(N\)との関係に密接に関わっている。非常に高い特徴量次元\(D\)において、サンプル数\(N\)が十分でない場合や、特徴量の間に強い相関がある場合に発生し、この時、特徴量が多重共線性を有しているという。
重回帰演習
上記の導出を踏まえて、重回帰でのパラメータ推定を実装してみる。
演習(重回帰)
こちらにおいたデータ ( multiple_regression_data.npy ) を読み込み、重回帰のパラメータを推定するプログラムを実装せよ。データのダウンロードにはECCSアカウントが必要である。
- 手元にダウンロードしてGoogle Colabに導入する場合は左側のフォルダアイコンをクリックして、アップロードアイコンからアップロードする。
- 入力データの次元数は12、出力は1次元で、含まれるサンプル数は1000である。
np.shape()で確認するとよい - 800を訓練データ、200を評価データに分ける。ここでは後半の200サンプルを評価データとする。
- 重みを推定して、評価データに対してMMSEを評価すること。
基底関数を用いた線形回帰
一旦\(D=1\) の世界に戻ってくる。\(D\) を大きくして、複数の要因を考慮することは、良いモデルを考える一つの方法であるが、入力そのものと出力の関係がつねに線形であることに変わりはない。実際のデータでは、入力と出力の関係が単純な直線では表現できないことが多い。
そこで、入力 \(x\) に対して基底関数(basis function)
を導入する。
このとき、出力は
として表現する。
基底関数ベクトル
を用いると、
と書ける。
重要なのは、入力 \(x\) に対しては非線形な変換を行っていても、重み \(\mathbf{w}\) に対しては線形である点である。そのため、このモデルも線形回帰と呼ばれる。この時、基底関数は一種の特徴量抽出器の役割を果たしている。のちに深層学習に触れる時にこの視点は重要になってくる。
多項式基底
最もよく用いられる基底関数の一つが多項式基底である。
例えば、
を用いると、
となる。
このモデルは入力 \(x\) に対して非線形な曲線を表現できる。
ガウス基底関数
局所的な特徴を表現したい場合には、ガウス基底関数が利用される。
ガウス基底関数は
で定義される。
ここで、
- \(\mu_m\) は中心
- \(\sigma_m\) は幅
を表す。
複数のガウス基底を配置することで、複雑な非線形関数を近似できる。
基底関数と信号処理
基底関数の考え方は信号処理にも広く現れる。
例えばフーリエ変換では、
を基底関数として用い、信号を周波数成分の線形結合として表現する。
これは三角関数基底による線形展開とみなせる。
ウェーブレットとの関係
ウェーブレット変換も基底関数展開の一種である。
ウェーブレット基底
を用いることで、
のように信号を表現する。
フーリエ基底が周波数成分を表現するのに対し、ウェーブレット基底は時間的な局所性と周波数情報を同時に扱えるという特徴を持つ。
基底関数回帰における最小二乗法
基底関数回帰では、入力 \(x\) を基底関数によって変換した特徴量
を用いて、
と表現する。
ここで通常は
として切片項を含める。
このモデルは入力 \(x\) に対しては非線形な関数を表現できるが、重み \(\mathbf{w}\) に対しては線形である。そのため、重回帰と同様に最小二乗法を適用できる。
誤差関数
観測データ
が与えられたとする。
各入力に対する予測値は
である。
したがって二乗和誤差は
となる。
最小二乗法では、この誤差関数を最小にする重みベクトル \(\mathbf{w}\) を求める。
設計行列
各データ点に対する基底関数の出力を並べて、
を定義する。
この行列も同様に設計行列(design matrix)という。
また、
とすると、予測値ベクトルは
と表せる。
行列表現
誤差関数は
と書ける。
この式は重回帰の場合と全く同じ形である。
違いは、入力ベクトル \(\mathbf{x}\) の代わりに基底関数ベクトル \(\mathbf{\phi}(x)\) を用いている点だけである。
正規方程式
誤差関数を重みベクトルで微分すると、
となる。
最小値では勾配がゼロとなるため、
を得る。
これを正規方程式(normal equation)という。
さらに、
が可逆であれば、
となる。
重回帰との関係
重回帰では入力ベクトル
を直接用いた。
一方、基底関数回帰では
を新たな特徴量として利用する。
したがって基底関数回帰は、
「入力を基底関数によって変換した後に行う重回帰」
とみなすことができる。
最小二乗法による重み推定も、そのまま同じ形で適用できる。
多項式基底と過学習
基底関数の数を増やすと、より複雑な関数を表現できるようになる。
例えば、多項式基底
を用いると、
というモデルになる。
ここで \(M\) を多項式の次数と呼ぶ。
次数による表現能力の違い
例えば、
- \(M=1\):直線
- \(M=2\):放物線
- \(M=3\):三次曲線
- \(M=10\):非常に複雑な曲線
を表現できる。
一般に、\(M\) を大きくするとモデルの表現能力は高くなる。
そのため、訓練データへの適合度は向上する。
訓練誤差だけでは判断できない
多項式の次数を大きくすると、訓練データ上の誤差は単調に減少する。
極端な場合には、\(N\)個のデータに対して
程度の次数を用いることで、すべての訓練データを通過する曲線を構成できる。
しかし、そのようなモデルが未知のデータに対して良い予測を行うとは限らない。
訓練データに過度に適合してしまう現象を過学習(overfitting)という。
リーブワンアウト交差検証
過学習を調べるためには、学習に使用していないデータで性能を評価する必要がある。
ここではリーブワンアウト交差検証 (Leave-One-Out Cross Validation: LOOCV) を用いる。
データ数を \(N\) とすると、
- 1点を検証用データとして取り除く
- 残りの \(N-1\) 点でモデルを学習する
- 取り除いた1点に対する予測誤差を計算する
という操作を全データについて繰り返す。
LOOCV誤差
第 \(i\) データを検証用として除いたときの予測値を
とする。
このとき、LOOCV誤差は
で定義される。
この値が小さいほど、未知データに対する予測性能が高いと考えられる。
次数と誤差の関係
多項式次数 \(M\) を変化させながら、
- 訓練誤差
- LOOCV誤差
を計算することを考える。
一般的には次のような傾向が見られる。
小さすぎる次数
例えば
の場合、モデルは単純すぎる。
真の関係が曲線であっても直線しか表現できないため、
- 訓練誤差が大きい
- LOOCV誤差も大きい
という状態になる。
これをアンダーフィッティング(underfitting)という。
適切な次数
次数を少しずつ増やしていくと、
- 訓練誤差が減少する
- LOOCV誤差も減少する
ようになる。
この領域ではモデルの表現能力が向上し、未知データへの予測性能も改善している。
大きすぎる次数
さらに次数を増やしていくと、
- 訓練誤差は引き続き減少する
- LOOCV誤差は増加し始める
ことがある。
これは訓練データ中の偶然のばらつきや観測ノイズまで学習してしまうためである。
この状態が過学習である。
モデル選択
モデル選択では、
についてLOOCV誤差を計算し、
が最小となる次数を選択する。
このとき選ばれた次数は、
訓練データへの適合度と未知データへの予測性能とのバランスが最も良いモデルであると考えられる。
多項式基底に関連する演習
本日の最後に多項式基底を用いた回帰でモデルの複雑度とデータへのフィッティングの関係について体験することとする。
演習(多項式基底と交差検証)
こちらのデータ を用いて多項式基底を用いた線形回帰を実装せよ。
- データは1次元入力1次元出力の2次元データで、サンプル数は10である。
- 多項式の基底数\(M\) を変化させながら重みベクトルを変化させる。
- 全てのデータを訓練データとして用いるが、1サンプルデータだけ検証データとして分けて、基底数\(M\)を変化させるのに用いる。
- LOOCVを行って、モデルがどのようにデータに適合していくかを示せ。可視化にmatplotlibを使うことでわかりやすく示せ。
- 元はどのようなデータであったと推定されるか考察せよ。
本日のまとめ
一連の線形回帰モデルを、次元の拡張、基底関数の導入を通じて学んだ。 ここからの発展的内容としては、確率分布的視点をさらに導入していくことで、ベイズ線形回帰やリッジ回帰など、モデルの複雑度を重みベクトルの制約等で制御していく手法に発展させることができる。興味がある人はぜひ調べて自身で実装してみるとよい。
また本日の内容を理論理解を飛ばして動かすだけであればscikit-learnを用いることもできる。本日の自身の実装とscikit-learnでの比較も面白い内容である。