忍者ブログ
統計、機械学習、AIを学んでいきたいと思います。 お役に立てば幸いです。

【Kaggle挑戦記】TPS Feb 2022:1.1万行の「重複データの罠」を破り、本番LBスコアを更新!

前回の検証では、手動での特徴量生成を行わずに素のデータをLightGBMへ投入した結果、手元CVが0.99553(約99.5%)という異次元の高精度を記録した一方で、Kaggle本番のLeaderboard(LB)では0.93937まで急降下するという大きなスコアギャップに直面しました。

今回は、この乖離の原因である「データ重複によるリーク(過学習)」を解消し、正しく検証環境を再構築してLBスコアの更新に挑みました。

1. なぜ手元スコアと本番スコアが大きくズレたのか?

分析を進めたところ、全20万行ある train.csv の中に、全く同じ特徴量(286列の数値)を持つ「重複行」が約1.1万行(全体の約5%以上)も存在していることが判明しました。

  • 丸暗記による過学習: 通常の StratifiedKFold で分割すると、全く同じデータが「学習用」と「検証用」の両方に分散して入り込んでしまいます。結果としてモデルがデータを暗記してしまい、手元のCVだけが異常に高く(0.99553)評価されていました。
  • 本番データでの失速: Kaggleのテストデータには未知のデータが含まれるため、暗記に頼ったモデルは本番環境(LB 0.93937)で威力を発揮できませんでした。

2. 対策:重複行の一元化と sample_weight の導入

単に重複行を削除(drop_duplicates)するだけでは、「そのデータがどれだけ高頻度で出現するか」という重要な確率情報(重み)が失われてしまいます。

そこで、groupby().size() を活用して重複行を1行に集約しつつ、出現回数を sample_weight(サンプルの重み)として LightGBM に与えて学習させるアプローチ(sample2.py)へコードを全修正しました。

import pandas as pd
import numpy as np
from sklearn.model_selection import StratifiedKFold
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score
import lightgbm as lgb
import warnings

warnings.filterwarnings("ignore")

# 1. データの読み込みと重複処理
print("Loading data...")
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')

X_raw = train.drop(columns=['row_id'])
X_test = test.drop(columns=['row_id'])

# 特徴量とターゲットが全く同じ行を集計して重み(sample_weight)を作成
features = [col for col in X_raw.columns if col != 'target']
train_dedup = X_raw.groupby(features + ['target']).size().reset_index(name='sample_weight')

X = train_dedup[features]
y_raw = train_dedup['target']
sample_weight = train_dedup['sample_weight']

print(f"元のデータ数: {len(train)} 行")
print(f"重複除去後のデータ数: {len(X)} 行")  # 123,993 行へ圧縮

# ターゲットの数値変換
le = LabelEncoder()
y = le.fit_transform(y_raw)

# 2. 多クラス分類パラメータ
params = {
    'objective': 'multiclass',
    'num_class': 10,
    'metric': 'multi_logloss',
    'verbosity': -1,
    'boosting_type': 'gbdt',
    'learning_rate': 0.1,
    'random_state': 42
}

# 3. Stratified K-Fold(sample_weight付き)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
oof_preds = np.zeros((len(X), 10))
test_preds = np.zeros((len(test), 10))

for fold, (train_idx, val_idx) in enumerate(skf.split(X, y)):
    X_train, y_train, w_train = X.iloc[train_idx], y[train_idx], sample_weight.iloc[train_idx]
    X_val, y_val, w_val = X.iloc[val_idx], y[val_idx], sample_weight.iloc[val_idx]
    
    model = lgb.LGBMClassifier(**params, n_estimators=1000)
    
    # fit時に sample_weight を適用
    model.fit(
        X_train, y_train,
        sample_weight=w_train,
        eval_set=[(X_val, y_val)],
        eval_sample_weight=[w_val],
        callbacks=[lgb.early_stopping(stopping_rounds=50, verbose=False)]
    )
    
    oof_preds[val_idx] = model.predict_proba(X_val)
    test_preds += model.predict_proba(X_test) / skf.n_splits

# 4. 正しい重み付き評価(Accuracy)の算出
oof_classes = np.argmax(oof_preds, axis=1)
cv_accuracy = accuracy_score(y, oof_classes, sample_weight=sample_weight)
print(f"修正後の手元交差検証スコア(Accuracy): {cv_accuracy:.5f}")

# 5. 提出用ファイルの作成
test_classes_num = np.argmax(test_preds, axis=1)
test_classes_str = le.inverse_transform(test_classes_num)

submission = pd.DataFrame({
    'row_id': test['row_id'],
    'target': test_classes_str
})
submission.to_csv('submission_tps_feb2022_dedup_lgb.csv', index=False)

3. 実行結果とスコア比較

重複一元化を行ったモデルの実行ログおよびKaggleへ再提出(Late Submit)した結果の推移です。

Loading data...
Processing duplicates...
元のデータ数: 200000 行
重複除去後のデータ数: 123993 行

Starting Cross Validation with sample_weight...
 Fold 1 finished.
 Fold 2 finished.
 Fold 3 finished.
 Fold 4 finished.
 Fold 5 finished.

========================================
修正後の手元交差検証スコア(Accuracy): 0.95766
========================================

【実験結果の比較表】

手法手元CV (Accuracy)Kaggle Public LB評価
1. 生データ (無加工) 0.99553 0.94192 (0.93937) 重複データによるリークでCVが歪んでいた
2. 重複除去 + sample_weight 0.95766 0.94317 (Private: 0.94024) CVが健全化し、本番スコアも着実に向上!

4. 考察とエンジニアとしての気づき

手元のCVスコアは前回の「丸暗記による異常値(0.99553)」から0.95766へと下がり、現実的で信頼できる検証値へと正常化されました。そして何より、Kaggle本番(Public LB)のスコアが 0.94192 → 0.94317 へとしっかり向上したことが大きな成果です。

  • 検証環境(CV)の重要性: 「CVスコアが高ければ良い」わけではなく、適切な前処理によって本番環境(テストデータ)の挙動を正しく反映する検証環境を作ることが最優先であると再認識しました。
  • 重み付けの有効性: 単純な drop_duplicates でデータ量を減らすだけでなく、出現回数を sample_weight としてモデルに伝えることで、情報量を維持したまま過学習を防止できました。

手元検証の基盤が整ったため、いよいよ次回はこのコンペの「真のキーパーツ」であるドメイン知識(GCD:最大公約数を用いた実験スケールの復元)を取り入れ、本番スコアの上位帯(0.98以上)への到達を目指します。


次回は、GCD(最大公約数)特徴量の計算・追加によるモデルの飛躍的な精度向上に挑戦します。

PR

【TensorFlow】2つのスカラー(定数)で四則演算を行う基本コード

TensorFlowを扱い始めるとき、まず押さえておきたいのが最も基本的なデータ単位である「スカラー(単一の数値)」の作成と、それらを使った基本的な計算方法です。今回は、2つのスカラー定数を作成し、加算・減算・乗算・除算を行う最小のサンプルコードを整理します。

1. 【 概要 】

TensorFlowにおける数値やデータは、すべて「Tensor(テンソル)」というオブジェクトとして扱われます。

0次元のテンソル(単一の数値)を「スカラー」と呼び、tf.constant() 関数を使うことで簡単に定義できます。Python標準の数値と同じ感覚で、TensorFlowの関数を使って四則演算を実行できます。


2. 【 基本手順(2つのステップ) 】

(1) tf.constant() によるスカラー定数の定義
a = tf.constant(10) のように、任意の数値を指定してテンソル(スカラー)を作成する。
(2) TensorFlowの演算関数(または標準演算子)の実行
tf.add()+ 演算子などを使用して、定義したスカラー同士の計算を行う。

3. 整理:スカラー演算の「基本コード」と出力の読み方

実際のコードと、計算結果がどのように出力されるかのポイントを詳しく見ていきましょう。

【 演算方法と結果のポイント 】

基本演算子の使用(+, -, *, /)
通常のPythonコードと同様に a + ba * b と記述するだけで、TensorFlow内部で自動的に演算処理が行われます。

専用関数の使用(tf.add, tf.multiply等)
tf.add(a, b)tf.multiply(a, b) といったTensorFlow独自の関数を使うことも可能です。可読性や処理明示のために使い分けられます。

出力値(tf.Tensor)の読み方
計算結果を出力すると tf.Tensor(15, shape=(), dtype=int32) のような形式で表示されます。shape=() は0次元(スカラー)であることを意味し、.numpy() を付けることで数値だけを取り出せます。

4. 関連して押さえたい「スカラー演算の主な関数」

TensorFlowで用意されている代表的な四則演算関数の一覧です。

加算(足し算)tf.add(a, b) (演算子: a + b
減算(引き算)tf.subtract(a, b) (演算子: a - b
乗算(掛け算)tf.multiply(a, b) (演算子: a * b
除算(割り算)tf.divide(a, b) (演算子: a / b


5. 補足:Pythonでの実装コード例

Google Colab等でそのまま実行できるスカラー演算のサンプルコードと実際の実行結果です。

# コード実行例
import tensorflow as tf

# 1. 2つのスカラー定数を定義
a = tf.constant(10)
b = tf.constant(5)

# 2. 四則演算の実行
add_result = tf.add(a, b) # 加算 (10 + 5)
sub_result = tf.subtract(a, b) # 減算 (10 - 5)
mul_result = tf.multiply(a, b) # 乗算 (10 * 5)
div_result = tf.divide(a, b) # 除算 (10 / 5)

# 3. 結果の表示
print("加算(Tensorオブジェクト):", add_result)
print("加算(数値のみ抽出):", add_result.numpy())
print("減算:", sub_result.numpy())
print("乗算:", mul_result.numpy())
print("除算:", div_result.numpy())

# 出力結果
# 加算(Tensorオブジェクト): tf.Tensor(15, shape=(), dtype=int32)
# 加算(数値のみ抽出): 15
# 減算: 5
# 乗算: 50
# 除算: 2.0

6. まとめ

TensorFlowにおける数値操作の最小単位である「スカラー」は、tf.constant() で生成し、直感的な演算子や専用関数で簡単に計算できます。.numpy() メソッドを使えば通常のPython数値として結果を取り出せる点もポイントです。まずはこの基本演算からTensorFlowの記法に慣れていきましょう!

【TensorFlow】Google Colabで始める環境構築手順と動作確認

「TensorFlowでAIや機械学習を始めてみたいけれど、PCの環境構築が難しそう…」と悩んでいませんか?今回は、ブラウザとGoogleアカウントさえあればインストール不要・無料でTensorFlowを動かせる「Google Colaboratory(通称 Colab)」を使った環境構築と動作確認手順を整理します。

1. 【 概要 】

Google Colaboratory(Google Colab)とは、Googleが提供するブラウザ上でPythonのコードを実行できる開発環境です。

面倒なライブラリのインストールや環境構築が一切不要で、あらかじめTensorFlowやNumPyといった主要なAIライブラリが組み込まれています。さらに、AIの計算を高速化する「GPU」環境も無料で利用できるため、初心者の学習に最適です。


2. 【 基本手順(2つのステップ) 】

(1) ノートブックの作成とGPU設定
Google Colabにアクセスして新しいノートブックを作成し、高速処理用の「GPU環境」を有効化する。
(2) テストコードによる動作確認
TensorFlowのバージョン確認およびGPUの認識確認を行うコードを実行する。

3. 整理:環境構築の「ステップ順」徹底解説

Google Colab上でTensorFlowを動かすための具体的な手順を詳しく見ていきましょう。

【 各ステップの具体的な操作 】

ステップ1:Colabにアクセスしノートブックを作成する
ブラウザで Google Colab にアクセスし、Googleアカウントでログインします。画面右下の「ノートブックを新規作成」をクリックして新しい作業画面を開きます。

ステップ2:ハードウェアアクセラレータ(GPU)を設定する
上部メニューの「ランタイム」>「ランタイムのタイプを変更」を開き、ハードウェアアクセラレータで「T4 GPU」(または「GPU」)を選択して保存します。(※簡単な文法確認のみであればCPUのままでも構いません)

ステップ3:動作確認コードを実行する
コードセルに動作確認用のPythonコードを入力し、再生ボタン(▶)または Shift + Enter キーを押して実行します。

4. 関連して押さえたい「実行時の注意点と仕様」

Google Colabでコードを実行した際、「すぐに実行されずに待たされる」ことがあります。これらはColabの仕様によるものです。

初回接続(初期化)の待ち時間:ノートブックを開いて一番最初にコードを実行する際は、裏側で仮想サーバーを起動する処理(割り当て・初期化)が行われるため、数秒〜数十秒の待ち時間が発生します。
GPUのキュー待ち(順番待ち):無料版のGPUサーバーはユーザー間で共有されているため、混雑する時間帯は「割り当て待ち」のキューに入ることがあります。
セッションの保持:一定時間操作を行わないと接続が切断され、実行中のメモリ内容が初期化されます。(作成したコード自体はGoogleドライブに自動保存されます)


5. 補足:動作確認用Pythonコード例

以下のコードをColabに貼り付けて実行することで、TensorFlowが正常に動作しているか、またGPUが認識されているかを確認できます。

# コード実行例
import tensorflow as tf

# 1. TensorFlowのバージョン確認
print("TensorFlow バージョン:", tf.__version__)

# 2. GPUの認識確認
gpu_devices = tf.config.list_physical_devices('GPU')
if gpu_devices:
    print("使用可能なGPU:", gpu_devices[0].name)
else:
    print("GPUは認識されていません(CPUモードで動作中)")

# 出力イメージ
# TensorFlow バージョン: 2.20.0
# 使用可能なGPU: /physical_device:GPU:0

6. まとめ

Google Colabを使えば、難解なローカル環境の構築を行うことなく、**「ブラウザを開いてすぐにTensorFlowを動かす」** ことが可能です。出力結果にバージョン番号と「/physical_device:GPU:0」が表示されれば、環境構築は無事成功です。まずは手軽なColab環境からディープラーニングの学習をスタートしてみましょう!



【DS検定対策】深層学習の最大の壁!「勾配消失問題」の原因と対策

ディープラーニング(深層学習)において、ネットワークの層を深くすればするほど学習が難しくなる大きな原因の一つが「勾配消失問題(Vanishing Gradient Problem)」です。そのメカニズムと克服のための対策を整理しましょう!

1. 【 問題 】

ニューラルネットワークの層を深くした際、誤差逆伝播法において出力層から入力層に向かって遡るにつれて勾配(微分値)が小さくなりゼロに近づくことで、入力層に近い重みが更新されず学習が進まなくなる現象を何と呼ぶでしょうか?

① 勾配消失問題(Vanishing Gradient Problem)
② 勾配爆発問題(Exploding Gradient Problem)
③ 欠損値問題(Missing Value Problem)
④ 多重共線性(Multicollinearity)


2. 【 解答 】

正解: ① 勾配消失問題(Vanishing Gradient Problem)

3. 整理:なぜ勾配が消失してしまうのか?

主な原因は、従来使われていた活性化関数「シグモイド関数」の微分特性と、連鎖律(チェーンルール)にあります。

要素仕組みと影響
シグモイド関数の微分値 シグモイド関数の微分係数は最大でも 0.25(1未満)です。
連鎖律による掛け算 誤差逆伝播では、層を1つ遡るごとに微分値を掛け合わせます。
「1未満の数(0.25以下)」を何十層も掛け合わせると、数値は急速にゼロに近づきます(消失)
結果(学習の停止) 入力層に近いパラメータの勾配がほぼ 0 になるため、重みが更新されなくなります。

4. 勾配消失を解決した「3つの画期的アプローチ」

AIの歴史の中で、勾配消失問題を克服するために様々な技術が開発されました。

① 活性化関数の見直し(ReLU関数の採用):
・入力が正のとき微分値が常に「1」となる ReLU(Rectified Linear Unit) を使うことで、何層遡っても勾配が小さくなりません。

② 適切な重みの初期化方法:
・Xavier(ザビエル)の初期値(シグモイド向け)や He(ハー)の初期値(ReLU向け) を使い、データの広がりを適正化します。

③ ネットワーク構造の工夫(残差構造等):
・CNNでは ResNet(Skip Connection)、RNNでは LSTM / GRU を導入し、勾配をダイレクトに過去(手前の層)へ流すバイパス構造を作りました。

5. DS検定形式:実戦4択クイズ

問:ニューラルネットワークの学習における「勾配消失問題」とその対策に関する記述として、最も適切なものはどれか。

① 勾配消失問題は、活性化関数にReLUを採用することで、正の入力領域における微分値が0になるため発生しやすくなる。
② 重みの初期値を極めて大きな値に設定することで、誤差逆伝播時の勾配消失を完全に防止することができる。
③ シグモイド関数を深層ニューラルネットワークの各層に使用すると、微分値の最大値が1未満であるため勾配消失が発生しやすい。
④ 勾配消失問題が起きると、出力層に近い層ほどパラメータの更新量が小さくなり、入力層に近い層ほど急速に学習が進む。

【 正解: ③ 】

解説: 勾配消失の原因と対策の正誤を問う重要問題です。
③が正解です。シグモイド関数の微分値は最大0.25のため、層を重ねると勾配が消失します。
①ReLUは正の領域で微分値が「1」となるため、勾配消失を防ぐ効果があります。
②重みの初期値を大きくしすぎると、逆に「勾配爆発(Exploding Gradient)」を起こします。
④勾配逆伝播は出力層から入力層へ遡るため、影響を受ける(更新が止まる)のは「入力層に近い層」です。


6. まとめ

DS検定や資格試験で「誤差逆伝播で勾配がゼロになる」「シグモイド関数の多層利用」「入力層側の重みが更新されない」が出たら、正解は「勾配消失問題」です! 対策としての「ReLU関数」「Heの初期値」「ResNet(Skip Connection)」もセットで完璧に覚えておきましょう!

【DS検定対策】大量の文書からテーマを発見!「トピックモデル(LDA)」の仕組み

大量のニュース記事やレビュー文を分類したい時、1つずつ手作業で分類するのは不可能です。文章の集合から潜在的なテーマ(トピック)を自動で抽出・分類する手法が「トピックモデル(Topic Model)」です。その仕組みと代表例を整理しましょう!

1. 【 問題 】

自然言語処理やテキストマイニングにおいて、文章集合(コーパス)の中から潜在的なテーマや概念(トピック)を統計的に発見し、各文書がどのトピックにどれくらいの割合で関連しているかを分類・抽出する手法を何と呼ぶでしょうか?

① トピックモデル(Topic Model)
② 単語埋め込みモデル(Word Embedding)
③ 分散表現モデル(Distributed Representation)
④ 形態素解析(Morphological Analysis)


2. 【 解答 】

正解: ① トピックモデル(Topic Model)

3. 整理:トピックモデルの基本的な考え方

トピックモデルでは、「1つの文書は複数のトピックが一定の割合で混ざり合ってできている」と仮定します。

構成要素内容・イメージ
トピック(潜在的テーマ) 関連する単語の確率分布(例:「スポーツ」トピック = 球団・選手・試合・勝利 などの単語が出やすい)。
文書の分解・分類 ある記事を「スポーツ 70% + 経済 30%」のように確率的な比率(割合)で表現・分類する。

4. 超重要!代表的手法「LDA(潜在ディリクレ配分法)」

DS検定や資格試験で「トピックモデル」とセットで必ず問われるのが「LDA(Latent Dirichlet Allocation)」です。

LDA(Latent Dirichlet Allocation):
トピックモデルの中で最も代表的な「教師なし学習」の確率モデル。
事前分類(ラベル)のない大量のテキストから、文書ごとのトピック割合と、トピックごとの単語出現分布を同時に自動推定します。

主な用途:
ニュース記事の自動タグ付け、顧客レビューの不満テーマ抽出、類似文書の推薦システムなど。

5. DS検定形式:実戦4択クイズ

問:テキストデータの分析手法に関する記述として、最も適切なものはどれか。

① LDA(Latent Dirichlet Allocation)は、各文書があらかじめ指定された単一のカテゴリに属することを前提とした教師あり分類アルゴリズムである。
② トピックモデルは、文書集合の中に潜む潜在的なテーマ(トピック)と単語の確率分布を統計的に推定する「教師なし学習」の手法である。
③ トピックモデルを実行する前に、文章中の文字n-gramや形態素解析を行うことは原理的に不可能である。
④ TF-IDFは、文書ごとの潜在的なトピック割合を確率分布として直接出力する代表的なトピックモデルである。

【 正解: ② 】

解説: トピックモデルの定義と特徴を問う標準問題です。
②が正解です。教師ラベルなしで文章から潜在的なトピック(テーマ)を発見します。
①LDAは単一カテゴリ固定ではなく、複数のトピック比率を持つ「教師なし学習」モデルです。
③形態素解析等で単語に分かち書きしたデータを入力として使うのが一般的です。
④TF-IDFは単語の重要度(出現頻度とレア度)を算出する手法であり、トピックモデル(確率分布の推定)とは異なります。


6. まとめ

DS検定や資格試験で「文章から潜在的なトピック(テーマ)を発見」「教師なし学習」「LDA(潜在ディリクレ配分法)」といったキーワードが出たら、正解は「トピックモデル」です! 「文書は複数のトピックの混ぜ合わせ(確率分布)で表現できる」という考え方をしっかり押さえておきましょう!