AWS認定アソシエイト3資格対策~ソリューションアーキテクト、デベロッパー、SysOpsアドミニストレーター~ の書評です。
AWSへこれから入門される方やある程度まとまったAWSの体系的な知識が欲しい方、また弊社のお若い人におすすめです!
AWS認定アソシエイト3資格対策~ソリューションアーキテクト、デベロッパー、SysOpsアドミニストレーター~ の書評です。
AWSへこれから入門される方やある程度まとまったAWSの体系的な知識が欲しい方、また弊社のお若い人におすすめです!
株式会社ホクソエム常務取締役のタカヤナギ=サンです、主にバックオフィス業務を担当しています。
自分メモに書き溜めていたポエムネタが溜まってきたので少しずつ放出していこうと思い筆をとりました。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?
以前、社のお若い方が技術的に楽しそうな案件を持ってこられて、その価格設定をどうするかについて悩まれておられた時がありました。
その際に社内のSlackにいわゆる”おじさんの小言”のようなものをちらほら書いていたので、それを改めて文章にし、更に「あ、この話は私がちょいちょい感じているおじさんエンジニアの辛さと賃金の話にもつながってくるな」思い、そことも絡めて書いたものになります。
既にご存じの方もいるかもしれないですが、弊社は全員が副業として回している会社で21世紀にふさわしい働き方改革ダイバージェンス2020を体現した会社となっております。 なので、OSSを開発するかの如く「うわぁ、この案件技術的に超楽しいナリ〜」と土日を潰してほぼ無料のご奉仕価格で開発しちゃうというのもありといえばあり、”ありよりのあり”になるのですがそれはやっていません。
なので、”ありよりのなし”、です。
「なんでじゃい、楽しければお安く引き受けてもいいんじゃないんかい💢(表現はもっとマイルドで低姿勢)」とお若い方がご本人の謙虚さからくる面もあってそう言われていたのですが、そこを諭す際によく引用させてもらっているのがこの「とんかつ屋の悲劇」という記事です。これを一部引用させていただくと
年金が形を変えた補助金に? 「何十年も変わらない値段と、チェーン店ではありえない品質の高さと格安さ」などとグルメサイトでも称賛されていることが多い。しかし、それを可能にしているのは、すでに減価償却の終わった古い設備、ローンを払い終えた自社店舗、そして年金をもらいながら夫婦で切り盛りしていることなどだ。 ある意味、年金が経営継続への補助金のようになっているわけだ。こうした経営を続けてきた場合、いよいよ世代交代の時期になると若い現役世代にはとても生活をしていけるだけの収入を得ることができない。 「そうなってから、急に値段を大幅に上げるなどはできないし、設備更新などに多額の費用がかかるので、後継者にとっては重荷になるでしょう。」商業関係の支援事業を行う行政職員は、そう話す。先の外食産業の社員も、「夫婦二人で一人分の給与しかなく、それでやっと可能になっているような低価格がウリでは、いくら有名でも、のれん代を出してまで買収する意味はあまりない」と言う。
という話です。
要するに「(ご本人らは年金で食えるので)善意から値上げをしていないのかもしれないが、結局はそれが過度な価格下落競争を生んで、近隣の似たような(この場合だととんかつ屋をやりたいお若い方の)店を強制的に廃業に追い込でいる」という話になるわけです。
これに絡めて「いいかい?俺たちはとんかつ屋の悲劇を起こしてはいけない。技術的に面白い(主にRの)話だからといってもめちゃ低価格で引き受けるのはやめよう!それは巡り巡ってお若いRユーザの芽🌱を摘むことになる(Rが儲からない言語になるのは悲しい😢)」という話をしていたのです。
うんうん、わかる、過度なダンピングよくない、それは強制的に海を赤く染める行為なわけです(しかもそこに流れる赤い血は自分のものではなく人のもの)。
これと同じ話が一個人のおじさんエンジニアの賃金にも当てはまってくるぞと、もうちょっと機械学習っぽくいうと”おじさんエンジニアの辛さと賃金”というトピックベクトルは良い感じに”法人の価格設定問題”空間に射影できるぞと、そういうことです。
え?何が関係あるのかって?ちょっと落ち着いて私の経験を聞いて欲しい。
私自身は社会人歴14年で転職を繰り替えしたりホクソエムったりで、合計6社ほどの経験があるのですが、転職の際に年収を2~300万円ほど下げて転職した経験が2度ほどあります。 その意図は「正直、自分を優秀な技術者と仮定するならば、そのうち成果出して給料もあがるだろうし昇給がだめなら転職/副業すればいいや〜」くらいの楽観論が根底にあります。 その一方で、更にガッツリ年収を下げる(年収300万円くらいになるイメージ)のはいくら副業で稼げてもNGです。
もともとの「法人としての価格設定問題」の考えてに至った原点も実はこのへんにあります。 以前、同僚(私の心の中のインフラクラウド師匠)だった方が
「某D社では技術的に凄いおじさんエンジニアがたくさんいるんだが、彼らは全然給料交渉をしない。そして僕らは彼ら技術的に凄いおじさんエンジニアと給料を比較されるので僕らが交渉しても給料がまるで上がらないんだ。だから転職した。」
という話をしていてこれが原点です、めっちゃ印象的でした(かれこれ4〜5年前です)。
それまでは社会を知らなすぎてこの視点が私にはまるでなかったのです(当時社会人8年目くらい)、 pip install 社会性
が実行された瞬間ですくぅぅ。
もちろん人事(あるいは採用コストを管理されてる方々)の側としては「いや、技術的にめっちゃすごいおじさんエンジニアXさんの年収がY円なのに、それより技術的に優れていない(私から見れば優れているんだけど&色々パクらせてもらった)君(インフラクラウド師匠)の給料を上げる必要なくね?」と主張できるわけです。 いや、人事殿らの意見わかる、実に筋が通っている。その通りや!
この場合、誰が(お若い方の給料を上げるという観点で)悪いのかというと私は技術的に凄いおじさんエンジニア氏らだと考えます。 じゃぁどうすべきなのか?犯人探しや批判は猿🐵でもできるので、私のこの状況における解決策とボヤキを書くと
「技術的に凄いおじさんエンジニアはお若い方のお賃金上昇に迷惑をかけないように、会社の売上を上げる(あるいはコストを下げる)ような成果を出して、ある程度の高給を交渉して取っていく必要がある。 はぁ、おじさんエンジニアは辛いな、技術的なことだけ考えていてぇ」
となるわけです。 技術的に凄いおじさんエンジニア、ヤッテイキたい。
そんなポエムでした。 ・・・次回もまた、見てくれよな!
ホクソエムサポーターの白井です。 今回は前回 【翻訳】機械学習の技術的負債の重箱の隅をつつく (前編) の続きを紹介します。
※この記事は、Matthew McAteer氏によるブログ記事Nitpicking Machine Learning Technical Debtの和訳です。原著者の許可取得済みです。
後編では、コードのアンチパターンなど、エンジニアには身近な話題で、前編と比較して実践しやすいコンテンツも多いと思います。
再注目されているNeurIPS2015の論文を再読する (そして、2020年のための、より関連した25のベストプラクティス)
技術的負債論文の 「アンチパターン」の章は前の章よりも実践可能でした。 このパートは前パート(フィードバックループ)よりもより高レベルな抽象的なパターンを扱います。
(これは実際は技術的負債論文の情報を表にしたもので、さらにその修正方法に関するアドバイスへのハイパーリンクをつけています。
この表は、著者らがML特有のコードの臭いとアンチパターンについて議論したもので、もしかしたらちょっとやりすぎかもしれませんが、 すべてはじめに着手すべき標準的なソフトウェアエンジニアリングのアンチパターンに相当しています。)
設計の失敗 | 種類 |
---|---|
機能の横恋慕 (Feature Envy) | コードの臭い |
データクラス (Data Class) | コードの臭い |
長すぎるメソッド (Long Method) | コードの臭い |
長すぎるパラメーターリスト (Long Parameter List) | コードの臭い |
巨大なクラス (Large Class) | コードの臭い |
神オブジェクト (God Class) | アンチパターン |
マルチツールナイフ(Swiss Army Knife) | アンチパターン |
機能分解 (Functional Decomposition) | アンチパターン |
スパゲッティコード (Spaghetti Code) | アンチパターン |
(訳注: 日本語訳はwikipediaのアンチパターン、コードの臭いとソフトウェア開発 アンチパターンのまとめ を参考にした)
これらのパターンはその90%以上が、MLコードのモデル更新ではなく“モデルを維持している”部分に関連するものです。 これは、Kaggleコンペのほとんどの参加者は存在すると考えたことのないような配管 (plumbing) です。 細胞のセグメンテーションを解くのはずっと簡単です、モデルの入力をTacan Evo cameraとつなぐコードに大半の時間を浪費しない限りは。 (訳注: 上記Kaggleコンペのリンクは細胞のセグメンテーションコンペ)
Best practice #9 定期的なコードレビューを使おう (そして・またはコードチェックツールを使おう)
最初のMLのアンチパターンは 「グルーコード (glue code)」です。 グルーコードは汎用的な目的のパッケージから持ってきたデータやツールを、自分の持っているとても限定されたモデルにフィットしたい時に書いたコード全てを指します。 RDKitのようなパッケージでなにかしようとしたことのある人なら誰もが、私の言っている意味がわかるでしょう。
基本的に、 utils.py
に押しやったものは大抵、これに該当します (誰もがやったことがあるはずです)。
これらは (できれば) もっと具体的なAPIエンドポイントに依存性をリパッケージして修正すべきです。
私たちはみな、自慢できない utils.py を持っているものです。
Best practice #10 汎用的な目的の依存関係は特定のAPIにリパッケージしよう
「パイプラインジャングル (pipeline jungles)」は少し厄介で、グルーコードが大量に蓄積された場所を指します。 すべてのちょっとした新しいデータに対する変換が、醜い混合物 (amalgam) として積み重なった場所です。 グルーコードとは異なり、著者らは、スクラッチからのコードベースを手放して、再設計するように強く薦めています。
今や他の選択肢があると言いたいところですが、グルーコードがパイプラインジャングルに変貌した時には、Uber's Michelangeloのようなツールですらむしろ問題の一部になりえます。
もちろん、著者らのアドバイスのメリットは、変換したコードを興奮するようなかっこいい名前の新しいプロジェクトにみせられるということです。 トルーキンの参照が義務付けられている「Balrog」のように。 (プロジェクトの名前の不幸な暗喩を無視しているのはPalantirのドメインだけではありません。 あなたもまた自由です。)
(訳注: Barlog(バルログ) も Palantir(パランティーア)もトルーキンの指輪物語 に登場する。 Palantirは物語において「見る石」であり、危険な道具である。)
ソフトウェアの形
Best practice #11 トップダウンの再設計・再実装によってパイプラインジャングルを取り除こう
考えたくない話題、試験的コード (experimental code)について見ていきましょう。 あなたは後々のために試験的コードを保存しようと当然思うはずです。 使っていない関数や参照していないファイルにおいておけば、問題ないと思ったでしょう。 あいにく、このようなものは後方互換性の管理が頭痛のタネになる理由のひとつになります。
Tensorflowを深く掘り下げたことのある人であれば覚えがあるでしょう、一部だけ吸収されたフレームワークや、試験的コード、のちの日に気が付くエンジニアのために残された 未完了の「TODO」 コードに。 Tensorflowコードの謎の失敗をデバッグしようとした時、あなたは偶然これらを見つけたのではないでしょうか。 これは間違いなく、Tensorflow 1.Xと2.Xの間にある互換性の遅れを浮き彫りにしています。
自分自身のためにも、5年もコードベースの剪定から逃げるのはやめましょう。 実験をつづけても、他のコードから実験的コードを隔離する基準を定めましょう。
Google Researchの散らかった (少なくとも上位5位に入る) githubレポジトリ、しかも公開されているものです。 そして、READMEは同じぐらい詳細とは程遠く、実際は真逆です。
Best practice #12 定期的な点検を定めて、コードを取り除くための基準をつくろう、もしくはビジネスに重大な影響を及ぼすコードとは程遠いディレクトリやディスクにMLコードをおこう
古いコードといえば、ソフトウェアエンジニアリングが以前からなにをやってきたか知っていますか? それは本当に素晴らしい抽象化です! 関係データベースの概念からウェブページの表示まで、ありとあらゆることです。 応用カテゴリ理論には、コードを整理するためのベストなやり方を発見することに執念を燃やしている分野があります。
応用カテゴリ理論が、何にいまだ悩みつづけているか知っていますか? そう、機械学習のコード整理です。 ソフトウェアエンジニアリングは壁に抽象的なスパゲティーを投げて、なにが刺さるかを見る経験がありました。
機械学習? Map-Reduce (これは関係データベースのように印象的な概念ではない) や Async Parameter servers (どうやってすべきなのか誰も同意できない) や sync allreduce (多くの場合大失敗している) は別として、私たちが見せられるものはありません。
先ほど言及した、Sync AllReduce serverのアーキテクチャの大まかな概要 (かなり簡略化したバージョン)
先ほど言及した、Async parameter serverのアーキテクチャの大まかな概要 (少なくともそのバージョンのひとつ)
実際、ランダムネットワークの研究をしているグループとPytorchがニューラルネットワークのノードがいかに流動的であるかを宣伝している間に、 機械学習は抽象的なスパゲティーを窓から綺麗に投げ捨ててしまったのです! 著者らは、この問題が時間と共にとても悪くなっていることに気づいていたとは思いません。 私のおすすめ? 大まかな抽象化について、有名な文献を読みましょう。あと、プロダクションのコードにはPytorchを使わないほうがいいと思います。
Best practice #13 時間と共に強固になる抽象化を最新の状態にしておこう
私は今まで、お気に入りのフレームワークがあってほとんどの問題に対してそれをつかうのを好むシニアな機械学習エンジニアとたくさん会ってきました。 私はまた、彼らのお気に入りのフレームワークを新しい問題に適用するとき、そのフレームワークが破綻したり機能的に区別できない他のフレームワークに変更されたりするのを目の当たりにした同じエンジニアを見てきました。
これは特に、分散させて計算するタイプの機械学習処理を行うチームに多く見られます。 私にははっきりさせておきたいことがあります。
MapReduceは別として、なにかひとつのフレームワークに執着しすぎるのは避けるべきです。
あなたの “シニア(先輩)” 機械学習エンジニアが「Micheloangeloが最高で、全てを解決できる」と信じているならば、彼らはそれほどシニアではないかもしれません。
MLエンジニアリングは成熟してきた一方で、まだ相対的には早熟なのです。
本当の “シニア” なシニアMLエンジニアは、フレームワークにとらわれないワークフローを重要視するでしょう、なぜなら、フレームワークの多くは寿命が長くないと知っているからです。 ⚰️
さて、前のセクションで機械学習における技術的負債の区別できるシナリオや質について言及しましたが、技術負債論文では機械学習開発のための大まかなアンチパターンの例も提供しています。
これを読んでいる人の多くは、コードの臭い (code-smells) というフレーズを聞いたことがあるでしょう。 good-smellやPep8-auto-checking (さらにはみんながPythonコードに使っている、新しい自動フォーマッタ Black) のようなツールを使っているかもしれません。 正直に言うと、 「コードの臭い」という用語が好きではありません。
「臭い」は常に微妙なものを暗に意味しているようにみえますが、次のセクションに書かれたパターンは逆にとても露骨なものです。 それにもかかわらず、著者らは負債を大まかに示すコードの臭いの種類を少しだけ並べています (普通のコードの臭いの種類を超えて)。 なぜか、“コードの臭い”のセクションの途中からコードの臭いを記載し始めています。
「プレーンなデータ」の臭い (The "Plain data" smell)
numpy float形式のデータを大量に扱うコードをもっているかもしれません。 RNAの読み込み回数がベールヌイ分布からのサンプルを表現しているのかどうか、floatが数値の対数かどうかといった、データの性質を保持する情報がほとんどないことがあるかもしれません。
技術的負債論文では言及されていませんが、これはPythonのtypingが助けられる領域のひとつです。
不必要なfloatや、正確すぎるfloatの使用を避けることが大いに役に立ちます。
繰り返しますが、組み込みの Decimal
や Typing
パッケージを使うことはとても助けになります (コードを誘導するだけでなく、CPUのスピード向上にもなります)。
Best practice #14
Typing
やDeimal
のようなパッケージを使って、すべてのオブジェクトに対して 'float32' を使うのはやめよう
過半数のハッカソンコード、そして残念なことにベンチャーキャピタルに資金提供されている「AI」スタートアップのコードの中身はこんな感じです。
「試作品」の臭い(The "Prototyping" smell)
ハッカソンに参加した人なら知っているでしょうが、24時間以内に寄せ集めでつくられたコードはこんな形をしています。 これは、先ほど言及した実際には誰にも使われない試験的コードともつながっています。 生物学データのための PHATE次元削減ツールを試したくて興奮するかもしれませんが、コードをきれいにするか、投げ捨てるかしてください。
Best practice #15 進行中のものを同じディレクトリに置かない。片付けるか、きれいにしよう。
「多言語」の臭い (The "Muitl-Language" smell)
プログラミング言語の種類について言うと、多言語のコードベースは技術的負債が複数積み重なるように、その積み重なりがより早くなるよう振る舞います。 もちろん、すべての言語にはそれぞれ利点があります。 Pythonはアイデアを素早く構築するためには良いです。 Javascriptはインタフェースに向いています。 C++はグラフィックに最適で、計算もはやいです。 PHPは、うーん、特にこれといったものはないです。 GolangはKubernetesを使う (もしくはGoogleで働く) には便利です。
しかし、これらの言語でお互いに会話するとして、エンドポイントが壊れたり、メモリリークによって、うまくいかない点がたくさんあるでしょう。 少なくとも機械学習では、言語の間で似たセマンティクスを持つSparkやTensorflowのようなツールキットは少ないです。 もし複数の言語をどうしても使わなくてはいけなくても、少なくとも2015年以降はそれが可能になってはいます。
C++のプログラマーがPythonに転向したら。この人が書いたレポジトリ全体のコードを思い描いてみてください
Best practice #16 エンドポイントが説明されていることを確認し、言語間で似たような抽象度を持つフレームワークを使用しよう
(これをコードの臭いと呼ぶのは奇妙な選択です、普通のコードの臭いを基準としても、これはとても露骨なパターンなので。)
技術的負債論文の「構成の負債」の章はおそらく楽しいものではないですが、そこに記述されている問題は簡単に修正できます。
基本的に、機械学習のパイプラインについて、すべての調整可能で構成可能な情報を一箇所にまとめて、確かめる習慣であり、これはいうなればあなたの2番目のLSTMレイヤーのユニット数がどう設定されているか調べるために複数の辞書を探す必要のない状況のことです。 設定ファイルをつくる習慣を持っていても、すべてのパッケージと技術があなたを悩ませないわけではありません。
一般的な方針は別として、技術的負債論文のこの部分は詳細を十分に深堀りしていません。 技術的負債論文の著者らはCaffeのようなパッケージを使うのに慣れていたのではないかと疑っています (Caffeのプロトコルバッファーで設定ファイルを設定することは客観的に見てバグだらけで恐ろしいものでした)。
個人的には、もし設定ファイルを設定したいのであれば、tf.Keras
や Chainer
のようなフレームワークを使うことを提案します。
クラウドサービスはバージョンの構成管理機能をもっていますが、それ以外では、config.jsonか パラメーターフラグを使う準備をする必要があります。
Best practice #17 ファイルパスやハイパーパラメーター、レイヤーの種類、レイヤーの順番や他の設定が一つの場所から設定できるか確かめよう
設定ファイルの素晴らしい例。 技術的負債論文はもっと例をあげたり、当時存在した設定ファイルのガイドについて指摘して欲しかったと切に思います。
もしコマンドラインをつかってこれらの設定を調整したいなら、ArgparseのかわりにClickのようなパッケージをつかってみましょう。
7章は、技術的負債の管理の多くが、現実世界の絶えない変化を扱っているという事実のために準備することであると認めています。 例えば、モデルの出力を分類に変換するために閾値決定が必要なモデルや、あるいは真(True)または偽(False)のBool値を直接選ぶことができるモデルがあるかもしれません。
生物学的もしくは健康データを扱うグループや企業は、診断の基準が急速に変化することをよく知っています。 特にベイズ機械学習をしているときは、あなたの扱っている閾値が永遠に続くとは仮定してはいけません。
オンライン学習アルゴリズムによる決定境界……デプロイから12分後
Best practice #18 モデルの現実世界でのパフォーマンスと、決定境界を常に監視しよう
このセクションではリアルタイムの監視の重要性を強調しています、私は間違いなくこれの良さがわかります。 どのようなことを監視するかについて、この論文は包括的なガイドではありませんが、いくつかの例を与えています。
ひとつは観測した観測したラベルの要約統計量と、予測ラベルの要約統計量を比較することです。 完璧ではありませんが、小さな動物の体重をチェックするようなものです。 何かがすごく間違っていれば、その問題をすぐに警告できます。
数えきれないほどたくさんのツールがあります。 近頃はだれもが、その母親さえもが、監視ツールのスタートアップを作っています。 ここでは、他に比べてバグが少ないSageMakerとWeights & Biasesを例に挙げてみました。
(訳注: それぞれのリンクは、Amazon SageMaker Model Monitor と Weights & Biases)
Best practice #19 予測ラベルの分布が、観測ラベルの分布と似ているか確認しよう
あなたのシステムが現実世界のなにかを意思決定しているなら、レート制限(何某かの呼び出し回数制限)をしたいと思うでしょう。 たとえシステムが株の入札のための数百万ドルを任せられているものではなく、細胞培養インキュベーターのなにかが正しくないと警告を出すだけのシステムであっても、 単位時間あたりの行動制限を設定していないことでのちのち後悔することになります。
ML製品の初期の頭痛のタネのいくつかは、レート制限をしていないシステムで起こるでしょう。
Best practice #20 機械学習システムによってもたらされる意思決定に制限を設けよう
MLパイプラインが消費している、データの上位生産者(データを提供してくれている人・会社たち)によるどんな変化も、心に留めておきたいところです。 例えば、人間の血液やDNAサンプルの機械学習を走らせているどんな企業も、当然ながら、それらのサンプルがすべて標準化された手順で収集されていることを確認したいと考えています。 もしサンプルの多くが特定の集団からきたものであれば、企業は分析がゆがんでいないか確かめなければいけません。 もし培養された人間の細胞のシングルセルシーケンシングを扱っているのであれば、薬剤が作用したために死滅したがん細胞と、インターンが誤って培養した細胞を脱水させてしまったために死滅したがん細胞を混同していないことを確認する必要があります。
著者らは、理想的には、人間が対応可能でない時も、これらの変化に反応できるシステム (例えばログをつける、調節を自ら止める、決定閾値を変更する、技術者や修復可能な人に警告する) が必要だと言っています。
今は笑えるが、このようなミスは、思った以上に危険にさらされているのかもしれません。
(訳注: DNA採取で用いる綿棒が汚染されていたことで、複数の事件で同一のDNAが検出され、架空の犯人ハイルブロンの怪人として騒がれた事件のこと。 上記画像の記事リンク。)
Best practice #21 入力データの背後にある仮説を確かめよう
技術的負債論文の最後から2番目の章は他の領域について言及しています。 著者らは以前、技術的負債の種類として抽象化の失敗について言及してました、そしてどうも、論文の最初の7章の中に技術的負債の種類に収められなかったことにまで及んでいるようです。
データのサニティーチェック(訳注: プログラムのソースコードの整合性・正当性を検査すること)をすることは非常に重要です。 新しいモデルを学習しているとき、モデルが少なくともデータのカテゴリーの一つの種類に過学習する可能性があることを確かめたいでしょう。 もし学習が収束しないなら、ハイパーパラメーターを調整する前に、データはランダムなノイズでないか確認した方がいいかもしれません。 著者らはこのように具体的ではなかったですが、言及するのに良いテストだと考えました。
Best practice #22 モデルが過学習する可能性があることを確認し、データの全てがノイズであったり、無信号でないことを確かめよう
再現性。研究チームにいるあなた方の多くがこれに遭遇すると思います。 seedが設定されていないのコードや、故障中のノートブック、パッケージバージョンなしのレポジトリをみたことがあるでしょう。 技術的負債論文以降、何人かが、再現性チェックリストをつくっています。 ここに、4ヶ月前 hacker newsが特集したかなり良いリストがあります。
これはとても素晴らしいチェックリストの一つで、私も使っており、共に働くチームにも使うよう勧めています。
(訳注: このチェックリストのpdfのリンクはここ)
Best practice #23 研究のコードを公開するときは、再現性に関するチェックリストを使おう
アカデミアからのコードを再現したことのある、産業側の人間なら、私の言っている意味がわかると思います。
今まで議論してきた技術的負債の種類の多くは単一の機械学習モデルについて言及してきましたが、プロセス管理負債はおなじ時間にたくさんのモデルを実行した時に起こります、 1つの遅れたモデルが終わるのを待っている間に、すべてを止めようとは考えないでしょう。 システムレベルの臭いを無視しないのが重要で、また、モデルの実行時間をチェックすることが極めて重要です。 技術的負債論文が書かれて以降、機械学習エンジニアリングは少なくても大まかなシステム設計について考えて改良されてきています。
ボトルネックの同定に使われるチャートの一例
Best practice #24 機械学習モデルのために実行時間を確認・比較する習慣をつけよう
文化的負債はとても厄介な種類の負債です。 著者らは、研究とエンジニアリングの間には時として格差があり、異種混合チームでは負債の返却行為は促しやすいと指摘しています。
個人的には、最後のこの部分は好きではありません。 私は、エンジニアディレクターとリサーチディレクターの両方に、最終的に報告する個人がいるチームをたくさん目撃しました。 エンジニアたちに、負債の修正に必要な変更のための権限のない異なる2つの部門にレポーティングさせることは技術的負債の解決策ではありません。 一部のエンジニアが、この技術的負債の矛先になるという意味では、これは解決策です。 そのようなエンジニアはNo Authority Gauntlet Syndrome (NAGS) (訳注: 権力なき挑戦症候群) になり、燃え尽き、最も同情的なマネージャーがバーニングマンに出ている間に、そのエンジニアが達成すべき作業を目標として持っていたマネージャーによって解雇されてしまうのです。 もし異種混合が助けになるなら、チームの 全体 に渡ることが必要です。
(訳注: バーニングマン は8~9月にアメリカで開催される大規模なイベントのこと)
それに加え、著者らは、チームや企業文化を語る時に、多くの人と同じ間違いをしていると思います。 特に、文化と価値観をごちゃまぜにしています。 企業やチームにとって向上心のある規則を列挙し、それを文化と呼ぶのは簡単です。 実行するのにMBAを持つ必要はないですが、それは実際の文化というより価値観です。 文化とは、二つの重みのある価値観のどちらかを選ぶよう要求した状況で、最終的に人々が実行することです。
これはUberが大きな問題に陥った原因です。 競争力と誠実さの両方が企業の価値観の一部でしたが、最終的には、文化は競争力がなによりも強調されることを要求しました、 例え人事部が絶対的に嫌なやつを会社に留めるために法律を破ることを意味しても。
(訳注: Uberの話はおそらく Reflecting on one very, very strange year at Uber で述べられている話)
悪循環
技術的負債についてのこのイシューは似たような状況でも起こります。 どれぐらい“メインテイナブル(保守可能な)”コードが必要かについて話すのは簡単です。 しかし、誰もが締め切りで追われ、ドキュメントの執筆がJIRAボードの優先度の中で下がっていくなら、最善の努力をしていても負債は積み重なっていきます。
技術的負債の様々な理由
(訳注: 「無鉄砲/慎重」と「意図的/不注意」の2つの基準を使った技術的負債の四象限)
Best practice #25 (それがどのような形であっても)技術的負債に対処するために、定期的に、交渉の余地のない時間を確保しておこう
「負債」の一部は単なるメタファーだということを思い出してください。 どれだけ著者らがより厳格に見せようとしても、それにすぎません。
多くの負債と違って、機械学習の技術的負債は測るのが難しいものです。 与えられた時間であなたのチームがどれぐらいはやく動くかはどれぐらい負債をもっているかの指標にはなりません (新卒のプロダクトマネージャーが主張しているようにみえるにもかかわらず)。 指標にかかわらず、著者らは自身に問うための5つの質問を提案しています(ここではわかりやすく言い換えています)
もちろん、2015年以降、他の記事や論文がより正確なスコアリングメカニズムを考えようとしています (scoring rubicsのように)。 ツールのいくつかは、不正確であっても、一眼でわかるスコアリングメカニズムを作成可能で、技術的負債を追跡する助けになります。
また、技術的負債のいくつかの解決策になると称賛されている解釈可能なMLツールは多くの進歩を遂げています。 それを心に留めたうえで、改めて “Interpretable Machine Learning” by Christoph Molnar (オンラインで利用可能) をおすすめします。
これまでに言及したベストプラクティスを以下にまとめます。 これよりたくさんあるでしょうが、技術的負債の解消のツールはパレートの法則に従います: 技術的な負債の救済策の20%は、あなたの問題の80%を修正することができます。
Typing
や Deimal
のようなパッケージを使って、すべてのオブジェクトに対して 'float32' を使うのはやめよう今回翻訳する上で、ブログの元ネタとなる 技術的負債論文 と同じ著者で、技術的負債論文 が公開される1年前のNeurIPS2014で公開された論文 (Machine Learning:The High-Interest Credit Card of Technical Debt)について、日本語で解説している以下の情報を参考にさせていただきました。
ちなみに、日本語の情報としては「High-Interest Credit Card~」のほうが豊富ですが、前編で紹介した、機械学習の技術的負債を象徴する図 (下記) は技術的負債論文が初出です。
改めて、翻訳の許可をしてくれた原著者のMatthew McAteer氏に感謝します。
ホクソエムサポーターの白井です。 今回は Matthew McAteer氏によるブログ記事Nitpicking Machine Learning Technical Debtの和訳を紹介します。
原著者の許可取得済みです。 Thank you!
アメリカの国内ネタも含んでいて、日本語だと理解しにくい箇所もありますが、機械学習の技術的負債をどう対処していくかについて、とても役に立つ記事だと思います。
再注目されているNeurIPS2015の論文を再読する (そして、2020年のための、より関連した25のベストプラクティス)
最近 Hidden Technical Debt in Machine Learning Systems (Sculley et al. 2015)を読み返しました。 (簡潔に、明確にするため、この投稿では技術的負債論文とします) この論文は NeurIPS 2015 で公開されましたが、 Ian GoodFellowによる 「Generative Adversarial Network」技術に皆が夢中になっていたので、そこまで注目されませんでした。
今日、技術的負債論文がまた盛り返してきています。 これを書いている間にも、直近75日のあいだに25の論文が引用しています。 これは、機械学習について私たちが技術的負債について心配しなければいけなレベルにまで来たという意味でしょう。
ですが、多くの人々がこの論文を引用しようとしているならば (「機械学習の技術的負債」というフレーズを持つ論文全てを引用しているだけでないならば)、少なくともどの箇所が本質的なもので、どこがそうでないのか認識すべきです。 そのことを念頭に置いて、どの部分が時代遅れなのかを紹介し、また取って代わる新しい方法を示すことは、機械学習に関係する人々全員の時間と手間を大幅に省くことができると考えました。 これが私がこのBLOG POSTを書いた理由です。 急成長中のスタートアップからグーグル (技術的負債論文の著者の企業) のような大企業で働いてきて、 同じような機械学習の技術的負債による失敗がどこでも行われているのをみているので、私はこの件について執筆する資格があると考えます。
MLコードは小さくごくわずかなブラックボックスであることに注目してください。 この”小ささ”がこの論文の良い部分の一つですが、悲しいことにこの画像を参考に作られた画像はボックスの大きさの違いを考慮しておらず、この”小ささ”という要点を逃しています。
この記事は技術的負債論文と関連する点のいくつかを網羅しながら、5年前にはないアドバイスを付け加えています。 そのいくつかのアドバイスはその当時存在しなかったツールの形をとっています。 また、ツールや技術自体は間違いなく存在していたものの、当時の著者らが持ち出さなかったことで見逃してしまったアドバイスもあります。
技術的負債は、エンジニアが他の何よりもデプロイの速い設計を選んだ時のコスト増加を例えたものです。 技術的負債を修正するのは骨の折れる仕事です。 「早く動いて、破壊せよ (Move fast and break things)」から「ああ大変だ、急ぎすぎたから片付けなきゃ (Oh no, we went too fast and gotta clean some of this up)」に方向転換です。
キャッチーでなくとも、Mark Zuckerburgが二つ目のスローガンに口を挟まなかった理由が私にはわかります。
(訳注: “Move fast and break things” はFacebookのMotto。その後 “Move fast with stable infrastructure” に変更した。 (wikipediaより) )
ソフトウェアにおける技術的負債は悪いものですが、MLシステムの技術的負債は もっと悪い と論文の著者らは強く主張しています。 技術的負債論文はMLにおける技術的負債の種類と、いくつかの解決策を紹介しています (いろいろな種類のゴミ箱があるのと同じで、いろいろなクソコードに対し、いろいろな手法が必要です🚮) 技術的負債論文は、元は人々の注意を引きつけるための意見記事であったことを考えると、論文中のいくつかのアドバイスはもはや関係なかったり、現在ではよりよい解決策があることを覚えておくのが重要です。
みなさんはもう、技術的負債についてご存知でしょう。 技術的負債論文ははじめに、技術的負債の解消というものがコードに新しい機能を追加することを意味しないことを明確にしています。 技術的負債の解消はユニットテストを書いたり、可読性を向上させたり、ドキュメントを追加したり、未使用の部分を取り除いたりする、華やかさにかけるタスクであり、将来の開発が楽になるためのタスクです。 まあ、標準的なソフトウェアエンジニアリングは機械学習エンジニアリングで必要なスキルの一部なので、より一般的なソフトウェアエンジニアリングの技術的負債は、MLの技術的負債の一領域にすぎません。
言い換えてはいますが、(Sculley et al. 2015) が言っていることとほぼ同じです。
技術的負債論文の、イントロダクションの後の章は、機械学習モデルの漠然とした性質が、どのように技術的負債の扱いを難しくしているかに続きます。 技術的負債を防いだり直したりするための大部分は、コードが適切に整理され、モジュールとして分離されているかを確かめることです。 正確なルールや、コーディングでは細かい指定が難しい場合に、私たちは機械学習を使います。 データを出力するためのルールをハードコーディングする代わりに、多くの場合、アルゴリズムにデータと出力を与えてルールを出力しようとしています(時にはそれさえしないことがあります)。 その際、私たちは分離や整理に必要なルールが何なのかすらわからないのです。
Best practice #1 解釈可能・説明可能なツールを使おう
ここで、もつれた (Entanglement) 問題が出てきます。 要するに、もしモデルのどこかを変更すると、全体のパフォーマンスを変えてしまう恐れがあるということです。 例えば、個人の健康記録について100の特徴量を持つモデルに対して、(例えば、大麻を吸ったことがあるかをヒアリングしてデータ化するといった話です)101個目の特徴量を加えることを考えます。 全てがつながっているのです! まるで、カオスシステムを扱っているようなものです。(皮肉なことに、何人かの数学者はニューラルネットワークを二重振り子や気象システムのようにカオスなアトラクターとして表現しようとしています)
一本の紐(調整パラメータ)をチューニングしていくと(実際は何百本もの紐がありもつれています)、全体がつながっているのでうっかりブラインドを完全に消すこともできるわけです (the blinds can go OUT the window too)。 (訳注: go out the windowは「完全に消えてなくなる」という口語表現)
アンサンブルモデルや高次元可視化ツールを用いてこの問題を修正できる可能性について著者らは示していますが、もしアンサンブルモデルの出力が相関していたり、データが 超 高次元である場合は不十分です。 解釈可能なMLとして提案されるものの多くは少し曖昧です。 それを踏まえた上で、私はFacebookの高次元可視化ツールをおすすめします。 また、解釈可能な機械学習についての最高の資料“Interpretable Machine Learning” by Christoph Molnar (オンラインで利用可能)を読むこともおすすめします。
時には、決定木のような、より説明可能なモデルが、もつれた問題の手助けになるでしょうが、ニューラルネットワークで解決するベストプラクティスがあるので、確定したわけではありません。
Best practice #2 可能であれば、説明可能な種類のモデルを使おう
修正の伝播 (Correction Cascades) (訳注: Hidden Technical Debt in Machine Learning Systems参照)は漠然とした機械学習モデルへ入力の一部、それ自体が漠然とした機械学習モデルである場合に起こります。 エラーのドミノラリーを準備しているようなものです。
例えば、事前に存在しているモデルを新しいドメイン (もしくは多くの人が “startup pivot” と呼ぶもの) に適用する時、このようにモデルを繋げるのは非常に魅力的です。 ランダムフォレストの前に、教師なし次元削減ステップはさんだことがあるかもしれませんが、t-SNEのパラメーターを変化させると残りのモデルのパフォーマンスが急落します。 最悪の場合、全体のシステムのパフォーマンスを下げることなく、元のモデルや次元削減の部分などの “サブな部分” の精度を改善するのは不可能です。 機械学習パイプラインは、正の合算価値を生み出すものからゼロサムになります (これは技術的負債論文の用語ではないですが、載せるチャンスを逃しただけだと感じています)。
XKCDのRandal Munroe提供
(訳注: Cueball「これを見て。必要な情報全てを集めて処理した完全自動化データパイプラインを作りました。」 Ponytail「行き当たりばったりのスクリプトで構成された巨大な (トランプの家のような) 不安定な構造で、変な入力があった瞬間に崩れませんか?」 Cueball「そう…ではないかもしれない」 Ponytail「私が推測するに、なにか」 Cueball「おっと、壊れた。ちょっと待って、修正します。」 Explain xkcdの書きおこしを参考に翻訳)
これを防ぐためには、greedy unsupervised layer-wise pretraining (or GULP) のvariantがよりよい技術の一つです。 なぜうまくいくかという数学的理由については意見がわかれているものの、基本的にはモデル初期段階やアンサンブルの最初部分を学習し、それらを固定してから、残りのシークエンスを順々に作業します (これまた、技術的負債論文で言及されていませんが、この技術自体は少なくとも2007年から存在しているので、機会を逃しています)。
Best practice #3 常に、順番に下流モデルを再学習しよう
他にも機械学習モデルの不都合な特徴があります。 あなたが認識している以上に、ただの機械学習モデルの域を超えて、その機械学習の出力を頼っている消費者がいるかもしれないということです。 著者らはこれを 宣言していない消費者 (Undeclared Consumers) として言及しています。 ここでの問題は、出力データが非構造だったり正しくフォーマットされていないことではなく、出力に依存しているシステムがどの程度あるかを把握できる人が誰もいないことです。
例えば、kaggleのようなサイトでは、たくさんのカスタムデータセットがあり、そのデータセットの多くは機械学習の出力です。 多くのプロジェクトやスタートアップは、初期の機械学習モデルの構築や学習を、内部のデータセットのかわりにこれらのデータを使うことがあります。 ほぼ予告なしでデータセットが変更される可能性があるにもかかわらずに、です!(しかもこれらに依存したスクリプトやタスクがその変更を検知できることは稀です)
また、データにアクセスするためサインインなどが必要ないAPIの場合、問題がより複雑になります。 アクセスキーやサービス水準合意のように、モデルにアクセスする入り口に障壁を作らない限り、扱いが難しくなります。 モデルの出力をファイルとして保存したら、共有ディレクトリにデータがあるからという理由で、チームのメンバーがその出力データを使うかもしれません。 試験的なコードだとしても、まだ確認できていないモデルの出力にアクセスできる人がいるかどうか、気をつけないといけません。 これはJupyterLabのようなtoolkitにとって大きな問題となる傾向があります (もし私が過去に遡って技術的負債論文に警告を付け加えるなら、JupyterLabについて警告するでしょう)。
基本的に、このような技術的負債の解決は、機械学習エンジニアとセキュリティーエンジニアの協力が必要です。
Best practice #4 アクセス鍵、ディレクトリの権限、サービス水準合意をセットアップしよう
下流の消費者グラフはこのようにシンプルで包括的であればよいと思います。
3つめの章はデータ依存関係の問題について、深く掘り下げます。 ソフトウェアエンジニアリングにおける通常のコードに加え、機械学習システムは、開発者が認識する以上に不安定な、膨大なデータソースにも依存しています。 これは悪いニュースです。
例えば、入力データは、裏で変化するルックアップテーブルや、連続的なデータストリームであったり、所有していないAPIからのデータを用いているかもしれません。 MolNetデータセットのホストが、より正確な数値で更新することを決めたとしたらどうなるかを想像してみてください(どうやって更新するかは無視するとして)。 更新したデータはより正確に現実を反映するかもしれませんが、数えきれないモデルが古いデータで構築されており、先週は確実に動いたノートブックを再実行したとき、多くの製作者が、精度が急降下したことに気づくでしょう。
著者らによる提案のひとつはPhotonのようなデータ依存追跡ツールを使うことです。 (訳注: Photon: Fault-tolerant and Scalable Joining of Continuous Data Streams) 2020年においては、新しいツール DVC (文字通り「Data Version Control」の意味) があり、Photonを多くの点において時代遅れにしています。 このツールはgitと同じように振る舞い、データセット・データベースの変更の追跡を保持するDAGを保存します。 他に2つのバージョン管理のために一緒に使われるツールとして挙げられるのは、 Streamlit (実験とプロトタイプの追跡を保持) とNetflix’s Metaflowです。
どの程度バージョン管理をするかは、メモリ使用量の増加と、学習過程の大きなギャップ防止のトレードオフの関係にあります。 それでも、不十分ないし不適切なバージョン管理は、モデル学習のときに、過剰な生存バイアスが起こり(可能性を無駄にし)ます。
DVCのページです。ダウンロードしましょう。今すぐ!
Best Practice #5 データのバージョン管理ツールを使おう
データ依存性の怖い話はまだ続きます。 不安定なデータ依存性と比較すれば、十分に活用していないデータは悪くないようにみえますが、そうやってハマるんです! つまり、使っていないデータ、一度使ったもののレガシー扱いになったデータ、他と関連しているため冗長になったデータに注意し続ける必要があるのです。 あなたがデータパイプラインを管理していて、全体のギガバイトが過剰だと気づいた時、それだけで開発コストが発生しています。
相互に関連したデータは特に扱いにくいです。 なぜならばどの変数が関連していて、どれが原因となるか、理解する必要があるからです。 これは生物学のデータにとって大きな問題です。 ANCOVA (訳注: 共分散分析)のようなツールはますます旧式となり、残念ながらANCOVAの仮定には間違いなく適用していないいくつかの事態で使われています。 いくつかのグループはONION (訳注: arXivリンク) やDomain Aware Neural Networksのような代替案を提案しようとしていますが、多くは特に印象的でない標準的な手法を改良しています。 MicrosoftやQuantumBlackのような企業は因果関係のもつれのためのパッケージを開発しました(それぞれ、DoWhyとCasualNexです)。 私は特にDeepmindのベイジアン因果推論が気に入っています。
これらの多くは技術的負債論文の書かれた時期にはなく、多くのパッケージは独自の操作性という負債を抱えていますが、 ANCOVAはどんな場合でもうまくいく (one-size-fits-all) 解決策では ない ことを知ってもらうことが重要だと思っています。
Best practice #6 使っていないファイル、膨大な関係する特徴量を削除し、因果関係推論ツールを使おう
ところで、著者らはこれらの修正に対してあまり悲観的ではありませんでした。 クリック予測の中でGoogleが使っているものを例として、彼らはデータ依存性の静的な分析を提案しました。
技術的負債論文が出版されて以降、対処するための選択肢はとても発展しました。 例えば、Snorkelのような、データのスライスを、どの実験で使ったか追跡するツールがあります。 AWSやAzureのようなクラウドサービスは、DevOpsのためのデータ依存性追跡サービスを持っていて、Red Gate SQL dependency tracker のようなツールもあります。 著者らが楽観的だったのは正しかったようです。
Best practice #7 データの依存関係を追跡する、無数にあるDevOpsツールのいくつかを使おう
前のセクションで、希望が少し見えましたが、データ依存性に関する悪いニュースはまだ終わりではないです。 論文の4章では未チェックのフィードバックループが機械学習の開発サイクルに対して、どのような影響をもたらすかに踏み込みます。 半教師あり学習や強化学習のような直接のフィードバックループ、他の機械学習の出力からエンジニアが選択する直接でないループについても言及しています。
技術的負債論文において、イシューとして最低限しか定義されていないもののひとつですが、いまや数えきれないぐらいの組織がフィードバックループ問題に取り組んでいます、OpenAI全体がやっているようなものも含めて (少なくとも、彼らの宣言の「長期的安全性」の章ではそうなっています、「上限つき利益 (Capped Profit)」騒動の前から)。 なにが言いたいかというと、直接または間接的なフィードバックループの研究・調査をするのであれば、この論文よりも はるかに 優れた具体的な選択肢があるということです。
この章では、前のセクションよりも少し絶望的に見える解決策を掲げています。 彼らはバンディッドアルゴリズムを直接のフィードバックループに耐性がある例としてあげていますが、このアルゴリズムはスケールしないだけでなく、スケールしたシステムを構築 しようと するとき、技術的負債が最も蓄積されます。 役に立ちません。
間接的なフィードバックの修正はあまり良くないです。 実際、間接的なフィードバックループのシステムは同じ構造の一部ですらありません。 お互いにメタゲームをしようとする、異なる企業の交易アルゴリズムのようで、代わりにフラッシュ・クラッシュを起こすかもしれません。 もしくは、バイオテックでより関連した例として、様々な実験装置の誤り確率を予測するモデルを考えてみてください。 時がたつにつれ、実際の誤り率は、人がより練習することで下がったり、科学者が装置をより頻繁に使うことで下がっていきますが、この部分を補正するためのモデルのカリブレーション頻度は増えません。 (訳注: カリブレーション (較正)は、この文脈では確率の較正ではなくパラメータ再調整だと思えばよい) 究極的には、これを修正するために大事なのは、高いレベルでの設計上の決定と、モデルのデータの背後にある仮説(特に独立性の仮説)を可能な限り多く確認することです。
これはまた、セキュリティエンジニアリングの多くの原則と実践がとても有効になる領域です (例えば、システムを通るデータのフローを追跡し、悪質な関係者が利用する前にシステムが防ぐ方法を探す)。
直接的なフィードバックループの一例です。 実際には、このダイアグラムのいくつかのブロックがMLモデルです。 これは間接的な相互作用をうわべだけ扱っているわけではありません。
Best practice #8 モデルの背後にある独立性の仮定を確認しよう (セキュリティーエンジニアの近くで働こう)
今や、特にANCOVAについてコメントした後であれば、あなたはおそらく仮定の検証というテーマに気づいているでしょう。 私は、著者らがこの話題に少なくとも1セクションを使えばよかったのにと思っています。
元のブログ は1つの記事ですが、少々長いので、前後編に分けさせていただきました。
続きでは「コードのアンチパターン」や「構成の負債」、「実世界での活用」について紹介します。
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)
(2020/08/14 flavorについての記載を一部修正)
こんにちは、ホクソエムサポーターの藤岡です。 最近、MLflowを分析業務で使用しているのですが、お手軽に機械学習のモデルや結果が管理できて重宝しています。 また、特定のライブラリに依存しないなど、使い方の自由度も非常に高いところが魅力的です。
ただ、ザ・分析用のPythonライブラリという感じでとにかく色々なものが隠蔽されており、 サーバにつなぐクライアントさえもプログラマあまりは意識する必要がないという徹底っぷりです。 もちろんマニュアル通りに使う分には問題ないですが、 ちゃんと中身を知っておくと自由度の高さも相まって色々と応用が効くようになり、 様々なシチュエーションで最適な使い方をすることができるようになります。
というわけで、今回はMLflowの記録部分を担う、 Experiment, Run, Artifactについてその正体に迫ってみます。
なお、MLflow自体についての機能や使い方についての解説はすでに良記事がたくさんあり、 ググればすぐにヒットするのでここでは割愛します。
MLflowのデータ管理はRun、Experiment、Artifactの三つの要素から構成されています。 大まかには、それぞれの役割は以下の通りです。
これらの関係は、図のようになっています。
なお、Artifact LocationはArtifactの格納先のことであり、 あるExperimentに対して一つのArtifact Locationが紐づいています。 逆に、あるArtifact Locationが複数のExperimentと結びつくようにすることも可能ですが、 経験的には管理の点からしてそのような設計は避けるべきだと思います。
では、これらの三要素について、その役割と実装について説明していきます・
まず、全ての基本となるのはRunです。 文字通り、一回の試行を表すものです。 例えば、データセットをXGBoostに投げ込んで学習させる実験を一回やったとすれば、それに対応するのが一つのRunです。
MLflowを使うときはmlflow.start_run
関数から始まることがほとんどだと思いますが、ここでRun
オブジェクトが生成されています。
Runの基本要素は、Parameter、Tag、Metricsの三つです。 XGBoostの例で言えば、
みたいな感じでしょうか。 いずれも、複数のキーバリューを取ることができます。
上では一例を挙げてみましたが、ParameterもTagもMetricsも自由に定義できるので、 個々人の裁量で最適な設計をしてあげるのがいいと思います。 特に、TagにするかParameterにするかはその後のMLflow UIのユーザビリティを大きく左右するので、 よく考えるのがいいと思います。 また、そもそもParameterのうち実験で扱わないようなものは敢えて記録しないなど、 スリム化することも念頭に置いておくといいかと思います。
Runはいくつかのコンポーネントに分かれて定義されています。 低レベルAPIを触る際にキーとなる部分なので、それぞれ細かく解説していきます。
まず、Run
オブジェクトはTag、 Parameter、 MetricsとRunに関するメタデータを記録したデータコンテナです*1。メタデータは以下のように階層的に格納されています*2。
Run ┝ data │ ┝ params │ ┝ tags │ ┗ metrics ┗ info ┝ run_id ┝ experiment_id ┝ user_id ┝ status ┝ start_time ┝ end_time ┝ artifact_uri ┗ lifecycle_stage
なお、これらの要素は全てイミュータブルオブジェクトとして定義されています。 言い換えると、直接の書き換えは非推奨です。
実際にインスタンス化する場合には、MlflowClient
のcreate_run
メソッドを使います。これはstart_run
関数の内部でも呼ばれていて、実はこのオブジェクト*3が返されています。
Runを記録するときに直接呼び出すことは無いので、サンプルプログラム等ではたいてい捨てられてしまっていますが。
一方、サーバから読み出す場合には、MlflowClient.get_run
メソッドを使います。
start_run
の場合と違ってrun_id
が必要になるので注意しましょう。run_id
はMLflow UIからも取得できますが、スクリプト中ではExperimentを通じて取得するのが楽です。
その取得方法についてはExperimentの節で扱います。
Run
オブジェクトの内部構造と呼び出し方さえ分かってしまえば、実験結果の検索や操作もスクリプトから思いのままに実現可能です。
MLflow UIはリッチなUIを提供してくれている反面、小回りが利かないこともあるので、困ったらRun
を自分で直接触るのがいいと思います。
Run
のdata
プロパティにはRunの基本要素となる3要素が格納されています。このプロパティはRunData
というデータコンテナとして定義されており、Parameterがparams
に、Tagがtags
に、Metricsがmetrics
にそれぞれ辞書オブジェクトとして格納されています。
Run
のinfo
プロパティにはRunのメタデータが格納されています。このメタデータには以下の情報が入っています。
RunのIDです。
あるrun_id
は任意のExperimentに対して一意になります*4。
そのRunの属するExperimentのIDです。
そのRunを実行したUserのIDです。
Runの状態です。以下の5ステータスが定義されています。
Runが開始 / 終了した時間です。
ArtifactのURIです。
Runの削除判定用フラグです。Runが削除されていればDELETED、そうでなければACTIVEが設定されています。 あるRunをAPI経由で削除した場合、データ自体が削除されるのではなくこのフラグがDELETEDに更新されます。
Runを束ねるのがExperimentの役割です。 モデルをたくさん投げ込んだときでもExperimentごとに整理しておけばアクセスしにくくなるのを防げますし、 MLflow UIの可視化機能やMetrics比較等を十全に活かすためにも、適切な単位ごとにExperimentで分けることは重要です。
例えば、あるKaggleコンペでExperimentを作って、その中に作成したモデルをRunとして記録するのはもちろん、 コンペによってはXGBoostやRFのようにモデルの種別で複数のExperimentを作ることも有効かもしれません。
Experimentの実現形式はメタデータの格納方式に依存します。 利用可能な格納方式は
のいずれかです。
例えばファイルストア(Run, Experimentをファイルとして保管する形式)の場合、 以下のようなディレクトリがExperimentの実態となります。
<experiment-id> ┝ meta.yaml ┝ <run-1-id>/ │ ┝ meta.yaml │ ┝ metrics/ │ ┝ params/ │ ┗ tags/ ┝ <run-2-id>/ ... ┗ <run-N-id>/ ┝ meta.yaml ┝ metrics/ ┝ params/ ┗ tags/
experimentとrunの親子関係がディレクトリ構成で表現され、各種メタデータがmeta.yamlに記録されています。 metrics, params, tagsの中には各種データがファイルで格納されています。 なお、artifact_uriが指定されなければ、tags等と同じフォルダにartifactsというフォルダが作成されてその中に格納されます。
Experiment
オブジェクトの扱いはRun
オブジェクトとよく似ているので、相違点だけ述べて詳細な解説はスキップします。
主な相違点として、以下のものが挙げられます。
mlflow.start_run
ではなくmlflow.create_experiment
MlflowClient.create_run
ではなくMlflowClient.create_experiment
get_experiment
関数) だけではなく name経由 (get_experiment_by_name
関数) も可能(1つのサーバ内で名前がユニークであるという制約のため)name
: Experimentの名前experiment_id
: IDartifact_location
: Artifact LocationのURIlifecycle_stage
: Runと同様のACTIVE/DELETEDtags
: Runと同様Artifactとは、Runの結果や途中経過で生じたファイルを格納するためのストレージです。 Artifactを置く先のファイルストレージが扱えるファイルであればなんでも格納が可能です。 もちろん、csv化ができるpandas DataFrame や pickle化ができるPythonオブジェクトも同様です。
Artifactは以下のファイルシステムに対応しています。
上記のファイルシステムであればRunの場所に関わらず任意の格納場所 (Artifact Location) をURIで指定できますが、図で示したようにExperiment単位で指定しなければならないことに注意してください。 このURI以下にRunごとのフォルダが切られ、そこにRunの中間生成物等が入ります。
Artifactのもっとも重要な機能としては、モデルオブジェクトの格納が挙げられます。 モデルの格納の場合、単にファイルにして格納するだけでなく、それをあとで読み込んでデプロイできるようにしなければ、あまり意味がありません。 MLflowは多くの外部ライブラリに対してこのデプロイ機能を、それも環境の変化に対しても頑健な形で実装しています。
Artifactに格納されるモデルファイルは、どのような形でセーブ/ロードが実現されているのでしょうか。 答えはシンプルで、セーブの際にはローカルに一回保存してからファイルシステムごとに定められたプロトコルで指定したURIへと転送し、ロードはその逆のプロセスです。
ただし、モデルファイルについては概要の節で述べたとおりのデプロイ機能を実現するために複数のファイルが生成・格納されます。
この格納方法は扱うモデルの実装されたモジュールごとに異なります。
MLflowでは、あるモジュールで生成したモデルを格納・呼び出しするための格納方法をflavorと読んでいます flavorと呼ばれるモジュールの中で定義しています*6。
例えば、sklearn flavor, xgb flavorといった具合です。
flavorには、例えば、sklearn flavor, xgboost flavorといったものがあります。
各flavorはmlflow以下に.pyファイル モジュールとしてそれぞれ実装されています。
例えば、XGBoostのflavorを見てみると、中には呼び出し可能な関数として、
get_default_conda_env
: デフォルトのconda envを生成save_model
: ローカルへのモデルのセーブlog_model
: Artifactへのモデルの保存load_model
: ローカル/Artifactからのモデルの読み込みの4つの関数が定義されています*7。
これは他のflavorでも同様です。
とはいっても、多くの場合ではこの中で直接使うのは 頻度は高くないものの、他の二つも外部から使用する可能性があるものです。log_model
とload_model
だけであり、それ以外は内部的に呼ばれるだけかと思います。
これらの関数を使い、モデル本体のファイル(xgbの場合はmodel.pkl) conda env (conda.env) , MLmodelの三つが入ったフォルダを作成 / 読み込みします。 このフォルダが、MLflowにおけるモデルオブジェクトになります。
MLmodelファイルはモデル作成に使ったflavorの情報等が格納されたコンフィグファイルです。 この内容を元に読み込み方法を決定し、実行します。 また、モデルを実行(mlflow runコマンド)する場合にはconda envファイルを元に実行環境を作成します。
flavorは上述の四つの関数がカギであり、これらを理解することができればオリジナルのflavorを作ることもできます。
(2020/08/17 誤った内容を修正。なお、MLflow公式によるflavorの正確な定義はこちらをどうぞ)。
MLflowは本当に便利で知名度も高くて少しずつ記事も増えてきているのですが、Run, Experiment, Artifactの三つについての解説が物足りなかったので本記事を書いてみました。 話題を絞ったぶん少し深いところまで掘り下げてみましたが、いかがだったでしょうか?
MLflowの実装は、flavorのあたりはちょっと無理矢理感がある気もしますが、低レベルのAPIのあたりは実装も参考になるし使いやすいしでぜひ読んで欲しいライブラリの一つです。 これを機に利用はもちろん、実装に興味を持っていただければ幸いです。
では、よきPythonライフを!
MLflowのWeb UIを触ると、Runにも名前 (Run Nameの項目) が設定できるようになっているのが分かります。 しかし、ここまでの説明の通り、RunのメタデータとしてRunの名前に該当する項目はありません。
実は、Run Nameはtagsに"mlflow.runName"というタグ名で記録することで表示させることができます。 他にも、このような特殊なタグはmlflow.utils.mlflow_tags内で定義されています。
Artifactを英和辞典で調べると、遺物とか人工物とかそんなふんわりとした意味しか出てこなくて、使い始めた当初は具体的に何を入れるものなのかがよく分かりませんでした。
本記事を書くにあたって改めて辞書で調べてみると以下のような定義となっていました。
Something observed in a scientific investigation or experiment that is not naturally present but occurs as a result of the preparative or investigative procedure.
ざっくりと訳すと、科学的調査や実験で生じた人工物、というような感じです。 機械学習の実験で使うことを考えると、やっぱり、中間生成物やモデル等なんでも入れていいというような意図を感じます。
以前に業務で使用していたときには学習に使うデータマートとかもMLflowで管理してみたのですが、案外できてしまった(しかも割と便利だった)ので、本当に何を入れてもいいんだと思います。 もっとも、環境に特別な制約がなくマート管理に特化したものを採用できるのであれば、そちらの方がいいと思いますが。
入ります。Parameterも受けつけます。 ただし、Metricsだけはエラーを吐きます。
*1:実際にはもう少し別の機能が定義されていますが、コミッターでもない限り使わないと思うので省略しています
*2:v1.7.2時点でdeprecateされたものは省略しています
*3:正確にはその子クラスのActiveRunオブジェクトなのですが、コンテキストマネージャ化されていること以外は同じなのでここでは同一のオブジェクトとして扱います
*4:例えば、ファイル形式のストアの実装を見てみると、uuid.uuid4を使っているので、重複はまず無いです。REST形式のストアの方は不明ですが、sqlalchemyのストアでも同様です
*5:使ったことがないので詳細は不明ですが、おそらくDatabricksやKubernetesと連携した際に使用されるパラメータ
*7:autologはexperimentalかつoptionalなので省略しました
ホクソエムサポーターの白井です。今回はICLR2020 の論文を紹介します。
The International Conference on Learning Representations (ICLR) は機械学習の中でも特に深層学習 を専門とした国際会議です。 OpenReview.net によるopen peer reviewを採用しているので、submitされた論文はだれでも閲覧可能です。(ICLR2020 open review)
2020年はエチオピアで開催予定でしたが、COVID-19の影響でvirtual conferenceとなりました。
今回はNLP系の論文について5本紹介します。 すでに日本語ブログ記事で紹介されているような論文もありますが、自分が興味を持った部分を中心としてざっくりと紹介したいと思います。
以降、とくに記載がない場合、図は論文またはブログからの引用です。
タイトル通り、Transformerを効率的にした Reformer (Trax Transformer) という手法を提案する論文です。
Transformer (Vaswani et al., 2017) は学習に膨大なリソースが必要という欠点がありますが、提案手法ではこの欠点を Locality-Sensitive Hashing Attention (LSH attention) と Reversible Transformer の2つによって解決します。
まずは LSH attention について説明します。
次に、Reversible Transformer について説明します。
(a)
)となります。(b)
, (c)
)これら2つのテクニックによって、計算コストとメモリコストを効率化することで、長文も一度に扱えるようになります。
ちなみに、この論文を紹介しているいくつかのブログでは、「罪と罰」全文を理解できてすごいのがウリっぽい見出しになっていますが、あくまで、モデルが小説のような長いテキストを 一度 に読み込めるだけです。 そこから要約するのか、翻訳するのか、読解タスクを解くのかは学習データ次第だと私は考えます。
ブログでは画像生成タスクの結果が記載されています。(下の図) 効率化によって、断片的な画像から復元するような large-context data も扱えるようになったといえます。
この論文では、例えば Dickens is the author of と Dickens wrote は次の単語 (作品名・書籍名) に対して同じ分布を持つことが理解できるように、予測問題よりも表現学習 (representation learning) のほうが簡単であるという仮説を立てた上で、既存の言語モデルの類似度 (similarity) によって補完する手法を提案しています。
具体的には、学習済みの言語モデルに対して、kNN (K近傍法) を用いた拡張を行う kNN-LMs を提案します。
式で表すと下記のようになります。
は学習済み言語モデル (この論文ではTransformerLM)、 は図の上部分を表しています
kNN-LMsの図
既存のニューラル言語モデルをkNNという単純な手法によって拡張することでパフォーマンスが向上しているのが興味深いです。
NMT (ニューラル機械翻訳) において、画像情報を利用する論文です。
画像情報はNMTによっても有用な情報ですが、複数の言語について対応した画像+文のコーパスは少ないため、 画像+単一言語のアノテーションデータを利用して補う手法を提案しています。
English-to-German+画像コーパスMulti30K の画像情報を利用したEnglish-to-Romanian、English-to-German、English-to-Frenchの翻訳タスクにおいて、いずれも画像情報を用いた方が精度が上がる結果となっています。
アイデアとしてはシンプルですが、テキスト以外の情報を利用するマルチモーダルな手法かつ、複数言語への対応というアプローチが新しいと感じました。
ELECTRA (Efficiently Learning an Encoder that Classifies Token Replacements Accurately) は最初に紹介した Reformer と同じく、効率的であることがウリの手法です。
系統としてはNNを用いた言語モデルですが、 BERTのような masked language model (MLM) と大きく異なるのは、GeneratorとDiscriminatorというGANのような構造を用いて、replaced token detection(RTD) で事前学習をする点です。(正確にはGANモデルでありません)
ELECTRAの図
興味深い点は、効率的に学習するために以下のような拡張 (MODEL EXTENSIONS) が述べられている点です。
特に最後の学習アルゴリズムについては、敵対的学習よりも提案手法であるMLE (最尤推定) によるGeneratorの方がGLUE scoreが優れていたことが述べられています。 テキストとGANsの相性については次の論文 (LANGUAGE GANS FALLING SHORT) で紹介します。
実験結果として、ELECTRAは従来のSOTAモデルよりも小さいモデル・単一GPUでの学習であっても、優れたGLUE scoreを達成しています。
ちなみに論文中では計算量をfloating point operations(FLOPs) (FLOPSではない)で表しています。 (FLOPsについて参考: Chainerで書いたニューラルネットの理論計算量を推定するchainer_computational_cost)
前述のELECTRAが引用している論文です。(今年のICLRに採択されていますが、論文自体はarXivに2018年からアップロードされていた模様。)
従来の自然言語生成 (NLG)において、MLE(最尤推定)を学習に利用した手法では、学習と生成の入力が異なっていました。学習時には常に正解データを与えられますが、生成時には前のステップで予測した値のみ与えられます。
from Sequence Level Training with Recurrent Neural Networks (関東CV勉強会 強化学習論文読み会)
このようなサンプルの品質の低下の問題 exposure bias の解決案として、 generative adversarial networks (GANs) が提案されています。しかしながらGANsのモデルは品質についての検証が多く、多様性については無視されていました。
この論文では自然言語生成の評価において、temperature sweep によって質と多様性のトレードオフ(quality-diversity trade-off) を特徴付ける方法を提案しています。 ここでいう temperature は ボルツマンマシン における温度の意味で使われています。
Generator G と temperature の関係を式で表すと下記のようになります。
実際に を変更した MLE(最尤推定) での結果が以下の表のようになります。
の例は統語的には合っていますが、文章全体の一貫性に欠けています。 を大きくすると統語的には間違った文が生成され、 を小さくすると生成される文が一意になります。
temperature sweepを利用し、seqGAN (Yu et al., 2017) ベースのGANモデル RL-GANとそこから敵対的学習を取り除いたMLEモデルを比較したところ、MLEモデルの方が質と多様性のトレードオフの側面において、優れた結果であることがわかりました。
つまり、MLEのexposure bias はGANsモデルの最適化よりも問題が少ないということです。 また、MLEによる学習は質と多様性に応じて良いpolicyのGeneratorに改善する一方で、GANsの学習では学習した分布からエントロピー下げることで高品質なサンプルが得られると解釈することもできます。
パラメーターによって自然言語生成の出力を変化させて評価するアプローチを提案していると同時に、自然言語におけるGANsの扱いについて、疑問を提示するような論文にもなっています。
自然言語とGANsはなにかしらのボトルネックがあるのかもしれません。
TransformerやBERTがメジャーである一方で、効率的な手法や拡張手法の提案がされている印象をうけました。
モデルサイズが小さく・学習時間が短くなることで、機械学習の活用が、より手軽になっていく気がします。
ICLRについて
LSH
GANs
exposure bias
NLG
実践GAN ~敵対的生成ネットワークによる深層学習~ (Compass Booksシリーズ)
日々、最先端で高度なテクノロジーに基づくビジネス改善”施策”を実施されている読者諸氏の皆さんこんばんわ、株式会社ホクソエム・常務取締役(博士(統計科学))の高柳です。
"XXXというKPI(売上とか)を向上させるために、XXXを構成するYYYという要因(PVとか広告単価とか1人あたりの売上とか)を向上させれそうな施策を試してみたんだけど、ZZZというまた別の売上を構成する要因(Impressionとか来店客数)も増えてたおかげで、結局、施策が売上全体にどのくらいのインパクトがあったのかよくわからないんだ〜助けて〜” ・・・という状況、あると思います。
この記事ではこういった複数の要因が混み入った状況でも ”各要因ごとに施策効果を分解して「PV要因で売上X円UP!」などと評価することができますよ、という話を紹介したい。
あまりやってる人見たことないからメジャーじゃないとは思うんだけど、「引いて掛ける」という簡単なさんすうで計算することができるので覚えておきましょうという話でもある。
まず、スーパーの店長になった気持ちで一日あたりの ”売上(円)” というKPIが以下のように分解されるとしておこう。 え?私の手元のKPIツリーではこんな簡単な算数で綺麗に分解できていない?その場合は下記の一般論のケース「結局なんなの?・・・もっと一般論をプログラマティックにやりたい貴方へ」を見てもらいたい。
字面通りに式を読み解くと、これは"売上(円)"というKPIを
という2つの要因に分けているということだ。 そして今、店長としてのあなたは店の売上をあげるために ”1人あたりの売上(円/人)”を向上させようと躍起になっている、そんな状況を想像してもらいたい。
それでは早速本題に入ろう。 まず現状、各々の要因が
だったとしよう。このときの売上(円)はもちろん単純に掛け算をして 1,000(円/人)× 100(人)= 10万円となる。 これはさすがに簡単だ。小学3年生だって暗算でできちゃう。
さて、次にあなたは店長として”1人あたりの売上(円/人)”を向上しようと何某かの単価向上キャンペーンを打ったと思おう。 なんかこう一回来店したときにたくさん買ってくれるような施策だ。牛肉がお買い得!マスクまとめ買いチャンス!トイレットペーパー無制限購入可!などなど何でも良い。
さて、そのおかげもあって"1人あたりの売上(円/人)"が以下のように改善したとしよう(一方、天気が悪かったのか来店客数がしれっと減ってる点に注意)。
やったー! ”1人あたりの売上(円/人)”は確かに 1,000(円/人)から 1,200(円/人)に向上している! 今回の”単価向上キャンペーン”は効果があったと言えそうだ!
あった、効果はあった、あったはいいが、その効果は”いかほどの金額”だったのだろうか? 定量にうるさいおじさんたちはきっとこれを要求してくるだろう。これを見積もりたい。 だって貴方は店長だもの。エリア長が褒めてくれなくたっていい。自分だけでも自分を褒めてあげたい。 売上はすべてを癒す、そういうことです。
さて、雑に考えると、単純に売上の増加分の8,000円((1,200(円/人)× 90(円) - 100,000円)だけ効果があったと見積っていいのだろうか?
ここで示す要因分解はこの問題に答えを出してくれる。
答えを先に言っちゃうと、各要因による売上増の効果を以下のように計算すればよい。
上の3つの要因を足してもらうと元の売上増加分の8,000円に等しくなっていることがわかると思う。 つまりこういう分解が行われているのだ。
したがって、この計算方法でいうと "今回の単価向上キャンペーンによって1人あたりの売上は200円向上した。その売上への効果は20,000円程度あったと見込まれる"ということができる(一方、悪天候効果により来店客数が減少したことによる売上への影響は -10,000円あったと言える、また謎の”両要因の混合要因”とやらは、他の2つの要因と比べて1桁小さい点にも注目だ)。 どういう計算をしているのかを日本語で書くとこういうことだ。
要するに「要因ごとに施策前後での差分を計算し、それに”施策前”のもう一方の要因を掛けてやる」ということだ。 「引いて掛ける」という簡単な”さんすう”で施策効果の要因分解ができましたね?
この計算をGoogle SpreadSheetにしたものを用意したので自分で数式を確かめたい人は参考にしてほしい 。
あぁ、よかった。これでちゃんと単価向上キャンペーンの効果を見積もれるようになったぞ。 店長としての貴方は枕を高くして眠ることができるわけです。
ここでやってる分解は要するに「 ”施策や確率的なノイズによってもたらされる各要因の向上度合い” を微小量だと思ってそいつで一次近似してる」だけです。 ここでは2要因の場合を紹介したけど、N要因ある場合には(未定義な変数は心の目👀で補間してください)以下のように考えればよい。
まず売上の増分はN個の要因(x)の変化(Δで書いてるやつ)を用いて以下のようにかける。
この右辺をΔ(向上度合い)が小さいと思って頑張って展開をしてやると
と書ける。
2要因の場合、この第一項が上記の"1人あたりの売上(円/人)"要因(x_1)と"来店客数(人)"要因(x_2)に相当しています。 ちゃんと導出したければ Sales(x_1, x_2) = x_1 * x_2 と定義して、これをx_1やx_2で微分してみると見通しよく分かると思います。
また最後に残った”両要因の混合(相互作用)要因”と称しているものは上の数式の第二項、要するに高次のオーダーからの寄与ってことになります。
なんで、N要因への拡張も簡単にできるし、Codeで計算しようと思えば簡単にできるわけです(売上関数をちゃんと構築できれば!)。
いろんな要因がごちゃごちゃ同時に変化してしまったとしても、意外と施策効果って簡単な算数で測れるもんだな〜ってのがわかっていただけると幸甚です。
そういえば、こういう”数理を用いたモデリング”(今回は要因分解のモデリング)に詳しくなりたい人にぴったりの本が最近出たようですよ。 (この記事は本書の1章に刺激を受けたのでスッと書きました)