🟰

log変換で見えてきた落とし穴──Targetの12%が“変動なし”だった話

に公開

はじめに

log変換は、株価のような桁の大きな時系列データを扱う上で、非常に有効なスケーリング手法です。
本研究でも、XGBoostモデルにおいて価格スケールの歪みを是正するために、log変換を導入しました。

しかし、logを使って予測Target(log収益率)を定義した結果、思わぬ落とし穴に遭遇したのです。


Target定義:log差による収益率

本研究では、以下のようにして翌営業日の収益率(logベース)を予測ターゲットとしました。

df["LogClose"] = np.log(df["Close"])
df["Target"] = df["LogClose"].shift(-1) - df["LogClose"]

logの性質上、これは「翌日と今日の価格比率のlog」つまりlog収益率(log return)を意味します。
この定義は金融の世界でも理論的に一般的であり、モデルが価格水準に引っ張られないようにするためにも有効です。


検証結果:0に張り付いたTargetの存在

しかし、予測の結果を観察していると、以下のように「actualが0.000000」のデータが非常に多く含まれていることに気づきました。

なぜそんなに“変動なし”なのか?

これは以下のような仮説に基づき、調査しました:

  1. 株価が前日とまったく同じであった
  2. log差が非常に小さく、浮動小数点的に0と扱われた
  3. スケール変換により、実質的に“平坦なデータ”が増えた

実際に調べてみた

以下のコードを使用して、Targetがほぼ0(=変動がない)と判定された行の割合を集計しました。

def calc_flat_target_ratio(self, threshold: float = 1e-4) -> float:
    total_rows = 0
    flat_rows = 0

    for f in tqdm(self.__all_files, desc="Reransforming files"):
        df = self.load(f)

        if "Target" not in df.columns:
            continue
        df = df.dropna(subset=["Target"])
        total_rows += len(df)
        flat_rows += (df["Target"].abs() <= threshold).sum()

    return flat_rows / total_rows if total_rows else 0.0

結果:


これは問題なのか?

答えは Yes and No

  • YES:この“動かないデータ”が多すぎると、モデルは「変化がないことを学習」してしまう
  • NO:log差が0なのは理論的には正しい。変化がなければ収益率も0で当然。

結論と今後の対応

  • log変換は正しいアプローチだが、それによって浮かび上がる実務的なデータ構造の偏りには注意が必要。
  • 今後は、「変化のないデータの扱い方」にも設計方針を持つ必要がある。

まとめると…

発見 log変換でTargetが0になる行が全体の約12%存在した
背景 株価変動がない or log差が極小でゼロ扱い
対策 モデル学習時に除外
学び log変換で正規性・スケール統一は実現できるが、「ゼロの山」は別問題として扱うべき

▶ 次回予告

次回は、実際にこのflatなTargetを除外した上でモデルを再学習し、SHAP可視化にどのような違いが出たのかを分析します。


Discussion