セーターの備忘録

大学編入やIT関連の内容を記していく予定です

データサイエンティスト・機械学習エンジニア志望で就活した20卒の話

自分はデータサイエンティスト・機械学習エンジニア志望で就活を行っていました.多くの企業の方と面談・面接をさせていただき,その中で見えてきた今のAI分野での就活などについて自分なりにまとめてみました.

簡単な自己紹介

自分は地方の大学院で機械学習分野の研究をしている修士学生です.Kaggleや小規模なハッカソンには何回か参加したことがありますが,特別スキルが高いわけでもないです.なので,Kaggleつよつよマンとかトップカンファレンスに論文出してますみたいな人は参考にならないと思います.というかそのレベルの人は就活しなくても声かかるんじゃないですかね(適当).

AI分野で就活するにあたっての企業の見方

最近はよくAI人材が~とか言われてたりしますが,実際AI分野で働きたいと言っても非常に曖昧なので,自分の中でざっくり分けて考えてみました.

データサイエンティストか機械学習エンジニアか

データサイエンティストとして働くか機械学習エンジニアとして働くかによって,仕事内容が大きく変わってきます.2つの違いについては色々言われてたりしますが,自分の場合は

・データサイエンティスト:アナリストの発展版.データの分析と提案など幅広く.
・機械学習エンジニア:エンジニアの発展版.データの処理や分析について深く.

みたいなイメージで分類しました.TJOさんの考え方を参考にしています*1.あとはshakezoさんの記事なんかも参考になるかと思います*2.まず自分がどちらよりで働きたいかによって選ぶ企業も変わってくると思いますが,企業によって言葉の意味合いが違っていたので面談などで確認するといいかと思います.

IT企業かそれ以外か

IT企業以外でもデータサイエンティストや機械学習エンジニアを求めている企業が多いようです.例としてコンサルティング系やメーカー系などが挙げられるかと思います.自分も何社かIT企業以外を見ましたが,所属する業界などによって雰囲気が違ったりするのかなという印象を持ちました.

事業会社かベンダーか

事業会社で働くかベンダーで働くかによって,仕事で触れるデータが変わってきます.
事業会社で働く場合,その企業が開発しているシステムやサービスから得られるデータを用いて分析を行うことがメインになるため,企業としても事業分野のドメイン知識を持っており,特定の分野により詳しくなれるかと思います.ただし,触れることのできるデータや分野はある程度限られます.一方,ベンダーの場合は様々な業界・分野のデータを扱うため,幅広いデータに触れることができます.ただし,企業として特定の分野に特化しているわけではないので,1つの案件ごとに見た場合,事業会社と比べると弱いという課題があるかと思います.
個人的には,しっかりとした技術力のあるエンジニアを目指すのであれば事業会社の方がいいのかなと思います.

いつ就活するか

最近は就活の時期について色々ニュースになってたりしますが,ほかの業界と比べるとIT業界はちょっと特殊な印象を持ちました.IT企業の場合,早いところで10月頃には面接が始まっており,5月には採用が終わっている企業もありました.一方,IT業界以外(メーカー系とか)だと3月とか4月頃に面接を始めた企業が多い印象を持ちました.

就活でやったこと

逆求人系のサービスをフル活用した

サポーターズさん,キャリアセレクトさん,逆求人ナビさんには逆求人イベントなどで大変お世話になりました.また,サポーターズさんとキャリアセレクトさんでは,担当の人と面談をした上でオススメの複数の企業を紹介していただき,その中で関心を持った企業との面談や面接をセッティングしていただきました.それ以外にもイベント参加時の交通費支援など,地方学生の強い味方でした.というかこのサービスがなかったらお金的な都合で就活成り立たなかった……

インターンシップに参加した

就活を始める前の夏,逆求人系のサービスや魔法のスプレッドシート*3などを通していくつかのインターンシップに参加しました.実際に参加した企業や選考過程でお会いした企業とはその後の就活でも何かと縁があったりしたので,余裕があれば参加するといいかと思います.
ちなみにデータサイエンスインターンシップに参加した時の記事もあるのでぜひ.楽しかったです(小並).

resweater.hatenablog.com

いろんな企業の方と面談した

上記逆求人系のサービスを介して,いろんな企業の方と面談しました.就活の時ほど多くの企業を見る機会ってなかなかないと思うので,色々見てみると面白い&勉強になるかと思います.

ポートフォリオサイトを作った

自分の場合これまでハッカソンや趣味などで作ってきたものがあったので,簡単なポートフォリオサイトを作成しました.サイトはWeb上から閲覧できるように公開した上で,リンクを上記逆求人系サービスのプロフィールなどにも貼っておきました.実際,「ポートフォリオサイト見ました.ぜひ一度面談を~」と声を掛けていただいた企業もあったので,作った意味はあったのかなと思います.

まとめ

ざっくりですが上記のような形で就活を行いました.自分は来年以降はデータ分析に関する事業部がある企業で働く予定です(正式配属は入社後なのでどうなるか分かりませんが……).今後この分野で就活をする人の参考になれば幸いです.

Chaotic PSO でベンチマーク関数を評価する

概要

カオス粒子群最適化法 (CPSO, Chaotic Particle Swarm Optimization) とは,鳥や魚などの群れの行動をヒントにしたメタヒューリスティックアルゴリズムの一つであるPSOにカオスを組み込んだ最適化アルゴリズムです.他のPSOアルゴリズムと同様多くの変形モデルがありますが,大元となるモデルの論文は2005年に発表されました*1

CPSOをPythonで実装した上で,ベンチマーク関数で評価を行ったのでまとめます.

実装内容

CPSOは大まかに下のような手順で実装していきます.

  1. PSOの実行(慣性係数においてAIWF*を使用)
  2. 上位1/5のParticleを抽出
  3. 抽出したParticleに対してCLS*を行い,位置を更新
  4. CLSを行ったParticleの位置情報を基に探索範囲を削減
  5. 新しい探索範囲内で下位4/5のParticleをランダムに再生成
  6. 各Particleの評価を行い,停止条件を満たしていれば終了,満たしていなければ1.に戻る

*AIWF (Adaptive inertia weight factor): 適応慣性荷重係数.PSOにおけるパラメータの一つである慣性係数に対する提案手法.

*CLS (Chaotic local search): カオス的局所探索.PSO実行後,局所探索のために用いる提案手法.

1. PSOの実行

まず,PSOを実行します.パラメータである慣性係数にはAIWFを使い,加速係数には一般に多く用いられている2.0の定数を指定しています. 特に慣性係数は結果に大きく影響を与えると言われており,値を大きくするほど大域探索に有効ですが,局所探索には向かないという課題があります.そこで,AIWFではParticleごとに毎回値を変えることで課題に対応しています.AIWFにおける慣性係数wは以下の式で求めることができます.


w=
\left\{
\begin{array}{}
\frac{(w_{max} - w_{min})(f-f_{min})}{(f_{avg}-f_{min})},  ( f \leq f_{avg}) \\
w_{max},  ( f > f_{avg})
\end{array}
\right.

AIWFのコード

# Adaptive Inertia Weight Factor (AIWF) 関数
def aiwf(scores, W):
    for s in range(SWARM_SIZE):
        if scores[s] <= np.mean(scores):
            W[s] = (np.min(W) + (((np.max(W) - np.min(W)) * (scores[s] - np.min(scores))) / 
                    (np.mean(scores) - np.min(scores))))
        else:
            W[s] = np.max(W)
    return W

2. 上位1/5のParticleを抽出

CLSを行うParticleを決めるため,PSOの結果Scoreの良かった上位1/5を抽出します.

3. 抽出したParticleに対してCLSを行い,位置を更新

CLSによって局所探索を行います.CLSは以下の手順で行います.

1.\(cx_{i}^{(k)}\)を求める


\displaystyle  cx_i^{(k)}=
\frac{x_{i}-x_{max,i}}{x_{max,i}-x_{min,i}}

2.\(cx_{i}^{(k)}\)を基に\(cx_{i}^{(k+1)}\)を求める


cx_{i}^{(k+1)}=
4cx_{i}^{(k)}(1-4cx_{i}^{(k)})

3.\(cx_{i}^{(k+1)}\)を基に位置\(x_{i}^{(k+1)}\)を求め,評価する


x_{i}^{(k+1)}=
x_{min,i}+cx_{i}^{(k+1)}(x_{max,i}-x_{min,i})

4.新しい評価結果が\(X^{(0)}=[x_{1}^{0},...,x_{n}^{0}]\)より良い場合,または最大反復数に達した場合,結果を出力.それ以外の場合は2.に戻る

CLSのコード

# Chaotic Local Search (CLS)
def cls(top_scores, top_positions):
    cx, cy = [], []

    min_x, min_y = min(top["x"] for top in top_positions), min(top["y"] for top in top_positions)
    max_x, max_y = max(top["x"] for top in top_positions), max(top["y"] for top in top_positions)
    
    for i in range(len(top_scores)):
        cx.append((top_positions[i]["x"] - min_x) / (max_x - min_x))
        cy.append((top_positions[i]["y"] - min_y) / (max_y - min_y))

    for i in range(K):
        logistic_x = []
        logistic_y = []
        chaotic_scores = []
        chaotic_positions = []
        for j in range(len(top_scores)):
            logistic_x.append(4 * cx[j] * (1 - cx[j]))
            logistic_y.append(4 * cy[j] * (1 - cy[j]))
            # 位置の更新
            chaotic_x, chaotic_y = chaotic_update_position(logistic_x[j], logistic_y[j], min_x, min_y, max_x, max_y)
            chaotic_positions.append({"x": chaotic_x, "y": chaotic_y})
            # score評価
            chaotic_scores.append(evaluation(chaotic_x, chaotic_y))
        # 新しいscoreが前より優れていれば値を返し,それ以外はcx, cyを更新して繰り返す
        if min(chaotic_scores) < min(top_scores):
            print("Better Chaotic Particle found")
            return chaotic_scores, chaotic_positions
        cx = logistic_x
        cy = logistic_y
    return chaotic_scores, chaotic_positions

# Chaotic position更新
def chaotic_update_position(x, y, min_x, min_y, max_x, max_y):
    new_x = min_x + x * (max_x - min_x)
    new_y = min_y + y * (max_y - min_y)
    return new_x, new_y

4. CLSを行ったParticleの位置情報を基に探索範囲を削減

CLSの結果を基に次の探索範囲を削減します.CLSで用いたParticleの位置情報を基に,以下の式を用いて探索範囲を再指定します.

ただし,実際の実装では最小値に対して複数の最小値の中の最大値を用い,最大値に対して複数の最小値の中の最小値を用いると最小値と最大値が逆転してしまったため,最小値には最小値,最大値には最大値を用いて探索範囲を広く取りました(この辺ちょっと理解が怪しい……).

探索範囲削減のコード

# 探索範囲縮小
def search_space_reduction(top_positions):
    min_x, min_y, max_x, max_y = [], [], [], []

    min_x.append(min(top["x"] for top in top_positions))
    min_y.append(min(top["y"] for top in top_positions))
    max_x.append(max(top["x"] for top in top_positions))
    max_y.append(max(top["y"] for top in top_positions))
    x = [top.get("x") for top in top_positions]
    y = [top.get("y") for top in top_positions]
    
    for i in range(len(top_positions)):
        min_x.append(x[i] - R * (max_x[0] - min_x[0]))
        min_y.append(y[i] - R * (max_y[0] - min_y[0]))
        max_x.append(x[i] + R * (max_x[0] - min_x[0]))
        max_y.append(y[i] + R * (max_y[0] - min_y[0]))
    
    # 論文通り new_min_x = max(min_x) などにすると最小値と最大値が逆転してしまうので,修正
    new_min_x, new_min_y = min(min_x), min(min_y)
    new_max_x, new_max_y = max(max_x), max(max_y)
    search_space_x = {"min": new_min_x, "max": new_max_x}
    search_space_y = {"min": new_min_y, "max": new_max_y}

    return search_space_x, search_space_y

5. 新しい探索範囲内で下位4/5のParticleをランダムに再生成

削減した新しい探索範囲内には下位4/5のParticleが入らないので,範囲内に収まるように新しく生成します.Particleの生成に関しては初期設定と同様ランダムに生成しています.

6. 各Particleの評価を行い,停止条件を満たしていれば終了,満たしていなければ1.に戻る

各Particleに対して評価を行います.この時,事前に閾値として設定したScoreより良かった場合,または最大探索回数に達した場合は探索を終了し,結果を出力します.条件を満たしていない場合は1.に戻り,またPSOから実行して探索を継続します.

ベンチマーク関数での評価結果

最適化アルゴリズムを評価するための関数として,多くのベンチマーク関数が存在します.ベンチマーク関数が詳しく紹介されているものとして@tomitomi3さんの記事*2があります. 今回はこの中でも紹介されているSphere functionを用いました.この関数の最適解は \(f_{min}(0,...,0)=0\) となっています.

今回は \(Score < 1.000e-15\) または反復回数10回を終了条件としました.結果として,グラフのように収束していく様子が観測できました.

f:id:resweater:20190602063036j:plain
CPSO (1, 2, 3 lap)

また,画像の描画無しで実行した場合及びPSOのみを実行した場合の結果は以下のようになりました.CLSによって精度が向上した場合はScoreも更新され,精度が向上しなかった場合はCLS前のScoreが引き継がれています.探索範囲は毎回削減され,Scoreも向上していることが確認できます.また,下のPSOのみを実行した結果と比較すると,精度が向上している反面,時間が掛かっていることが分かります.

CPSO log and result

Lap: 1
CLS:
before: 1.856166459555566e-09
after: 1.7476630375799616e-07
Search Area:
before
x: {'min': -5.0, 'max': 5.0}
y: {'min': -5.0, 'max': 5.0}
after
x: {'min': -0.0010838869646188037, 'max': 0.0013954791030881871}
y: {'min': -0.001201690486501598, 'max': 0.0016078160576153975}
Score: 1.856166459555566e-09

Lap: 2
CLS:
before: 2.0926627088682597e-13
Better Chaotic Particle found
after: 7.821496571701076e-14
Search Area:
before
x: {'min': -0.0010838869646188037, 'max': 0.0013954791030881871}
y: {'min': -0.001201690486501598, 'max': 0.0016078160576153975}
after
x: {'min': -7.880347663659637e-06, 'max': 1.5561134225910913e-05}
y: {'min': -1.83517959693168e-05, 'max': 1.853229861175588e-05}
Score:   7.821496571701076e-14

Lap: 3
CLS:
before: 6.562680339774457e-17
Better Chaotic Particle found
after: 5.0984844476716916e-17
Search Area:
before
x: {'min': -7.880347663659637e-06, 'max': 1.5561134225910913e-05}
y: {'min': -1.83517959693168e-05, 'max': 1.853229861175588e-05}
after
x: {'min': -3.794413350751951e-07, 'max': 1.6057623588141383e-07}
y: {'min': -2.7039514283878003e-07, 'max': 8.373690854089289e-08}
Score:   5.0984844476716916e-17

Best Position: {'x': 6.92085226884776e-09, 'y': 1.7568859807915068e-09}
Score: 5.0984844476716916e-17
time: 0.7126889228820801

PSO result

Best Position: {'x': -0.23085945825227583, 'y': -0.13733679597570791}
Score: 4.218973135139706e-06
time: 0.006306886672973633

まとめと反省

今回実験してみて,CPSOの精度のと実行時間について知ることができました.今回は最適解が一つ(単峰性)のシンプルな関数で評価しましたが,多峰性の関数などで評価した場合などについても機会があれば調べたいと思います.

反省として,元々は今後活用するために実装していましたが,拡張性のないプログラムになってしまいました……また,一部不安なところもあるのでもし間違っているところがあればご指摘お願いします.

CPSOソースコード

github.com

参考文献

*1:Bo Liu, Ling Wang, Yi-Hui Jin, Fang Tang, De-Xian Huang, "Improved particle swarm optimization combined with chaos", Chaos, Solitons & Fractals, Vol. 25, Issue 5, p.p. 1261-1271, (2005).

*2:最適化アルゴリズムを評価するベンチマーク関数まとめ - Qiita

2018年の振り返りと成果物

今年は自分にとっても転機となる年でした。夏に参加したFJCTのデータサイエンスインターンシップ以降、毎日GitHubにPushすることを目標に多くのことに取り組んできました。

 

 

1~3月

学部卒論

http://www.kansei.soft.iwate-pu.ac.jp/abstract/2017/abstract0312016305.pdf

今年は学部を卒業したので、その際に卒業研究の論文を提出しました。学部時代は音声系の研究室に所属し、専門的な音声認識システムの言語モデルに関する研究に取り組みました。編入生で講義を多く受けながらの卒研だったこともあり、いろんな意味で苦しい卒論でした💦

 

8, 9月

インターンシップ

エイチームコンテスト型インターンシップ:Sembatsu

エイチームさんのコンテスト型インターンシップゲームプログラマーコースで参加しました。

これまで使う機会のなかったUnityやC#を初めて触り、試行錯誤しながら取り組んだインターンシップでした。

 

Wanoハッカソンインターンシップ

Wanoさんのハッカソンインターンシップに参加し、「mixme」というクリエイター向けWebサービスを提案、制作しました。

この開発ではチーム内でフロントエンド・バックエンド・機械学習・Webデザインの4つに分かれて開発を行いました。 自分は機械学習班で小説のレコメンド機能の構築に関わり、プロトタイプ用学習データ構築のためのWebスクレイピングプログラムや 文章をベクトル化する上で必要な形態素解析のプログラムの作成を行いました。

この開発を通じて、これまで意識することのなかった機械学習をシステムに組み込む場合になってくるバックエンドの知識などについて学ぶ機会になりました。

 

富士通クラウドテクノロジーズ:データサイエンスインターンシップ

resweater.hatenablog.com

富士通クラウドテクノロジーズ (FJCT) さんで行われたデータサイエンスインターンシップに参加し、データ分析の手法から結果を活用した提案まで一通りの流れを学びました。このインターンシップを通じて学ぶことが多くあり、これ以降の開発のモチベーションに繋がりました。

 

イベント

manifes2018

resweater.hatenablog.com

ローカルイノベーションさんが主催されたmanifes2018というLTイベントに参加しました。この時にエンジニアの方と話したことがきっかけで、アウトプットの方法の一つとしてブログを書くようになりました。

 

Kaggle

titanicコンペ

resweater.hatenablog.com

resweater.hatenablog.com

FJCTで学んだことを実践するため、タイタニックコンペに取り組みました。この中で、特徴エンジニアリングの重要性や自分で作った特徴量で精度が上がった時の楽しさなどを学びました。

 

10, 11, 12月

コンペ

SIGNATE:Jリーグコンペ(学生コンペ)

SIGNATEでマイナビ主催で行われたJリーグコンペに取り組みました。後半は研究が忙しくなりあまり取り組めなかったのですが、1週間取り組まないだけで一気に順位が下がるというコンペの怖さを知りました(;´・ω・)

 

Kaggle:MNISTコンペ

resweater.hatenablog.com

研究でCNNを使うため、CNNの実装について学ぶためにMNISTのコンペに取り組みました。

 

研究

resweater.hatenablog.com

大学院での研究が忙しくなり、そちらがメインになってきました。その中で、論文のまとめ方なども試行錯誤しながら形ができてきました。

 

その他

TrySail分類器

TrySailという3人組の声優グループがあります。ナンスが可愛い (*>△<)

そんな彼女たちを画像から自動で分類できるようにしたいなと思い、制作しました。

シンプルなCNNを使って実装し、結果としてtestデータで7割くらいは分類できるようになりました。

 

サンタさん生成

resweater.hatenablog.com

クリスマスに院生2人で勢いで制作しました。GANを使ってサンタさんの画像生成をしています。

 

まとめ

f:id:resweater:20181230233903p:plain

夏以降ペースを上げてデータ分析や開発に取り組み、GitHubにも反映出来ました(/・ω・)/

来年以降、特に機械学習についてもう少し理論的なところも重視して理解しながら取り組んでいければと思います。

 

github.com

クリスマスなのでGANでサンタさん作ってみた

クリスマスの夜、なんとかの6時間っていうのがあるらしいんですけど、6時間もあったら何か作れるんじゃね?ということで何か作ってみることにしました。

 

作ったもの

クリスマス、情報系ならサンタさん作るところからやるべきだよなぁ?ってことでGANを使ってサンタクロースの画像を生成することにしました。ソースはGitHubに公開しています。

github.com

 

手順

今回は同じ研究室の方と2人で取り組んだので、画像収集と前処理を自分が、学習と画像生成の部分をもう一人が担当しました。

画像収集と前処理

Googleの画像検索から「サンタクロース」とか「Santa Claus」とかでスクレイピングしてきて、約500枚集めました (scraping.py)。このとき、ぱっと見で全く関係なさそうな画像は手作業で削除しました。

次に、集めた画像に対してエッジ抽出したり回転させたりして1万5千枚程度まで水増しさせ (padded.py)、その上で64x64のサイズに統一させました (resize.py)。

f:id:resweater:20181226161628p:plain

集めて処理したサンタたち

学習と画像生成

ぶっちゃけGANよく知らないのでラボの方が作ってるのを見ながら学んでました……(;´・ω・)

学習ではKerasを使って書いています (train.py)。ネットワーク構造に関しては参考論文で使われてる構造をそのまま用いたとか。

画像生成で一番エラー吐いて四苦八苦してました (gen.py)。

 

 生成されたサンタたち

f:id:resweater:20181226162950j:plain

クリぼっち学生によって生み出されたサンタ

 クリぼっちな学生の怨念を感じるサンタさんが生み出されました。

画像によってはそれっぽい形をしてるのもあり、予想より上手く出来たなーという所感です。

ちなみに学習のlossはこんな感じでした。

f:id:resweater:20181226163242j:plain

 

改善点

今回は夜中の数時間でやろうとしたのでゴリ押しのところがいくつもありました。

まず、収集した画像が500枚程度なのに対して水増しさせた最終的な画像が1万5千枚の時点でちょっと強引すぎました……

また、生成された画像からも分かる通り、画像水増しで行った回転がそのまま生成にも反映されてしまっているので、回転とかは使わないほうが良かったです。水増し手法はCNNとかで認識させるときと同じ方法をそのまま用いましたが、GANのような画像を生成させる場合使い方を変える必要があるなと気づきました。

あとは学習部分を担当した方曰く、GAN特有のオーバーフィッティングが起きてるかもとのことなのでその辺も要改善です。

 

その他

クリぼっちの院生二人で過ごすステキな夜でした☆

サーベイした論文のまとめ方メモ

大学院に入って以降、論文を読む量が増え、整理が大変になってきました。試行錯誤の末、何となく自分なりに良さそうなまとめ方が出来てきたのでまとめます(英論文前提ですが、英論文でなくとも使えるやり方だと思います)。

 

 

手順

論文を読んでからまとめるまでの手順としては、

  1. 月単位でディレクトリを用意
  2. 論文のファイル名を変更し、ナンバーを振る
  3. 2つのWordと1つのPPTにメモ
  4. 特に気になった論文は印刷して保管

という形です。

(2019/06/15 追記) 
3. に関して、1つのWordと1つのPPTでいいかなという感じになってきてます……。あと、Mendeley使いましょう。便利。

1. 月単位でディレクトリを用意

一つのディレクトリにするとファイルが多くなりすぎるので、月毎でまとめています。自分の場合、サラッと目を通しただけの論文も合わせると10~20本/月 くらいですが、今のところ上手くまとめられています。

 

2. 論文のファイル名を変更し、ナンバーを振る

論文のファイル名は論文のタイトルでないことが多いので、タイトルに変更します。この時、図のようにファイル名の先頭にナンバーを振ることで、後述する3つのファイルと合わせて見るときに探しやすくしています。

f:id:resweater:20181011230035p:plain

 

3. 2つのWordと1つのPPTにメモ

2つのWordと1つのPPTを用意します。これらはそれぞれ

  • 気になったところをまとめるWord
  • 自分なりに考えたことや発想をメモするWord
  • 1論文1枚にまとめて特徴を明確化したPPT

の3つになります。

(2019/06/05 追記)
2つのWordに関して、1つにまとめてしまってもいいかなと思います。でないとファイルが多くて段々複雑になってくる……。また、PPTに関して、読んだ中で特に関心を持ったり自分の論文の引用で使うかもという論文だけでいいかなと思います。

気になるところをまとめるWord

「あの論文のあの辺りで言ってたよな」と思い出す度に英語を訳すのは大変なので、気になったところや大事そうだなと思ったところは自分なりに訳した上で丸ごと書いています。3つの中で一番量も多くなり、細かいところまで残しておける反面、見やすさは低く、場合によっては論文を読み返した方が早いときもあります。

自分なりに考えたことや発想をメモするWord

一つ目のWordに自分の考えまで書いてしまうと、どこまでが論文の内容でどこからが自分の考えなのか分からなくなるので分けました。自分なりに気になったところのメモや考えを書けるので思い出しやすい反面、このファイルだけでは論文の内容は分かりにくくいので、1つ目のWordや3つ目のPPTとセットで見る必要があります。

1論文1枚にまとめて特徴を明確化したPPT

ここでは、一部の界隈で有名な(?)筑波大の落合先生の論文まとめフォーマットを自分の分野向けに変えて活用しています。落合先生のフォーマットについては以下の記事を参考にしました。

lafrenze.hatenablog.com

自分は機械学習分野の論文を読んでおり、特徴量やデータセットが重要になってくるので、その辺りを下の図のように載せるようにしました。

f:id:resweater:20181011232857p:plain

 

4. 特に気になった論文は印刷して保管

読んでいくとこれは先行研究として重要だなとか大切な内容が多いなという論文が出てくるので、そう思った論文は印刷して特に気になる部分にチェックを入れたりした上で1つのファイルにまとめて保管します。自分の場合、教授との相談やゼミで使いたいなと思った論文は印刷するようにしています。

 

まとめ

論文を読む量が増えてから試行錯誤してきましたが、今のところ上記したやり方が一番上手くいっています。特に論文をナンバリングしたことで後から探して見返しやすくなりました。

CNN始めました

初めてCNNの実装を行ったのでメモしておきます。

きっかけ

今取り組んでいる研究でCNNを使おうとしています。CNNの理論はある程度理解していましたが、実装したことがなかったので7層のCNNの実装を行いました。

CNNの構築を行う上で、以下のkernelを参考にしました。

Introduction to CNN Keras - Acc 0.997 (top 8%):  https://www.kaggle.com/yassineghouzam/introduction-to-cnn-keras-0-997-top-6/notebook

実装

前処理

最初に画像を読み込んだ上で正規化をします。

train = pd.read_csv("../data/mnist_train.csv")
test = pd.read_csv("../data/mnist_test.csv")
X_train = train.drop(columns='label')
y_train = train['label']

X_train = X_train/255.0
test = test/255.0

次に画像を28×28で1チャンネルの画像として読み込ませます。 reshape内の28は画像の縦横、1はチャンネル数を示します。 -1は任意を示しているようですがまだよく分かってないです……

X_train = X_train.values.reshape(-1,28,28,1)
test = test.values.reshape(-1,28,28,1)

次に、ラベルエンコーディングをして0~9まであるラベルを1つのベクトルに変換します。

例: 2 -> [0,0,1,0,0,0,0,0,0,0]

y_train = to_categorical(y_train, num_classes=10)

あとはデータ分割を行い、学習データの方で回転させたりランダムにノイズを混ぜた画像を入れることで学習データを増加させて過学習を起こしにくくしました(詳しくは参考ページ)。

CNNのモデル構築と実行

今回はちょうど読んでいた論文で使われていた畳み込み層が7つあるCNNの構造に近い形で構築しました。

構造: In -> 4(Conv2D -> ReLU) -> MaxPool2D -> 2(Conv2D -> ReLU) -> MaxPool2D -> (Conv2D -> ReLU) -> MaxPool2D -> Flatten -> Dence -> Out

padding: 'Same' or 'Valid' (zero_paddingの場合はSame)

Flatten: 1次元データに変形

Dropout: 括弧内の割合でネットワークをランダムに切る

Dense: 全結合層

model = Sequential()

model.add(Conv2D(filters=32, kernel_size=(3,3), padding='Same',
                 activation='relu', input_shape=(28,28,1)))
model.add(Conv2D(filters=32, kernel_size=(3,3), padding='Same',
                 activation='relu'))
model.add(Conv2D(filters=32, kernel_size=(3,3), padding='Same',
                 activation='relu'))
model.add(Conv2D(filters=32, kernel_size=(3,3), padding='Same',
                 activation='relu'))
model.add(MaxPool2D(pool_size=(2,2)))

model.add(Conv2D(filters=64, kernel_size=(3,3), padding='Same',
                 activation='relu'))
model.add(Conv2D(filters=64, kernel_size=(3,3), padding='Same',
                 activation='relu'))
model.add(MaxPool2D(pool_size=(2,2), strides=(2,2)))

model.add(Conv2D(filters=128, kernel_size=(3,3), padding='Same',
                 activation='relu'))
model.add(MaxPool2D(pool_size=(2,2), strides=(2,2)))

model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))

最適化関数はRMSpropを用いました。他の方の記事を見ると、MNISTではこの手法が精度が良いことが多いようです。これ以外にも確率的勾配降下法 (SGD) やAdamなど様々な方法があります。

optimizer = RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0)

それ以外の設定は以下の通りで行いました。

epoch数は5、バッチサイズは100で行いました。

model.compile(optimizer = optimizer , loss = "categorical_crossentropy",metrics=["accuracy"])
learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc', 
                                            patience=3, 
                                            verbose=1, 
                                            factor=0.5, 
                                            min_lr=0.00001)
epochs = 5
batch_size = 100

history = model.fit_generator(datagen.flow(X_train,y_train, batch_size=batch_size),
                              epochs = epochs, validation_data = (x_test,y_test),
                              verbose = 2, steps_per_epoch=X_train.shape[0] // batch_size
                              , callbacks=[learning_rate_reduction])
print("\nfinish!")

結果は以下の通りです。

Epoch 1/5
 - 225s - loss: 0.4281 - acc: 0.8587 - val_loss: 0.0491 - val_acc: 0.9838
Epoch 2/5
 - 238s - loss: 0.0918 - acc: 0.9723 - val_loss: 0.0356 - val_acc: 0.9879
Epoch 3/5
 - 256s - loss: 0.0644 - acc: 0.9798 - val_loss: 0.0279 - val_acc: 0.9910
Epoch 4/5
 - 237s - loss: 0.0521 - acc: 0.9844 - val_loss: 0.0333 - val_acc: 0.9917
Epoch 5/5
 - 250s - loss: 0.0467 - acc: 0.9864 - val_loss: 0.0385 - val_acc: 0.9902

finish!

f:id:resweater:20181008044200p:plain

また、今回のモデルでKaggleのDigit Recognizerに投稿してみたところ、0.99128とそこそこのスコアを出すことができました。

ただ、このモデルの学習を自分のi5-6200UのCPUを積んだノートPCで回すとおおよそ20分程度かかり、かなり高温になりました(汗)。この前にGPUを積んだPCで実行したときやKaggleのkernelでGPUを使って実行した時は30~60秒位で終わったのでやはりGPUを使った方がいいようです(当たり前)。

可視化

結果を見てみると、誤って4や8と認識したものが多かったようです。 f:id:resweater:20181008045142p:plain

まとめ

無理矢理CPUでやろうとしたせいで無駄に苦労しました……次からGPU使ってやります……

また、モデルそのものや各関数で多くのパラメータがあり理解できてないところもあるので、他のデータでも試しつつ覚えていこうかと思います。

今回のソースコード:

github.com

初めてKaggleに挑戦した話2 特徴量エンジニアリングをしてみた

「初めてKaggleに挑戦した話1」を書いた後、特徴量エンジニアリングなどをしたりしてもう少しTitanicに取り組んだのでまとめます。

精度が低く、順位も下から数えてすぐだったので、ひとまずTop50%に入ることを目標に取り組みました。

 

 

やったこと

前回からやったこととしては、

  • 特徴量エンジニアリング
    ・史実を参考にした特徴量生成
    ・似た特徴量の結合と細かすぎる特徴量の再構成
  • 特徴選択とパラメータチューニング
  • 学習結果から得られた正解ラベルを用いて学習データを追加

辺りを行いました。

学習では前回と同じくLightGBMを用いました。

 

特徴量エンジニアリング

前回の時点で使用する特徴量のパターンをいろいろ試しましたが、Accuracyはあまり高くありませんでした。そこで、史実を基にデータを分割したり結合、再構成など特徴量エンジニアリングをすることで精度向上を図りました。

史実を参考にした特徴量生成

まず当日のことを知るために、wikiタイタニック号沈没事故についてのページを読みました。すると、その中で救命ボート乗船時の状況について、

「ライトラーは左舷側のボートを担当し、マードックは右舷側のボートを担当した。一等航海士マードックと二等航海士ライトラーはそれぞれ「ウィメン・アンド・チルドレン・ファースト」について異なる解釈をした。マードックは女性と子供から乗せると解釈したが、ライトラーは女性と子供を乗せると解釈した。そのため、ライトラーは女性と子供が全員乗り込んだのを確認すると、スペースに余裕があっても救命ボートを降ろしたが、マードックは女性と子供の他にわずかだが男性も乗せた」

という記述がありました。このため、避難時、船の左舷右舷どちらに向かったかによって生存率が変化したことが考えられ、広い船内でどちら寄りに自分の客室があったかに影響されたと考えました。そして、船の客室番号は左舷と右舷でそれぞれ偶数と奇数に分かれていることも分かりました。

さらに、客室のある階層 (A~G) によって救命ボートまでの距離が異なり、特に下の方の階層である3等客室は1, 2等客室との間に障壁があるなど、階層も大きく影響していることが分かりました。

コンペで渡されている"Cabin"にはそれぞれの客室番号が書かれており、左舷よりか右舷よりかと階層が分かるようになっていました。そこで、客室番号を分割して右舷左舷情報を"RoomLorR"、階層情報を"RoomLayer"として特徴量を生成しました。このとき、"Cabin"は欠損値が多かったので、存在しないものに対しては存在しないことを示すラベルを付与しました。

それぞれ生成したデータをグラフ化すると、下のようになりました。

f:id:resweater:20180916203511p:plain

f:id:resweater:20180916203516p:plain

1枚目が"RoomLorR"、2枚目が"RoomLayer"で、オレンジ色の部分が生存者、一番右がデータの存在しなかった人です。これを見ると、まず客室番号データが無かった人の生存率が低いことが分かります。また、左舷右舷や階層によっても有意差があると言えるかは怪しいですが違いがあることが分かりました。

似た特徴量の結合と細かすぎる特徴量の再構成

SibSpとParchの結合

基から存在するデータのうち、"SibSp"及び"Parch"は

  • SibSp: 同乗している兄弟・配偶者の人数
  • Parch: 同乗している親・子供の人数

と似た内容のデータであることが分かりました。そこで、これらを「同乗している身内の人数」として一つに結合し、"SibSpParch"としました。

f:id:resweater:20180916204023p:plain

グラフ化すると、身内が1~3人いると生存率が高いことが分かりました。

AgeとFareの再構成

"Age"及び"Fare"について、それぞれ値が細かく示されていることが分かりました。

f:id:resweater:20180916202649p:plain

そこで、Ageは10歳ごと、Fareは50ごとで区切ってまとめることで、細分化されたまま学習されないようにし、それぞれ"aboutAge"、"aboutFare"としました。

 

特徴選択とパラメータチューニング

特徴選択

今回試した特徴量をすべてまとめると、下の通りです。

  • Pclass - チケットクラス
  • Sex - 性別
  • RoomLorR - 客室の左舷右舷
  • RoomLayer - 客室の階層
  • SibSpParch - 同乗している身内の人数
  • Age - 年齢
  • AboutAge - 年齢を10歳ごと区切ったもの
  • Fare - 料金
  • aboutFare - 料金を50ポンドずつ区切ったもの
  • Embarked - 出航地
  • (Title) - 上では省略した、Qiitaの記事で見かけたMr. や Ms. などで分類したもの

これらを試していくと、最終的に精度が良かったのは

  • Pclass
  • Sex
  • RoomLorR
  • Age
  • Fare
  • SibSpParch

の6つの特徴量を用いたときでした。このときのFeature importanceは下のようになりました。"RoomLorR"の寄与度は思ったより低かったです。

f:id:resweater:20180916214647p:plain

パラメータチューニング

パラメータを最適化するため、GridSearchCVを使ってパラメータチューニングを行いました。ただ、仕組みをあまり理解できておらず、手作業で調整したときの方が精度が良く、過学習などしてたわけでもないようなのでもう少し学んでいきたいです。

 

学習結果から得られた正解ラベルを用いて学習データを追加

当たり前と言えばその通りなんですが、上記の方法で学習して予測を行うことで、testデータのSurvivedに関するラベルが生成されたので、それを学習データに追加することで合計1308人分のデータを用いて学習を行い、予測をしました。

 

Accuracy

結果として、0.78947の精度を出すことができ、目標だったTop50%入りは楽々超えることができました!(2018年9月16日時点での結果です)

f:id:resweater:20180916212443p:plain

f:id:resweater:20180916212454p:plain

(画像の0.79425は間違って参考にした方のプログラムで実行してしまった方の結果です…すぐ下が0.78947なので25%辺りに入れたのかなと思います…)

 

まとめ

Top50%に入るまでやると宣言したはいいものの、51%からなかなか上がらず苦しみました…

一方で、先日言われつつも実感が湧かなかった特徴量エンジニアリングの必要性について理解でき、GridSearchCVなど様々なツールについても知ることができました。ただ、グラフ化するときの表示方法が今のやり方だと見にくいのでもう少し慣れて上手く表示できるようにしていきたいです。

次はDeep Learning系を使ってコンペに参加したい…