kddcup2012で年齢性別予測!
hivemallを使ってkddcup2012のデータセットで年齢と性別を予測するよー。
dataset
training 1億5000万レコード
test 2000万レコード
本当はCTR予測のデータセットのため、
trainingにはimpressionとclickという項目があるがtestには入っていない。
その他共通の項目
displayurl
adid
advetiserid
queryid
keywordid
titleid
descriptionid
userid
depth : 1つのsessionで表示された広告の数(1~3)
position : その時の広告の位置(1~3)
なのでsession毎の広告毎に1レコードか??(英語..orz)
別途usersテーブルにuseridとage,genderが入っている。
Step1) データの前処理
useridや年齢性別が欠損しているデータも存在する。
CTR予測の場合は補完したりしてもよいが、今回は目的変数となるものなので欠損しているデータは省く。
また、データ量が多すぎてこのままだと一回の試行に時間がかかってしまうので、1割程度をランダムサンプリングして速い回転で試行錯誤したいと思う。
SELECT
*
FROM(
SELECT
*,
rand() as rnd
FROM(
SELECT
t.*,
u.age,
u.gender
FROM
training as t
LEFT OUTER JOIN users as u ON (t.userid=u.userid)
WHERE
t.userid <> 0
AND u.age is not NULL
AND u.gender is not NULL
AND u.gender <> 0
)
)
WHERE
rnd < 0.1 --random sampling
test用のデータも同様に作成。
次にそれらを特徴ベクトル化。
とりあえず1回目はdisplayurlだけ長いのでhash化、depthとpositionは量的変数と考えてやってみる。
useridを特徴で使ってしまうとリークしてoverfitしてしまうので使わないよう注意する。
SELECT
-- ageとgenderの組み合わせた目的変数labelを作った。
-- 単純に concat(age,"-",gender) as label でもよいか。
case
when age = 1 AND gender =1 then 0
when age = 1 AND gender =2 then 1
when age = 2 AND gender =1 then 2
when age = 2 AND gender =2 then 3
when age = 3 AND gender =1 then 4
when age = 3 AND gender =2 then 5
when age = 4 AND gender =1 then 6
when age = 4 AND gender =2 then 7
when age = 5 AND gender =1 then 8
when age = 5 AND gender =2 then 9
when age = 6 AND gender =1 then 10
when age = 6 AND gender =2 then 11
else 12
end as label,
add_bias(array_concat(
categorical_features(
array('displayurl','adid','advetiserid','queryid','keywordid','titleid','descriptionid'),
mhash(displayurl),adid,advetiserid,queryid,keywordid,titleid,descriptionid
),
quantitative_features(
array('depth','position'),
depth2,position2
)
)) as features
FROM(
SELECT
*,
-- depth,posistionを量的変数と考えてrescale
rescale(depth,1,3) as depth2,
rescale(position,1,3) as position2,
FROM
training2
) t
testデータも同様に。predict後の評価用にrowidをつけるのを忘れずに。
Step2) 学習
train_multiclass_scw2
を使用。
SCW
,AROW
,SCW2
と試したが、
SCW
は途中計算でNaNが出てしまいうまく予測できず。
AROW
は偏った結果となってしまった。
学習させる時にCLUSTER BY rand(1)
で入力データのシャッフルを忘れずに。
SELECT
label,
feature,
argmin_kld(weight,covar) as weight
FROM (
SELECT
train_multiclass_scw2(features,label) as (label,feature,weight,covar)
FROM (
SELECT features,label
FROM training_featured
CLUSTER BY rand(1)
) t1
) t2
GROUP BY
label,feature
Step3) 予測
hivemallでは、LATERAL VIEW explode(features)
で予測用データの特徴を展開。
modelとJOINして計算を行うことでpredictしている。
SELECT
rowid,
m.col0 as score,
m.col1 as label
FROM(
SELECT
rowid,
maxrow(score, label) as m
FROM(
SELECT
t.rowid,
m.label,
sum(m.weight * t.value) as score
FROM(
SELECT
rowid,
label,
extract_feature(feature) as feature,
extract_weight(feature) as value
FROM
testing_featured LATERAL VIEW explode(features) t as feature
) t
LEFT OUTER JOIN model m ON (t.feature = m.feature)
GROUP BY
t.rowid, m.label
) t1
GROUP BY rowid
) t2
Step4) 評価
年齢6パターン × 性別2パターンの合計12パターンで予測したものをaccuracy(予測データの正答率)で評価。
ちなみにRandom Predictionの結果は約9.33%だった。
SELECT
COUNT(1) / 2187606.0 --testデータのレコード数
FROM(
SELECT
p.rowid,
p.label,
t.label as actual
FROM
predicted as p
LEFT OUTER JOIN testing_featured as t ON (p.rowid=t.rowid)
)
WHERE
actual = label
結果 | 16.84% |
---|
試行錯誤
・impressionとclicksを特徴として使用する。
testデータにはimpressionとclicksという項目がなかったため、今回は特徴として使わなかった。
が、よく考えたらclickしたかどうかは年齢・性別にかなり関わってるのではないかと気づき、
impressionとclicksが分かるtrainingデータの中からtrainingとtestに分けて試行してみる。
impressionとclicksは量的変数なのでrescaleして行う。
結果 | 18.293% |
---|---|
ちょっと上がった。 |
・depthとpositionは質的変数と考えてみる。
結果 | 18.288% |
---|
ほとんど変わらない。
・clicksとimpressionではなくctr(clicks/impression)を特徴として使用する。
結果 | 18.23% |
---|
confusion_matrix
闇雲に特徴設計するのではなく、何が正しく分類できて何が誤分類が多いのか確認して特徴設計するべきである。
そのために confusion_matrixを書いてみる。
SELECT
actual,label,count(1) as cnt
FROM
predicted_data
GROUP BY
actual,label
でもよいが、12^2=144行眺めるのは微妙なので横持ちのテーブルに変換してみる。
SELECT
actual,
kv[0] as predicted_0,
kv[1] as predicted_1,
kv[2] as predicted_2,
kv[3] as predicted_3,
kv[4] as predicted_4,
kv[5] as predicted_5,
kv[6] as predicted_6,
kv[7] as predicted_7,
kv[8] as predicted_8,
kv[9] as predicted_9,
kv[10] as predicted_10,
kv[11] as predicted_11
FROM(
SELECT
actual,
to_map(label, cnt) as kv
FROM(
SELECT
actual,
label,
count(1) as cnt
FROM
predicted_data
GROUP BY
actual,label
) t2
GROUP BY
actual
) t3
actual\predicted | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 11970 | 3024 | 15377 | 5378 | 16236 | 9353 | 6579 | 5447 | 4907 | 3877 | 2094 | 1574 |
1 | 5662 | 3529 | 8231 | 5610 | 11602 | 10252 | 5379 | 5873 | 3991 | 3738 | 1852 | 1631 |
2 | 14382 | 5686 | 50906 | 17149 | 55483 | 24175 | 15326 | 11391 | 9846 | 7214 | 4532 | 3561 |
3 | 7676 | 4049 | 20630 | 21110 | 26339 | 22475 | 9162 | 9634 | 6206 | 5473 | 3012 | 2760 |
4 | 12502 | 9408 | 44532 | 19262 | 117500 | 57874 | 38691 | 30561 | 21449 | 14349 | 10273 | 8003 |
5 | 10186 | 8597 | 25022 | 18781 | 67380 | 81092 | 29364 | 34815 | 17163 | 15396 | 8910 | 7810 |
6 | 9392 | 7561 | 22290 | 12404 | 65436 | 40956 | 39037 | 25472 | 18495 | 12364 | 8440 | 6484 |
7 | 8220 | 6726 | 14753 | 11131 | 42794 | 46111 | 22832 | 33725 | 13887 | 13105 | 6832 | 6035 |
8 | 8579 | 5603 | 14869 | 8712 | 39902 | 26120 | 21543 | 16772 | 19195 | 9821 | 6545 | 4938 |
9 | 7441 | 4889 | 10603 | 7704 | 25930 | 27442 | 14390 | 17062 | 10170 | 12768 | 4851 | 4432 |
10 | 3344 | 2436 | 6617 | 3781 | 17667 | 11813 | 9684 | 7442 | 7378 | 4622 | 4921 | 2527 |
11 | 2275 | 1969 | 4009 | 3058 | 10683 | 10927 | 6177 | 7094 | 4482 | 4124 | 2414 | 3136 |
・・見にくい!
ということで合わせて、性別だけ・年齢だけのconfusion matrixを書いてみる。
actual\predicted | male | female |
---|---|---|
male | 775919 | 447114 |
female | 480510 | 484063 |
actual\predicted | 1~12 | 13~18 | 19~24 | 25~30 | 31~40 | 41~ |
---|---|---|---|---|---|---|
1~12 | 24185 | 34596 | 47443 | 23278 | 16513 | 7151 |
13~18 | 31793 | 109795 | 128472 | 45513 | 28739 | 13865 |
19~24 | 40693 | 107597 | 323846 | 133431 | 68357 | 34996 |
25~30 | 31899 | 60578 | 195297 | 121066 | 57851 | 27791 |
31~40 | 26512 | 41888 | 119394 | 69767 | 51954 | 20766 |
41~ | 10024 | 17465 | 51090 | 30397 | 20606 | 12998 |
性別は特に女性をうまく分類できていないようだ。
年齢は特に19~24歳と判定されている率が高い。
actualを見ると、そもそも19~24歳のデータだけ多そう。
age | test | train |
---|---|---|
1~12 | 153166 | 612219 |
13~18 | 358177 | 1428616 |
19~24 | 708920 | 2839792 |
25~30 | 494482 | 1980388 |
31~40 | 330281 | 1326127 |
41~ | 142580 | 570365 |
trainingのデータも、19~24歳だけ特に多いと気づく。
そこにしかない特定の特徴について重みが学習されないのであまりよくはないが、
19~24歳だけ少しundersamplingしてみたらどうなるだろうか?
となるわけである。
数字だけ眺めても気づきを得にくいので
pythonでグラフ化したりするとなおよし。
特徴の組み合わせ
場合によっては、2次か3次の組み合わせによる特徴量を作ってもよいかもしれない。
hivemallでは、polynomial_features関数を使うことで簡単に2次、3次の組み合わせ特徴量を作ることができる。
SELECT
label,
polynomial_features(features, 2, true, false)
FROM
training_featured
上のqueryだと全特徴量の2次の組み合わせとなる。
それだと学習や予測に時間もかかってしまうし、1回しか訓練事例に現れないような逆効果となる組み合わせが出てくる可能性もある。
なので、組み合わせることで目的変数と関係性のありそうな特徴量を考えてpolynomial_features
する必要がある。
今回だと、、queryid,adid,clicks の組み合わせは何かしら年齢性別に関わっていそうだと考える。
clickの回数ではなく、"clickした"or"clickしなかった"の"1"or"0"に変換したclick01という変数を使って各変数を組み合わせてみる。
SELECT
label,
add_bias(array_concat(
categorical_features(
array('displayurl','advetiserid','keywordid','titleid','descriptionid'),
mhash(displayurl),advetiserid,keywordid,titleid,descriptionid
),
quantitative_features(
array('depth','position','ctr'),
depth2,position2,ctr
),
polynomial_features(
categorical_features(
array('adid','queryid','click01'),
adid,queryid,click01
),
2, true, false
)
)) as features
FROM
training_data
結果 | 18.76% |
---|
上がった!
これくらいが限界か、、?
感想
特徴量設計が一番難しくて、重要で、面白いことな気がする。