kddcup2012で年齢性別予測！

kddcup2012で年齢性別予測！
===  

hivemallを使ってkddcup2012のデータセットで年齢と性別を予測するよー。  

dataset
---
[kddcup2012](http://www.kddcup2012.org/c/kddcup2012-track2)  

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関数](https://github.com/myui/hivemall/wiki/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%|
|---|---|

上がった！  
これくらいが限界か、、？

感想
---
特徴量設計が一番難しくて、重要で、面白いことな気がする。