Kaggleに挑戦してみた
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
はじめに
クリエーションラインの藤田です。
今回はSparkを使ってデータ解析を行いました。データサイエンティストのコミュニティであるKaggleで行われている競技に参加しました。挑戦した課題は『Springleaf Marketing Response』です。
Kaggleとは
今回とりあげたKaggleについて少し説明いたします。
Kaggleとは世界中のデータサイエンティスト達が課題解決の最適モデルを競い合うコミュニティサイトです。URLは http://www.kaggle.com/ です。
サイトには様々なコンペティションが用意されており、新しいコンペティションも随時追加されています。コンペティションには色々な種類があり、中には賞金が付いているものもあります。各コンペティションの開催期限は月単位から長いものでは年単位で開催されているものがあります。コンペティションの結果によってランキング付けも行なわれ、NOVICE、KAGGLER、MASTERの順にあがっていきます。
コンペティション以外にも自分のスクリプトを公開したり、他のユーザーに質問したり、自分のブログを書くことができる機能もあります。さらにデータ解析関係の求人ページもあるので、Kaggleで優秀なスコアを取っていればデータ分析会社に就職できるかもしれません。
もしみなさまのなかでKaggleに興味を持たれた方がいたら、挑戦してみることをお勧めいたします。Kaggleの競争には誰でも参加することができます(注意: 一部のコンペティションは何回かKaggleの一般コンペティションに参加していないと参加できないものもあります)。
Springleaf Marketing Response
多数あるコンペティションの中から今回は『Springleaf Marketing Response』に挑戦しました。その内容は金融会社であるSpringleaf社が数多くの個人宛に送る融資やローンについてのダイレクトメールの中で、実際に反応する人を見極めることができるかを、統計的に予測しようというものです。
ダイレクトメール全体の中で反応する人が多くなるほど、Springleaf社はダイレクトメールを効率的に活用することができ、コストを削減することが可能です。このコンペティションでは、匿名化が施された顧客データを解析しそれぞれの顧客がダイレクトメールにどのような反応を示すかを予測します。
データの構造と解析戦略
さて、『Springleaf Marketing Response』で用意されたデータはどのようなものになっているでしょうか。データの構造を以下の図にしてみました。
ID | VAR_0001 | VAR_0002 | … | VAR_1934 | target |
1 | “R” | 360 | … | “IAPS” | |
... | ... | ... | ... | ... | ... |
290463 | “H” | 228 | … | “IAPS” | 0 |
データ形式や、どのような種類のデータかわからない列が1934個もあります。つまり、列の性格を意識して分類することは不可能である、という状態になっています。またデータの行数も30万行あり、到底Excelで扱えるようなデータではありません。
このようなデータが900MB程度の2つのファイルtrain.csvと test.csvに分けられて配布されます(train.csvは”target”カラムに値が入っており、test.csvには値が入っていません)。
このデータからユーザーの反応を予想するにはどうすればよいでしょうか。私は統計学やデータ解析には詳しくないのですが、以下のような戦略を立てました。
目的 : test.csvの各ユーザーのtargetカラム値を予想する。
仮説 : targetカラム値はユーザーの他の特定のカラム値と依存関係がある。
仮説よりtest.csvの各ユーザーがtrain.csvの反応の有ったユーザー群とどのくらい似ているかを計算すればよいのではないだろうかと考えた。
具体的な手順としては
- トレーニングデータを反応のあったグループと無かったグループに分ける。targetの値が0か1かで判断する。
- 類似度の基準となるデータを作る。反応の有ったグループの平均値を出し基準とする。各パラメータの標準化のために分散・標準偏差も求めます。
- テストデータのユーザデータがどれくらい反応のあったグループに似ているかを調べる。
解析環境
今回はDigital Oceanのクラウド環境を使って分析を行いました。
環境は以下の通りです。
- CPU: 2コア、 2スレッド
- メモリ: 4GB
- SSD: 60GB
- OS: CentOS7
- Spark 1.5.0
- Hadoop 2.6
- SBT 0.13.9
データ解析を始める
それではデータ解析を始めましょう。今回解析プログラムはSpark+Scalaで作成しました。
1. データ(train.csv)の分割
トレーニングデータの分割を行います。
「データ構造と解析戦略」で図示したデータ構造の最後のカラム"target”の値がユーザーの反応を表わしています。targetの値が1であれば、そのユーザーはDMに反応をしています。値が0ならば反応をしていません。
そこでtargetの値を使い、ユーザーを反応の有無で分割します。
//トレーニングデータの読み込みと分類 val dataset = sc.textFile(“train.csv”).map(line => line.split(“,”)) val train0 = dataset.filter(line => line.last==”0”) //反応無し val train1 = dataset.filter(line => line.last==”1”) //反応有り
2. 平均・分散などの計算
train0とtrain1の平均・分散・標準偏差を計算し、平均の差の割合も求めます(data0は反応の無かったグループ、data1は反応の有ったグループ)。
それを各ユーザIDでまとめたRDDを作成します。
var rlist: List[String] = List() val leng = dataset.first.length for ( i <- 1 to leng -1 ) { var data0 = train0.map(line => line(i)).filter(x => !(x contains “\””)).filter(x => x!=”NA”).map(x => x.toDouble).cache var data1 = train1.map(line => line(i)).filter(x => !(x contains “\””)).filter(x => x!=”NA”).map(x => x.toDouble).cache var diff = (data0.mean - data1.mean)/data1.mean.abs rlist = i +”,”+ diff +”,”+ data0.mean +”,”+ data1.mean +”,”+ data1.variance +”,”+ data1.stdev :: rlist } rlist = rlist.reverse val rlistrdd = sc.parallelize(rlist).map(line => line.split(“,”))
これで{ユーザID, 平均差の割合, data0の平均, data1の平均, data1の分散, data1の標準偏差}の要素を持ったRDDが作成されました。
3. テストデータの各ユーザの類似度を調べる。
トレーニングデータとテストデータが同じ母集団を持つとして、テストデータのユーザの要素を前述で求めたtrain1の平均と標準偏差で標準化を行う。標準化されたデータは平均が0で標準偏差が1なので、-3~3の間に99.74%のデータが存在することになります。
つまり類似度関数は定義域が-3≦x≦3で、x=±3でf(x)≒0、x=0でf(x)=1となるように定義すればよい。
こうすることによって1つの要素がどれだけ似ているかがパーセンテージでわかり、それを要素間で掛けあわせれば全体としてどれだけ類似しているかが分かるはずです。
//類似度計算用関数の定義 def similarityFunc(line: Array[String], diffsort: Array[Array[String]]): (String, Double) = { var sum = 1.0 var LENG = diffsort.length for ( i 1/line(1).toDouble.abs).filter(line => line(1)!=”Infinity”).take(100) // 上記のような方法で関係が深そうな要素をいろいろ試す。 //テストデータの読み込み var source = Source.fromFile(“test.csv”) // scala.io.Sourceのインポートが必要 //var simil: List[(String, Double)] = List() val savefile = new PrintWriter(“result.csv”) for ( line <- source.getLines) { var linesplit = line split ‘,’ var data = similarityFunc(linesplit, diffsort) // simil = data :: simil file.write(data._1 +”,”+ data._2 +”\n”) } source.close file.close
diffsortはどの要素の類似度を計算するかを決定しています。この方法はどの要素を使うかが非常に重要になってきます。上記のプログラムではdiffsortで平均差が大きいものを選択して類似度を計算させました。
計算結果
result.csvの中身は以下のような形式になります。第1カラムがユーザーIDで、第2カラムが予測値になります。
1,0.218461508716 3,0.174768860363 6,0.179894746315 9,0.272701919919 10,0.0399027661514 11,0.234088594181 12,0.226296085687 13,0.273507234459 15,0.306056192286 ~(以下略)~
計算結果を投稿してみる
計算結果が出来たので、結果をKaggleに投稿してみましょう。投稿はKaggleの挑戦しているコンペティションのページで行なえます。左にあるメニューから"Make a submission”に移動し、提出したいファイルを選択しましょう。
投稿するとすぐ答え合わせが自動で開始されます。少し(30秒くらい)待つと答え合わせが終了し、結果が表示されます。
今回挑戦したコンペティションでは、結果は1以下で小数点第5位までの数で表示され、1に近いほど予測の精度が良く、得点が高いです。
今回の結果
私の最終的な得点は0.55012でした。順位でいえば2226チーム中の2076位でした。1位のチームの得点は0.80427でした。
参加者は自分が作成したscriptを公開して、他の参加者と意見を交換したり、アドバイスを貰ったりすることができ、他の人が公開している情報を見て自分のデータ解析に役立てることが可能です。今回私は平均を求めて関係のありそうなパラメータを調べて類似度として予測を導きましたが、他の参加者にはクラスタリングを行なってパラメータ間の関係を調べたり、匿名化されたデータの元の意味を探っている人もいました。
まとめ
Kaggleに挑戦することを決めた当初は入賞して賞金を頂こうと大きく息巻いていましたが、実際に挑戦してみるとデータ解析には様々な知識が必要とされ、あえなく無惨な結果に終わってしまいました。しかし、解析を進めるなかで色々と調べたことや、他の参加者のscriptを見たりと多くの得るものがありました。特に上位陣が公開している情報は初心者である私には難しかったですが、非常に為になるものが多かったです。
他の参加者の多くはRやPythonを使用していました。
Sparkでデータ解析を行なった感想としましては、RDDの操作に手こずりました。各RDDの行単位での処理が上手く扱えずに苦労して、結局Scalaのリストやファイル操作を使ってしまいました。なんでもかんでもSparkで処理するのではなく、分散処理などのSparkが得意とする分野で使ったりと、組み合わせが大切だと感じました。
Sparkについての良い勉強にもなったと思います。これからもSparkを使って様々な分析に挑戦していきたいと思います!