Higu`s diary

大学院生の日記です。主にプログラミングとカメラについて。最近はデータ分析・Kaggleにハマっています

2019年を振り返りと2020年の目標

こんにちは、ひぐです。

もう年の瀬ですね〜
今年のトピックは大きく分けて、研究、就活、プログラミングの勉強の3つという感じでした。

今年のよかったこと、反省点を踏まえて来年も頑張りたいので、
それぞれまとめていきたいと思います!

概要

自分を4行でまとめる

2019年4月に工学系学部を卒業して同大学大学院に進学。
プログラミングは1年半前に始める。
研究内容は、学部:政治と自然言語処理→院生:深層学習を用いた医療画像における腫瘍の自動識別
21卒で就活中

今年をざっくりまとめる

就活

インターンシップ

下記インターンシップに参加しました。

zerebom.hatenablog.com

参加することで、企業の風土や開発環境を知れるだけでなく、 自分の目標となる先輩や、優秀な同期と出会うことができました。
1月からCAのAI事業本部で長期インターンをさせていただけることになったので、こちらも頑張りたいです。

自己分析

企業との面接でどういうエンジニアになりたい?という質問になかなか答えられず大変でした。 そのため、10月以降は特に自分の将来を真剣に考えました。

多動な人間なのでその時その時でやりたいことは常にたくさんあるのですが、
もっと軸足を定めて考えるべきだったと反省してます。

ざっくりですが、
- 社会的に正しいことをする
- 頭とコミニュケーション両方を使う
- 自分の学びを同業者・同期に還元していく

この3つは自分の中で大きな軸だなぁと思っています。 面接で取り繕って話すことは絶対したくないので、
ちゃんと考えて言葉にできるようにしていきたいです。

研究

学会発表

新規研究

4年次の研究を経て、もっと深層学習ちゃんと勉強したくてテーマを変えました。 大変ですが、後悔はしてないです。

プログラミングの勉強・成果

その他

  • TOEIC 755点になった
  • Twitterのフォロワーが610人になった
  • ブログの月間PVが1500~2500くらいになった
  • 筋トレが4ヶ月くらい続いた、10回*3セットで上がる重量が増えた
    ベンチプレス30->45kg
    デッドリフト40kg->65kg
    スクワット60kg->75kg

今年できたこと

  • たくさん行動する経験する
  • 尊敬できる人に会いにいく
  • 規則正しい生活
  • 人を傷つけない

今年できなかったこと

  • 目標に到達する前にやめてしまった(KaggleとかKaggleとか)
  • ブログ以外のアウトプット(LT/論文投稿など)
  • 部屋をきれいに保つこと
  • 一つ一つを丁寧にこなすこと(手広くやりすぎた)

来年の目標(抽象)

  • 将来の夢を考える(人生の目標)
  • 基礎を固める(線形代数統計学・CS)
  • 専門分野を深く学ぶ

来年の目標(具体)

  • 論文投稿
  • LT登壇
  • TOEIC 850以上
  • Kaggle Master
  • 統計検定1級

まとめ

今年1年間は自分に対して向き合って、たくさん勉強できたなと思っています。
勉強や就活をする上でいろんな人に話を聞きに行ったり、新たな友達ができたのも大きな成果でした。

その一方でエンジニア・院生以外の人とは殆ど会わず、
世間一般から遠ざかったような気持ちもしました。

今年は就活、学校、研究全部あったのでしょうがない部分もありますが、
小粒の成果がいっぱいって感じになってしまいました。 来年以降は一つの目標に対してじっくり取り組んで大きな成果を出していきたいです。

勉強のための勉強ではなく、
なんのために勉強するかも今まで以上にしっかり考えていきたいです。

来年もよろしくお願いします〜

おわり

マイナビ × SIGNATE Student Cup2019に参加して9位でした

こんにちは、ひぐです。

先日マイナビ × SIGNATE Student Cupに参加し、9/342位になりました!
この記事ではどんな取り組みを行ったかを書きたいと思います。
なるべく本コンペに参加してない人にも内容がわかる記事にしたいと思います。

本コンペの基礎情報

マイナビ × SIGNATE Student Cupとは年に1度開かれる、学生のみが参加できるデータ分析のオンライン大会です。
お題とデータが渡され、機械学習を用いて目的変数を予測し精度を競う大会です。

本コンペのテーマは「東京都23区の賃貸物件の家賃予測」です。
各物件に対し、「面積、方角、所在階」などの情報が与えれ、そのデータを元に家賃を予測する、といった内容でした。

f:id:zerebom:20191108215858p:plain
引用:https://signate.jp/competitions/182

データ量はTrain,Testどちらとも3万程度でした。

本コンペの特徴

本コンペのデータは以下4つの特徴があり、これらをうまく取り扱うことが精度向上の鍵になったかと思います。

  • 外れ値がある上に、評価指標がRMSE
    目的変数である賃料は非常に右に裾が長い分布であり、最も高い物件の賃料は250万円もするものでした。
    評価指標がRMSE(二乗平均平方根誤差)であるため、これらの高級物件の誤差をいかに小さくするかが重要でした。

    f:id:zerebom:20191108224222p:plain
    目的変数の分布

  • データが汚い&数値ではなく文字データとして与えられている
    データがすぐに使える形で与えられていなかったため、正規表現等を駆使して情報を取り出す必要がありました。
    また、欠損値や書き間違いも多く含まれ、丁寧に処理する必要がありました。

  • Train,Testで同じようなデータが含まれている
    物件データの中には、同じアパートの別の部屋などが含まれており、 普通に学習するより、学習データと同じ値で埋めるほうが精度が高くなりました。

    f:id:zerebom:20191108224133p:plain
    同じようなデータ群

  • 外部データ使用可能
    このコンペでは外部データの使用が認められていました。
    土地データのオープンデータは非常に多く、どれをどのように使うかが大事になったかと思います。

弊チームの取り組み

最終的なPipelineは以下の通りです。

f:id:zerebom:20191109000508p:plain
Pipeline

基本的に「各物件のデータから推論より、同じ・似た物件データの賃料からキャリブレーションする」 というつもりで進めていました。

コンペ全期間の大まかな流れと精度の変化は以下の通りです。

  • 3人とも個別で学習(17000~16000程度)
  • チームマージしアンサンブル。LogをとってMAEで学習(15000程度)
  • K-meansで近傍データの作成(14500程度)
  • 住所の修正、単位面積あたりの賃料を推定(13000程度)
  • パラメータ調整、SeedAverage(12400程度)

特に住所の修正、単位面積あたりの賃料の推定が大きかったと思います。
順を追って説明します。

Plotlyを用いて予測誤差の原因を追求

簡単に予測モデルを作ってからは、出力誤差をPlotlyを用いて地図上にMapして、どういった物件が誤差が大きいか確認しました。
Plotlyはインタラクティブに描画されるため、ズームしながら一つ一つ確認できました。
詳しくはQiitaに記事を書いたので良ければ見てください↓
qiita.com

同じ住所なのに、違うアパートが含まれていること、
またそういった物件の誤差が大きいことを確認しました。

f:id:zerebom:20191108225251p:plain

欠損値、異常値補完

今回配布されたデータの物件の所在地には
A:「東京都〇〇区××n丁目x-yy」と正確に記載されているデータもあれば、
B:「東京都〇〇区××n丁目」と丁までしか含まれていないデータも多くありました。

これらを注意深く観察すると、同じアパート(賃料・面積などから判断)でも
Aの形で所在地が埋められてるデータもあればBの形のデータもあることがわかりました。

そこで、面積、所在階、室内設備などの複数条件が同じであれば、同一アパートとみなし、
Bの形で所在地が記載されているデータを同じ物件のAの形の所在地に変換しました。
図にするとこんな感じです。
f:id:zerebom:20191109115533p:plain

こうすることで住所や緯度経度をkeyとした集約特徴量が正確な値になり、精度が向上しました。

外部データの使用

今回収集した外部データは以下の通りです。
- 地価データ
- 駅データ
- 路線数
- 1日の利用者数
- 緯度経度

これらから作成した以下のデータは精度向上に寄与しました。
- 物件とその物件から最も近い駅の距離
- 物件から最も近い距離にある公開されている地価
- 上記の地価の2012年から2017年の変化率
- 六本木ヒルズからの距離

K-meansを用いた近傍データの使用

地域によって賃料が全然違うことから近傍データが効くと考えられました。
そこで、緯度、経度、築年数を元にK-meansでクラスタリングし、 このCategorycal変数から以下のような特徴量を作成しました。

  • 同一クラスタ内の平均地価(賃料/面積)
  • 同一クラスタ内の平均地価×自身の面積
  • 同一クラスタ内の平均地価と自身の地価の差分・比率
  • 同一クラスタ内の平均築年数と自身の築年数の差分・比率

差分や比率を入れることで、各物件がクラスタの中でどのような位置付けがわかります。

前処理を丁寧に行なったこともあり、強力な特徴量となりました。 築年数をk-meansの判断材料に入れることにより、より似た性質の物件を同じクラスタに入れることができました。

外れ値に強いモデルの作成

外れ値も外れ値でない値も正確に予測したかったため、
Logをとってmaeで学習をしました。

また、賃料は面積との相関が強かったため、単位面積あたりの賃料を予測するモデルも作成しました。
面積で割り、さらに築年数を考慮したクラスタリングで特徴量を作ることで、
賃料という立地×築年数×面積×その他要員という複雑な変数をモデルに理解させることができたと思っています。

最終予測結果はlightgbm、Kfold、k-meansのシードを1ずつ変えて
30シード×2モデル×10Foldの600個のモデルから作成しました。

k-meansのSeedによって大きく精度が変わってしまっていたのですが平均をとることで
大きくshakedownすることのない頑健な出力結果となりました。

チームでのコミニュケーション方法

チームメンバーはそれぞれ就活や修論で忙しかったため、
それぞれが進められるときに進めて行きました。

Github,Line,Trelloでやりとりを進めていたのですが、特にTrelloが便利でした。 25MB以下のファイルはほぼ無制限に共有できること、各人の取り組んでる内容、進捗状況がすぐにわかったので、非常にスムーズにコミニュケーションを取れました。

f:id:zerebom:20191108233928p:plain
使い倒されるtrello

参考にしたサイト

飯田コンペ上位手法 signate.jp

Lightgbmのパラメータ調整 nykergoto.hatenablog.jp

Kaggle本 Stacking, Validationの考えをしっかり学べました

感想・まとめ

良かった取り組み

  • チームを想定してコードを書いた
    早い段階でチームで関数の書き方にルールを作ったのでコードのマージが楽だった。(引数も返り値もtrain,testをまとめたDataFrameにする等)
    前処理担当、モデル担当、外部データ担当と分けることで責任感を持ちつつ作業ができた。

  • 一度使ったらおしまいのコードを書かないようにした。
    よく使う関数はクラス、関数化した(target_encoding,save_data,lgb_predictorなど)

  • Lightgbmのバージョンを上げる
    なんと精度が上がります

改善するべき取り組み

  • どんなコンペにも対応できる柔軟なPipelineコードを作っておく。
  • 実験のログをもっと綺麗にとる
  • lightgbmに詳しくなる(最後まで気づかなくて、max_depth=8,num_leaves=31とかだった)

まとめ

今までこれほど良い順位でコンペを終えられたことがなかったので嬉しい反面、
入賞する気概で取り組んでいたので9位という結果は非常に悔しいです。

個人で取り組むと、だれてしまったり諦めてしまいがちなコンペもチームでやればモチベーションも上がる上に、
他の人のアイデアから異なるアイデアが浮かんだりと、アンサンブル学習の威力を実感でき、非常に楽しかったです。

最後3日で順位が20位くらい上がったこともあり、停滞期で諦めないことも大事だなと思いました。 (とはいえ、上位の人たちはずっと上位だったので地力の差も感じました)

今後は今回学んだことをしっかり復習してKaggleでメダルを取れるように頑張っていきたいと思います。 また研究や、企業でデータ分析を生かして社会に貢献できるようにも頑張りたいです。

それでは最後までご覧いただきありがとうございました!

よければTwitterのフォローもよろしくお願いします( ^ω^ )

Pythonにおける勾配ブースティング予測モデルをラクチンに作成するラッパーを公開しました

こんばんは、ひぐです。

今回はPythonの勾配ブースティングライブラリを使いやすくしたラッパーについて紹介します。 今回紹介するラッパーを使うと以下のメリットがあります。

  • PandasのDataFrameといくつかの引数を渡すだけで予測結果が返ってくる
  • 本来はそれぞれ使い方が微妙に異なるライブラリを、全く同じ記法で使える
  • 出力した予測値を正解データとすぐに比較できる、可視化メソッドが使える
  • パラメータやValidationの分け方を簡単に指定できる
  • ターゲットエンコーディングが必要な場合、列と関数を渡すことで自動でリークしないように計算してくれる

機械学習を用いたデータ分析で必ず必要になる予測モデルを作成するプロセスですが、
これらをいつも同じ使い方で使用できるのは大きなメリットだと思います!

よければ是非使ってください!

使い方

用意するもの
- train/testデータ(DataFrame)
- ハイパーパラメータ(dict)

まず使用するハイパーパラメータを定義します。

from script import RegressionPredictor
cat_params = {

    'loss_function': 'RMSE',
    'num_boost_round': 5000,
    'early_stopping_rounds': 100,
}

xgb_params = {
        'num_boost_round':5000,
        'early_stopping_rounds':100,
        'objective': 'reg:linear',
        'eval_metric': 'rmse',
    }

lgbm_params = {
    'num_iterations': 5000,
    'learning_rate': 0.05,
    'objective': 'regression',
    'metric': 'rmse',
    'early_stopping_rounds': 100}

そしてインスタンスの呼び出し、学習します。

catPredictor = RegressionPredictor(train_df, train_y, test_df, params=cat_params,n_splits=10, clf_type='cat')
catoof, catpreds, catFIs=catPredictor.fit()


xgbPredictor = RegressionPredictor(train_df, train_y, test_df, params=xgb_params,n_splits=10, clf_type='xgb')
xgboof, xgbpreds, xgbFIs = xgbPredictor.fit()


lgbPredictor = RegressionPredictor(train_df, train_y, test_df, params=lgbm_params,n_splits=10, clf_type='lgb')
lgboof, lgbpreds, lgbFIs = lgbPredictor.fit()

rfPredictor = RegressionPredictor(train_df, train_y, test_df, sk_model=RandomForestRegressor(rf_params), n_splits=10, clf_type='sklearn')
rfoof, rfpreds, rfFIs = rfPredictor.fit()

これだけです!
fitの返り値はそれぞれ、trainの予測データ、testの予測データ、Feature Importanceのnumpy arrayです 。 Kaggleなどのデータ分析の場合、これらをcsvにするだけですぐに提出できるようになります。

予測値についてデータ可視化したい場合は以下のようにします。

lgbPredictor.plot_FI(50)
lgbPredictor.plot_pred_dist()

ソースコード

class RegressionPredictor(object):
    '''
    回帰をKfoldで学習するクラス。
    TODO:分類、多クラス対応/Folderを外部から渡す/predictのプロット/できれば学習曲線のプロット
    '''
    def __init__(self, train_X, train_y, split_y, test_X, params=None, Folder=None, sk_model=None, n_splits=5, clf_type='xgb'):
        self.kf = Folder(n_splits=n_splits)
        self.columns = train_X.columns.values
        self.train_X = train_X
        self.train_y = train_y
        self.test_X = test_X
        self.params = params
        self.oof = np.zeros((len(self.train_X),))
        self.preds = np.zeros((len(self.test_X),))
        if clf_type == 'xgb':
            self.FIs = {}
        else:
            self.FIs = np.zeros(self.train_X.shape[1], dtype=np.float)
        self.sk_model = sk_model
        self.clf_type = clf_type

    @staticmethod
    def merge_dict_add_values(d1, d2):
        return dict(Counter(d1) + Counter(d2))
   
    def rmse(self):
        return int(np.sqrt(mean_squared_error(self.oof, self.train_y)))
    
    def get_model(self):
        return self.model

    def _get_xgb_callbacks(self):
        '''nround,early_stopをparam_dictから得るためのメソッド'''
        nround=1000
        early_stop_rounds=10
        if self.params['num_boost_round']:
            nround=self.params['num_boost_round']
            del self.params['num_boost_round']

        if self.params['early_stopping_rounds']:
            early_stop_rounds=self.params['early_stopping_rounds']
            del self.params['early_stopping_rounds']
        return nround ,early_stop_rounds

    def _get_cv_model(self, tr_X, val_X, tr_y, val_y, val_idx):

        if self.clf_type == 'cat':
            clf_train =Pool(tr_X,tr_y)
            clf_val =Pool(val_X,val_y)
            clf_test =Pool(self.test_X)
            self.model=CatBoost(params=self.params)
            self.model.fit(clf_train,eval_set=[clf_val])
            self.oof[val_idx]=self.model.predict(clf_val)
            self.preds += self.model.predict(clf_test) / self.kf.n_splits
            self.FIs += self.model.get_feature_importance()

        elif self.clf_type == 'lgb':
            clf_train = lgb.Dataset(tr_X, tr_y)
            clf_val = lgb.Dataset(val_X, val_y, reference=lgb.train)
            self.model = lgb.train(self.params, clf_train, valid_sets=clf_val)
            self.oof[val_idx] = self.model.predict(val_X, num_iteration=self.model.best_iteration)
            self.preds += self.model.predict(self.test_X, num_iteration=self.model.best_iteration) / self.kf.n_splits
            self.FIs += self.model.feature_importance()

        elif self.clf_type == 'xgb':
            clf_train = xgb.DMatrix(tr_X, label=tr_y, feature_names=self.columns)
            clf_val = xgb.DMatrix(val_X, label=val_y, feature_names=self.columns)
            clf_test = xgb.DMatrix(self.test_X, feature_names=self.columns)
            evals = [(clf_train, 'train'), (clf_val, 'eval')]
            evals_result = {}

            nround,early_stop_rounds=  self._get_xgb_callbacks()
            self.model = xgb.train(self.params,
                                    clf_train,
                                    num_boost_round=nround,
                                    early_stopping_rounds=early_stop_rounds,
                                    evals=evals,
                                    evals_result=evals_result)

            self.oof[val_idx] = self.model.predict(clf_val)
            self.preds += self.model.predict(clf_test) / self.kf.n_splits
            self.FIs = self.merge_dict_add_values(self.FIs, self.model.get_fscore())

        elif self.clf_type == 'sklearn':
            self.model = self.sk_model
            self.model.fit(tr_X, tr_y)
            self.oof[val_idx] = self.model.predict(val_X)
            self.preds += self.model.predict(self.test_X) / self.kf.n_splits
            self.FIs += self.model.feature_importances_
        else:
            raise ValueError('clf_type is wrong.')

    def fit(self):
        for train_idx, val_idx in self.kf.split(self.train_X, self.train_y):
            X_train = self.train_X.iloc[train_idx, :]
            X_val = self.train_X.iloc[val_idx, :]
            y_train = self.train_y[train_idx]
            y_val = self.train_y[val_idx]
            self._get_cv_model(X_train, X_val, y_train, y_val, val_idx)
        print('this self.model`s rmse:',self.rmse())

        return self.oof, self.preds, self.FIs

    def plot_FI(self, max_row=50):
        plt.figure(figsize=(10, 20))
        if self.clf_type == 'xgb':
            df = pd.DataFrame.from_dict(self.FIs, orient='index').reset_index()
            df.columns = ['col', 'FI']
        else:
            df = pd.DataFrame({'FI': self.FIs, 'col': self.columns})
        df = df.sort_values('FI', ascending=False).reset_index(drop=True).iloc[:max_row, :]
        sns.barplot(x='FI', y='col', data=df)
        plt.show()
    
    def plot_pred_dist(self):
        fig, axs = plt.subplots(1, 2, figsize=(18, 5))
        sns.distplot(self.oof, ax=axs[1], label='oof')
        sns.distplot(self.train_y, ax=axs[0], label='train_y')
        sns.distplot(self.preds, ax=axs[0], label='test_preds')
        plt.show()

以上です!
未実装な部分はいっぱいあるので逐次修正していきたいと思います!
ゆくゆくは親クラスを作って、分類回帰でクラスを分けて継承していくみたいにしたいと思います。
こういうふうに実装した方がいいよなど知見があればコメント頂けたら幸いです。

最後まで読んでいただきありがとうございました~

Wantedlyの機械学習エンジニアインターンに3週間いってきました

ひぐです!8/19~9/6の期間にWantedly社でMLエンジニアコースで働かせていただきました!
f:id:zerebom:20190908180454j:plain
楽しかったのでブログを書きたいと思います。

志望動機と選考

魔法のスプレッドシートでやれること・日程・給与などなどを見比べながら決めました。

2019夏のITエンジニアインターンの情報が集まる魔法のスプレッドシート - Google スプレッドシート

メンターさんが1on1で付いてくれること、サービスを知っているからこそ裏側を知るのが楽しそうと言った理由も大きかったです。

選考はES->コーディングテスト->Skype面接でした。
コーディングテストはAtCorder ABCのBくらい?の難易度でした。

何をしたか

Wanteldy Peopleのユーザにタグをつける。

Wantedly Peopleという名刺管理アプリケーションの改善するためDeepLearningでなんとかすることになりました。

people.wantedly.com

はじめに抽象度の高い課題をいくつか提示していただいて、 サーバーからデータをクエリで拾い、データを見ながら、実現可能性がありそうなアプローチを考えていくという段階からスタートしました。

僕が取り組んだタスクは、ユーザの職業欄からタグをつけるというものです。 例えば職業欄が
[取締役執行委員 社長]/[CEO]/[社長 取締役]⇨[社長]タグを付与
[〇〇営業所 部長]/[セールスエンジニア]⇨[営業]タグを付与

のようになります。要は名寄せとかカテゴリ付与みたいなタスクです。

名寄せができれば以下のメリットがあります

  • ユーザがフォロワーを検索するときの足がかりになる
  • 広告などのレコメンドのターゲッティングに使える指標となる

しかし、この課題には以下のような障害がありました

  • ユーザ一人当たりの情報が少ない
    読み込まれた名刺の持ち主はPeopleのユーザではないことが殆ど、プロフィール文やフォロワーの分布から予測はできない

  • 表記揺れがマジで多い
    社長を表す表現一つにとっても、「社長」「取締役代表」「代表取締役社長」「CEO」みたいに色々な種類がある
    「取締役代表 補佐」とか「取締役 秘書」とかは取り除かないといけないなどの問題も

最終的には
事前学習モデルから分散表現を獲得し、名刺に含まれる単語の平均ベクトルを学習して予測を立てる」 というアプローチになりました。

簡単にいうと、単語の意味を表すベクトルを使って、そのベクトルの近さとかでグループ分けをしようって感じです。

分散表現についてはこちらのサイトでわかりやすく説明されていました。
deepage.net

職業が同じ単語(「社長」や「代表取締役」など)は意味空間でのベクトルが近いので同じタグを貼れるだろうというアプローチです。
前処理はこんな感じです。
f:id:zerebom:20190907193758p:plain

今回はアノテーションはルールベースで行いました。 例えば名刺に「医」「療」という単語が含まれていれば医療ラベルを付与といった感じです。

結果としては予測精度は高かったのですが、精度の高さ=ルールをどれだけ守っているかになってしまい、 オフラインでの評価は難しかったです。

一応ルールベースでのアノテーションに変わる方法を何個か提示しました。 時間があれば半教師あり学習とかもできたらなぁって感じで終わりました。

機械学習エンジニアとして働くことを体験して

Kaggleなどでは決められたタスクに対して高速で実装していく力などが求められますが、
実務では、どんなアプローチがあって、どのデータなら使えるかなど、課題設定から始めなくてはいけないと改めて気づかされました。
また評価指標やアノテーションの仕方も考える必要があって、やりがいがありました。 そういった意味ではアイデア力も必要ですし、アイデアを産むために数理的な知識も、ビジネスの知識も必要だなぁと痛感しました。

こう言ったことを知れたのはメンターの縣さんが何を持ち帰ってもらおうか考えてインターン生をサポートしてくださったからこそだと思います。
本当にありがとうございました!

できるようになったこと・学んだこと

  • GItHubを綺麗に使えるようになった
    レポジトリを見てWantedly社は相当GItHubを使いこなしているなという印象を受けました。 他のレポジトリなどを見ながら、コードレビューしてもらいやすいプルリクの作り方や、後世に遺産が伝わりやすいIssueの書き方などを知れました。

  • 便利なオープンソースコードを知れた インターンでFastTextやBERTなどの事前学習モデルをクローンして使うことがあったのですが、あんなにお手軽に再学習や、分散表現を手に入れらるとは思いませんでした。 特にFastTextはgensimのWord2Vecクラスは便利なメソッドが多くて、今後も使おうと思います。
    またBigQueryとかPlotlyとかも書けるようになりました。

  • 機械学習エンジニアのリアルを知れた
    上記に書いたとおりです。

Wantedlyインターンの印象

たしかに就業型インターンでした

取り組んだ課題がインターン生のために用意された課題ではなく、インターン生を一人の社員として見てくださっている感じがありました。 給料をいただいているし、メンターの方の時間も割いていただきながら仕事をするので、進捗産まなきゃ。。。みたいな緊張感はありましたが、詰まったらいつでも聞いてねという感じだったのでありがたかったです。

オフィスも街も綺麗

オフィスが白金台だしピカピカだしテンション上がりました。   f:id:zerebom:20190908180530j:plain

あと会議室の名前が全部ジョジョのスタンドの名前でウケました。 「ザ・ワールドで話そうか」みたいな。出てこれなくなっちゃいそう。

業務後は卓球もできました。
f:id:zerebom:20190909084544j:plain

同じタームの子と仲良くなった

席が近い子とは毎日昼ごはんに行ったり、シャッフルランチが毎週あったりで仲良くなれました。
ただインターン生全員と席が近いわけではないので、全員と話すには割と能動的に行動しないといけないかもです。

f:id:zerebom:20190909083540j:plain
打ち上げのボドゲ大会

インターンの選考対策にしたこと

自分も去年はこういうブログを見て、インターン行きてえ。。。!ってなってたのですが、
「おすすめなので是非行ってみてください!」
的な文章を見るたびに、いや受かる前提やんけ!みたいな気持ちになってました。

誰得だよって感じですが、自分なりにES/面接で気をつけたことを書きたいと思います。

  • 成果ベースで話す
    〇〇を勉強中です!みたいなことを推すのは弱いと思ったので、なるべく成果ベースで話しました。 アウトプットが社外に公開されるような会社で長期インターンをしつつ、大きな仕事があれば積極的に関われるようにコミニュケーションを取るととっかかりやすいかもです。 あとはKaggleとかもメダルは取れたわけではないのですが、ちゃんとSubmitして順位が出ているので伝えました。

  • 自分の関心領域と業務内容が近い(近そう)ということを語る
    当たり前ですが、業務内容のミスマッチは企業側も避けたいと思っているので、
    自分の経験や興味が企業に取り組む内容に近いことを推していくのは大事だと思います。
    株主総会向けのプレゼン資料とかに、企業のビジョンや今後注力する領域がわかりやすくまとめてあるのでオススメです。

  • やる気を見せる
    記述式の問題や、ESの文字数制限ないところは人よりたくさん書いたと思います。

  • 最強の技術力があれば関係ない
    それはそう

  • 応募しないと絶対受からないので、いっぱい出す
    それはそう

終わり

こういったインターン行くと成長の糧になることをいっぱい学べるし、
仲間やメンターさんとのつながりもできるし、本当に参加できてよかったと思ってます!

今回学んだことを研究や将来の職業でも活かせるように今後も頑張ります!
3週間ありがとうございましたー!

GitHubに画像解析用のKerasディレクトリを公開しました。

お久しぶりです、ひぐです!

大学院に入ってからニューラルネットワークを使って、医療画像の補完を行う研究をするようになりました。

そこで今日は自分が普段使ってる研究用のコードを紹介したいと思います!
結構KaggleやQiitaとかでNNライブラリ用のソースコードを検索すると、使い切りなコードが多くないですか?

今回は繰り返し実験できるようにディレクトリごとコードを公開しました!
モデルの保存や、出力結果の記録をクラス単位で実装しています。よかったら参考にしてください。

なかなか上手に書けない部分もあるので、ご教授いただければ幸いです。。。笑
というかまだまだ絶賛修正中なので温かく見守ってくださいw

URLはこちら github.com

コードの概要

全体的にはこんなイメージです。 f:id:zerebom:20190518110310j:plain

主な機能は以下のようになっています。
* main.pyを走らせると自動で、loss関数のグラフ、出力画像を自動作成
* batch size training rateなどのハイパーパラメータははmain.py の引数で渡せる
* データをジェネレーターで読み込むのでデータ量が多くなってもメモリエラーにならない
* 結果を出力するディレクトリに使用したModelの名前とlossの値を使用する(ので見やすい)

main.py

import いろいろ(省略)

INPUT_SIZE = (256, 256)
CONCAT_LEFT_RIGHT=True
CHANGE_SLIDE2_FILL = True
def train(parser):
    configs = json.load(open('./settings.json'))
    reporter = Reporter(parser=parser)
    loader = Loader(configs['dataset_path2'], parser.batch_size)
    
    if CHANGE_SLIDE2_FILL:
        loader.change_slide2fill()
        reporter.add_log_documents('Done change_slide2fill.')

    if CONCAT_LEFT_RIGHT:
        loader.concat_left_right()
        reporter.add_log_documents('Done concat_left_right.')


    train_gen, valid_gen, test_gen = loader.return_gen()
    train_steps, valid_steps, test_steps = loader.return_step()

    # ---------------------------model----------------------------------

    input_channel_count = parser.input_channel
    output_channel_count = 3
    first_layer_filter_count = 32

    network = UNet(input_channel_count, output_channel_count, first_layer_filter_count)
    model = network.get_model()

    model.compile(optimizer='adam', loss='mse')
    model.summary()

    # ---------------------------training----------------------------------
    batch_size = parser.batch_size
    epochs = parser.epoch

    config = tf.ConfigProto()
    config.gpu_options.per_process_gpu_memory_fraction = 0.9
    config.gpu_options.allow_growth = True
    sess = tf.Session(config=config)

    # fit_generatorのコールバック関数の指定・TensorBoardとEarlyStoppingの指定

    logdir = os.path.join('./logs', dt.today().strftime("%Y%m%d_%H%M"))
    os.makedirs(logdir, exist_ok=True)
    tb_cb = TensorBoard(log_dir=logdir, histogram_freq=1, write_graph=True, write_images=True)

    es_cb = EarlyStopping(monitor='val_loss', patience=parser.early_stopping, verbose=1, mode='auto')

    print("start training.")
    # Pythonジェネレータ(またはSequenceのインスタンス)によりバッチ毎に生成されたデータでモデルを訓練します.
    history = model.fit_generator(
        generator=train_gen,
        steps_per_epoch=train_steps,
        epochs=epochs,
        validation_data=valid_gen,
        validation_steps=valid_steps,
        # use_multiprocessing=True,
        callbacks=[es_cb, tb_cb])

    print("finish training. And start making predict.")

    train_preds = model.predict_generator(train_gen, steps=train_steps, verbose=1)
    valid_preds = model.predict_generator(valid_gen, steps=valid_steps, verbose=1)
    test_preds = model.predict_generator(test_gen, steps=test_steps, verbose=1)

    print("finish making predict. And render preds.")

    # ==========================report====================================
    reporter.add_val_loss(history.history['val_loss'])
    reporter.add_model_name(network.__class__.__name__)
    reporter.generate_main_dir()
    reporter.plot_history(history)
    reporter.save_params(parser, history)

    input_img_list = []
    # reporter.plot_predict(train_list, Left_RGB, Right_RGB, train_preds, INPUT_SIZE, save_folder='train')
    reporter.plot_predict(loader.train_list, loader.Left_slide, loader.Left_RGB,
                          train_preds, INPUT_SIZE, save_folder='train')
    reporter.plot_predict(loader.valid_list, loader.Left_slide, loader.Left_RGB,
                          valid_preds, INPUT_SIZE, save_folder='valid')
    reporter.plot_predict(loader.test_list, loader.Left_slide, loader.Left_RGB,
                          test_preds, INPUT_SIZE, save_folder='test')
    model.save("model.h5")


def get_parser():
    parser = argparse.ArgumentParser(
        prog='generate parallax image using U-Net',
        usage='python main.py',
        description='This module generate parallax image using U-Net.',
        add_help=True
    )

    parser.add_argument('-e', '--epoch', type=int,
                        default=200, help='Number of epochs')
    parser.add_argument('-b', '--batch_size', type=int,
                        default=32, help='Batch size')
    parser.add_argument('-t', '--trainrate', type=float,
                        default=0.85, help='Training rate')
    parser.add_argument('-es', '--early_stopping', type=int,
                        default=20, help='early_stopping patience')

    parser.add_argument('-i', '--input_channel', type=int,
                        default=7, help='input_channel')

    parser.add_argument('-a', '--augmentation',
                        action='store_true', help='Number of epochs')

    return parser


if __name__ == '__main__':
    parser = get_parser().parse_args()
    train(parser)

ディレクトリのパスはsetting.jsonで一括管理しています。
trainという巨大な関数にargparserで引数を渡して、ハイパーパラメータを用いています。
自分の研究では、入力、教師データどちらにも画像を使うため独自のジェネレータを作成しています。

repoter.py

importあれこれ

class Reporter:
    ROOT_DIR = "Result"
    IMAGE_DIR = "image"
    LEARNING_DIR = "learning"
    INFO_DIR = "info"
    MODEL_DIR = "model"
    PARAMETER = "parameter.txt"
    IMAGE_PREFIX = "epoch_"
    IMAGE_EXTENSION = ".png"
    
    def __init__(self, result_dir=None, parser=None):
        self._root_dir = self.ROOT_DIR
        self.create_dirs()
        self.parameters = list()
    # def make_main_dir(self):

    def add_model_name(self, model_name):
        if not type(model_name) is str:
            raise ValueError('model_name is not str.')

        self.model_name = model_name
    def add_val_loss(self, val_loss):
        self.val_loss = str(round(min(val_loss)))

    def generate_main_dir(self):
        main_dir = self.val_loss + '_' + dt.today().strftime("%Y%m%d_%H%M") + '_' + self.model_name
        self.main_dir = os.path.join(self._root_dir, main_dir)
        os.makedirs(self.main_dir, exist_ok=True)

    def create_dirs(self):
        os.makedirs(self._root_dir, exist_ok=True)

    def plot_history(self,history,title='loss'):
        # 後でfontsize変える
        plt.rcParams['axes.linewidth'] = 1.0  # axis line width
        plt.rcParams["font.size"] = 24  # 全体のフォントサイズが変更されます。
        plt.rcParams['axes.grid'] = True  # make grid
        plt.plot(history.history['loss'], linewidth=1.5, marker='o')
        plt.plot(history.history['val_loss'], linewidth=1., marker='o')
        plt.tick_params(labelsize=20)

        plt.title('model loss')
        plt.xlabel('epoch')
        plt.ylabel('loss')
        plt.legend(['loss', 'val_loss'], loc='upper right', fontsize=18)
        plt.tight_layout()

        plt.savefig(os.path.join(self.main_dir, title + self.IMAGE_EXTENSION))
        if len(history.history['val_loss'])>=10:
            plt.xlim(10, len(history.history['val_loss']))
            plt.ylim(0, int(history.history['val_loss'][9]*1.1))

        plt.savefig(os.path.join(self.main_dir, title +'_remove_outlies_'+ self.IMAGE_EXTENSION))

    def add_log_documents(self, add_message):
        self.parameters.append(add_message)


    
    def save_params(self,parser,history):
        
        #early_stoppingを考慮
        self.parameters.append("Number of epochs:" + str(len(history.history['val_loss'])))
        self.parameters.append("Batch size:" + str(parser.batch_size))
        self.parameters.append("Training rate:" + str(parser.trainrate))
        self.parameters.append("Augmentation:" + str(parser.augmentation))
        self.parameters.append("input_channel:" + str(parser.input_channel))
        self.parameters.append("min_val_loss:" + str(min(history.history['val_loss'])))
        self.parameters.append("min_loss:" + str(min(history.history['loss'])))

        # self.parameters.append("L2 regularization:" + str(parser.l2reg))
        output = "\n".join(self.parameters)
        filename=os.path.join(self.main_dir,self.PARAMETER)

        with open(filename, mode='w') as f:
            f.write(output)

    @staticmethod
    def get_concat_h(im1, im2):
        dst = Image.new('RGB', (im1.width + im2.width, im1.height))
        dst.paste(im1, (0, 0))
        dst.paste(im2, (im1.width, 0))
        return dst

    def plot_predict(self, img_num_list, Left_RGB, Right_RGB, preds, INPUT_SIZE, max_output=20,save_folder='train'):
        if len(img_num_list) > max_output:
            img_num_list=img_num_list[:max_output]
        for i, num in enumerate(img_num_list):
            if i == 1:
                print(preds[i].astype(np.uint8))
                        
            pred_img = array_to_img(preds[i].astype(np.uint8))
           
            train_img = load_img(Left_RGB[num], target_size=INPUT_SIZE)
            teach_img = load_img(Right_RGB[num], target_size=INPUT_SIZE)
            concat_img = self.get_concat_h(train_img, pred_img)
            concat_img = self.get_concat_h(concat_img, teach_img)
            os.makedirs(os.path.join(self.main_dir,save_folder), exist_ok=True)
            array_to_img(concat_img).save(os.path.join(self.main_dir, save_folder, f'pred_{num}.png'))

データの保存を担っています。
Kerasではfit関数を動かすとその返り値にhistoryオブジェクトという出力のログが入ったインスタンスを返します。
これと、parserをmain.pyから受け取ってデータを保存しています。
保存先はResult dir以下に、使用パラメータ・出力結果・lossグラフなどをまとめて格納します。

f:id:zerebom:20190518112328p:plain

loader.py

import あれこれ

config = json.load(open('./settings.json'))


class Loader(object):
    # コンストラクタ
    def __init__(self, json_paths, batch_size, init_size=(256, 256)):
        self.size = init_size
        self.DATASET_PATH = json_paths
        self.add_member()
        self.batch_size = batch_size



    # def define_IO(self):
    def add_member(self):
        """
        jsonファイルに記載されている、pathをクラスメンバとして登録する。
        self.Left_RGBとかが追加されている。
        """
        for key, path in self.DATASET_PATH.items():
            setattr(self, key, glob.glob(os.path.join(path, '*png')))
    
    #左右の画像を結合してデータを二倍にする
    def concat_left_right(self):
        self.Left_slide += self.Right_slide
        self.Left_RGB += self.Right_RGB
        self.Left_disparity += self.Left_disparity
        self.Right_disparity += self.Right_disparity
        self.Left_bin += self.Left_bin
        self.Right_bin += self.Right_bin
        print('Done concat_left_right.')
    
    #入力で使う画像を平均値で埋めた画像にする
    def change_slide2fill(self):
        self.Left_slide = self.Left_fill
        self.Right_slide = self.Right_fill


    def return_gen(self):
        self.imgs_length = len(self.Left_RGB)
        # self.train_paths = (self.Left_slide, self.Right_disparity, self.Left_disparity)
        # sel = self.Left_RGB
        self.train_list, self.valid_list, self.test_list = self.train_valid_test_splits(self.imgs_length)
        self.train_steps = math.ceil(len(self.train_list) / self.batch_size)
        self.valid_steps = math.ceil(len(self.valid_list) / self.batch_size)
        self.test_steps = math.ceil(len(self.test_list) / self.batch_size)
        self.train_gen = self.generator_with_preprocessing(self.train_list, self.batch_size)
        self.valid_gen = self.generator_with_preprocessing(self.valid_list, self.batch_size)
        self.test_gen = self.generator_with_preprocessing(self.test_list, self.batch_size)
        return self.train_gen, self.valid_gen, self.test_gen

    def return_step(self):
        return self.train_steps, self.valid_steps, self.test_steps

    @staticmethod
    def train_valid_test_splits(imgs_length: 'int', train_rate=0.8, valid_rate=0.1, test_rate=0.1):
        data_array = list(range(imgs_length))
        tr = math.floor(imgs_length * train_rate)
        vl = math.floor(imgs_length * (train_rate + valid_rate))

        random.shuffle(data_array)
        train_list = data_array[:tr]
        valid_list = data_array[tr:vl]
        test_list = data_array[vl:]

        return train_list, valid_list, test_list

    def load_batch_img_array(self, batch_list, prepro_callback=False,use_bin=True):
        teach_img_list = []
        input_img_list = []
        for i in batch_list:
            LS_img = img_to_array(
                load_img(self.Left_slide[i], color_mode='rgb', target_size=self.size)).astype(np.uint8)
            LD_img = img_to_array(
                load_img(self.Left_disparity[i], color_mode='grayscale', target_size=self.size)).astype(np.uint8)
            RD_img = img_to_array(
                load_img(self.Right_disparity[i], color_mode='grayscale', target_size=self.size)).astype(np.uint8)

            if use_bin:
                LB_img = img_to_array(
                    load_img(self.Left_bin[i], color_mode='grayscale', target_size=self.size)).astype(np.uint8)
                # LB_img=np.where(LB_img==255,1,LB_img)

                RB_img = img_to_array(
                    load_img(self.Right_bin[i], color_mode='grayscale', target_size=self.size)).astype(np.uint8)
                # RB_img = np.where(RB_img == 255, 1, RB_img)

                input_img = np.concatenate((LS_img, LD_img, RD_img, LB_img, RB_img), 2).astype(np.uint8)
            else:
                input_img=np.concatenate((LS_img, LD_img, RD_img), 2).astype(np.uint8)


            teach_img = img_to_array(
                load_img(self.Left_RGB[i], color_mode='rgb', target_size=self.size)).astype(np.uint8)
               
            input_img_list.append(input_img)
            teach_img_list.append(teach_img)

        return np.stack(input_img_list), np.stack(teach_img_list)

    def generator_with_preprocessing(self, img_id_list, batch_size):#, *input_paths
        while True:
            for i in range(0, len(img_id_list), batch_size):
                batch_list = img_id_list[i:i + batch_size]
                batch_input, batch_teach = self.load_batch_img_array(batch_list)

                yield(batch_input, batch_teach)

class DataSet:
    pass

Data dirからデータ(画像)を読み取ってmain.pyにジェネレータ形式で渡します。
このコードは特にまだまだ改善の余地があります...

実験ごとに入力チャンネル数を変えたいのですが、
ジェネレータに読みだした後、それらを結合するとことが難しく、悩んでいます。

jsonからディレクトリのパスを受け取って、その直下の画像ファイルをすべてクラスメンバにして
読み込むようにしているのがおしゃれポイントです

        for key, path in self.DATASET_PATH.items():
            setattr(self, key, glob.glob(os.path.join(path, '*png')))

おわりに

ザックリですが以上になります!
わからないところや修正したほうがいいと思う部分がありましたら、連絡いただけたら幸いです!

今後は他の人でも使えるように、どんなタスクでも使えるように、調整して再度公開したいです。

就職したときにも、恥ずかしくないようにきれいで再利用性の高いコードをかけるように頑張っていきたいです!
では~

Santanderコンペで学ぶ、EDA(探索的データ解析)の手法

お久しぶりです、ひぐです!

f:id:zerebom:20190412103328p:plain  

先日、Kaggleの Santander Customer Transaction Predictionコンペティション(以下、Santanderコンペ)
に参加したのですが、凄惨な結果に終わってしまいました。。。

  そこで今回の記事では自分の復習もかねて、上位の方の解法をまとめつつ
データ分析に必要なEDAの手法を紹介していきたいと思います!  

この記事を読むのに必要な前提知識

  • Kaggleとは?
    インターネット上で開催されているデータ分析コンペティション
    企業が公開したデータセットを前処理、モデル構築を行い、
    予測データを作成し、その精度で競います。

くわしくは↓
www.codexa.net

  • EDAとは?
    Explanatory Data Analysis の略。日本語で言うと、探索的データ解析。
    データの特徴や構造を理解するためにグラフを作成し、
    特徴量の相関やターゲットとの関係性を調べることです。

くわしくは↓

www.codexa.net

Santanderコンペってどんなコンペ?

f:id:zerebom:20190412105118p:plain

Santanderというアメリカの銀行が開いたコンペ。

レーニング・テストデータどちらも
20万行、かつ200列の特徴量から構成されている。
特定の取引を行った行にtarget==1が付与され、それ以外にtarget==0が付与されている。

全ての特徴量がfloat型かつvar_iと命名されていて、どんな特徴量か分からない様に匿名化されている。
f:id:zerebom:20190412105125p:plain

どんなことに気づけばスコアが伸びたのか

このコンペでは大きく分けて、

  • testデータにある、計算に用いられない偽データの存在
  • 逆向きになっている列の存在(0行目の値が200000行目の値、1行目→199999,2→199998...)
  • 値が重複しているかどうかの特徴量
  • 各列が独立であるということ

これらに気づけた場合、精度があげることが出来たそうです(どうやってわかるんだよ

これらを踏まえてEDAしていきましょう。

1位の方の解法はこちら↓
#1 Solution | Kaggle

EDAの手法紹介

基礎編(どんなコンペでも用いるEDA)

基礎編ではこの方のKernelを参考にしました。ありがとうございます。
https://www.kaggle.com/gpreda/santander-eda-and-prediction

0.ライブラリimport

import gc
import os
import logging
import datetime
import warnings
import numpy as np
import pandas as pd
import seaborn as sns
import lightgbm as lgb
from tqdm import tqdm_notebook
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error
from sklearn.metrics import roc_auc_score, roc_curve
from sklearn.model_selection import StratifiedKFold
warnings.filterwarnings('ignore')

1.データ読み込み

import feather as ftr
train = ftr.read_dataframe("../data/input/train.feather")
test = ftr.read_dataframe("../data/input/test.feather")

read_csvより早いので、feather形式で読み込みます。 こちらを参考にさせていただきました↓
Kaggleで使えるFeather形式を利用した特徴量管理法 - 天色グラフィティ

2.データの大きさ、最初の数行確認

print(train.shape)
train.head()

3.各列の欠損値を確認

def missing_data(data):
    #欠損値を含む行の合計数
    total = data.isnull().sum()
    #欠損値を含む行の合計(%表示)
    percent = (data.isnull().sum()/data.isnull().count()*100)
    tt = pd.concat([total, percent], axis=1, keys=['Total', 'Percent'])
    types = []
    for col in data.columns:
        dtype = str(data[col].dtype)
        types.append(dtype)
    #各列の型を確認
    tt['Types'] = types
    #行列を入れ替える

missing_data(train)

結果↓
f:id:zerebom:20190412193310p:plain
欠損値は全くなく、すべての特徴量がfloat64です。

4.列ごとの統計量の確認

train.describe()

結果
f:id:zerebom:20190412193812p:plain

target=1が全体の10%ほどという、不均衡データであることを確認しておきましょう。

5.各特徴量のtarget==0の行,target==1の行の分布比較

def plot_feature_distribution(df1, df2, label1, label2, features):
    i = 0
    sns.set_style('whitegrid')
    plt.figure()
    fig, ax = plt.subplots(10,10,figsize=(18,22))

    for feature in features:
        i += 1
        plt.subplot(10,10,i)
        sns.kdeplot(df1[feature], bw=0.5,label=label1)
        sns.kdeplot(df2[feature], bw=0.5,label=label2)
        plt.xlabel(feature, fontsize=9)
        locs, labels = plt.xticks()
        plt.tick_params(axis='x', which='major', labelsize=6, pad=-6)
        plt.tick_params(axis='y', which='major', labelsize=6)
    plt.show();

t0 = train.loc[train['target'] == 0]
t1 = train.loc[train['target'] == 1]
features = train.columns.values[2:102]
plot_feature_distribution(t0, t1, '0', '1', features)

結果↓
f:id:zerebom:20190412194118p:plain

こちらで使っているseabornのkdeplotはカーネル密度推定と呼ばれるものです。
分布の密度関数推定に使われる、ノンパラメトリック推定の一種です。
簡単に説明すると、ヒストグラムと同じような分布の推定方法だけど、滑らかになるように操作を加えています(雑)
詳しくはこちらなどを参考にしてください。
http://www.ocw.titech.ac.jp/index.php?module=General&action=DownLoad&file=200927244-21-0-19.pdf&type=cal&JWC=200927244

6.trainとtestデータの各行の平均値の分布を比較

plt.figure(figsize=(16,6))
features = train_df.columns.values[2:202]
plt.title("Distribution of mean values per row in the train and test set")
sns.distplot(train_df[features].mean(axis=1),color="green", kde=True,bins=120, label='train')
sns.distplot(test_df[features].mean(axis=1),color="blue", kde=True,bins=120, label='test')
plt.legend()
plt.show()

結果↓
f:id:zerebom:20190412200923p:plain

train,testの分布がほとんど同じということが分かります。
.mean(axis=1)の部分を.mean(axis=0)にすれば列の平均値を集約し、分布の推定が行われます。
他にも、std,min,max,skew,kurtosisなどとすることで、標準偏差、最小値、最大値、尖度、歪度などの分布も確認できます。

train,とtestの分布が異なる場合は、適切にバリデーションを行わないと、
過学習を起こしてしまうので要注意です。

u++さんの記事が非常にわかりやすいのでよかったら参考にしてください。
upura.hatenablog.com

これを応用して、trainデータをtarget==0,target==1で分けて同様に分布を確認することが出来ます。

#分け方↓
t0 = train.loc[train['target'] == 0]
t1 = train.loc[train['target'] == 1]

7.重複の存在する特徴量の確認

features = train.columns.values[2:202]
unique_max_train = []
unique_max_test = []
for feature in features:
    values = train[feature].value_counts()
    unique_max_train.append([feature, values.max(), values.idxmax()])
    values = test[feature].value_counts()
    unique_max_test.append([feature, values.max(), values.idxmax()])

np.transpose((pd.DataFrame(unique_max_train, columns=['Feature', 'Max duplicates', 'Value'])).\
            sort_values(by = 'Max duplicates', ascending=False).head(15))

結果↓
f:id:zerebom:20190412202108p:plain

これが非常に大切だったのです...(のちに紹介)

8.各列ごとの相関係数確認

#各列の相関係数を求め、絶対値を取り、行列を1列に直し、相関係数の大きさで並べ替え、indexを再度付与
correlations = train[features].corr().abs().unstack().sort_values(kind="quicksort").reset_index()
#同じ列同士の行は消去
correlations = correlations[correlations['level_0'] != correlations['level_1']]
correlations.head(10)

結果↓
f:id:zerebom:20190412202851p:plain

非常に相関係数が小さいことが分かります。
このことから、それぞれの特徴量は独立に扱ってよいという考えに至れると、
新たな学習方法を行うことが出来ます。

9.各特徴量毎のtarget==0,target==1の散布図。

train.loc[train['target']==0]['var_180'].plot(figsize=(30,30),style='.')
train.loc[train['target']==1]['var_180'].plot(figsize=(30,30),style='.')

結果↓
f:id:zerebom:20190412203426p:plain

このKernelを参考にしました。
In Search of Weirdness | Kaggle

横軸が行番号、縦列が各要素の値です。 青がtarget==0,オレンジがtarget==1です。

自分は各特徴量毎にこの散布図を作成し、なめるように見ていました。
ここでは、0.48付近にtarget==1が多く含まれていて、効きそうな特徴量が作れそうですが、
この図だけでは青がどれだけ密集しているか詳しくはわからないので要注意です。

手法5で確認を行えばよいですが、

plt.figure(figsize=(30,30))
sns.kdeplot(train.loc[train['target']==1]['var_108'], bw=0.2)
sns.kdeplot(train.loc[train['target']==0]['var_108'], bw=0.2)

とすることで、特徴量毎に大きく表示し、確認することもできます。
f:id:zerebom:20190412204322p:plain

応用編(Santanderで役に立つEDA)

それぞれ長くなりそうなので、別の記事にしました。

  1. 各特徴量を独立と仮定した修正ナイーブベイズ法によるtarget確率分布推定

初めて、本気で参加したコンペについての感想

Santanderコンペについて

欠損値もなく、データも大きくなく、すべて数値型だったので取り組みやすい!
と思って始めたのですが、実際は人と差をつけることが難しく、スコアも伸びず大変でした…

LBスコアが非常に密集していて、
kernelで公開されている、0.901スコア代の人が3000人くらいいたのが特徴的でした。

自分の足りなかった部分

  • 着手し始めるのが遅すぎた。
  • NNライブラリを扱えず、アンサンブルができなかった
  • モデルの理解度が低く、LGBMでは効かない特徴量を作ってしまった。
  • 英語力が足りず、discussionから必要な情報を収集しきれなかった。
  • discussionに書いてあることをデータにうまく落とし込めなかった。
    EX)各列が無相関→各列を縦に連結し、(4000万行,2列)のデータセットにするなど

参加して得られたこと

  • Kaggleにしっかり参加するときの流れがつかめた。

u++さんが公開してくださった、ディレクトリを用いて
デバッグ、特徴量の追加削除を自由にできる環境でのKaggleを行うことが出来るようになった。

upura.hatenablog.com

  • のちのコンペに生かせそうな関数を知れた
    EDA等、dfを引数にした汎用的な関数をいくつか知ることが出来たので、今後のコンペに対してスムーズに動生きだせそう。

  • やるき
    何の成果も得られず悔しかったので、次はやってやるぞという気持ちになれた。

   

機械学習を基礎から見直すためにPRML(パターン認識と機械学習)の学習を始めてみた話

 

f:id:zerebom:20190115220651j:plain


こんばんは、ひぐです。

 卒論と学会論文とインターン(楽しい)のプロジェクトがかぶって、泡吹いています

 

今日はPRMLをやっていくぞという話をします。

 

難しい難しいとさんざん言われていますが、

数学苦手な自分なりに取り組み方がすこしわかってきたので、他の人の参考になればなと思います。

 まだ、やっている途中なので間違った表記や表現が含まれる可能性があります。(T_T)

 

自分のスペック

・地方国立大4年

・入学後は3年生の秋までサークル漬けでした

・学部はゴリゴリの理系(工学・情報系)だったので大学の

 線形、微積分、確率はやったことはある(一夜漬けばっかでしたが)

・数学は結構苦手(センター数Ⅱ54点…)

・いろいろな参考書を買うけど、どれも味見程度(はじパタ、0からDL、達人サイエンティストによる理論と実践などなど…)

 

PRMLってなに?

これです。下巻もあります。

 

割と網羅的に理論を追って機械学習について根っこから解説されており、

名著と呼ぶ方も多くいらっしゃいます。

 

ただ、線形代数、確率論、微積分などの計算量がかなり多く、挫折者を多く生んでいるとか汗

なんで始めようと思ったの?

 

自分は5月ごろから機械学習の勉強を始めました。

 今まではいろいろな参考書やQiitaをちょっとずつかじったり、

kaggleをすこーしだけやって勉強していました。

 

知識が飛び飛びで難しいモデルに対してはよくわかんないけどとりあえずコードコピペしちゃえ!

みたいな感じだったのです。

 

しかしこれではいつまでたっても本質的な力はつかないなと思い腹をくくって始めました。

 

あとは、自分は勉強をかなり適当に済ませて、やった気になる癖があるので

2019年はそういうのをなくしていきたい!と思い、重めの本を腰を据えてやることにしました。

 

 

 

どうやって進めているの?

今の自分だとこの本だけだと、かなり厳しいので随所調べながら進めています。

仮にも理系の4年生だというのに出来ないのは悲しいですが、今までのツケを支払っていきたいと思います;;

 

具体的には一つずつ数式を追っていき、理解できない式展開が行われた場合は手計算をしてみるといった感じです。

1ページに30~50分くらいかかるときもあります。

 

有名な本で勉強会が多く開かれているため、slideshareなどの資料を参考にすると良いと思います。

 

また、過去にこの本を読み進めるにあたって必要な数学の知識をPDFに

まとめてくださったかたがいらっしゃいます。(神)

 

こちらも参考にしてみてはいかがでしょうか。

パターン認識機械学習の学習という名前ですw)

d.hatena.ne.jp

 

また、PRMLを進める前にベイズについて勉強しといたほうがいいよといわれたので、事前にこの本でベイズ統計については学んでいました。

 

この本はかなり丁寧なのでおすすめです。僕的には結構難しかったです。

 

 

 

現在の進捗

1章をサラッと進め、2章をじっくりやっています。

2.3くらいまでは終わりました。

 

今年度中には終わらせたいです。

 

感想

確かに難しいですが、きっちりやればかなり力がつくと思います。

 

そして今まで何となくの理解だった部分が腑に落ちた!

ってなる部分がいっぱいあります。

 

例えば、

多次元曲線で過学習が起きるのは各係数が非常に大きくなるからとか、

ガウス分布最尤推定は、MSEの最小化と一致するとか

ベータ分布は二項分布の事前分布として導入するとすっきりする、とか…!

(↑このあたり間違ってたらごめんなさい)

 

kaggleや最新の研究では主流じゃないようなすこし古いモデルや、手法も出てくるので

効率的でないと思った節もあったのですが、

 

この本の意義としては、

機械学習、深層学習で必要になってくる数学の知識と理論を一定水準まで引き上げられる。

 

ことだと思います。ですので、式展開とかもちゃんと追っていきたいです。

この本が読み終われば、大抵の論文の式変形などにウッってならなくなりそうです。

 

 

 

 

 

 

まとめ

 

サクッっと書かせていただきました。

 研究室の先生にはいつも

「わかるとは『人にわかりやすく説明できる』こと」

と言われているので、とりあえず流して終わったぞ~ってならない様にブログで

アウトプットできたらなと思います💦

 

そんなこんなでやっていくぞという気持ちです、途中でやめない様に記事にしました。

一緒に勉強してくれる人も募集しております。

 

では~

 

 

 

 

 

 

 

google-site-verification: google1c6f931fc8723fac.html