推薦の特徴量エンジニアリング
「アルゴリズムの選択も大事だが、推薦の精度を決定的に左右するのは特徴量だ。」
田中VPoEがH&Mコンペの上位解法を分析した資料を見せる。
「Kaggleの推薦コンペでは、アルゴリズムよりも特徴量エンジニアリングで差がつく。ユーザー、アイテム、コンテキストの3軸で良い特徴量を設計しよう。」
特徴量の3つの軸
推薦システムの特徴量は、ユーザー特徴量、アイテム特徴量、コンテキスト特徴量の3軸で設計する。
推薦特徴量の3軸:
ユーザー特徴量: 「誰が」
→ ユーザーの嗜好、行動パターン、属性
アイテム特徴量: 「何を」
→ アイテムの属性、人気度、品質
コンテキスト特徴量: 「いつ・どこで・どうやって」
→ 時間、場所、デバイス、セッション状況
ユーザー特徴量
基本属性
import pandas as pd
import numpy as np
def build_user_features(transactions_df, customers_df):
"""ユーザー特徴量を構築"""
features = customers_df.copy()
# --- 基本属性 ---
# 会員ランク
features['member_rank'] = customers_df['rank']
# 登録からの日数
features['days_since_registration'] = (
pd.Timestamp.now() - pd.to_datetime(customers_df['registered_at'])
).dt.days
return features
行動統計量
def build_user_behavior_features(transactions_df, user_id_col='user_id'):
"""ユーザー行動統計量"""
features = pd.DataFrame()
# --- 購買統計 ---
user_txn = transactions_df.groupby(user_id_col)
features['total_purchases'] = user_txn.size()
features['total_spend'] = user_txn['price'].sum()
features['avg_order_value'] = user_txn['price'].mean()
features['std_order_value'] = user_txn['price'].std().fillna(0)
features['max_order_value'] = user_txn['price'].max()
features['min_order_value'] = user_txn['price'].min()
# --- 購買頻度 ---
features['unique_items'] = user_txn['item_id'].nunique()
features['unique_categories'] = user_txn['category'].nunique()
# --- 時間的特徴量 ---
features['days_since_last_purchase'] = (
pd.Timestamp.now() - user_txn['purchase_date'].max()
).dt.days
features['days_since_first_purchase'] = (
pd.Timestamp.now() - user_txn['purchase_date'].min()
).dt.days
# 購入間隔の平均
def avg_purchase_interval(group):
dates = group['purchase_date'].sort_values()
if len(dates) < 2:
return np.nan
intervals = dates.diff().dt.days.dropna()
return intervals.mean()
features['avg_purchase_interval'] = (
transactions_df.groupby(user_id_col).apply(avg_purchase_interval)
)
# --- リーセンシー・フリークエンシー・マネタリー (RFM) ---
features['recency'] = features['days_since_last_purchase']
features['frequency'] = features['total_purchases']
features['monetary'] = features['total_spend']
return features
嗜好特徴量
def build_user_preference_features(transactions_df, user_id_col='user_id'):
"""ユーザー嗜好特徴量"""
features = pd.DataFrame()
# カテゴリ別購入割合
cat_counts = transactions_df.groupby(
[user_id_col, 'category']
).size().unstack(fill_value=0)
cat_ratios = cat_counts.div(cat_counts.sum(axis=1), axis=0)
cat_ratios.columns = [f'cat_ratio_{c}' for c in cat_ratios.columns]
features = pd.concat([features, cat_ratios], axis=1)
# 好みの価格帯
price_stats = transactions_df.groupby(user_id_col)['price'].agg([
'mean', 'median', 'std'
])
price_stats.columns = ['preferred_price_mean', 'preferred_price_median',
'preferred_price_std']
features = pd.concat([features, price_stats], axis=1)
# 好みのブランド(最頻ブランド)
top_brand = transactions_df.groupby(user_id_col)['brand'].agg(
lambda x: x.mode()[0] if len(x.mode()) > 0 else 'unknown'
)
features['top_brand'] = top_brand
# 嗜好の多様性(エントロピー)
def category_entropy(group):
probs = group['category'].value_counts(normalize=True)
return -(probs * np.log2(probs + 1e-10)).sum()
features['preference_entropy'] = (
transactions_df.groupby(user_id_col).apply(category_entropy)
)
return features
アイテム特徴量
def build_item_features(articles_df, transactions_df, item_id_col='item_id'):
"""アイテム特徴量を構築"""
features = articles_df.copy()
# --- 基本属性 ---
# カテゴリ、ブランド、色、サイズ等はそのまま使用
# --- 人気度指標 ---
item_stats = transactions_df.groupby(item_id_col)
features['total_sales'] = item_stats.size()
features['total_revenue'] = item_stats['price'].sum()
features['unique_buyers'] = item_stats['user_id'].nunique()
# 直近7日間の売上
recent = transactions_df[
transactions_df['purchase_date'] >= pd.Timestamp.now() - pd.Timedelta(days=7)
]
recent_sales = recent.groupby(item_id_col).size()
features['sales_last_7d'] = recent_sales.reindex(features.index, fill_value=0)
# 売上トレンド(直近7日 / 過去30日の日平均比)
last_30d = transactions_df[
transactions_df['purchase_date'] >= pd.Timestamp.now() - pd.Timedelta(days=30)
]
daily_avg_30d = last_30d.groupby(item_id_col).size() / 30
daily_avg_7d = recent_sales / 7
features['sales_trend'] = (daily_avg_7d / (daily_avg_30d + 1e-6)).reindex(
features.index, fill_value=0
)
# --- 品質指標 ---
if 'rating' in transactions_df.columns:
features['avg_rating'] = item_stats['rating'].mean()
features['rating_count'] = item_stats['rating'].count()
# --- 新しさ ---
features['days_since_launch'] = (
pd.Timestamp.now() - pd.to_datetime(features['launch_date'])
).dt.days
return features
コンテキスト特徴量
def build_context_features(timestamp, user_session):
"""コンテキスト特徴量を構築"""
features = {}
# --- 時間特徴量 ---
features['hour'] = timestamp.hour
features['day_of_week'] = timestamp.dayofweek
features['is_weekend'] = 1 if timestamp.dayofweek >= 5 else 0
features['month'] = timestamp.month
# 季節性
month = timestamp.month
if month in [3, 4, 5]:
features['season'] = 'spring'
elif month in [6, 7, 8]:
features['season'] = 'summer'
elif month in [9, 10, 11]:
features['season'] = 'autumn'
else:
features['season'] = 'winter'
# イベント(セール期間など)
features['is_sale_period'] = is_sale_period(timestamp)
# --- セッション特徴量 ---
features['session_length'] = len(user_session['viewed_items'])
features['session_duration_min'] = user_session['duration_seconds'] / 60
features['n_categories_viewed'] = len(set(
item['category'] for item in user_session['viewed_items']
))
# --- デバイス ---
features['device'] = user_session.get('device', 'unknown')
features['is_mobile'] = 1 if features['device'] == 'mobile' else 0
return features
H&Mコンペの特徴量設計
# H&M Personalized Fashion Recommendationsでの実践的な特徴量
h_and_m_features = {
"ユーザー特徴量": {
"基本": ["age", "club_member_status", "fashion_news_frequency"],
"購買統計": [
"total_purchases_all_time",
"total_purchases_last_7d / 14d / 30d / 90d",
"unique_articles_purchased",
"unique_product_groups",
"avg_price", "max_price",
],
"嗜好": [
"favorite_product_group(最頻カテゴリ)",
"favorite_colour(最頻カラー)",
"preference_entropy",
"price_sensitivity(購入価格の分散)",
],
},
"アイテム特徴量": {
"基本": [
"product_type_name", "product_group_name",
"colour_group_name", "perceived_colour_value_name",
"department_name", "section_name", "garment_group_name",
],
"人気度": [
"total_sales", "sales_last_7d",
"sales_trend(直近/過去比)",
"unique_buyers",
],
"テキスト": [
"detail_desc_tfidf(TF-IDFベクトル)",
"detail_desc_embedding(Sentence Embedding)",
],
},
"インタラクション特徴量": {
"ユーザー×アイテム": [
"user_has_purchased_same_product_type",
"user_has_purchased_same_colour",
"user_has_purchased_same_department",
"price_diff_from_user_avg(ユーザー平均価格との差)",
],
},
}
特徴量選択のポイント
推薦での特徴量選択の指針:
1. リーセンシーが最重要
- 直近の行動は遠い過去の行動より予測力が高い
- 時間窓を複数設定(7日/14日/30日/90日)
2. 相対指標が有効
- 「購入数10」より「カテゴリ内での購入割合30%」の方が有用
- ユーザー平均との差分、カテゴリ内順位
3. ターゲットリーケージに注意
- 未来の情報を使わないこと
- 時系列分割の徹底
4. 特徴量の鮮度管理
- 定期更新が必要な特徴量を明確にする
- リアルタイム更新 vs バッチ更新の切り分け
まとめ
| 軸 | 代表的な特徴量 | ポイント |
|---|---|---|
| ユーザー | RFM、嗜好エントロピー、カテゴリ比率 | リーセンシーが最重要 |
| アイテム | 人気度、売上トレンド、テキストEmb | 鮮度と品質指標 |
| コンテキスト | 時間、デバイス、セッション状況 | 今の意図を捉える |
| インタラクション | ユーザー×アイテムの関係性 | 最も予測力が高い |
チェックリスト
- ユーザー特徴量のRFM分析を実装できる
- 嗜好の多様性(エントロピー)を計算できる
- アイテムの人気度と売上トレンドを算出できる
- コンテキスト特徴量(時間・デバイス・セッション)の重要性を理解した
- H&Mコンペでの特徴量設計をイメージできる
次のステップへ
特徴量エンジニアリングを理解したところで、次は推薦結果にビジネスルールを適用するリランキングについて学ぼう。
推定読了時間: 30分