ふぁむたろうのブログ

機械学習系のお話やポエムを投稿します

論文: Objects as Points

arxiv.org

先日下記の2イベントでこの論文について発表させていただきました。

lpixel.connpass.com

d3m.connpass.com

上記のいずれも発表された方々ががっつり読み込まれていて、 非常に有意義な議論を交わすことができました!

資料も一応2通り作りましたが、前半は 手抜き LT用でしたので後半の資料を見ていただければ幸いです。

speakerdeck.com

speakerdeck.com

この論文で提案されている CenterNet(同名の他のネットもあるので注意) は CornerNet の派生形になります。 ですが CornerNet がボックスの対角の 2点を予測するのに対し、CenterNet では中心のみを予測します。(検出の場合) 発想としてはむしろ自然であり、Backbone(DLA・Hourglass) から後は非常にシンプルなネットワークです。 これで YOLOv3 よりも速くて精度が高いらしいので、Object Detection 周りの進歩も非常に速いことが伺えます。

Tensorコアを使った PyTorch の高速化について

faster-python.connpass.com

上記の Faster Python Meet up LT会#1 で発表させていただいた時の資料です。

speakerdeck.com

Tensorコアはまだ NVIDIAGPUの一部にしか搭載されていないのですが、 今後もっと普及したら役に立つかもしれません(その頃にはフレームワーク側でよしなに対応してくれそうですが…)。

論文: M2Det: A Single-Shot Object Detector based on Multi-Level Feature Pyramid Network

注意

  • 元論文:https://qijiezhao.github.io/imgs/m2det.pdf
    • 本記事中の図はここを参照しています
  • この解説は元論文の Google 日本語訳に対し毛が生えたようなものです
    • ですので可能な限り元論文を読むことを推奨します
    • 綺麗にまとめた訳ではないので似たような文が何度か繰り返される箇所があります
    • 真剣に読んでまとめましたが間違えてる可能性はあります。マサカリください
  • Experiments, Discussion, Conclusion については本記事では扱ってません
    • M2Det って何?構造上これまでのモデルと何が違うのか?をしっかりまとめたかったので
    • Experiments は後日追記するかもしれません

導入

2018年の物体検出では、速度精度の両方を見た場合 YOLOv3 が最もインパクトが大きかったと思います。 特に機械学習触ったことがない人にとって比較的導入しやすい(導入しやすいとは言っていない)のもポイントだったと思います。

そんな YOLOv3 に対し M2Det は もっと速くてもっと精度良い らしいです。 元論文は AAAI2019 にも採択されたらしいです。 今回はそんな M2Det の論文を頭からしっかり読んでいきます。

f:id:fam_taro:20190219202350p:plain:w600

  • 詳細な論文の訳が知りたい人は下記の内訳を確認してください。
  • scale と level について
    • 論文内では scale と level の 2つの観点で feature を分けています
      • scale: 画像内での大きさ、scale が大きい -> 画像内で対象の占める面積が大きい
      • level: 特徴としての抽象度や複雑度
        • level が低い feature -> より単純な特徴
        • level が高い feature -> より複雑な特徴や、抽象度が高い特徴
  • base feature について
    • この論文内での base feature は、backbone となるネットワーク (VGG16 とか) から得た feature を FFMv1 モジュールによって統合したものです

ぶっちゃけ何がすごいの?

  • YOLOv3 より速くて精度良い
  • multi-level な特徴を multi-scale に扱えるようにした
  • 過去のメジャーなモデルほとんでについて実験してる
    • すごすぎる
    • ここまでやれば AAAI 通るのか…

論文内訳

1. 著者情報

  • 著者一覧
    • Qijie Zhao(1)
    • Tao Sheng(1)
    • Yongtao Wang(1)
    • Zhi Tang(1)
    • Ying Chen(2)
    • Ling Cai2 and Haibin Ling(3)
  • 著者所属
    • 1: Institute of Computer Science and Technology, Peking University, Beijing, P.R. China
    • 2: AI Labs, DAMO Academy, Alibaba Group
    • 3: Computer and Information Sciences Department, Temple University

中国すご…

2. Abstract

  • Feature pyramids めっちゃ流行ってるよな
    • one-stage object detectors (e.g., DSSD, RetinaNet, RefineDet)
    • two-stage object detectors (e.g., Mask R-CNN, DetNet)
  • でも上記のモデルたちは backbone のタスク(classification) 用のマルチスケール feature 使ってるからある程度制限かかってるよな?
    • 🤔
  • 俺らは "Multi-Level Feature Pyramid Network (MLFPN)" を提案するぜ
    • 上記より detection に特化した feature pyramids だぜ
  • 以下 3 step で作るぜ
    • base feature として extract した fuse multi-level features (i.e. multiple layers) を融合する
    • 以下2つ
      • base feature を次の2つを組み合わせたブロックに入れる
        • Thinned U-shape Modules
        • Feature Fusion Modules
      • また U-shape module の decoder layers は Object detection 時に活用する
    • 同じ scale(size) の decoder layer を集約して Detection 用の feature pyramid を作る
      • どの feature map も multiple-level な layer(feature) を含む
      • だから精度上がってる???
  • 上記の MLFPN を SSD アーキテクチャに組み込んだものを M2Det と呼ぶことにするわ
  • 性能は one-stage detector 間で SOTA だぜ
    • MS-COCO benchmark
    • AP of 41.0 at speed of 11.8 FPS with single-scale inference strategy
    • AP of 44.2 with multi-scale inference strategy
    • 上記2つの strategy の違いは???
  • コードは GitHub - qijiezhao/M2Det: M2Det: A Single-Shot Object Detector based on Multi-Level Feature Pyramid Network にあるぜ
    • 4月頃コードが載るらしい?

3. Introduction

  • Object detection において scale variation はメジャーなテーマ
  • scale variation は大きく 2つの戦略がある
    • 1: image pyramid
      • いろいろなサイズに resize してコピーしたイメージ群等
      • test 時にしかできないぞ
      • もちろんメモリ使用量や計算量は増えるぞ
      • よって効率はめっちゃ悪くなるぞ
    • 2: detect objects in a feature pyramid extracted from the input image
      • train 時も test 時も使えるぞ
      • 1 に比べてメモリ使用量や計算量が少ないぞ
      • feature pyramid は SOTA detection model への統合が楽にできるぞ

f:id:fam_taro:20190219202527p:plain:w600
これまでの代表的な検出モデル

  • SSD, FPN, RetinaNet, Mask R-CNN 強いよな
  • でもクラス分類用に設計された feature pyramid のせいで限界あるよな
    • 例えば SSD は backbone(VGG16) の層を 2つそのまま使ってる
    • STDN は DenseNet の最後の dense block を使って feature pyramid を作ってる
      • pooling
      • scale-transfer operations
    • FPN は top-down に 浅い層と深い層を融合して feature pyramid を作ってる
  • 一般に上記の feature pyramid 作成方法は 2つの限界がある
    • 1: pyramid 内の feature map の表現力が十分でない
      • Object detection 用として考えると
      • Base net は object classification 用に設計したからね
    • 2: pyramid 内の各 feature map は、主に single-level layer in backbone から構成されている
      • 各 feature map は特定範囲サイズの object detection 用
      • そのため各 feature map は主に single-level の情報のみ含んでいる
  • 一般に high-level feature と low-level feature は以下の違いがある
    • high-level
      • 分類サブタスクに役立つ
      • 複雑な見た目の物体を特徴づけるのに役立つ
    • low-level
      • 物体位置回帰サブタスク(Object Location Regression) に役立つ
      • 単純な見た目の物体を特徴づけるのに役立つ
  • 実際にはサイズが同じでも見た目がかなり違う場合がある
    • 例:「信号機」と「遠くにいる人間」-> サイズは同じくらいでも人間の方がかなり複雑な見た目をしている
    • pyramid 内の各 feature map は全てもしくはそのほとんどが single-level な特徴しか含んでいない
      • -> Detection の performance が準最適(最適ではない)
  • 上記の問題に対処するため以下 3step で M2Det 作った
    • 1: 各 level の feature を混ぜて base feature とした
    • 2: 以下の操作を行う
      • 以下2つを交互に組み合わせたネットワークに入力する
        • Thinned U-shape Modules(TUM)
        • Feature Fusion Modules(FFM)
      • より multi-level, multi-scale な feature を抽出するため
      • 大事なこと:U-shape Module の各 decoder layers が同じ深さを共有している
    • 3: detection 用の feature pyramid を構築するために、同じ scale の feature map を集約する
      • final feature pyramid 内の各 feature map は multi-level decoder layer を含んでいる
      • Hence, we call our feature pyramid block Multi-Level Feature Pyramid Network (MLFPN).

4. Related Work

  • 皆、one-stage や two-stage に限らず、様々な scale に対応するため頑張ってきた。それらは大きく2つの戦略がある

5. Proposed Method

f:id:fam_taro:20190219202625p:plain
ネットワーク全体図

  • M2Det は backbone と Multi-Level Feature Pyramid Network (MLFPN) を使って入力画像から特徴抽出を行っている
    • SSD に似てる
    • 密な bounding-box と category score を出力する
    • その後 NMS をして最終的な結果を出力する
  • Multi-Level Feature Pyramid Network (MLFPN) は以下の 3つのモジュールを含んでる
    • Feature Fusion Module (FFM)
    • Thinned U-shape Module (TUM)
    • Scale-wise Feature Aggregation Module (SFAM)
  • FFMv1 は backbone の特徴を統合することで意味的な情報を強化している
  • 各 TUM は multi-scale features 群を出力し、TUM と FFMv2 を交互に適用することで multi-level で multi-scale な features を抽出している
  • SFAM は scale 毎の feature concat と attention 機能を適用することで各 feature を集約して multi-level feature pyramid を作る

5.1 Multi-level Feature Pyramid Network

  • ネットワーク全体図では MLFPN は 3つのモジュールから構成されれる
    • 1: FFMv1 では base feature 生成用に shallow な feature と deep な feature を統合する
      • 例: VGG16(Simonyan and Zisserman2015) の conv4_3 と conv5_3
      • ココらへんは SSD とかでもよく出てくる
      • これらは MLFPN 用の multi-level な意味的情報を得るのに役立つ
    • 2: いくつかの TUM と FFMv2 を交互に重ねる
      • 特に各 TUM は異なる scale の feature map をいくつか生成する
      • FFMv2 では、base feature と前の TUM から得られた feature map 内で最も大きいものを統合する
      • 統合された feature map は次の TUM の入力となる
      • ただし最初の TUM は他の TUM からの事前知識がないので、{ \textbf{X}_{base} } から学習する
      • multi-level multi-scale features は下記のように計算される
      • f:id:fam_taro:20190219210407p:plain:w400
        • { \textbf{X}_{base} } : base feature
        • { x_{i}^{l}} :  {l} 番目の TUM の  {i} 番目の scale の特徴
        • { \textbf{T}_{l} } :  {l} 番目の TUM の処理
        • { \textbf{F} } : FFMv1 の処理
    • 3: SFAM で multi-level multi-scale features を統合
      • scale をまたいだ concat
      • channel-wise attention

5.1.1 FFMs

  • FFM モジュールは M2Det 内の異なる level の feature を統合する
  • 以下の操作を行う
    • 1x1 Conv 層を使ってチャンネルを圧縮
    • concat で各 feature map をまとめる
  • 特に FFMv1 では backbone 内の scale の異なる 2つの feature map を扱うため、片方に対し upsample を適用して同じ scale にしてから concat する
  • 一方 FFMv2 では base feature と一つ前の TUM で出力された最も大きな feature map を扱い、これらは同じ scale である

f:id:fam_taro:20190219212736p:plain:w600
FFMv1(a) と FFMv2(b)

5.1.2 TUMs

  • FPN(Lin et al.2017a)RetinaNet(Lin et al.2017b) との違いとして TUMは 薄い U型構造となっている(下図)
  • 薄い U型構造?🤔
  • encoder は "3x3 convolution layers with stride 2" 連続でつなげてる
  • decoder は これらを feature map として受け取る
    • FPN(Lin et al.2017a) では last layer of each stage in ResNet backbone
    • 私達はさらに upsample の後に 1x1 の Conv 層を足し、decoder での要素和をとることによって学習能力を高めかつ特徴の滑らかさを保った
  • 各 TUM の decoder の出力は、ある level での multi-scale な feature となります
  • 前から後ろの TUM にかけて low-level な特徴から deep-level な特徴を出力するようになる

f:id:fam_taro:20190219213432p:plain

5.1.3 SFAM

  • 目的: 各 TUM から生成された multi-level multi-scale features を統合して multi-level feature pyramid に集約する

f:id:fam_taro:20190219215432p:plain
SFAM

  • SFAM は大きく分けて 2つのステップがある
    • Step1: 同じ scale の feature をチャンネルも一緒に concat する
      •  {\textbf{X} = [ \textbf{X}_1, \textbf{X}_2, ..., \textbf{X}_i ] }
      •  {  \textbf{X} = Concat(\textbf{x}_i^{1}, \textbf{x}_i^{2}, ..., \textbf{x}_i^{L}) \in \mathbb{R}^{W_i \times H_i \times C}  }
        •  {i} 番目に大きい scale の特徴
      • まとめられた各 feature pyramid は、各 level の feature を含んでいる
      • ただし単に concat するだけでは不十分
    • Step2: channel-wise attention module を使って有用なチャンネルを絞る
      • SENet(Hu, Shen, and Sun2017) で使われた SE ブロックを使う
      • squeeze step では GAP(Global Average Pooling) を使ってチャンネル別統計 { \textbf{z} \in \mathbb{R}^C } を得る
      • 次にチャンネルの依存関係を捉えるため 2つの FC(Fully Connected)層を使って attention を実現する
      • { \textbf{s} = \textbf{F}_{ex}( \textbf{z},  \textbf{W}) = \sigma( \textbf{W}_1 \delta( \textbf{W}_2\textbf{z} ) ) }
    • { \textbf{s} } で重み付けした最終的な出力は以下の通り
      •  { \tilde{\textbf{X}}_{i}^{c}  = \textbf{F}_{scale}(\textbf{X}_i^{c}, s_c) = s_c  \cdot \textbf{X}_{i}^{c}   }
      •  { \tilde{\textbf{X}}_i = [  \tilde{\textbf{X}}_i^{1},  \tilde{\textbf{X}}_i^{2}, ..., \tilde{\textbf{X}}_i^{C} ] }
        • 各 feature は rescale 操作によって強弱がついている

5.2 Network Configurations (ネットワークの設定)

  • M2Det では backbone を 2種類使った(同時ではない)
  • backbone は ImageNet で pre-traine 済
  • デフォルトの MLFPN は以下の構成
    • 8 TUMs
    • 各 TUM は 5 つの Conv 層と 5つの Upsample 層を持つ
  • scale は 6つ
  • 各 scale の TUM feature のチャンネルは 256 のみ(学習パラメータ数を減らすため)
  • 画像サイズは 320, 512, 800 (SSD, RefineDet, RetinaNet を参考に)
  • 6 つの pyramidal features に対し、それぞれ 2 つの Conv 層を加え、detection(location regression と classification ) をできるようにした
  • 各 pyramidal feature の pixel に対し、6つの anchor と 3つの比率(ratios) で候補ボックスを定義した
  • 上記候補ボックスに対する score のしきい値は 0.05 とした
  • 後処理として線形カーネルを使った soft-NMS(Bodla et al.2017) を使ってより正確なボックスを残した
  • しきい値を 0.01 とするとよりよい結果になったけど、推論時間がめっちゃ長くなったので実用上このしきい値は採用しなかった

6. Acknowledgements

  • 全て強い学会のもの
    • ICCV
    • ECCV
    • CVPR
    • ICLR
    • NeurIPS
    • IEEE
    • IJCV
    • CoRR
  • YOLOv3 だけ arxiv
    • v2 や v1 が既に入っているからか…
  • "強い論文は強い論文からしか生まれない" ようなイメージを抱きました

「機械学習のエッセンス」を読んだ感想とかメモ

先日同期から 機械学習のエッセンス -実装しながら学ぶPython, 数学, アルゴリズム- (Machine Learning) | 加藤 公一 | Amazon を推され、神(著者ご本人様)からの啓示圧力もいただきましたので、買って読みました。

以下に各所感や自分が読んだ際のメモを残します。 全実装は終わってないので注意

結論

  • この記事読むくらい買うか悩んでるなら買いましょう
    • まあ 2,800 円(税抜き)なんて人生において許容誤差ですし
  • 演習問題もちゃんとやりましょう
  • 誤植はある(でも第一刷からに第二刷にかけてかなり減った)ので、数式まで追う人は気をつけましょう
    • 正誤表 は予め見ておきましょう
    • kindle 版買えば刷新されたときに Update できるかも(要確認)
    • 僕は紙版(2刷)買って最初に正誤表見てボールペンで書き込みました
      • そんなに多くなかったのですぐ修正終わりました
    • ただ僕が読んだときは誤植はそこまでクリティカルに感じなかったです
  • 地味にページめくる時にうるさいです
    • 紙版ですとページめくるたびに表紙やカバーがギチギチ言うので、地味に気になります
    • 図書館だと読めないなってくらいです
      • どちらにしろ PC 叩くこと多いので図書館では読むことはなさそうですが
    • プロフェッショナルシリーズとかはそんなことなかったので、気になってきます

個人的には各回帰(リッジ・ロッソ)や SVM の実装において必要となる式の導出から Python 実装までをカバーされていて非常に勉強になりました(Python 3 で動くということも大変ありがたいです)。

SVM 等は 2014〜2016 年頃(自身が修士の頃)ですと、僕の周辺では サポートベクターマシン入門 | ネロ クリスティアニーニ, ジョン ショー‐テイラー, Nello Cristianini, John Shawe‐Taylor, 大北 剛 | Amazonパターン認識と機械学習 上 | C.M. ビショップ, 元田 浩, 栗田 多喜夫, 樋口 知之, 松本 裕治, 村田 昇 | Amazon を参考文献にしていたのですが、これらの文献は深いところまで記載されている一方、理論からコードに落とし込もうとすると「???」となって手が全く動かなくなることがあり、この本のように理論から実装につなげてくれる著書は無かった(見つけられなかった)です。当時欲しかったなあ

一方実装までカバーされている分、理論面では足りないところ(例えばカーネル SVMカーネル部分とか)もあります。ですのでこの本読んでもっと知りたい気持ちを膨らませて上記の本とかにチャレンジするのが良いと思います。

あと演習問題もあってプログラミングに限らず手を動かす場面もあって良かったです。

この本を薦めたい人

  • エンジニアやってきて、もう少しちゃんとキカイガクシュウシタイナーって人
  • 理系学部1年生等の、線形代数微積の授業やってて寝ちゃう人
    • 使い途わかればもう少しちゃんとやれる人って結構いる気がするので
  • 人工知能って単語にワクワクした大学生
  • 行列系の数式読んでて「ウッ」となる人
  • 数式から Python(numpy) コードに落とし込む際に「ウッ」となる人

この本を薦めない人

  • 手を動かしたくない人
    • この本に出てくる計算や演習問題・Python コードは自分で書いて動かしてなんぼなので
      • 今買えば本文中のコードがそのまま動くのに動かさないのはもったいないです
    • 紙版買ったらまず正誤表見て直した方が良いので、手を動かす前提でこの本は買いましょう
  • サクッと概要だけ押さえたい人
    • この本はほどよく深く(浅く)ほどよく広い(狭い)ので、広く浅く概要だけ掴みたい場合には不向きだなと思います
  • モデル選択や細かいパラメータチューニングについて知りたい人
    • 本書の目的にもある通りココらへんはあまり触れてません
  • とりあえず Deep Learning を動かしたい人
    • 著者も述べていますが、他に詳細な本がたくさんあります
    • とはいえ著者が言う "この本が Deep Learning の理解に役に立つ"というのは同意します

各章の所感とか個人用メモ

前提

  • Python の環境構築・基礎あたりはちゃんと読んでません
    • 手元に環境あったので
  • 自分の環境は Python 3.7 です
    • 自分でコード書く時は以下の環境を都度都度(気分で)変えてやってました
      • iPython
      • jupyter notebook
      • jupyter lab

第1章「学習を始めるまえに」

  • 本書の目的と本書が含まないものが載ってて良かったです
    • 初めに必ず目を通すべき

第2章「Python の基本」

  • 例外処理まで触れられてて良いなと思いました
  • Python 3.6 からは "a: {:2d}".format(a)f"a : {a:2d}" で書けたりするので興味がある人はお試しください
    • "fstring" とか "フォーマット済文字リテラル" とかで調べると出てきます
  • オブジェクト指向
    • ここは深追いすると二度と帰ってこれなくなるので、これくらいの深度で十分だと思います
    • クラス=メソッド+データ 程度の理解だけ頭において先に進んだ方が良いです

第3章「機械学習に必要な数学」

  • "ブロック化"とかたしかにそんな単語あった気がする…
  • 機械学習に必要な線形代数微積がピックアップされてるのすごくありがたい
    • 逆行列とか固有値周りのお話はいつまでも着いてきます
    • 逆行列を持つための条件は知っておくといつか役に立ちそう
      • 3次元以上の行列でも  \rm{det} A \neq 0 の場合に逆行列を持つこととかは自分で調べた方が良い
  • 対称行列(半正定値・正定値)やとかも、単語だけ覚えておいてまたどこかで出てきたらこの本とか他の詳しい本を見れるようにしておきたい
  • 多変数関数の極値とヘッセ行列の関係は知らなかった…(一度聞いて忘れたのだと思われ)
  • 二次形式の勾配の形はまれによく出るので知っておくと良い

第4章「Python による数値計算

  • 桁落ちとか有効桁数・誤差
    • 忘れかけてたので良い戒めになった
    • 機械学習続けてると "1e-8 の誤差とか知らんがな" と思う時が結構あったので
  • 疎行列の扱い
    • めっちゃ参考になりました
    • 行とるか列をとるかで crc_matrixcsr_matrix を使い分ける
    • それぞれ getrow()getcol() の効率が違う
  • 逆行列・numpy の solver・LU分解を使った一次連立方程式の解法
    • 個人的にすごく楽しかったです
    • これまで必死に手計算で方程式を解いてきた人類にとっては、これってものすごく衝撃的だと思うんですよね
      • 人によっては解放感とか爽快感があると思います
      • 大学デビューとはかくあるべき
  • データの可視化
    • 僕自身細かい文法忘れることが多いので、たまにこの本とかに帰ってくる気がしてます
  • 数理最適化
    • 今後機械学習やるなら絶対押さえなきゃいけないところ
    • 線形計画法や2次計画法等の解析的に解ける(≒式変形等で解ける≒手計算で解を出せる)問題から解析的に解けない 問題に対しどのように解を出そうとしているかが載っています
    • 最急降下法ニュートン法について以下の違いは押さえておきましょう
      • 更新式
      • 収束条件
    • "数値微分に関する補足" も地味に大事なお話だなと思いました
    • ラグランジュの未定乗数法
      • チッ…はいはいラグランジュラグランジュ
      • 議論の抽象度的にはここ結構難しかったです…
      • きついと思ったら一旦軽く流して先に進んでも良いと思います
        • 気になったら他の文献とかまたここに戻ってくれば良いですし
    • 統計
      • ここも高校までは手計算地獄だった分「数行で書けちゃう俺Tueeeee」できたりして楽しかったりします
      • 後半は数式多めだけど大事なので体力ある人は追いましょう

第5章「機械学習アルゴリズム

この本のメインテーマ(だと思ってます)

  • 準備
    • 回帰と分類の違いは押さえておきましょう
    • インターフェースについては、ここで押さえておくと他のライブラリを使った時なども楽になれます
  • 回帰
    • なにを最適化(最小化 or 最大化)しようとしているのかは押さえましょう
    • 3次元空間でのプロット法が載ってるのすごくありがたいです
    • 外部データを自分で持ってきて読み込ませるのは大事な経験なので初めての人は必ずやりましょう
    • 評価指標は必ず押さえてください
  • リッジ回帰
    • 線形回帰 + 正則化項(L2)
    • 正則化項足すと何が嬉しいのか自分の手で動かして実感しましょう
    • さりげなくベクトルwで微分してるけど、ちゃんと自分の手でもできるか確認しましょう
    • ここで出てくる "ハイパーパラメータ" と "チューニング" は長い付き合いになる可能性があります
      • 人間が手で調整しないために機械学習してるのになんでまたパラメータが出てくるのか
      • 上記は自分の手を動かすと実感が湧く可能性あるので是非やってみましょう
  • 汎化と過学習
    • 交差検証(クロスバリデーション)はめっちゃ大事。めっちゃ大事
  • ラッソ回帰
    • 線形回帰 + 正則化項(L1)
    • 線形回帰・リッジ回帰と何が違うのか
    • 座標降下法(corordinate descent) を絶対値関数にどう適用するか
    • 必要な式変形(うまく j=k の部分のみ抜き出せるか等)
    • 上記の実装部分
  • ロジスティック回帰
    • 回帰と分類がつながる箇所
    • ココらへんも式変形が続くので、気を抜いて一行飛ばしたりすると訳分からなくなるので注意
    • 式変形が長いと変形ばかり気にして何をしようとしているのか頭から抜けちゃうのも注意
  • サポートベクターマシン(要実装)
    • この本の中で一番きつい箇所
      • 本当にきつかったら一度とb(
      • もう、ゴールしてもいいy
      • 実装まで追いきれなかったので余裕あれば別記事にします…
    • 解くべき問題がどんどん変形していくのでゆっくり読み進めた方が良いです
    • KKT 条件出てくるので忘れた方は第4章の該当箇所読み返しましょう
    • 最終的にサポートベクタ周りだけ着目
    • ハードマージン・ソフトマージン・カーネルという単語は押さえる
      • 3つとも必要な数式の導出と(動く)実装まであるのは貴重
  • k-Means 法
    • SVM 超えた方にとってはボーナスステージ
  • 主成分分析(PCA)

個人用メモ(コード周り)

第4章「Python による数値計算

1次連立方程式の解法の比較(逆行列・numpy.linalg.solve・scipy.linalg.lu_factor)

この規模の小ささですと numpy.linalg.solve が最も速かったですが、 行列が大きくなったり b が複数列になってくると LU 分解(lu_factor) が速くなってくるのだと思います。

In [1]: import numpy as np

In [2]: a = np.array([[3, 1, 1], [1, 2, 1], [0, -1, 1]])
   ...: b = np.array([1, 2, 3])

In [3]: np.linalg.inv(a) @ b
Out[3]: array([-0.57142857, -0.14285714,  2.85714286])

In [4]: np.linalg.solve(a, b)
Out[4]: array([-0.57142857, -0.14285714,  2.85714286])

In [5]: from scipy import linalg as slinalg

In [6]: lu, p = slinalg.lu_factor(a); slinalg.lu_solve((lu, p), b)
Out[6]: array([-0.57142857, -0.14285714,  2.85714286])

In [7]: %timeit np.linalg.inv(a) @ b
13.2 µs ± 968 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [8]: %timeit np.linalg.solve(a, b)
8.03 µs ± 79.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [9]: %timeit lu, p = slinalg.lu_factor(a); slinalg.lu_solve((lu, p), b)
28.4 µs ± 569 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [10]: %timeit slinalg.lu_solve((lu, p), b)
17.5 µs ± 96.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

第5章「機械学習アルゴリズム

ラッソ回帰
import numpy as np
import csv

def soft_thresholding(x, y):
    return np.sign(x) * max(abs(x) - y, 0)

class Lasso:
    def __init__(self, lambda_, convergence_thres=0.0001, max_iter=1000):
        self.lambda_ = lambda_
        self.convergence_thres = convergence_thres
        self.max_iter = max_iter
        self.w_ = None
    
    def fit(self, X, t):
        n, d = X.shape
        self.w_ = np.zeros(d + 1)
        avg_L1 = 0.
        for _ in range(self.max_iter):
            avg_L1_prev = avg_L1
            self._update(n, d, X, t)
            avg_L1 = np.abs(self.w_).sum() / self.w_.shape[0]
            if abs(avg_L1 - avg_L1_prev) <= self.convergence_thres:
                break
        
    def _update(self, n, d, X, t):
        self.w_[0] = (t - (X @ self.w_[1:])).sum() / n
        w0vec = np.ones(n) * self.w_[0]
        for k in range(d):
            ww = self.w_[1:]
            ww[k] = 0
            q = (t - w0vec - (X @ ww)) @ X[:, k]
            r = X[:, k] @ X[:, k]
            self.w_[k + 1] = soft_thresholding(q / r, self.lambda_)
    
    def predict(self, X):
        if X.ndim == 1:
            X = X.reshape(X.shape[0], 1)
        Xtil = np.c_[np.ones(X.shape[0]), X]
        return Xtil @ self.w_


def main():
    # Read data
    Xy = []
    with open("winequality-red.csv") as fp:
        for row in csv.reader(fp, delimiter=";"):
            Xy.append(row)
    Xy = np.array(Xy[1:], dtype=np.float64)
    
    # Split train & test data
    np.random.seed(0)
    np.random.shuffle(Xy)
    n_test = 1000
    train_X, train_y = Xy[:-n_test, :-1], Xy[:-n_test, -1]
    test_X, test_y = Xy[-n_test:, :-1], Xy[-n_test:, -1]

    # Show results reach hyper parameters
    for lambda_ in [1., 0.1, 0.01]:
        model = Lasso(lambda_)
        model.fit(train_X, train_y)
        y = model.predict(test_X)
        print(f"--- lambda: {lambda_} ---")
        print("coefficients:")
        print(model.w_)
        mse = ((y - test_y) ** 2).mean()
        print(f"MSE: {mse:.3f}")
    

if __name__ == '__main__':
    main()

機械学習エンジニアに転職して4ヶ月が経ちました

注意事項

  • 本記事は転職ポエムになります
    • 1/3 の純情なエモさです
  • このポエムを読んでもあなたの Python コードは 1行も進みません
  • これは個人の意見であり、会社の見解を代表するものではありません
  • 見返したら SES dis 色が強かったです
    • そんなつもりは無かったんです :;(∩´﹏`∩);:

結論

  • 転職活動はしたほうが良いけどとても疲れます
    • 転職するかは別
  • 機械学習周りの市場は活気づいてます
  • アウトプットゎ大事です

導入

この会社に機械学習系エンジニアとして転職し気づけば 4ヶ月となりました。
転職活動時の気持ちや転職してみた感想を、忘れる前にここに残そうと思います。
このポエムが転職を考えている人や機械学習エンジニアに興味がある人にとって有用な1サンプルになれば幸いです。

著者情報

  • 2016年度情報系院卒
    • 学部・院卒時は NLP やってました
    • 今回の転職活動時は 27歳でした
  • 現在 3社目
    • 1社目
      • 数百名ほどの金融系システム開発会社
      • 保守してました
      • 4ヶ月ほど所属してました
    • 2社目(前職)
      • 30名ほどの中小 SES (技術者派遣)会社
      • 大きい SIer 様のところで常駐しつつ OracleDB チューニングとか研究のお手伝いしてました
      • 1年10ヶ月ほど所属してました
    • 3社目(現職)

なんで転職したのか

  • 特定ドメインでの機械学習をしてみたくなったから
    • HR かライフサイエンスの領域で
  • 自身の裁量でいろいろ挑戦してみたかったから
    • SES 常駐マンにそんな裁量はないです
      • 常駐先の良し悪しの問題ではなく、契約上他社の人間に裁量与える訳ないだろってだけの話です
      • 請負じゃないし責任の取りようもないので
    • とはいえ常駐先の上司や社員の方には可能な限りいろんなことをやらせていただいたので本当に感謝しています
      • マシンスペック良かったですし(RAM 32GB)
  • 心の底から SES 会社は不要だなと思ったから
    • 中抜きしてるだけにしか見えなかった
    • 常駐先の数々のリスクの投げ先になってるだけにしか見えなかった
  • 賃金・スキル的に伸び代を感じられなかったから
    • 僕は 10, 20 年のスパンで見たときに OracleDB チューニングには未来を感じませんでした
    • 加えて SES で働き続けると SES 以外に転職できる気がしなかったのもあります
  • 働きづらかったから
    • 以下は全 SES のお話ではなく前職の本社のお話です
    • 勤怠管理サービス使ってなかった
      • もちろん Excel 大活躍でした
    • 会社(常駐先ではなく本社)の文化に対し馴染みきれなかった
      • 社長と上司に年賀状送ったほうが良い
      • 忘年会とか社員旅行行かないと「評価に値しない」とか定例で言われる
      • 常駐先固有の休日なのに僕の有給が強制消費される
        • 他の会社だと公休扱いだし、せめて休むか本社で作業するか選ばs(
      • 若手が病気で休養すると議事録でお叱りを頂ける

転職活動について

いつからいつまで

  • 2018年4月下旬〜7月上旬(3ヶ月)
    • GW 中はほとんどしてませんでした

何社ぐらい受けたか

  • 30 社ぐらい
    • 面談以降(書類選考含め)に進んだもののみカウントしてます
      • 書類で落ちた会社も含まれます
    • サイトでイイね押しただけの会社含むなら倍ぐらいになります
      • これなんて出会い系サイト
    • 週3ペースで面接してた気がします
      • 非常にきつかったのでおすすめしません

正直この数は多すぎたと思います。 今回はいろんな会社を見たかったのでこれだけ多く受けてみましたが、 平日は面談とか面接で埋まりがちで肉体・精神的に辛かったです。

加えて下記の Green や Paiza を使うと、①日程調整は自分で行う必要がある・②企業によって選考結果や日程の通知に時間かかる場合があるため、Google カレンダー上のほとんどの平日が候補日で埋まりました。

ですので希望条件とか志望動機をしっかり固めた方は、応募する会社数を絞った方が良いと思います。

どの転職サービス使ったか

  • Green・Paiza・エージェントによる紹介(Team AI)を使いました
    • 今の会社は エージェントによる紹介 で入社しました
    • Team AI は以前勉強会に出てお世話になった縁で使いました
  • 1 社だけ直接応募しました
  • 初めての転職ではワークポート・リクナビdoda を使いました
    • 以下の理由から今回は使いませんでした
      • 紹介する会社が微妙・広く浅い紹介が多い
      • 最終面接付近(面接前や内定後)で辞退するとめっちゃ電話かけてきて説得してきた
        • 30分ほど説教説得されたのは思い出です
      • 電話が多くて時間とられた
      • よく知らない他人に任せる気になれなかった
      • 業種をまたぐような転職ならありかもしれません
      • エージェントガチャがある
    • そもそもエージェントはその人の転職先での年収割合分の成功報酬をもらうことが多いので、年収高いところ行けバイアスが強いです
      • 年収上げたいだけの方はエージェント使う選択肢もありだと思います
        • ですがその時の採用にかかった費用は回り回って自分に来るものだと思います

以下に今回お世話になった各サービスの比較を記載します。

サービス 良い点 クソな 気になる点
Green ●完全広告型なので、エージェントが一切絡んで来ない(内定時は絡むかも?)
ベンチャー気質(ベンチャーとは言っていない)な会社が多く、他のサービスだと書類が通らないような魅力的な企業と面談できた
●代表とお話できる機会が多かった(代表は大抵熱量あるから刺激や学びになった)
●ほんとにいろんな企業を見れる
魅力的な人事も多数いました
●面談の概念が企業によってバラバラ(ただの面接の場合あり)
●企業によってはこちらの状態(面談前・面接後とか)を選考中に変えないので、面談のフィードバックが書けない(しかもそういうところに限ってクソみたいな面接してくる)
●ミスマッチすぎる企業とも面談することが結構あった(なんで僕を通した?)
Paiza ●途中までエージェントが絡んでこない
●コーディングテスト楽しい(A しか取れない雑魚でしたが)
●日程調整がシステム上でできる
●エンジニアが気になる情報はしっかり載せてあった(開発環境とか)
●面接後企業からフィードバック(良い点悪い点)をシステム上でもらえる
●面接を進めると、急にエージェントがしゃしゃり出てきて電話とかで時間を奪ってくる
●企業からのフィードバックがためにならない場合がある(良い点:なし、とか)のに、こちらのフィードバックが適当だとエージェントに追求される場合がある
●コーディングテストで時間取ったのに、結局技術面接とか事前課題で時間取られる(意味ない)、想定年収も全く現実に寄与しない
●中の人のブログがアレ(煽るのうまいっすね)
ここで会社を探す分には良いけど、ここをを使って応募する意味があまりない
Team AI (エージェント) 機械学習系の勉強コミュニティ発なのでそこらのエージェントより精通してる
機械学習扱っているところを中心に紹介してもらえる
●代表の石井さんに対応してもらえる(今は分かりません)
●転職活動中はメールで細かくやりとりしてくれた
●システムではなくメールでのやり取りしかない
●そのため日程調整周りでグダグダになることが比較的多かった
●こちらの内定後の対応が雑(最低限のやり取りしかしたくない人にとっては良いかもしれません)
●応募者のキャリアを考えるというよりも、企業紹介に重きをおいている(これも最低限n)
直接応募 ●(上手くいく or 優秀な人なら)内定まで一瞬で決まる
●企業からすると採用コストを抑えられる
●(僕みたいな)微妙な人だと面接後めっちゃ待たされたり対応が雑、人事によるのかもですが

Green は良くも悪くもガチャって感じがしましたし、 そのような感覚でやってるなと思う人事の方もいらっしゃいました。 とはいえここ受けて良かったと思えるくらい素敵な企業も一定数ありました。

Paiza は初めて利用する場合、コスパ悪い(かけた時間の割にという意味で)なと感じました。 しかし一度スキルチェックを終えれば当分使えそうなのと、エンジニアとして気になる部分(開発環境等)は詳しく載っていることが多かったので、エンジニアとして転職するなら使って良いと思いました。

機械学習しかしたくない & エージェントでも OK な場合は、Team AI(エージェント)を使ってみて良いと思います。

ベース高めの人はビズリーチ使っても良い気がします。 もしくは LinkedIn もありかもしれません。

転職活動において、やって良かったこと

  • 在職しながら転職活動した
    • 1社目はつらすぎて転職先決まる前に辞めてしまったのですが、転職先が無いため非常に焦りました
      • 実家に住んでても焦ったので、一人暮らしとか家庭持ちだともっと焦ると思います
    • 自分が納得できることより早く手に職つけることを優先してしまいました
    • 在職中だと心に余裕を持って活動しやすかったです
  • 面接のときに背筋を伸ばす
    • 4,5 社ほど面接受けてから、自分が猫背であることに気づきました
    • 気持ちハキハキ喋れます
  • アウトプットを残した
    • 仕事で出せる人は職務経歴書にどう書くか意識しながらアウトプット意識すると良いと思います
      • 短期の案件回してる人はこまめに残した方が良いです。パッと出てこないので
    • あとは Qiita、はてブgithub に残すと良いと思います
      • その人の興味関心先とか分かったりするので
      • Qiita 書くだけでも日本語書ける能力とググる能力は示せますし
  • 機械学習系の論文を読んで実装した
    • これは5月下旬辺り(転職活動始めて1ヶ月ほど経ってから)から始めました
    • 上記の経歴に加え雑魚修論だったので、企業からするとよくいる機械学習エンジニアワナビー以外に評価のしようがなかったのだと思います
    • これやってから結構機械学習面で一定の評価(高いとは言っていない)を得やすくなったと気がします
      • 特にベンチャーとかですと人育てるほど整ってないので「環境与えれば勝手に学んでいきそうか」は必ず見られると思います
      • 必ず選考通るとは言っていない
    • 今なら arxiv に行けば実装未公開な NN が腐るほどあるし、 PyTorch という人間味溢れたパッケージ もあるので是非実装して記事とかで紹介お願いします
    • kaggler ならメダルとって実装 github に載っけた方がはやいです
      • それ通じない企業は辞退した方g(
  • 年収についてほとんど考慮しなかった(要注意)
    • 今回は優先順位が低かっただけです
      • (それでも若干上がった不思議)
    • 上げるほどのスキルがなかったとも言います
      • このキャリアパスで年収あげようとするとろくなところ行けそうになかったです
    • 一応 +100万ぐらいで提案してくださる企業もいました
      • 変な企業とか SES だったけどネ!
    • 選考中は「もし 5,000兆円手に入れてもこの会社で働き続けたいと思うか」を意識していました
      • これを確認するためにも誰か 5,000 兆円ください

今回の転職活動をして良かったこと

  • いろんな企業を見れた
  • 企業を見る目が養われた
    • 今回応募した中で魅力的に感じた企業の多くが、その後追加で資金調達したりステージを上げたりしてました
      • ですので世間並には企業を見れるようになったのかなと判断しました
      • (今の会社もしてます)
      • 結構嬉しかったです
  • この市場の活気さを肌で感じられた
    • 思ったより「ポジション名:機械学習エンジニア」で応募されている企業が多かったです
    • 世間の関心が「人工知能」から「機械学習」に移ってる証かもしれません
  • 自身の市場価値(僕の場合はその無さ)を測れた
    • 相手企業というフィルター越しではありますが、自身の価値ってどこにあっていくらぐらいあるのか知れたのは貴重な機会でした
    • 漠然とではありますが、こうすれば(なれば)市場価値上がりそうという目安も分かったので良かったです
  • 今の企業に転職できた

今回の転職活動を通した所感

  • tensorflow 民が多かった
    • 「tenslorflow できんの?」に近い質問は多かったです
    • この時点ではまだ Define and Run が激強だったのかもしれません
    • 2018年春夏段階だったからかも
  • 修論提出させるところが以外とあった
    • ゴミ修論でほんとごめんなさいって気持ちになりました

転職して 4ヶ月経ったけどどう?

日々転職して良かったなと思ってます(ポジトーク並感)。

機械学習エンジニアとして働いてみて、「機械学習界隈進歩速すぎ」「英語力雑魚すぎ」「ドメイン知識無さすぎ」「kaggle 雑魚すぎ」てキツイなと思うことは多々あります。 それでも自身の手でモデルを動かしたとき、様々な場面で社会実装されたりすごいモデルが出てくるのを見たとき、自身の工夫が活きたとき等のワクワク感は得難いものであり、これらのおかげで日々ワクワクしながら働くことができています。

現職の会社については、前職より非常に良くなりましたが、不満はまだあります。 しかしそれらを共有したり発言できる土台や、むしろ「僕が解決してあげないと(オタサーの騎士並感)」や「やってみよう・試してみよう」と思えるような社員の雰囲気や会社の文化があるため非常に好ましく、僕自身も引き続き維持できるように気をつけたいと思っています。 (あとフリードリンク・フードは大好きなので引き続き残して欲しいです)

まだまだスキル面・人間力面的に足りないことだらけなので引き続き頑張ります。

おまけ

今回の転職活動で出会えた個性的な企業 (or 人事) について

ここからはただの苦しい苦しいネガキャンです。

以下の企業様は今回応募した中の一部であり、ほとんどの企業様は応募して良かったと
思っていますのでその点ご留意ください。

  • 適性テストという名目で1時間近く百マス計算的なことを課してくる企業様
    • 正確には鉛筆片手に (a + b) % 10 を 1時間近くさせられました
    • 僕は確かに機械学習エンジニアという名目で応募したのですが…
      • このようなことをさせないためにエンジニアがいるのだと僕は思います
      • ちなみにこの転職期間中において最も無駄な 1時間でした
    • ここに入社すれば 刺身にタンポポ乗せる仕事 を経験できそうでした。ただし僕には適性が無かったようです
  • 面接の雰囲気と選考結果が 180度違う企業
    • 「あれ?違うな」くらいなら結構あったのですが 180度違う企業様は初めてでした。是非そのギャップは誇ってください
      • そしてできれば御社の紹介欄に記載いただきたく。でないと僕みたいなのが応募して凹むので
      • Paiza の応募者評価項目の記入欄が本当に自由記入欄なのg(ry
  • 全力でマウント取ってくる自称 PM
    • 出会って一言目が「これって1次面接?」
      • → 2次面接です。ご多忙の中大変恐縮ですが選考フローにつきましてはご理解いただきたく
    • 1つの質問に「なぜ」が 5回ほど飛んでくる
      • この歳にして初めてトヨタ式を経験できました
        • でもトヨタ式って真因を特定するために行うものであって、貴方様が納得できる脳内仮説にはめ込むためでは無いのd(
        • ですので「我核心突いたり」みたいなしたり顔はお控えください
    • 「君って自分の世界持ってる系だよね?(断定)」
      • → お前が思うんならそうなんd(
      • 「そうなんですね」以外の返しが思い浮かびませんでした

PyTorch-Ignite で学習用コードをスマートにする

  • 12月25日
    • Early Stopping、モデルの保存ができるように train と main を変更しました

皆さんこんにちは。みなさんメリークリスマス☆茜ちゃんだよ☆

今回は PyTorch 用のライブラリである PyTorch-Ignite(https://github.com/pytorch/ignite) を使って Training 用のコードを簡潔に書けるようにします。

本記事のキーワード
本記事の対象
  • 何度か PyTorch のコードを書いてきてもう "for input in XXX_loader" は飽きてきた方
  • 大体似たような Training 用のコードを書いている方
  • Training 部分についてある程度のテンプレートを身につけたい方
  • Ignite という響きに惹かれた方
本記事の対象でない方
  • まだ PyTorch を始めたばかりの方
    • Ignite は各バッチ毎に行っている操作 "output = model(input)" や "model.train()" を隠蔽してしまいます
      • そのため学習のイメージが掴みきれていないうちは Ignite は推奨しません。
    • 初めからそこら辺に興味がない場合は Keras の方が良い気がします
  • 機械学習を始めたばかりの方
  • Training 周りのラッパーを全てご自分で用意されている方
  • 各バッチやデータ毎に細かい処理を行いたい方

Ignite とは

PyTorch の Training 周りをスッキリ書くためのライブラリです。 Training 周りに絞ってあるので、試しやすそうです。

例えば kaggle や業務等で、モデルや学習率をざっくばらんに試したいときに使いそうですね。

Ignite Documentation — ignite master documentation github.com

  • 公式 Document の訳
    • Ignite は PyTorch の Training 周りに絞ったライブラリ
    • 各 metrics や earlystopping、model の保存などを含めた Training loop を扱えるよ

f:id:fam_taro:20181224180408p:plain
左:Ignite を使った場合の学習用コード、右:Ignite を使わない場合の学習用コード

上記は公式画像の Document 内にありましたが、学習用コードがスッキリしてます。

使ってみる

https://github.com/pytorch/ignite/blob/master/examples/mnist/mnist.pyhttps://github.com/pytorch/ignite/blob/master/examples/mnist/mnist_with_tensorboardx.py を元に MNIST 用の Training コードを書いてみます。

Python のバージョンと必要なライブラリ

  • Python 3.6.5
    • 3.6 以降じゃないと f-Strings 使えません

今回は以下のライブラリを使いました。

特に pytorch-ignite はちゃんと "pip install pytorch-ignite" でいれましょう。 ("pip install ignite" だと違うものが入ります)

pytorch-ignite      0.1.2
tensorboardX        1.4
torch               0.4.1
torchvision         0.2.1

データセット

今回は MNIST を使います。 自分で行う場合は別途 Dataset を定義しましょう。 ここでついでに DataLoader も定義します。

from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision.transforms import Compose, ToTensor, Normalize


def get_data_loaders(train_batch_size, val_batch_size):
    data_transform = Compose([ToTensor(), Normalize((0.1307,), (0.3081,))])

    train_loader = DataLoader(MNIST(download=True,
                                    root=".",
                                    transform=data_transform,
                                    train=True),
                              batch_size=train_batch_size, shuffle=True)

    val_loader = DataLoader(MNIST(download=False,
                                  root=".",
                                  transform=data_transform,
                                  train=False),
                            batch_size=val_batch_size, shuffle=False)
    return train_loader, val_loader

モデル

今回は参考サイトのままシンプルなネットワークを用います。 使ってみたいモデルがある人は是非ここで定義しましょう。

import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):
    def __init__(self, num_class=10):
        super(Net, self).__init__()
        self.num_class = num_class
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
        self.conv2_drop = nn.Dropout2d()
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, self.num_class)

    def forward(self, x):
        x = F.relu(F.max_pool2d(self.conv1(x), 2))
        x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
        x = x.view(-1, 320)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        return F.log_softmax(x, dim=-1)

train に必要な関数の定義

早速 train 本体を記述したいのですが、その前に必要になる関数をここで定義します。

  • write_metrics() は評価値をログに保存するついでに print もするための関数
  • score_function() は validation_loss を ignite.handlers.EarlyStopping オブジェクトに渡すために変換する関数
    • ignite.handlers.EarlyStopping は、与えたスコアが上がった場合に「スコアが改善した」と判断するため
def write_metrics(metrics, writer, mode: str, epoch: int):
    """print metrics & write metrics to log"""
    avg_accuracy = metrics['accuracy']
    avg_nll = metrics['nll']
    print(f"{mode} Results - Epoch: {epoch}  "
          f"Avg accuracy: {avg_accuracy:.2f} Avg loss: {avg_nll:.2f}")
    writer.add_scalar(f"{mode}/avg_loss", avg_nll, epoch)
    writer.add_scalar(f"{mode}/avg_accuracy", avg_accuracy, epoch)


def score_function(engine):
    """
    ignite.handlers.EarlyStopping では指定スコアが上がると改善したと判定する。
    そのため今回のロスに -1 をかけたものを ignite.handlers.EarlyStopping オブジェクトに渡す
    """
    val_loss = engine.state.metrics['nll']
    return -val_loss

train 本体の定義

ここで train する部分を記述します。 これまで頑張って以下のような for 文を書いていた部分です。

train(**args):
    for i, input in enumerate(train_loader):
        ...

これを pytorch-ignite を使うと以下のように書けます。

def train(epochs, model, train_loader, valid_loader,
          criterion, optimizer, writer, device, log_interval):
    # device: str であることに注意
    # この時点では Dataloader を与えていないことに注意
    trainer = create_supervised_trainer(model, optimizer, criterion, device=device)
    evaluator = create_supervised_evaluator(model,
                                            metrics={'accuracy': Accuracy(),
                                                     'nll': Loss(criterion)},
                                            device=device)

    @trainer.on(Events.ITERATION_COMPLETED)
    def log_training_loss(engine):
        i = (engine.state.iteration - 1) % len(train_loader) + 1
        if i % log_interval == 0:
            print(f"Epoch[{engine.state.epoch}] Iteration[{i}/{len(train_loader)}] "
                  f"Loss: {engine.state.output:.2f}")
            # engine.state.output は criterion(model(input)) を表す?
            writer.add_scalar("training/loss", engine.state.output,
                              engine.state.iteration)

    @trainer.on(Events.EPOCH_COMPLETED)
    def log_training_results(engine):
        evaluator.run(train_loader)
        metrics = evaluator.state.metrics
        write_metrics(metrics, writer, 'training', engine.state.epoch)

    @trainer.on(Events.EPOCH_COMPLETED)
    def log_validation_results(engine):
        evaluator.run(valid_loader)
        metrics = evaluator.state.metrics
        write_metrics(metrics, writer, 'validation', engine.state.epoch)

    # # Checkpoint setting
    # ./checkpoints/sample_mymodel_{step_number}
    # n_saved 個までパラメータを保持する
    handler = ModelCheckpoint(dirname='./checkpoints', filename_prefix='sample',
                              save_interval=2, n_saved=3, create_dir=True)
    trainer.add_event_handler(Events.EPOCH_COMPLETED, handler, {'mymodel': model})

    # # Early stopping
    handler = EarlyStopping(patience=5, score_function=score_function, trainer=trainer)
    # Note: the handler is attached to an *Evaluator* (runs one epoch on validation dataset)
    evaluator.add_event_handler(Events.COMPLETED, handler)

    # kick everything off
    trainer.run(train_loader, max_epochs=epochs)

ポイントとしては以下の通りです。

  • create_supervised_trainer() で trainer を定義
  • create_supervised_evaluator() で evaluator を定義
    • これは train_loader にも valid_loader に対しても使えます
  • @trainer.on(<実行したいタイミング>) で各処理を定義した関数をデコレートする
    • デフォルトでは以下のタイミングに処理を挟み込めるそうです(https://pytorch.org/ignite/engine.html#ignite.engine.Events)
      • COMPLETED
      • EPOCH_COMPLETED
      • EPOCH_STARTED
      • EXCEPTION_RAISED
      • ITERATION_COMPLETED
      • ITERATION_STARTED
      • STARTED
    • 上記の通り大体実行したいタイミングは網羅してそうです
  • 実行したいイベント(モデルの保存や EarlyStopping)は trainer や evaluator に add_event_handler() で追加する

また ModelCheckpoint() によって学習途中のパラメータを保存できるようにしています。 これが 2行で書けるのは嬉しいです。 加えて n_saved によって保存する直近のパラメータの個数を制限できるのも地味に嬉しいです!

さらに今回はコンペを意識して EarlyStopping を導入しました。 注意事項としては evaluator に対して追加していることと、実行するタイミングが Event.COMPLETED になっていることです。

main 関数

main 関数では train() の実行に必要なものを定義します。 コマンドライン引数や config ファイルの読み込みもここで定義すると良いと思います。

ポイントでは、学習結果を見るために tensorboardX を使っていることぐらいです。 もし他に logger を使っている方はそちらでも可能です(ただし train を修正する必要あり)。

あとは Early Stopping を確認するために epochs=100 と大きめに設定していることに注意してください。

import torch
import torch.nn.functional as F
import torch.optim as optim
from tensorboardX import SummaryWriter

from model import Net
from dataset import get_data_loaders
from train import train


def main():
    # 定数
    epochs = 100
    train_batchsize = 128
    valid_batchsize = 4
    log_interval = 50
    device = "cuda" if torch.cuda.is_available() else "cpu"

    # 各学習に必要なもの
    model = Net(num_class=10)
    train_loader, valid_loader = get_data_loaders(train_batchsize, valid_batchsize)
    criterion = F.nll_loss
    optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
    log_writer = SummaryWriter('./log')

    # 学習開始
    train(epochs=epochs, model=model,
          train_loader=train_loader, valid_loader=valid_loader,
          criterion=criterion, optimizer=optimizer,
          writer=log_writer, device=device, log_interval=log_interval)

    # モデル保存
    torch.save(model.state_dict(), './checkpoints/final_weights.pt')

    log_writer.close()


if __name__ == '__main__':
    main()

学習実行

以下のように Epoch が回れば OK です。 僕が回したときは epochs = 100 に設定したにも関わらず epoch[23] で学習が終わったので ちゃんと early stopping されていることが確認できました。

$ python main.py
<省略>
Epoch[2] Iteration[1450/1875] Loss: 0.17
<省略>
Epoch[23] Iteration[200/469] Loss: 0.07
Epoch[23] Iteration[250/469] Loss: 0.05
Epoch[23] Iteration[300/469] Loss: 0.24
Epoch[23] Iteration[350/469] Loss: 0.06
Epoch[23] Iteration[400/469] Loss: 0.12
Epoch[23] Iteration[450/469] Loss: 0.08
training Results - Epoch: 23  Avg accuracy: 0.99 Avg loss: 0.03
validation Results - Epoch: 23  Avg accuracy: 0.99 Avg loss: 0.03

学習が終わると以下のディレクトリが新しくできているかと思われます。

  • checkpoints
    • 学習中と学習後のモデルのパラメータが格納されています
    • 私が確認したときは以下のファイルがあり、final_weights.pt を除くとちゃんと n_saved=3 が反映されていることが確認できました
      • final_weights.pt
      • sample_mymodel_18.pth
      • sample_mymodel_20.pth
      • sample_mymodel_22.pth
  • log
    • 学習結果が格納されています
  • raw、processed
    • これは MNIST のデータです(ですので他の Dataset を使った場合は発生しません)

学習結果の確認

$ tensorboard --logdir log

その後該当ページ(デフォルトですと localhost:6006) にアクセスして下記のようなグラフが見れれば OK です!

f:id:fam_taro:20181225195054p:plain
tensorboard による学習結果の確認

まとめ

  • 思ったより自分で定義することが多くてびっくりしました😇。
    • ただし Training 周りをスッキリさせることができ、導入する価値はあるなと思いました(特に for 文周り)。
  • Early Stopping やモデルの保存はちゃんとカバーされてて地味に嬉しかったです
  • 特に PyTorch をある程度書いてきて Training 周りのコードが整理しきれてない人は、テンプレートを知るという意味でも触る価値があるかと思います
  • 今回は触れませんでしたが、example を確認すると pytorch-ignite は GAN や 強化学習にも対応しており 非常に自由度の高いライブラリであることが確認できています

これを使って fine-tuning のテンプレートを作ってみたいなと思いました。

ぜひ皆さんも触ってみてください! ウチもやったんだからさ

今後確認しておきたいこと

  • PyTorch 1.0 対応
  • Multi GPU 対応
  • より細かい操作
    • 他のロスや出力に softmax 等がかかっていないときの対応