kaggle1位の解析手法 「メルカリにおける値段推定」(3) 1位の手法

< kaggle1位の解析手法 「メルカリにおける値段推定」2.可視化

前回は過去kaggleコンペでメルカリが「メルカリにおける値段推定」(Mercari Price Suggestion Challenge)のデータ可視化を解説します。今回は1位の手法を解説したいと思います。

 

(3) 1位の解法
___3.1 1位の解法の概要
___3.2 1位の解法の特徴
___3.3 1位の解法のコード

(3) 1位の解法

Paweł and Konstantinのチームが終了後解法を公開しました。1位解法は非常にシンプルなNNで構成されており、多様性の出し方やモデリング方法など必見です。

いろいろ試行錯誤した結果から、最終的な解法は12個のMLPのアンサンブルになりました。最終的なモデルは同じデータで異なるモデルを学習させるよりも、異なるデータを同じモデルに学習させたほうが多様性を表現できることに気づき、モデルはMLPと決めたそうです。計算時間制約を考えてもMLPは都合が良かったとのこと。

1位解法の特徴

・MLPは2種類に大別できます。一つは損失をHuber lossとした回帰モデルで、もう一つが分類モデルを経由した回帰モデルです。分類モデルは、過剰適合が少ないため、それ自体でより良いスコアができました。
・学習/推論の直前で特徴量を全て2値化(非ゼロか否か)する処理を加えてると、スコア改善は見られなかったようです。
・1層目にL2正則化を加えることは効果的だったようです。RELUよりもPRELUが効果的だったそうです。
・最終的に選択した2つのsubmitはtensorflowとMXNetそれぞれで実装したモデルにしたそうです。MXNetモデルは高速でしたが、より多くのメモリを使用し、信頼性が低くになります。

1位解法のコード

import os; os.environ['OMP_NUM_THREADS'] = '1'
from contextlib import contextmanager
from functools import partial
from operator import itemgetter
from multiprocessing.pool import ThreadPool
import time
from typing import List, Dict

import keras as ks
import pandas as pd
import numpy as np
import tensorflow as tf
from sklearn.feature_extraction import DictVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer as Tfidf
from sklearn.pipeline import make_pipeline, make_union, Pipeline
from sklearn.preprocessing import FunctionTransformer, StandardScaler
from sklearn.metrics import mean_squared_log_error
from sklearn.model_selection import KFold

@contextmanager
def timer(name):
t0 = time.time()
yield
print(f'[{name}] done in {time.time() - t0:.0f} s')

def preprocess(df: pd.DataFrame) -> pd.DataFrame:
df['name'] = df['name'].fillna('') + ' ' + df['brand_name'].fillna('')
df['text'] = (df['item_description'].fillna('') + ' ' + df['name'] + ' ' + df['category_name'].fillna(''))
return df[['name', 'text', 'shipping', 'item_condition_id']]

def on_field(f: str, *vec) -> Pipeline:
return make_pipeline(FunctionTransformer(itemgetter(f), validate=False), *vec)

def to_records(df: pd.DataFrame) -> List[Dict]:
return df.to_dict(orient='records')

def fit_predict(xs, y_train) -> np.ndarray:
X_train, X_test = xs
config = tf.ConfigProto(
intra_op_parallelism_threads=1, use_per_session_threads=1, inter_op_parallelism_threads=1)
with tf.Session(graph=tf.Graph(), config=config) as sess, timer('fit_predict'):
ks.backend.set_session(sess)
model_in = ks.Input(shape=(X_train.shape[1],), dtype='float32', sparse=True)
out = ks.layers.Dense(192, activation='relu')(model_in)
out = ks.layers.Dense(64, activation='relu')(out)
out = ks.layers.Dense(64, activation='relu')(out)
out = ks.layers.Dense(1)(out)
model = ks.Model(model_in, out)
model.compile(loss='mean_squared_error', optimizer=ks.optimizers.Adam(lr=3e-3))
for i in range(3):
with timer(f'epoch {i + 1}'):
model.fit(x=X_train, y=y_train, batch_size=2**(11 + i), epochs=1, verbose=0)
return model.predict(X_test)[:, 0]

def main():
vectorizer = make_union(
on_field('name', Tfidf(max_features=100000, token_pattern='\w+')),
on_field('text', Tfidf(max_features=100000, token_pattern='\w+', ngram_range=(1, 2))),
on_field(['shipping', 'item_condition_id'],
FunctionTransformer(to_records, validate=False), DictVectorizer()),
n_jobs=4)
y_scaler = StandardScaler()
with timer('process train'):
train = pd.read_table('../input/train.tsv')
train = train[train['price'] > 0].reset_index(drop=True)
cv = KFold(n_splits=20, shuffle=True, random_state=42)
train_ids, valid_ids = next(cv.split(train))
train, valid = train.iloc[train_ids], train.iloc[valid_ids]
y_train = y_scaler.fit_transform(np.log1p(train['price'].values.reshape(-1, 1)))
X_train = vectorizer.fit_transform(preprocess(train)).astype(np.float32)
print(f'X_train: {X_train.shape} of {X_train.dtype}')
del train
with timer('process valid'):
X_valid = vectorizer.transform(preprocess(valid)).astype(np.float32)
with ThreadPool(processes=4) as pool:
Xb_train, Xb_valid = [x.astype(np.bool).astype(np.float32) for x in [X_train, X_valid]]
xs = [[Xb_train, Xb_valid], [X_train, X_valid]] * 2
y_pred = np.mean(pool.map(partial(fit_predict, y_train=y_train), xs), axis=0)
y_pred = np.expm1(y_scaler.inverse_transform(y_pred.reshape(-1, 1))[:, 0])
print('Valid RMSLE: {:.4f}'.format(np.sqrt(mean_squared_log_error(valid['price'], y_pred))))

if __name__ == '__main__':
main()

TimeLine #Log Message
1.5s1Using TensorFlow backend.
219.1s2X_train: (1407577, 200002) of float32 [process train] done in 217 s
232.2s3[process valid] done in 13 s
1156.9s4[epoch 1] done in 923 s
1160.1s5[epoch 1] done in 926 s
1160.2s6[epoch 1] done in 927 s
1162.2s7[epoch 1] done in 929 s
1651.9s8[epoch 2] done in 495 s
1654.1s9[epoch 2] done in 494 s
1655.1s10[epoch 2] done in 495 s
1656.5s11[epoch 2] done in 494 s
1922.2s12[epoch 3] done in 270 s
1924.1s13[epoch 3] done in 270 s
1926.0s14[epoch 3] done in 270 s
1927.4s15[fit_predict] done in 1694 s
1928.9s16[epoch 3] done in 274 s
1930.2s17[fit_predict] done in 1697 s
1931.9s18[fit_predict] done in 1699 s
1933.8s19[fit_predict] done in 1700 s
1933.9s20Valid RMSLE: 0.3872
1933.9s21
1933.9s23Complete. Exited with code 0.

 

< kaggle1位の解析手法 「メルカリにおける値段推定」2.可視化

 

詳細:

https://www.kaggle.com/c/mercari-price-suggestion-challenge/discussion/50256#latest-315679

https://github.com/pjankiewicz/mercari-solution