🌊

Strong bias arising from extremely difficult regression problems

2022/12/02に公開

予測問題におけるバイアス

きっかけは@JMLさんのツイートです

これに対して@yoshiso44さんがこんな反応をしていました。

マーケットデータ等ラベルが非常に予測困難な問題、特に0平均で分布している場合は、予測が0であることが非常に強力な正解へのバイアスになる
基本的に特徴量がラベルに対して持つ情報量が少ないほど、より尖度の高い分布になるはず

このstatementを見て、私はなるほどなーと思っていました。
直観的には正しそうです。しかし、本当にそうでしょうか?
ある性質を持った確率変数特有の現象なのか、どういうときにこの現象が観察できるのか。
定量的に検証することで、何かヒントを得られるかもしれません。

この記事は回帰問題において上記の現象を検証してみようというものです。
(先行研究があるかは調べていないので、あったら教えてほしいです)

検証

手順

  1. 機械学習モデルにとって簡単な予測問題を定義する。機械学習モデルがその問題をちゃんと解けることを確認する。
  2. 問題のS/N比を下げることで難易度を上げていくと、バイアスが強くなっていくかを確認する。

特徴量の準備

M次元特徴量\bm{x}_iN個用意します。i\in[1,N]です。これらはM次元正規分布から生成されるとします。
つまり、

\bm{x}_i\sim\mathcal{N}(\bm{0},\bm{1})
とします。
この特徴量はi.i.d.であるとします。これは\bm{x}_i,\bm{x}_j(i\neq j)が独立であることを意味します。
ここではM=10,N=10000としてデータを生成してみます。

import numpy as np
import pandas as pd
np.random.seed(42)
df = pd.DataFrame(np.random.normal(size=[10000, 10]), columns=[f"feat_{i}" for i in range(10)])
df[["feat_0"]].hist(bins=100, density=True)

簡単な予測問題

1.機械学習モデルにとって簡単な低S/N比の予測問題を定義する。機械学習モデルがその問題をちゃんと解けることを確認する。

簡単なターゲットラベルの作成

さて、特徴量は用意できたので、次はターゲットラベルy_i\in\mathbb{R}, i\in[1,N]を用意したいと思います。
「簡単な問題」ではターゲットラベルはpredictableであってほしいので、なにかしらの関数f:\mathbb{R}^{N}\rightarrow \mathbb{R}をもちいてy_i\coloneqq f(\bm{x}_i)とすれば良さそうです。
少しトリッキーですが、ここではfとしてパラメータをランダムに振ったNeural networkを用います。
そうすると、学習を行う際に同じ構造を持ったNNを用いればperfectな予測が可能なはずです。
(スケール変換している分はLinearレイヤーで吸収できるはず。)
また、このターゲットラベルを予測する問題はS/N比が0の問題になっています。
今回はtensorflowを用いて実装してみます。

import tensorflow as tf
import random

random.seed(42)
tf.random.set_seed(42)

f = tf.keras.models.Sequential()
f.add(tf.keras.layers.Dense(100, input_dim=10, activation='tanh', use_bias=False))
f.add(tf.keras.layers.Dense(1, activation='linear', use_bias=False))
y = pd.Series(f.predict(df).flatten())
y /= y.std() # normalize
y.plot(kind="hist", bins=100, density=True, title="y")

ここでbiasを0にして、activationをtanhにすることで、yの平均が0になるようにしています。
また、あとでノイズを加える際にターゲットラベルのスケールを固定するために、標準偏差が1になるようにスケール変換しています。
ちなみに非線形な活性化関数を使っているので、ターゲットラベルは正規分布ではなくなっているはずです。[1]

簡単な問題に対する学習

学習を実行してみます。

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(100, input_dim=10, activation='tanh'))
model.add(tf.keras.layers.Dense(1))
model.compile(loss="mean_squared_error", optimizer="adam")
history = model.fit(df, y, validation_split=0.25, epochs=200, batch_size=64)

損失関数の減少を確認

import matplotlib.pyplot as plt
plt.plot(history.history["loss"], label="training")
plt.plot(history.history["val_loss"], label="validation")
plt.ylabel("MSE")
plt.xlabel("epoch")
plt.yscale('log')
plt.show()


縦軸が対数であることに注意。
ハイパーパラメータの設定次第では際限なく損失が減少して0に到達するはずですが、今回はある程度のところで止めています。

学習済みモデルの出力の分布を確認

pred = pd.Series(model.predict(df).flatten())
def plot_dist(y, pred):
    y.plot(kind="hist", bins=100, density=True, label="y")
    pred.plot(kind="hist", bins=100, density=True, label="pred")
    plt.legend()
    plt.show()
plot_dist(y, pred)

ここで学習したのはS/N比が0の簡単な問題なので、ちゃんと学習できることを確認できました。

予測困難な問題

  1. 問題のS/N比を上げることで難易度を上げていくと、バイアスが強くなっていくかを確認する。

予測困難なターゲットラベルの作成

ここではターゲットラベルに正規分布でノイズを乗せることによってS/N比を大きくし、予測が困難な問題を作っていきます。
ノイズレベルll\in[10,20,\dots,100]で定義します。
先程作ったターゲットラベルに標準偏差がl/10の正規分布に従うノイズを乗せてます。
ターゲットラベルの標準偏差はもともと1であったので、例えばl=100だとノイズの方が10倍大きいことになります。

noise_levels = range(1, 101, 10)
noisy_y = {l: y + np.random.normal(size=y.shape, scale=l * 0.1) for l in noise_levels}
noisy_y = {k: v / v.std() for k, v in noisy_y.items()}

ここでもターゲットラベルのスケールを調整して標準偏差が1になるようにしています。

非線形な変換をはさんでいるので一般にはターゲットラベルは正規分布にならないですが、ターゲットラベルの尖度はノイズレベルによらずほぼ0であり、ほぼ(標準)正規分布のようなものになっています。

ノイズレベルl=10
target mean: 0.009, std: 1.000, kurtosis: -0.020

ノイズレベルl=100
target mean: 0.005, std: 1.000, kurtosis: -0.035

予測困難な問題の学習

先程作ったノイズありのラベルに対して、各ノイズレベルごとに学習を行います。

def create_model():
    model = tf.keras.models.Sequential()
    model.add(tf.keras.layers.Dense(100, input_dim=10, activation='tanh'))
    model.add(tf.keras.layers.Dense(1))
    model.compile(loss="mean_squared_error", optimizer="adam")
    return model
for l in noise_levels:
    model = create_model()
    history = model.fit(df, noisy_y[l], validation_split=0.25, epochs=200, batch_size=64, verbose=0)
    pred = pd.Series(model.predict(df, verbose=0).flatten())
    print(f"noise level: {l}")
    print(f"MSE: {np.mean((pred - noisy_y[l])**2):.3f}")
    print(f"target mean: {noisy_y[l].mean():.3f}, std: {noisy_y[l].std():.3f}, kurtosis: {noisy_y[l].kurtosis():.3f}")
    print(f"pred mean: {pred.mean():.3f}, std: {pred.std():.3f}, kurtosis: {pred.kurtosis():.3f}")
    plot_dist(noisy_y[l], pred)

ノイズレベルl=10の結果
noise level: 10
MSE: 0.478
target mean: 0.009, std: 1.000, kurtosis: -0.020
pred mean: 0.010, std: 0.750, kurtosis: -0.080

ノイズレベルl=100の結果
noise level: 100
MSE: 0.955
target mean: 0.005, std: 1.000, kurtosis: -0.035
pred mean: 0.038, std: 0.282, kurtosis: 0.554

実験結果からノイズレベルが大きいほど、

  1. (当然のことですが)MSEが大きい
  2. 予測の分散が小さい
  3. 予測の尖度(kurtosis)が大きい

ということが確認できました。

まとめ

人工データでターゲットラベルのS/N比を変化させることで、S/N比と予測の分布の関係を検証しました。
今回の設定では予測モデルからの出力はノイズレベルが大きくなるほど尖度が大きくなります。
この実験で@yoshiso44さんの

マーケットデータ等ラベルが非常に予測困難な問題、特に0平均で分布している場合は、予測が0であることが非常に強力な正解へのバイアスになる
基本的に特徴量がラベルに対して持つ情報量が少ないほど、より尖度の高い分布になるはず

というstatementを定量的に検証することができました。
ノイズが乗ったターゲットラベルが性質の悪い分布[2]ではなく正規分布っぽい分布であったとしても予測の分布の尖度が大きくなることは一つ面白いポイントです。

もっと一般的な状況であっても回帰問題だと上記のような強いバイアスが発生するかと思われます。
この問題を回避するためになにかしらの分類問題に置き換えたりすることが考えられそうです。

質問・コメント等大歓迎です、是非よろしくお願いします!

参考: 分類問題への置き換えについて

脚注
  1. 正規分布に従う確率変数の線形結合は正規分布になりますが、今回の設定では非線形な関数を通しているので一般的には正規分布になりません。 ↩︎

  2. コーシー分布とかの裾の厚い分布とか ↩︎

Discussion