🪿

KaggleのPlaygroundコンペ「Predict Podcast Listening Time」にベースラインモデルを提出しました

に公開

概要

  • KaggleではPlaygroundコンペが毎月開催されている
  • 2025年4月の課題は「ポッドキャストのエピソードの視聴時間を予測する(回帰)」
    • Playgroundコンペは「回帰」と「2値分類」のタスクが多い
  • この記事のテンプレート少し変えるだけで提出までは簡単にできる(5~10分程度)
    • このベースラインモデルの順位は毎回半分ちょい下くらい
  • 順位をちゃんと上げたいなら、EDAを行い、適切な前処理と学習アルゴリズムの選定、ハイパーパラメータチューニング、クロスバリデーションなどが必要

URL

ベースラインテンプレート

提出方法

  1. 読み込むファイル名を変更
  2. 特徴量リストを変更
  3. 特徴量リスト(object型のみ)を変更
  4. 2値分類なら'objective': 'binary'に変更
  5. 評価関数を変更

事前準備

必要ライブラリのインポート

python
import pandas as pd
import numpy as np

データセットの読み込み

  • 訓練データとテストデータをpd.concatで結合しておく(前処理を一括でするため)
python
train = pd.read_csv('/kaggle/input/playground-series-s5e4/train.csv')
test = pd.read_csv('/kaggle/input/playground-series-s5e4/test.csv')
df = pd.concat([train, test], ignore_index = True)

データ内容の確認

python
df.head()


データ数やデータ型の確認

python
df.info()
output
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 12 columns):
 #   Column                       Non-Null Count    Dtype  
---  ------                       --------------    -----  
 0   id                           1000000 non-null  int64  
 1   Podcast_Name                 1000000 non-null  object 
 2   Episode_Title                1000000 non-null  object 
 3   Episode_Length_minutes       884171 non-null   float64
 4   Genre                        1000000 non-null  object 
 5   Host_Popularity_percentage   1000000 non-null  float64
 6   Publication_Day              1000000 non-null  object 
 7   Publication_Time             1000000 non-null  object 
 8   Guest_Popularity_percentage  805138 non-null   float64
 9   Number_of_Ads                999999 non-null   float64
 10  Episode_Sentiment            1000000 non-null  object 
 11  Listening_Time_minutes       750000 non-null   float64
dtypes: float64(5), int64(1), object(6)
memory usage: 91.6+ MB

特徴量リストを出力

python
df.columns.tolist()
  • 以下のoutputtrain_featuresでコピペする
output
['id',
 'Podcast_Name',
 'Episode_Title',
 'Episode_Length_minutes',
 'Genre',
 'Host_Popularity_percentage',
 'Publication_Day',
 'Publication_Time',
 'Guest_Popularity_percentage',
 'Number_of_Ads',
 'Episode_Sentiment',
 'Listening_Time_minutes']

特徴量リスト(object型のみ)を出力

python
df.select_dtypes(include='object').columns.tolist()
  • 以下のoutputlabel_encode_columnsでコピペする
output
['Podcast_Name',
 'Episode_Title',
 'Genre',
 'Publication_Day',
 'Publication_Time',
 'Episode_Sentiment']

前処理

  • 学習アルゴリズムにLightGBMを指定するので欠損値補完はしない
  • LightGBMはobject型をそのまま入力できないので、ラベルエンコーディングだけはしておく

特徴量の選定

  • ベースラインモデルなので、とりあえず全部の特徴量をつっこむ
python
# 全特徴量のリストをコピペする
train_features = [
    'id',
    'Podcast_Name',
    'Episode_Title',
    'Episode_Length_minutes',
    'Genre',
    'Host_Popularity_percentage',
    'Publication_Day',
    'Publication_Time',
    'Guest_Popularity_percentage',
    'Number_of_Ads',
    'Episode_Sentiment',
    'Listening_Time_minutes'
]

df_features = df[train_features]

ラベルエンコーディング

python
from sklearn.preprocessing import LabelEncoder

# 特徴量リスト(object型のみ)をコピペする
label_encode_columns = [
    'Podcast_Name',
    'Episode_Title',
    'Genre',
    'Publication_Day',
    'Publication_Time',
    'Episode_Sentiment'
]

df_encoded = df_features

# ラベルエンコーディング
label_encoders = {}
for col in label_encode_columns:
    le = LabelEncoder()
    df_encoded[col] = le.fit_transform(df_encoded[col])
    label_encoders[col] = le

print(df_encoded.info())
output
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 12 columns):
 #   Column                       Non-Null Count    Dtype  
---  ------                       --------------    -----  
 0   id                           1000000 non-null  int64  
 1   Podcast_Name                 1000000 non-null  int64  
 2   Episode_Title                1000000 non-null  int64  
 3   Episode_Length_minutes       884171 non-null   float64
 4   Genre                        1000000 non-null  int64  
 5   Host_Popularity_percentage   1000000 non-null  float64
 6   Publication_Day              1000000 non-null  int64  
 7   Publication_Time             1000000 non-null  int64  
 8   Guest_Popularity_percentage  805138 non-null   float64
 9   Number_of_Ads                999999 non-null   float64
 10  Episode_Sentiment            1000000 non-null  int64  
 11  Listening_Time_minutes       750000 non-null   float64
dtypes: float64(5), int64(7)
memory usage: 91.6 MB
None

学習

  • 学習アルゴリズム:lightgbm
  • データ分割:hold-out
  • 評価関数:rmse
python
import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# target_labelに目的変数を指定
target_label = 'Listening_Time_minutes'
train_set = df_encoded[df_encoded[target_label].notnull()]
test_set = df_encoded[df_encoded[target_label].isnull()]
del test_set[target_label]

X = train_set.drop(columns=[target_label])
y = train_set[target_label]

# データ分割(hold-out)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    
# LightGBM用データセット作成
train_data = lgb.Dataset(X_train, label=y_train)
test_data = lgb.Dataset(X_test, label=y_test, reference=train_data)

# ハイパーパラメータ設定
params = {
    "objective": "regression", # 回帰:regression, 2値分類:binary
    "metric": "rmse", # 評価関数:rmse
    "boosting_type": "gbdt",
    "learning_rate": 0.1,
    "num_leaves": 31,
    "max_depth": -1,
    'verbose': -1,
    "random_state": 42
}

# モデル学習
model = lgb.train(params, train_data, valid_sets=[test_data], 
                  callbacks=[lgb.early_stopping(stopping_rounds=10, verbose=True)])

# 予測
y_pred = model.predict(X_test, num_iteration=model.best_iteration)

# RMSEの計算
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"RMSE: {rmse:.4f}")
output
Training until validation scores don't improve for 10 rounds
Did not meet early stopping. Best iteration is:
[99]	valid_0's rmse: 13.0543
RMSE: 13.0543

推論

  • テストデータを用いて推論
python
y_test_pred = model.predict(test_set, num_iteration=model.best_iteration)

提出

  • 提出フォーマットに合わせてCSVを出力
python
submission = pd.DataFrame({
        "id": test["id"],
        target_label: y_test_pred
    })

submission.to_csv('output.csv', index=False)
python
submission.head()

結果

2025/05/01追記(最終結果)

Public

Private

Discussion