スケールとトレードオフ:「Googleのソフトウェアエンジニアリング―持続可能なプログラミングを支える技術、文化、プロセス」を読んだ

いわゆるビックテックの一角として不動の地位を確立しているGoogleの、ソフトウェアエンジニアリングに関するトピックを余すところなく書き詰めた一冊である。

本書はまず、「ソフトウェアエンジニアリングとは何か」から書き出されている(以下に引用)。言葉遣いや用語の定義にうるさい私としては、この構成に非常に好感が持てる。

Google社内でときに言われるのは、「ソフトウェアエンジニアリングとは時間で積分したプログラミングである」ということだ。

1章 ソフトウェアエンジニアリングとは何か

生産的にプログラミングをし続けることができる―書籍のタイトルにもなっている「持続可能なプログラミングを支える技術、文化、プロセス」を確立し改善し続けることが、ソフトウェアエンジニアリングだと言っている。単にコードやシステムを作成するのがプログラミングや開発だとしたら、それらを将来に渡って効率的に継続させる仕組みを考えるのがソフトウェアエンジニアリングなのだ。

プログラミングや開発を継続したとき、すなわち時間が経過すると何が起こるか。一般的には、プロダクトが成長し、組織やチームの規模が増大していく。この際になってもまだ、仕組みが効果的に機能することが重要なのだと本書は主張する。「スケール(する)」や「スケーラブル」というキーワードが頻出することからもそれは分かる。

また、そのような仕組みを構築していく上で、慎重かつ入念にトレードオフを決定していった様子も伺える。ここで改めて書くまでもないが、一般的な問題解決には「銀の弾丸」は存在せず、解決策ごとに長所と短所がある。コードレビュー、バージョン管理、テスト、CI/CDなどの多岐にわたるトピックについて、Google内部で起こった問題と、解決策および考慮したトレードオフが詳細に解説されており、ケーススタディとして非常に参考になる。

あとがきにもある通り、

Googleのソフトウェアエンジニアリングは、大規模かつ発展中のコードベースの開発と保守の方法における、並外れた実験であり続けてきた。

本書を通読すると、Googleのソフトウェアエンジニアリングが並外れていることが理解できる。また、その歴史やノウハウを惜しげもなく公開する行為に、Googleにとっては本書の内容はただの通過点であり、競合他社が真似をしたとしても追いつけないだろうという自信が垣間見えるようにも感じる。

以下は特に印象に残った箇所を引用する。トピックごとにまとめている。

文化

存続期間の長いプロジェクトでは、最初の決定が行われた後に方針を変更する能力があることが不可欠である場合が多い。そして、重要なのは、それが、決定者が間違いを認める権利を有していなければならないのを意味することだ。

1.3.5 決定を再考すること、間違うこと

ここだけでなく、機敏さ (aglie) をもって改善していくことが繰り返し強調されている。

Googleに何年も在籍しているエンジニアでも、やっていることの内容を把握していると感じていない領域が依然としてある。それでいいのた!「それが何か知らないので、説明してくれませんか」と言うことを恐れてはいけない。何か知らないものがあることを恐れるよりは、またとない機会が眠る領域として受け入れよう。

3.4.1 質問を尋ねよ

Googleのエンジニアは無敵なのかと思っていたが、ちょっとホッとする一節である。

組織の文化というものは、ドロドロした人間的なものであり、多くの企業では後付けで扱われるものである。しかしGoogleにおいて我々が確信しているのは、その環境の成果物(例えばコード)にのみ焦点を当てるより、まずは文化と環境に焦点を当てる方が良い結果につながるということだ。

3.7.1 知識共有の文化を養う

成果物よりも文化と環境が優先されるべき。言うは易く行うは難し。

コーディング・コードレビュー

自分という存在と、自分のコードとは別だ。

2.4.3 謙虚、尊敬、信頼の実践

いろいろな記事や書籍で言及されているが、これは大事。

Googleには非常に力強いコードレビュー文化があり、リーダビリティはそのような文化の自然な延長である。リーダビリティは、1人のエンジニアの情熱から始まり、生身の人間である専門家たちがGoogleの全エンジニアをメンタリングするという公式プログラムへと発展していった。

3.8.2 何故このプロセスがあるのか

プログラミング言語ごとにリーダビリティをチェックする担当が存在する。また、コードごとにそのオーナーが存在する。レビュープロセスでは、これらのメンバーによるレビューが通ってはじめて、PR(GoogleではCL: Change Listと言うらしい)がマージできる。こうすることで、コードの可読性が担保されるとともに、誰がオーナーか分からないコードが生まれにくくなる。

「小さな」変更とは通常、約200行までのコードに限定されるべきだ。

(中略)

Googleでの変更の大半は、約1日以内にレビューされることが期待されている(これはレビューが1日以内に終わることを必ずしも意味せず、最初のフィードバックが1日以内に提供されることを意味している)。

9.4.2 小さな変更を書け

PRの大きさ、レビューのレスポンスの早さの参考に。

変更説明というものは、1行目に要約としてその変更がどんな種類のものかを示すべきだ。

(中略)

最初の行は変更全体のようやくとなるべきだが、何が変更されているのか、そして何故変更されるのかの詳細についても説明が及ぶべきである。「バグ修正」という説明があるだけでは、レビュアーや、将来のコード考古学者にとっては助けにならない。

9.4.3 良い変更説明を書け

テスト

我々は上記のように区別しており、もっと伝統的な「ユニット」または「インテグレーション」の区別は用いていない。その理由は、我々がテストスイートに望む最も重要な特性は、テスト範囲とは無関係に、速度と決定性 (determinism) だからである。

11.2.1 テスト規模

Googleではテストを規模に応じて(特大・)大・中・小に分類している。確かに言われてみれば、「ユニット」と「インテグレーション」の区別を鵜呑みにしていたが、そうではない区別も考えられる。

信頼不能性が1%に接近すると、テストは価値を失い始めるということだ。

11.2.1 テスト規模

信頼不能なテストとは、非決定性要素によりたまに失敗するテスト。

何故、テストを書くことを命令として強制するところから始めなかったのだろうか。  テスト小グループは、テストに関する命令を出すようシニア幹部陣に求めることを検討したが、すぐにそれは行わないことに決めた。コードの開発方法についての強制は、どんなものであろうが、Googleの文化に真っ向から逆らうものであると同時に、おそらく進歩を鈍化させるものだ。それはどんな思想が強制されるかに関係ない。成功する思想は広まるものであるというのが我々の信念であり、したがって専念するべき点は、成功を実証してみせることとなった。

11.4.4 今日のテスト文化

文化の醸成についての良い示唆。強制するのではなく、試させて価値を感じさせてはじめて根付く。

・テストダブルより本物の実装が優先されるべきである。 ・テスト内で本物の実装が利用できないなら、フェイクが理想的な解法である場合が多い。 ・スタビングを使いすぎると、不明確で脆いテストにつながる。

13.10 要約

自分の場合、テストではすぐにモックを使う傾向にあるが、本物を使ってしまう方が良いとのこと。

ドキュメンテーション

Googleで最も成功を収めている取り組みは、ドキュメンテーションをコードのように扱うとともに、伝統的なエンジニアリングのワークフローへ組み込むというものであり、エンジニアが単純なドキュメントを書いて保守するのを楽にしている。

10章 ドキュメンテーション

「コードのように扱う」というのは良いやり方。具体的には次の通り。

Googleが取っているアプローチは、C++APIはそのリファレンスドキュメンテーションをヘッダーファイル内に存在させておくのがふさわしい、というものだ。他のリファレンスドキュメンテーションもまた、直接JavaPython、Goソースコードに埋め込まれている。

10.5.1 リファレンスドキュメンテーション

また、wikiベースのドキュメンテーションをしている組織は多いだろうが、Googleもかつてはそうだった。しかし、スケールの途中で問題が発生している。おそらく、多くの方にも見覚えのある光景なのではないだろうか。

wikiドキュメントには真のオーナーがいないため、多くのドキュメントが廃れていった。新ドキュメント追加に関するプロセスが施行されていなかったため、重複したドキュメントやドキュメント集が現れ始めた。GooWikiの名前空間はフラットで、誰もドキュメンテーション集をうまく階層化できなかった。

10.3 ドキュメンテーションはコードのようなものである

ランディングページ(ポータル)の存在が重要であるとも主張されている。

要は、ランディングページが必ず目的を明確に特定しているようにし、それからさらなる情報を得るための他のページへのリンクのみを含めるようにするのだ。もしランディングページ上で交通整理の警官以上のことをやっているものが何かあったら、そのランディングページは本来の仕事をしていない。

10.5.5 ランディングページ

バージョン管理

Googleでは、Piperと呼ばれる独自のVCSを利用している。GitHubのようなVSCとの大きな差異は、リポジトリディレクトリやファイルごとにオーナーシップの概念が取り入れられていることのようだ。

Googleでは、VCSを掌握することで、オーナーシップと承認の概念をより明示的にすることができている。またその概念を、コミット操作の試行中ははVCSによって強制できる。

16.3 Googleでのバージョンコントロール

また、依存するコンポーネントやライブラリのバージョンを選ぶことができない。とんでもない話に思えるが、解説を読むと納得がいく。

「単一バージョンルール」は、組織の効率にとって驚くほど重要である。どこにコミットすべきか、あるいは何に依存すべきかについての選択の余地をなくすことで、著しい単純化を結果として得ることができる。

16.7 要約

CI

・CIシステムは、どんなテストを利用するべきか、またいつ利用すべきかを決定する。

(中略)

・CIは、早くて信頼性の高いテストがリポジトリー提出前に配置され、遅くて決定性の低いテストがリポジトリー提出後に配置されるように最適化されるべきである。

23章 継続的インテグレーション 23.4 要約

CIの勘所がよくまとまった要約。

CD

技術の状況が移り変わる速さと予測不可能性とを前提とすると、あらゆる製品にとって、競争的優位とは、迅速に市場への投入ができる能力の中に存在するものである。

24章 継続的デリバリー

リリーストレインと呼ばれる一定周期でのリリース作業が行われ、いかなる理由でもこれは延期されない。

リリースの責任の1つは、製品を開発者から守ることである。  トレードオフを行う際に、開発者が新機能をローンチすることについて感じる情熱と切迫感が、既存製品のユーザーエクスペリエンスに勝ることは、決してありえない。

24.7 チームの文化を変える:デプロイに規律を組み込む

コンピュート環境

データセンターの拡大は複雑な手動のプロセスで、特化したスキルセットを要し、(全マシンを用意した時点から)何週間もかかるので、内在的なリスクがあった。それにもかかわらずGoogleが管理するデータセンターの数が増加していることが意味するのは、データセンターの拡大が人間の介在を要しない自動プロセスとなっているモデルへGoogleが移行したということだ。

25.1 コンピュート環境を手なづける

「データセンターの拡大が人間の介在を要しない自動プロセスとなっている」というのは、とてつもない話だ。

マネジメント

マネジメントの仕事の定量化は、作ったウィジェットの数を数えるより難しい。だが自分のチームが満足かつ生産的でいられるようにすることは、マネジメントの仕事の重要な尺度である。とにかく、バナナを育てているのに林檎を数える罠に落ちてはならない。

5.2.1 恐れるべき唯一のものは……えっと、全てだ

管理職のみなさま、なにとぞ。

障害を取り除く助けになる答えの全てを知っている必要はないが、答えがわかる人々を知っているとたいてい役立つ。

5.5.4 障害を取り除け

人脈を作っておく意義。

このプロセスには3つの主なステップがある。まず、目隠しを特定しなければならない。次に、トレードオフを特定しなければならない。それから、決定を行って解法を反復しなければならない。

6.1.1 飛行機の比喩

ここでも「解法を反復」とある。一度決めたやり方は定期的に見直す。

よくある誤りは、チームに一般的問題ではなく特定製品を担当させることだ。

6.2.2 問題空間の分割

本当によくある誤り。視野を狭窄させてしまうだけでなく、モチベーションも下げる恐れがある。

つまり自分しかできない決定的に重要なものを意識して識別し、それらに完全に専念すべきだ。他の80%を落とすことを自身に明示的に許可しなければならない。

6.3.3 ボールを落とすことを学べ

改善のための計測

その利害関係者がそのデータを使わないのであれば、その計測プロジェクトは常に失敗だ。計測結果に基づいて具体的決定がなされるであろう場合にのみ、ソフトウェアプロセスの計測を行うべきである。

7.2 トリアージ:そもそも計測するほどの価値があるか

文章にしてみれば当たり前のように読めるが、これを守れていない計測もよく見かけるような…。

Partial Label Maskingでマルチラベル分類問題のデータ不均衡に対応する

まとめ

マルチラベル分類問題におけるデータ不均衡に対応する手法として、Partial Label Masking (PLM) が利用できる。同手法の概要は次の通り。

  • サンプルごとに各クラスに対する損失関数を確率的にマスクすることで、アンダーサンプリングに似た効果を期待する
    • 任意の損失関数に対して適用できる
  • マスクする確率は、分類器がそのクラスをどれだけ過剰に/過小に予測しているかに応じて決める

論文

K. Duarte, Y. Rawat and M. Shah, "PLM: Partial Label Masking for Imbalanced Multi-Label Classification," CVPR2021 Workshops, pp. 2739-2748.

Partial Label Masking (PLM)

いま、 i 番目のサンプルについて、 \mathbf{y}^{(i)} を目的変数(真値)、ネットワークの推論結果  \hat{\mathbf{y}}^{(i)} と書くとき、すべてのクラス( C個ある)に対する損失関数は次のように書ける。

 \displaystyle L(\mathbf{y}^{(i)}, \hat{\mathbf{y}}^{(i)}) = \displaystyle \Sigma^{C}_{j=1}l(y_j^{(i)}, \hat{y}_j^{(i)})

Partial Label Masking (PLM) では、特定のクラスに対する損失関数をマスクしてしまう。具体的には、2値のマスク \mathbf{g}^{(i)} = [g_1^{(i)}, \cdots , g_C^{(i)}]を前述の損失関数に適用する。すなわち損失関数は以下のようになる。

 L(\mathbf{y}^{(i)}, \hat{\mathbf{y}}^{(i)}, \mathbf{g}^{(i)}) = \displaystyle \Sigma^{C}_{j=1}g_{j}^{(i)}l(y_j^{(i)}, \hat{y}_{j}^{(i)})

式から明らかなように、PLMは任意の損失関数に対して適用できる。

適切にマスク \mathbf{g}^{(i)}を設定することで、損失関数に寄与する、それぞれのクラスに対するサンプル数を等しくできる。これはアンダーサンプリングの考え方と似ている。多クラス分類 (Multi-class classification) 問題では、アンダーサンプリングにより学習データ内のラベルの偏りを軽減することができる。しかしながら、マルチラベル分類 (Multi-label classification) では、同一のサンプルにおいて頻出なラベルと希少なラベルが共起する場合があり、アンダーサンプリングによりこのようなサンプルを取り除いても不均衡の解消にはつながらない。PLMを使えば、このようなラベルの共起に対応しつつ、学習データ内のラベル不均衡を解消できる。

マスクの生成

マスクの生成では、正例の負例に対する比率  r_c を考える。この比率は、学習データに含まれる正例の数  n_c^{+} を負例の数  n_c^{-} で割ることで算出する。また、クラスの過剰あるいは過小な予測を最小化する、理想的な比率  \bar{r}_c を仮定する。すると、マスクは確率的な関数として次のように書ける。

 \begin{eqnarray}
g_c^{(i)} = f(y_c^{(i)}) = \left\{ \begin{array}{lll}
\mathbb{1}\left[\frac{\bar{r}_c}{r_c}\right] & (r_c > \bar{r}_c\ \mathrm{and}\ y_c^{(i)}=1) \\\
\mathbb{1}\left[\frac{r_c}{\bar{r}_c}\right] & (r_c < \bar{r}_c\ \mathrm{and}\ y_c^{(i)}=0) \\\
1 & (otherwise)
\end{array}\right.
\end{eqnarray}

ここで、 1[p]は確率 pで1に、確率 (1-p)で0になる関数である。上式はすなわち、正例が過剰に予測されている(ネットワークが入力を正例だと推論する傾向にある)なら、正例のうち  \frac{\bar{r}_c}{r_c} だけを損失関数の計算に使うことを表している。反対に、正例が過小に予測されるなら(負例が過剰に予測されるなら)、負例のうち  \frac{r_c}{\bar{r}_c} だけを損失関数の計算に使う。これにより、それぞれのクラスについて、正例と負例の比率を指定しながら分類器(ネットワーク)を訓練できる。

比率の適応的選定

では、理想的な比率  \bar{r}_c をどのように選べば良いだろうか。簡単な方法としては、学習データにて各クラスの比率を計算し、それらを平均することが考えられる。しかしながらこの方法は、著者らによると、うまく機能しない場合(データセット)があるそうだ。そこで著者らは、分類器の予測結果の確率分布から適応的にこの比率を推定する手法を提案している。

あるクラス  c の正例と負例について、分類器が出力する確率の分布をそれぞれ  \hat{P}_c^{+}, \hat{P}_c^{-} とする。分布は  \tau 個のビンに分け、離散的に扱う。また、学習データ(真値)の分布を  P_c^{+}, P_c^{-} とする。ただし、学習データでは確率が分布しているわけではなく、正例あるいは負例であることが確定しているので、確率1または0を含むビンにすべてのデータが入っている。

分類器が出力する確率の分布と、真値の分布とのずれをKullback-Leibler divergence(KLダイバージェンス、KL情報量とも)で表す:

 \begin{eqnarray}
D_c^{+} & = & D_{KL}(\hat{P}_c^{+}||P_c^{+}) \\\
D_c^{-} & = & D_{KL}(\hat{P}_c^{-}||P_c^{-})
\end{eqnarray}

さらに、これら標準化して  \tilde{D}_c^{+} および  \tilde{D}_c^{-} を得る。 \tilde{D}_c^{+} が正のとき、分類器はクラス  c を過小に予測している傾向にある(反対に、負のとき、過剰に予測している)。この逆が  \tilde{D}_c^{-} に成り立つ。

 \tilde{D}_c^{+} \tilde{D}_c^{-} がバランスするように  \bar{r}_c を更新すればよい。エポック  t における  \bar{r}_{c,t} を、ひとつ前のエポックでの  \tilde{D}_c^{+} \tilde{D}_c^{-} の差  D_c = \tilde{D}_c^{+} - \tilde{D}_c^{-} を用いて以下のように更新する:

 \bar{r}_{c,t} = \exp(\lambda D_c)\bar{r}_{c,t-1}

ここで、 \lambda は比率を更新する割合を定めるハイパーパラメータである。

モデル訓練のループ

モデルを訓練するループは以下のようになる。

  1.  \bar{r}_{c,t} を使ってマスク  g_c^{(i)} を生成する
  2. すべてのミニバッチに対して、マスクした損失関数を使ってパラメータを更新する(一般的なニューラルネットと同様に、順伝搬→逆伝搬を使う)。このとき、順伝搬した結果(推論結果である確率分布) \hat{\mathbf{y}} を保存しておく
  3. 順伝搬した結果  \hat{\mathbf{y}} と真値  \mathbf{y} から、 D_c を算出する
  4.  D_c を用いて  \bar{r}_{c} を更新する

実験結果

素朴なBinary cross entropy, Focal loss, サンプル数の逆数での重み付けといった手法と性能を比較している。分量が多いため、詳細は元論文を参照されたい。

分類器の信頼度を使うならtemperature scalingでキャリブレーションしよう

論文

C. Guo, G. Pleiss, Y. Sun and K. Q. Weinberger, "On calibration of modern neural networks," ICML2017, pp. 1321-1330.

なお、本記事中の図は、論文から引用したものである。

まとめ

論文を読んで得られた知見をまとめると以下の通り。

概要

分類器が出力する信頼度 (confidence) が、分類結果が真に正しい可能性を表すように補正する (calibrate) ことを、信頼度キャリブレーション (confidence calibration) と呼ぶ。分類器が適切にキャリブレーションされていることは、自動運転や医療など広いアプリケーションで重要である。

著者らは、現代のニューラルネットワークは、一昔前のものと比べると、キャリブレーションが不十分であることを発見した。また、ネットワークの深さ、幅、重み減衰 (weight decay), バッチノーマリゼーション (Batch Normalization) がキャリブレーションに重要な影響を与えることを観測した。

いくつかの後処理 (post-processing) キャリブレーション手法について、画像や文書の分類データセットと最新のアーキテクチャを用いて評価した。この評価を通して、temperature scalingが有効な手法であることを示した。

信頼度キャリブレーションの評価指標

Reliability diagrams

f:id:mamo3gr:20210207100801p:plain

分類器のキャリブレーションの良し悪しを視覚的に表現にしたものがReliability diagramである(図の下段にあるものが該当する)。同図は、あるデータセットにおける推論結果(分類の正解・不正解と、その際の信頼度)を用いて作成できる。横軸は信頼度のビン (bin) で、ビンの分割数 Mは適当な数を指定する(図では M=10)。縦軸は、それぞれのビンの正解率 (Accuracy) である。すなわち、ビンに入る分類結果のうち、正解であるものの割合である。

分類器が完璧にキャリブレーションされている (perfectly calibrated) とき、正解率は信頼度の恒等関数 (identity function) になる。このときReliability diagramでは、各ビンの棒グラフが、図の右下半分の三角形をちょうど占めるようになる。

ちなみに、Reliability diagramと等価な図としてCalibration curveがある。同図は、Reliability diagramの各ビンの頂点をつないだ折れ線グラフである。scikit-learnにはCalibration curveを描くための関数が存在する:sklearn.calibration.calibration_curve — scikit-learn 0.24.1 documentation

Expected Calibration Error (ECE)

分類器のキャリブレーションの良し悪しをスカラーで表現したものがExpected Calibration Error (ECE) である。Reliability diagramは良し悪しが視覚的に分かりやすいものの、比較の上ではECEのように定量的な表現の方が有益である。ECEは、Reliability diagramの各ビンにおける正解率と信頼度の差を、重み付き平均することで算出できる。

ECEは重み付き平均を用いるが、重み付き平均ではなく最大値を取ることもできる。このように算出された指標はMaximum Calibration Error (MCE) と呼ばれる。信頼度と正解率の差異がクリティカルになるアプリケーションでは、MCEを用いることもある。

Negative Log Likelihood (NLL)

Negative log likelihood (NLL) は、確率的なモデルの品質を測る標準的な尺度である。深層学習では交差エントロピー損失 (cross-entropy loss) としても知られる。確率モデル  \hat{\pi}(Y|X) とn個のデータが与えられたとき、NLLは次式のように書ける。

 \mathcal{L}=-\Sigma^n_{i=1}\log\left(\hat{\pi}(y_i|x_i)\right)

NLLが最小化される必要十分条件は、 \hat{\pi}(Y|X) が真の分布  \pi(Y|X) を復元していることである。

ミスキャリブレーションの観察

f:id:mamo3gr:20210207100905p:plain

上図は、ネットワークのアーキテクチャを変更したり学習上の工夫を入れたりしたときの、不正解率 (error) およびECEが受ける影響を表している。以下が読み取れる。

  • ネットワークの深さを増すと、errorは下がるが、ECEは上がる
  • 幅を増すと、やはりerrorは下がるが、ECEは上がる
  • Batch Normalizationを適用すると、errorは下がるがECEは上がる
  • weight decayの重みを増すと、errorもECEも下がる。なお、右端でerrorやECEが悪化しているのは、正則化がかかりすぎているためだと考えられる

NLLの過適合

f:id:mamo3gr:20210207100930p:plain

上図はResNet-110をCIFAR-100で訓練したときの、test setにおけるerrorとNLLを表す。なお、epoch 250と375のそれぞれでlearning rateを1/10に落としている。グレーの領域は、validation setでerrorおよびlossが最良であった区間を表す。

著者らは、正解率(あるいはerror)とNLLとの間の断絶を観測した。すなわち、ニューラルネットワークは、0/1 lossに過適合せずにNLLに過適合できる、ということだ。

Epochを追うごとに、test errorが下がる一方で、test NLLが増加している。これは、次の2つを示唆している:ニューラルネットワークは、(1) 0/1 lossに過適合せずにNLLに過適合できる、(2) well-modeled probability(信頼度が正解率を表すこと)を犠牲に分類性能を上げている(errorを下げている)。

注:論文中でいきなり"0/1 loss"なる用語が出現するなど、やや意味が取れない部分があったので、自分なりに考えてみる。Deep Neural Network (DNN) の学習で用いるクロスエントロピー損失は、正解クラスに対する信頼度を各データについて合計することで求まる。すなわち、

 \mathcal{L}=-\Sigma^n_{i=1}{y_i\log q_i + (1-y_i)\log(1-q_i)}

を計算している。ここで、i番目のデータに対し、真のラベル (0/1) を y_i, 分類器が出力した信頼度を q_iとする。論文では、分類結果が真のラベルと一致しているか、すなわち真のラベルに対応するクラスに最大の信頼度を割り振れているかどうか、を0/1 lossと呼び、その信頼度が真の確率分布に近いかどうか、をNLL lossと呼んでいるのだと推測する。

信頼度キャリブレーションの手法

二値分類

Histogram binning

分類器が出力した信頼度をビンに分け、ビンごとに補正後の出力を決める。補正後の出力は、ビンに入った信頼度の平均とする。

Isotonic regression

補正前後の信頼度をマッピングする関数 fを学習する。任意の2つの信頼度は、補正前後で大小関係が前後しないという拘束条件をかける。ビンの区切りと出力を一緒に最適化しているという点で、histogram binningの一般化といえる。なお、scikit-learnに実装がある:sklearn.isotonic.IsotonicRegression — scikit-learn 0.24.1 documentation

Bayesian Binning into Quantiles (BBQ)

Histogram binningをBayesian model averagingによって一般化した手法らしいが、後述の実験によるとあまり有効ではないようなので省略。

Platt scaling

補正前の出力に対してロジスティック回帰を適用し、補正後の出力を求める。ロジスティック回帰のパラメータ推定にはvalidation setを用いる。なお、scikit-learnでは、SVMにPlatt scalingを適用してキャリブレーションした信頼度を出力するオプションがある:sklearn.svm.SVC — scikit-learn 0.24.1 documentation

多クラスへの拡張

ここまではクラス数 K=2の場合を述べてきたが、これを K>2に拡張する。

Binning methodの拡張

K個のone-versus-all問題として解く。すなわち、各クラスについてhistogram binningを行い、キャリブレーションされた信頼度を得る。キャリブレーションされた信頼度が最も高いクラスを分類結果とする。また、信頼度は正規化して最終的な出力とする(各クラスに対するキャリブレーション後の信頼度の総和で割る)。このアプローチはisotonic regressionやBBQにも適用できる。

Matrix and vector scaling

Matrix scalingは、Platt scalingを多クラスに拡張したものである。softmax層 \sigma_{\mathrm{SM}}に入力される前にロジットベクトルを \mathbf{z}_iを重み \mathbf{W}とバイアス \mathbf{b}でスケールする:

 \hat{q_i}=\max_k\sigma_{\mathrm{SM}}(\mathbf{W}\mathbf{z}_i+\mathbf{b})^{(k)}

ここで、 \hat{q}_iキャリブレーションされた信頼度、 kはクラスのインデックスである。 \mathbf{W} \mathbf{b}はvalidation setとNLLを用いて最適化する。 \mathbf{W}を対角行列にしたものをvector scalingと呼ぶ。

Temperature scaling

Temperature scalingは、Platt scalingのシンプルな拡張である。ロジットベクトルz_iが与えられたとき、すべてのクラスに対する信頼度をスカラーT>0でスケールする:

 \hat{q_i}=\max_k\sigma_{\mathrm{SM}}(\mathbf{z}_i/T)^{(k)}

 Tは温度 (temperature) とも呼ばれる。matrix/vector scalingのパラメータと同様に、 Tはvalidation setとNLLで最適化する。

 Tはsoftmaxの出力が最大となる位置(クラス)を変えない。言い換えると、temperature scalingは分類器の分類結果を変えないし、分類器の正解率を下げることもない。※一方で、binningに基づく手法はそうではない

信頼度キャリブレーションの評価

キャリブレーション結果

f:id:mamo3gr:20210207101024p:plain

画像・文書の分類データセットにおける、ResNetを始めとしたモデルについて、補正前後のECEを測定した結果が上表である。区切り線の上側が画像の分類データセットで、下側が文書のものである。

ほとんどのケースでミスキャリブレーションは発生する(ECEは4〜10%程度)。例外はSVHNとReutersで、ECEはどちらも1%を切っている。これらのデータセットでは、不正解率も低い(それぞれ1.98%, 2.97%)。また、ミスキャリブレーションはネットワークのアーキテクチャに依存しない。ResNetのようにskip-connectionを持つかどうか、reccurent(再帰的)かどうか、などは関係ない。

Temperature scalingは、そのシンプルさにもかかわらず、画像の分類データセットでその他の手法に優る。文章のデータセットでも他の手法と同等である。Matrix/vector scalingはtemperature scalingを一般化した手法だが、temperature scalingに劣るケースが多い。これは、ミスキャリブレーションが本質的に低次元であることを示唆している。

Temperature scalingでECEが唯一改善しなかったのは、データセットReuterである(temperature scalingに限らず、vector scalingを除くすべての手法で改善できていない)。キャリブレーション前のECEが良好で (< 1%), キャリブレーションの余地がなかったと考えられる。

Matrix scalingは、Birds, Cars, CIFAR-100などのクラスの多いデータセットで成績が悪かった。さらに、ImageNetのようなデータセットでは収束しなかった。これは、クラス数の二乗でパラメータ数が増え、過学習するためである。

Binningに基づく手法はほとんどのデータセットでECEを改善したが、temperature scalingよりも優れない。また、分類結果を変え、分類性能を低下させる傾向がある。これらの手法の中でもhistgram binningは最も単純なものだが、より一般的な手法であるisotonic regressionやBBQよりもECE改善の面で優れている。これは、シンプルなモデルでキャリブレーションを行うのが良いという著者らの主張を裏付けている。

計算時間

(本実験で扱った)すべての手法の計算時間は、validation setのデータ数に対して線形にスケールする。temperature scalingは、1次元の凸最適化問題を解くため、最も速い。共役勾配法 (conjugate gradient) ソルバーを用いた場合、10回のイテレーション以内で最適解が求まる。素朴な線形探索 (linear search) を使ってさえ、他のすべての手法よりも速い。

参考文献

次に読む

不均衡データはundersampling+baggingしろ、という話

まとめ

不均衡なデータの分類器を学習するときはundersampling+baggingすべし。 特に以下の場合に、コストを調整する手法やoversampling(SMOTEなど)に対して優れている。

  • 次元数が多い
  • 少数クラスのデータ数(の比率)が少ない
  • 学習データの規模が小さい

きっかけ

不均衡なデータを扱う問題に遭遇したので、先人たちの工夫について調べていたところ、以下のツイートを発見した。

ここで紹介されている論文、およびその解説記事などを読み漁った。本記事ではそれらをつまみ食いした知見をまとめる。

論文

Byron C Wallace, Kevin Small, Carla E Brodley and Thomas A Trikalinos, "Class imbalance, redux," Proc. Int'l Conf. Data Mining (ICDM), pp. 754–763. 2011.

不均衡データによって生じる分類器のバイアス

まず、不均衡データによって分類器にバイアスが生じる様子を示す。

f:id:mamo3gr:20210113224210p:plain
不均衡データで学習した線形分類器の識別境界(論文より引用)

簡単のため線形分類器で2値分類をする場合を考える。正規分布のような山は、それぞれ正例と負例の真の分布を表す(左側が正例、右側が負例である)。これらの分布から取り出されたサンプルを、バツ印(正例)と四角(負例)で表している。図から明らかなように、正例がminority classである。さらに、識別境界を w と表し、その左側の領域を  R^+_w, 右側の領域を  R^-_wと記す。

ここで、理想的な境界は、真の分布から  w^{*} とわかる。しかしながら、サンプルから(例えばマージン最大化で)学習された境界は、点線で示された  \hat{w} のようにバイアスがかかってしまう。

バイアスが発生する条件

次に、このようなバイアスが発生する条件を考える。

データセットに含まれる正例の集合を  D^+, 負例の集合を  D^- とする。データセット  D = D^+ \cup D^- で学習したときの経験損失 L_D(w) は、偽陰性偽陽性のコスト(ペナルティ)をそれぞれ  C_{fn}, C_{fp} とすると、

 L_D(w) = C_{fn}|\{ x|x\in D^+ \wedge x\in R^w_-\}| + C_{fp}|\{ x|x\in D^- \wedge x\in R^w_+\}|

となる。要は、偽陰性の数×コストと、偽陽性の数×コストの和である。このとき、バイアスが生じる必要十分条件は、次のように書ける。

 \exists w^\gamma s.t. \forall w' \in \{w: R^w_+ \geq R_+^{w^*}\}, L_D(w^\gamma) < L_D(w')

すなわち、理想的な境界  w^* の右側にあるすべてのどんな境界よりも、経験損失を小さくする境界が左側に存在するとき、バイアスが生じると言っている。

このようにバイアスが発生する、すなわち  w^\gamma が存在する条件を考える。正例と負例の真の分布をそれぞれ  P(x), G(x) とし、prevalence(母集団における正例の割合)を  \pi とすると、

 (1-\pi)C_{fp}\int_{R^+_{w^*}}G(x)dx>\pi C_{fn}\int_{R^-_{w^*}}P(x)dx

のとき、 w^\gamma が存在しうる。

バイアスを低減するためのアンダーサンプリング、およびbagging

上式を成立しにくくするためには、 \pi = 0.5とすればよい。すなわち、majority classをアンダーサンプリングし、クラス間のデータ数を揃えればよい。

アンダーサンプリングはバイアスを低減できる一方で、モデルのvarianceは増えてしまう(学習される識別境界が、ランダムサンプリングによってばらつくことを言っている)。このvarianceを抑えるために、baggingするのがよい。

コストの調整やオーバーサンプリング

コストの調整

不均衡データに対する対処法として、偽陰性偽陽性のコストに重みをつける手法が知られている。しかしながら、このような重み付きコストが効果を発揮しにくい場合がある。

いま、 C_{fn}=C_{fp}=1 (同じ重み)として、ある境界  \hat{w}_1 が学習できたとする。ここで、 C_{fn} \beta 倍することを考える。このコスト増加によって境界  \hat{w}_1 が動くのは、 \hat{w}_1 が1つ以上の偽陰性をもたらす場合だけである。このような偽陰性のデータが無いとき、 \beta にかかわらず、 \hat{w}_1 は既に損失関数を最小化している。なお、 \hat{w}_1 を動かすような偽陰性が観測される確率は次のように書ける。

 \pi|D|\int_{R^-_{\hat{w}_1}}P(x)dx

上式より、以下のような場合に  C_{fn} の増加が  \hat{w}_1 を動かす、すなわちバイアスの低減に寄与しにくいことがわかる。

  • prevalence  \pi が低い場合(正例の割合が小さい場合)
  • データセットの規模が小さい場合(データ数  |D| が少ない場合)

また、 P も寄与を左右する。具体的には、理想的な境界  w^* の周辺で  P が密である場合に、コストの調整はバイアス低減に寄与しやすい。

オーバーサンプリング (SMOTE)

重み付きコストの他に、SMOTEのようなオーバーサンプリングに基づく手法も知られている。SMOTEでは、minority classのデータ同士で近いものの間に新しいデータを内挿することで、データ数を増やす。したがって、SMOTEで新たに生成されるデータは、既に存在するminorityなデータの凸包 (convex hull) の外側に現れることはない。これより、SMOTEがバイアスの低減に効果を発揮するのは、コストを調整する手法が効果を発揮する場合と同様である。SMOTEによって新しく生成されたデータが境界  \hat{w}_1 を動かすには、このデータが偽陰性とならなければいけない。

実験

論文ではシミュレーションデータや実際のデータセットで実験し、ここまで述べた理論の裏付けをしている。内容は省略。

関連トピック

予測確率のバイアス

アンダーサンプリングによって、分類器の予測確率にバイアスが生じてしまう。アプリケーションにて予測確率が重要な場合は、キャリブレーションすべきとのこと。

参考:ダウンサンプリングによる予測確率のバイアス - sola

DNN (Deep Neural Network) への適用

モデルがDNNの場合は、訓練や推論の時間的にもモデルの容量的にもbaggingを単純に適用するのは難しいことが多い。これに対して、ミニバッチ内でアンダーサンプリングを行うことで、似たような効果があるようだ。

参考:不均衡データ分類問題をDNNで解くときの under sampling + bagging 的なアプローチ - BASEプロダクトチームブログ

参考文献

個人的Macbookセットアップメモ

主に自分のためのメモ。自動化するほど繰り返し行うわけでもないが、忘れて再検索するのも非効率なので。

Firefox

利益ではなく、人々のためのインターネット — Mozillaからダウンロードしてインストール。

Google日本語入力

Google 日本語入力 – Googleからダウンロードしてインストール。

システム環境設定

一般

  • スクロールバーの表示:常に表示

セキュリティとプライバシー

ファイアウォール

ネットワーク

接続しているネットワークの「詳細」から、DNSサーバに 8.8.8.88.8.4.4 を追加する。

キーボード

キーボード

  • キーのリピート:最速
  • リピート入力認識までの時間:最速
  • F1、F2などのキーを標準のファンクションキーとして使用

修飾キー

  • Caps Lock -> Control
Windows向け外付けキーボードを使う場合

ProgresTouch RETRO TINYを使っている。このキーボードを接続する場合、以下のようにOptionとCommandを入れ替えると、もとのキーボードに近いキー配置になる。

  • Option -> Command
  • Command -> Option

ショートカット

経緯は忘れてしまったが、長年 C-j で日本語・英語の切り替えをしている。

  • 前の入力ソースを選択:C-j

入力ソース

トラックパッド

ポイントとクリック

  • [x] タップでクリック
    • 誤クリックが増えそうな気もするが、クリックが楽なのでよい

ディスプレイ

Night Shift

目に良いらしいが、本当に効果あるのかな。

  • スケジュール:日の入から日の出まで
  • 色温度:「暖かく」最高

省エネルギー

バッテリー

  • [x] メニューバーにバッテリーの状況を表示

共有

かっこいいマシン名を設定する。

iTerm2

iTerm2 - macOS Terminal Replacementからzipをダウンロードして解凍。得られたバイナリを「アプリケーション」フォルダに放り込む。

隠しファイルを表示する

$ defaults write com.apple.finder AppleShowAllFiles -boolean true

共有フォルダで.DS_Storeを作成しない

defaults write com.apple.desktopservices DSDontWriteNetworkStores true

Homebrew

macOS(またはLinux)用パッケージマネージャー — Homebrew

Emacs

railwaycat/homebrew-emacsmacport: Emacs mac port formulae for the Homebrew package manager

% brew tap railwaycat/emacsmacport
% brew install emacs-mac

さまざまなバイナリや入手方法があるが、比較は[Home] Emacs For Mac OSが参考になる。上記の方法を採用しているのは、なるべく新しいバージョンを使いたいから。

その他アプリ

App Storeからインストール可能

個別に入手・インストール

フォント

Myrica M

プログラミングフォント Myrica / Estable | Myrica (ミリカ)は、フリーなプログラミング用 TrueType フォントです。

ターミナルやエディタはこれ。

白源

yuru7/HackGen: HackGen is Japanese programming font which is a composed of Hack and GenJyuu-Gothic.

たまにこっちも使う。

Cica

miiton/Cica: プログラミング用日本語等幅フォント Cica(シカ)

これもよい。

Migu 1C

Miguフォント : M+とIPAの合成フォント

Firefoxはこれ。読みやすくてかわいい。

参考にした記事

コーヒーミルのレビュー

大学の研究室時代から、珈琲を入れることが趣味になっている。コーヒーミルを新調した機会に、どんな観点から選んだか記録しておく。

選ぶポイント

手動

自分で苦労して挽いたほうが愛着が湧いて美味しく感じる。また、豆を挽く手間がかからないと、一日に何杯も飲んでしまう。加えて、電動のミルは総じて音が大きいのが気になる。

一度に1〜2杯分が引ければ十分。

豆の入れやすさ

入り口が広く、計量カップからこぼさずに入れられるか。また、蓋ができるものがいい。そうでないと、挽いている最中にこぼれてしまう。

お手入れのしやすさ

使っているうちに湿気?で粉が内部にこびりついてしまう。丸洗いできるものが良い。

均一さ

挽いた粉が均一であればあるほど、雑味が少ない(らしい)。とはいえ、定量的な基準があるわけではないので、レビューを参考程度に見る。

これまで使ったもの

HARIO CM-502C

蓋がシャッター式になっている。挽いているうちに勝手に空き、豆がこぼれる事件が発生したことがある。

Kalita C-90

研究室に置いてあったのがこれ。電動だから楽チンだし、欠点らしい欠点も感じなかった。家で使うには場所を取るかな。

Kalita K-2

どうしようもなくカッコイイ。ビジュアルに一目惚れして買ってしまった。欠点は、比較的大きくて重く、人によっては取り扱いにくい点。また、入り口が狭く豆がやや入れづらいこと、手に金属の匂いが移ってしまうことも気になる。

HARIO MSS-1TB

現状はこれがベストだと思う。口が広いので豆が入れやすく、蓋が閉まるので飛び散ることもない。粉受けに1杯分・2杯分の目盛りが付いており、豆の量を測る必要が無いのも良い。さらに丸洗い可能。ただし、レビューを見ていると、挽きの均一さはそれほどではない様子。

参考文献

PU learningことはじめ

一般的な教師あり学習では、各サンプルについて 正例 (positive) と負例 (negative) のラベルが与えられる。 しかしながら実際のタスクでは、正例とラベルなし (unlabeled) からなる データセットを扱う場合がある。 このような問題設定において精度良く分類を行うための手法がある。

本文

Charles Elkan and Keith Noto, "Learning classifiers from only positive and unlabeled data," KDD2008, pp. 213–220.

問題設定

サンプル  x\in\mathbb{R} について、ラベル  y\in{0,1} が 2値 (negative or positive) で与えられている。 また、サンプル  x にラベルが付与されているかどうかを  s\in{0,1} で表す。 0ならばunlabeled, 1ならばlabeledである。 これら  \langle x,y,s\rangle の組に対する不変の分布が存在する(もちろん未知である)。

正例(のうちの一部)だけがラベル付けされているとする。 すなわち、

 \displaystyle
p(s=1|x,y=0)=0

である。

このような条件のもと、 f(x)=p(y=1|x) なる 関数  f(x) を学習する。

なお、ラベル付けされる正例は、真の正例からランダムに選ばれると仮定する。 すなわち、

 \displaystyle
p(s=1|x,y=1) = p(s=1|y=1)

解法

先に結論を書く。

  1.  x s を使って、ラベル付けされている確率を出力する分類器  g(x) を学習する
  2.  s=1 なる  x に対し、  c=\frac{1}{n}\Sigma_{x\in P}g(x) を求める。
    • ここで、 P はdevセット内のラベル付けされているサンプルの集合で、  n はそれらサンプルの数である
    • ちなみに、 c は真の正例のうち、ラベル付けされているサンプルの割合である
  3.  sによる重み付けを行い  f(x) を学習する。具体的には、
    •  s=1 なる  x は単位重み(すなわち1)、ラベルを  y=1 とする
    •  s=0 なる  x は複製したのち
      • 片方に  w(x)=\frac{1-c}{c}\frac{g(x)}{1-g(x)} の重み、ラベルを  y=1
      • もう片方に  1-w(x) の重み、ラベルを  y=0 とする

詳細

 f(x) g(x) の関係

ラベル付けされる正例がランダムに選ばれるとすると、

 \displaystyle
\begin{align}
p(s=1|x) &= p(y=1\wedge s=1|x) \\
&= p(y=1|x)p(s=1|y=1,x) \\
&= p(y=1|x)p(s=1|y=1) \\
&= p(y=1|x)p(s=1|y=1)
\end{align}

ここで、 p(y=1|x)=f(x) であり、 g(x)=p(s=1|x),  c=p(s=1|y=1) とおくと、  f(x)=\frac{1}{c}g(x) である。 すなわち、もともと求めたい  f(x) は、ラベル付きである確率  g(x) に比例している。

[余談]  f(x) g(x) の増加関数になっているということは、  f(x) x のランキングのみを目的としているなら、  g(x) がそのまま  f(x) の代わりに使えるということである。

 c の推定

 c=p(s=1|y=1) は真の正例のうち、ラベル付けされているサンプルの割合である。 これを見積もる方法を考える。

論文では3つの方法が紹介されているが、ここではこのうち最良とされているものだけを述べる。 その方法とは、validation set (dev set) である  V のうち、  s=1 である  x の集合  P に対する  g(x) の平均値を取る、というものである。 この方法で  c が推定できるのは以下の式展開からわかる。

 \displaystyle
\begin{align}
g(x) &= p(s=1|x) \\
&= p(s=1|x,y=1)p(y=1|x) + p(s=1|x,y=0)p(y=0|x) \\
&= p(s=1|x,y=1)\cdot 1 + 0\cdot 0 \\
&= p(s=1|y=1) \\
&= c
\end{align}

注意

 g(x) を予測するモデルは、well-calibrated classifierである必要がある。 well-calibrated classifierとは、確率値を出力する分類器のこと。 例えば、logistic regressionは該当するが、 ナイーブベイズ、ディシジョン・ツリー、SVMは該当しない。 後処理でcalibratedになりうる。後処理の代表的なものは2つある:

  • isotonic regression
  • fitting a one-dimentional logistic regression function

unlabeledであるときにpositiveである確率

unlabeledであるときにpositiveである確率は以下のように求められる。


\begin{align}
p(y=1|x,s=0) &= \frac{p(s=0|x,y=1)p(y=1|x)}{p(s=0|x)} \\
&= \frac{[1-p(s=1|x,y=1)]p(y=1|x)}{1-p(s=1|x)} \\
&= \frac{(1-c)p(y=1|x)}{1-p(s=1|x)} \\
&= \frac{(1-c)p(s=1|x)/c}{1-p(s=1|x)} \\
&= \frac{1-c}{c}\frac{p(s=1|x)}{1-p(s=1|x)} \\
&= \frac{1-c}{c}\frac{g(x)}{1-g(x)}
\end{align}

重み付き学習

さらに、上記の結果を用いて、任意の関数  h(x,y) の期待値を算出してみる。


\begin{align}
E[h] &= \int_{x,y,s} h(x,y)p(x,y,s) \\
&= \int_x p(x)\Sigma_{s=0}^1 p(s|x) \Sigma_{y=0}^1 p(y|x,s)h(x,y) \\
&= \int_x p(x)\left(p(s=1|x)h(x,1) + p(s=0|x)[p(y=1|x,s=0)h(x,1) + p(y=0|x,s=0)h(x,0)]\right) \\
&\sim \frac{1}{m}\left(\Sigma_{\langle x,s=1\rangle}h(x,1)+\Sigma_{\langle x,s=0\rangle} w(x)h(x,1)+\left(1-w(x)\right)h(x,0)\right)
\end{align}

ここで、 w(x)=p(y=1|x,s=0) である。 上式は、 s=1 であるサンプルは単位重みで、  s=0 であるサンプルは、 重み  w(x) を持つ  y=1 のサンプルと、 重み  1-w(x) を持つ  y=0 のサンプルとして扱えばよいことを表している。

実験

従来手法であるbiased SVMに比べ、accuracy, F1-scoreで1〜2ポイント上回る。 また、従来手法は学習を何度も反復する必要があるが、提案手法ではその必要が無い ("relative time"は提案:従来=2:621)。 詳細は元論文を参照されたい。

サンプルコード

github.com

参考にしたページ