🪿
KaggleのPlaygroundコンペ「Predict Podcast Listening Time」にベースラインモデルを提出しました
概要
- KaggleではPlaygroundコンペが毎月開催されている
- 2025年4月の課題は「ポッドキャストのエピソードの視聴時間を予測する(回帰)」
- Playgroundコンペは「回帰」と「2値分類」のタスクが多い
- この記事のテンプレート少し変えるだけで提出までは簡単にできる(5~10分程度)
- このベースラインモデルの順位は毎回半分ちょい下くらい
- 順位をちゃんと上げたいなら、EDAを行い、適切な前処理と学習アルゴリズムの選定、ハイパーパラメータチューニング、クロスバリデーションなどが必要
URL
ベースラインテンプレート
提出方法
- 読み込むファイル名を変更
- 特徴量リストを変更
- 特徴量リスト(object型のみ)を変更
- 2値分類なら
'objective': 'binary'
に変更 - 評価関数を変更
事前準備
必要ライブラリのインポート
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()
- 以下の
output
をtrain_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()
- 以下の
output
をlabel_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()
Discussion