株式会社ホクソエムのブログ

R, Python, データ分析, 機械学習

MLflowのXGBoost拡張を読んでみる

はじめに

ホクソエムサポーターの藤岡です。会社を移りましたが、相変わらずPythonを書く仕事をしています。

前回の記事に引き続き、今回もMLflowについての記事です。 前回はトラッキング寄りでしたが、今回はモデルのデプロイにも関わってくる内容です。

MLflowはXGBoost, PySpark, scikit-learnといった多様なライブラリに対応していて、様々な機械学習タスクに活用することができるのが売りの一つです。 その実現のため、設計や実装に様々な工夫がされているのですが、 この部分について詳しくなることで、オリジナルの機械学習モデルをMLflowとうまく繋ぐことができるようになったり ETLのようなモデル学習にとどまらない使い方もできるようになったりします。

本記事では、XGBoostをMLflowで扱うためのモジュール mlflow.xgboost について解説することで、拡張性の鍵であるflavorについて紹介します。 また、その前提知識として、MLflow Model, PyFunc Modelについても関連する部分をさらっと解説します。

機械学習が完全に絡まない部分で使う必要があるのかはさておき、 機械学習が絡んでいるフローを広く一つのフレームワークで管理できるようにしておくのは有用だと思います。 実際、MLflowのサンプルコードにもETLを含めて管理している例があります*1

本記事を通してMLflowについて理解を深め、活用の幅を広げましょう!

なお、本記事はMLflowの使用方法と前回記事の内容 (のうちMLmodel周り) が前提知識となります。 また、MLflow v1.10.0 時点での内容です。

MLflow Model

今日では、日々新しいMLモデルが提案され、それらを実装したパッケージも次々と生まれています。 それらはsklearnの提供する形式に従っているものから、 xgboostのようなデータ構造まで自前で用意してパッケージングしたものまで様々であり、 統一的な形式のようなものはありません。

MLflowは抽象的なモデルフォーマットを定義し、それを通じてこれらのフレームワークやパッケージを統一的なインターフェースで扱います。 このモデルフォーマットは MLflow Model と呼ばれます。 モデルとは言っても機械学習モデルというよりそれにまつわる種々の内容を定めたものです。 以下の例をはじめとする種々の項目についてそれぞれ形式を定めています。

  • ストレージ
  • モデルの入出力形式(Signature)
  • モデルのAPI (セーブ、ロード、ロギング等)

このフォーマットはmlflow.models以下で実装されています。 例えば、本体となるクラスはmlflow.models.model.pyの中にModel()クラスとして定義されています。 ちなみに、このクラスがどんなメソッドやパラメータを持っているか一通り目を通しておくと、MLflow Modelについて少しイメージがしやすくなります。

flavor

MLflow Modelは抽象度が高く、それ単体では使うことができません。 実際、モデルのデプロイ部分の実装を見てみると、 入力とメソッド名のみを定義している以外はすっからかんとなっています。

そのため、MLflow Modelは既存の機械学習フレームワーク等のモジュールやスクリプトがこのフォーマットに従うようにするためのインターフェースを必要とします。 これを、flavorと呼びます。 例えば、xgboost.Booster (xgboostのモデルクラス) をMLflowで扱う場合、Boosterに特定のメソッドを生やすためのラッパなどをflavorとして実装すれば、 MLflowの学習トラッキングやモデルデプロイなどの機能をxgboost.Boosterに対して実行できます。

とはいっても、xgboost.Boosterについてはmlflow.xgboostモジュールとしてflavorが既に定義されているので、 ユーザ側ではただmlflow.xgboostの関数を呼び出すだけで使えます。

さらに、あるflavorをベースに別のflavorを実装することも可能です。 ベースにするといっても、実際には両方を併用するような形になります。

flavorの種類

flavorは大きく3種類に分けることができます。

Built-in flavor

XGBoostを始めとして、MLflowが対応している種々のモジュールに対してそれぞれflavorが定義されています。 一般的なデータ分析タスクで使うような機械学習モデルはたいていの場合はこれらのflavorで対応ができるので、 小さいコーディングコスト(log_model()save_mode()を呼び出すだけ)でMLflowの提供する機能を十全に使うことができます。

さらに、いくつかのflavorにはautolog機能を備えたflavorがあり、 その場合はほぼ全自動でMLflowの機能を使うことができます。

PyFunc Model flavor

PyFunc Modelとは、特定の機械学習モデルを指すモデルではなく、以下の入出力をもつ関数の抽象モデルです。

predict(model_input: pandas.DataFrame) -> [numpy.ndarray | pandas.(Series | DataFrame)]

これ自身を使うのではなく、これをベースに別のflavorを作るために用意されています。 上記の入出力をもつ関数として表現可能なものであればこのflavorを元に作成可能です。 全てのBuilt-in FlavorはPyFunc Model Flavorをベースに作成されています。

MLflowのdocumentやソースコードを読んでいるとき、PyFunc ModelとMLflow Modelを混同しやすいので注意しましょう。 基本的にはPyFunc ModelがMLflow Modelの一部だとみて問題ないとは思いますが、 とにかくmodelという単語がたくさん出てきて混乱するので区別を付けられるようになっておくのがいいです。

ここでは紹介しませんが、PyFunc Modelの詳細について知りたい方はこちらこちら が詳しいです。 backendの実装等を覗いてみると、PyFunc Model flavorが非常に多くの機能を実装し、その他のflavorの作成を助けているのかが見て取れておもしろいです。

Custom Python flavor

MLflowが提供しているflavor以外にもユーザ側がflavorを開発することでMLflow互換のモデルを定義することが可能です。

作成方法は以下の2通りがあります。

  1. 実装したいモデルのクラスをmlflow.pyfunc.PythonModelのサブクラスとして作成
  2. 既存のモデルをPyFunc Modelと相互変換するような入出力関数を定義

基本的には1が簡単かつ有用ですが、MLflowを使わないで保存したモデルオブジェクトとの互換性はないので、既に学習済みのモデルを取り込むなどの互換性が必要な場合は2の方法を採る必要があります。

XGBoost flavor

Overview

f:id:kazuya_fujioka:20200817090002p:plain
XGBoost Flavorとその関連する要素

図は、XGBoost flavor, MLflow, XGBoostの三者、そしてflavorを呼び出すスクリプトの4要素がどのオブジェクト (関数/クラス/モジュール) を通じて繋がっているのかを図示したものです。 黄色い四角で囲われた6つの関数がXGBoost flavorの主な構成要素です((autolog()等は本質ではなく、理解も容易なので省略しています。))。 なお、分かりやすさのためPyFunc Modelの実装であるpyfuncモジュールもMLflowに入れています。

ユーザ側はlog_model()(MLflow Modelの記録)、load_model()(モデルの読み込み)を操作します。 内部的には、log_model()の呼び出しでsave_model()(MLflow Modelの書き出し)が内部的に呼び出されるほか、_load_pyfunc()はモデルのデプロイ時に内部的に呼び出されます。

これらは、基本的には全てのflavorに共通です。

一方で_load_model()_XGBModelWrapper()はxgboostライブラリに固有の実装です。 XGBoost flavor内から必要に応じて呼び出されます。

Components

では、これらを順を追って詳細に読んでいきます。 なお、解説のために動作が変化しない程度にコメントや変数等を変更している部分があります。

_load_pyfunc() & _load_model() & _XGBModelWrapper

def _load_pyfunc(path):
    xgb_original = _load_model(path)
    return _XGBModelWrapper(xgb_original)


def _load_model(path):
    import xgboost as xgb
    model = xgb.Booster()
    model.load_model(os.path.abspath(path))
    return model


class _XGBModelWrapper:
    def __init__(self, xgb_model):
        self.xgb_model = xgb_model

    def predict(self, dataframe):
        import xgboost as xgb
        return self.xgb_model.predict(xgb.DMatrix(dataframe))

これらの3つの関数で実現していることは、引数で与えられたパスpathにある学習済みxgboostモデルをロードする処理を、 _load_pyfunc()という名前の関数として実装することです。

_load_pyfunc()関数はpyfunc.__init__.py内に定義されているload_model()関数内で呼び出され、 PyFuncModelクラスの初期化に使用されます (実装)。

モデルのロード部分はxgboostライブラリの機能をそのまま使って_load_model()関数として実装しています。 ただし、xgboost.Booster()predict()メソッドを実装しているものの、その入力がPandas DataframeではなくDMatrixなのでそのままではPyFuncModelに渡せません。 その間を埋めるためのラッパとして_XGBModelWrapperクラスが定義されています。

save_model()

def save_model(xgb_model, path, conda_env=None, mlflow_model=None,
               signature: ModelSignature=None, input_example: ModelInputExample=None):
    import xgboost as xgb

    path = os.path.abspath(path)
    if os.path.exists(path):
        raise MlflowException("Path '{}' already exists".format(path))
    os.makedirs(path)
    if mlflow_model is None:
        mlflow_model = Model()
    if signature is not None:
        mlflow_model.signature = signature
    if input_example is not None:
        _save_example(mlflow_model, input_example, path)
    model_data_subpath = "model.xgb"
    model_data_path = os.path.join(path, model_data_subpath)

    # Save an XGBoost model
    xgb_model.save_model(model_data_path)

    conda_env_subpath = "conda.yaml"
    if conda_env is None:
        conda_env = get_default_conda_env()
    elif not isinstance(conda_env, dict):
        with open(conda_env, "r") as f:
            conda_env = yaml.safe_load(f)
    with open(os.path.join(path, conda_env_subpath), "w") as f:
        yaml.safe_dump(conda_env, stream=f, default_flow_style=False)

    pyfunc.add_to_model(mlflow_model, loader_module="mlflow.xgboost",
                        data=model_data_subpath, env=conda_env_subpath)
    mlflow_model.add_flavor(FLAVOR_NAME, xgb_version=xgb.__version__, data=model_data_subpath)
    mlflow_model.save(os.path.join(path, MLMODEL_FILE_NAME))

MLflow Modelmlflow_modelをartifact化し、渡されたローカルのフォルダパスpathに保存する関数です。 具体的には、フォルダの中に以下のファイルが作成されます。

  1. signatureとして渡されたSignatureのダンプファイル
  2. xgb_modelとして渡されたxgboost.Boosterのダンプ。
  3. conda_envとして渡された辞書/ファイル名のconda envファイル (ただし、無ければデフォルトを生成)
  4. MLflow Modelファイル(MLmodel)

最も重要なのは、以下の3行です。

pyfunc.add_to_model(mlflow_model, loader_module="mlflow.xgboost",
                        data=model_data_subpath, env=conda_env_subpath)
mlflow_model.add_flavor(FLAVOR_NAME, xgb_version=xgb.__version__, data=model_data_subpath)

MLmodelファイル(yaml)にはflavorごとの設定項目を記録するセクションがあり、 その内容をここで生成しています。

あるflavorの設定項目を記録するには、Model.add_flavor()メソッドを使って、 flavor名(第一引数)とその設定項目(キーワード引数)を渡します。

例えば、3行目で、XGBoost flavorの項目が以下のように作成されます。

xgboost:
    xgb_version: <xgb.__version__の値>
    data: subpath/from/MLmodel/root/to/model.xgb

なお、セクション名"xgboost"はmlflow.xgboost内で

FLAVOR_NAME = "xgboost"

と定義されているところから来ています。

さらに、PyFunc Model flavorをベースに作られているflavorを使った場合、 PyFunc Modelのセクション"python_function"を作成します。

1-2行目の"pyfunc.add_to_model()"関数は"Model.add_flavor()"メソッドの呼び出しをPyFunc Model flavor向けにラップしたものです。 この中で conda env等のPyFuncの基本的な構成要素へのパスとPythonのバージョンを含んだ"python_function"セクションがmlflow_modelに記録されます。

log_model()

def log_model(xgb_model, artifact_path, conda_env=None, registered_model_name=None,
              signature: ModelSignature=None, input_example: ModelInputExample=None,
              **kwargs):
    Model.log(artifact_path=artifact_path, flavor=mlflow.xgboost,
              registered_model_name=registered_model_name,
              xgb_model=xgb_model, conda_env=conda_env,
              signature=signature, input_example=input_example, **kwargs)

mlflow.models.Modellogメソッド(classmethod)のラッパです。 大まかには、以下の3ステップを実行します。

  1. MLflow Modelの生成
  2. artifactの生成
  3. 生成したMLflow Model(artifactを含む)をrunとして記録

なお、モデルのartifact化に使用するパラメータはModel.logkwargs引数に入ってからflavor.save_model()呼び出し時に渡されます。 save_model()xgb_model変数はkwargsを通じてsave_model()に渡され、artifact化されます。

load_model()

def load_model(model_uri):
    local_model_path = _download_artifact_from_uri(artifact_uri=model_uri)
    flavor_conf = _get_flavor_configuration(model_path=local_model_path, flavor_name=FLAVOR_NAME)
    xgb_model_file_path = os.path.join(local_model_path, flavor_conf.get("data", "model.xgb"))
    return _load_model(path=xgb_model_file_path)

この関数の処理内容ですが、

  1. artifactをダウンロードして
  2. flavorの設定を読み込んで
  3. xgboost.Boosterをartifactからロード

と、中で呼び出されている関数名を読んだ通りの処理内容です。

特に解説することもないのですが、 _download_artifact_from_uri()_get_flavor_configuration()といった 便利なutility関数が実装されていることを知っているとflavor実装などで役に立ちます。

おわりに

駆け足となりましたが、XGBoost flavorの内容とその前提となる知識を軽く紹介しました。 シンプルなflavorなので、MLflowの理解のために読むのはもちろんですが、flavorを作ってみる際の手本としても最適だと思います。 実際、今回書いていてすごく理解が深まり、前回記事の内容が思いっきり間違っていることにも気付けました。

当該箇所は修正済みです。本当にすみませんでした……。

他にも、純粋にPythonモジュールの実装としても、多層に分けて段階的に抽象度を下げていくなど勉強になることが多かったです。 汎化と実装コストのトレードオフにはいつも苦心しているので、今後書くプログラムに色々と取り入れようと思っています。

では、良きPython Lifeを!

Reference

*1:この例では、今回の記事の内容は不要です

私の人生のロックマン(あるいは星のカービィ)戦略について

株式会社ホクソエム常務取締役のタカヤナギ=サンです。

会社では主にα崩壊を起こしそうなシャチョーを制御するための制御棒を担当しています。

これは何の話なの?

私のやり方というか能力の上げ方はタイトルにあるように基本的に「ロックマン(あるいは星のカービィ)戦略」なんですが、それについて明示的に書いたポエムです。

勘の鋭い方はこのタイトルだけでどういう戦略なのかお分かりになられると思うので、ここでこの記事を読むのをストップすれば作業時間を確保できて良いかもしれない。

「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?

とりあえずの結論

なんか長いんでとりあえず結論を書くと

  • 技術をたくさん持ってる/生み出してそうな優秀な技術者(エンジニア)に暗に陽に弟子入りしてたくさんの技術(Not技能)をコピーさせてもらおう!
  • 高度な技能(スキル)を持っている技能者(テクニシャン)さんを真似ようとする場合には「彼はコピー可能なもの(テクノロジーのレベルにまで至っているもの)を持っているのか?」を意識しよう
    • もしそうでないならばその人からコピーして学ぶことをやめよう、それは時間の無駄です
      • 技能(スキル)は定義からしてコピー不可能なものだから
      • このインターネット時代においては学ぶべきリソースは山ほど転がっているから

です。

私の人生のロックマン(あるいは星のカービィ)戦略について

↓こんなかんじでロックマンって敵(BOSS)を倒すとそいつの能力使えるようになるじゃないですか?

youtu.be

あるいは、カービーでも吸い込だ敵の能力コピーできるじゃないですか?

youtu.be

これらと同じように、私の人生戦略って基本的に単純で「良いな・イケてるなと思った人のアイディア・やり方・技術・振る舞いを何でもコピーする」って話しなんですね。

あるいはバトルボーナス中にトキを降臨させちゃう北斗の拳のケンシロウ(パチスロ)とかとか。

www.youtube.com

「こ…この動きは…ト…トキ!」

「ラオウよ、天に還る時が来たのだ」

最高でしたね???あの頃は皆トキの動きのマネしてた。バトルボーナス、確定!!!

なんでこのやり方をするのか?それは私に個性がないからです

なんでこういうコピー戦略をするのかというと、それは私に個性がないからです。

私はキャンバス、ほぼ真っ白なキャンバス。

私を知ってる人は「いや、お前髪が変な色だったり大体似たような熊のTシャツ来てるし個性的じゃねぇかよ」と思うかも知れないが、それは違う。

私の髪の色が変なのは親愛なる美容師殿への正しい課金手段として「好きな色にしていいよ(ブリーチやカラー入れた方が課金額があがる)」からやっているのであって特に私の好みではないし、逆に常に黒い髪をKeepされ続けている皆さんのほうがよほど個性的だ。

”髪 is 黒いであるべし”という強いこだわりを感じる。もしこだわりがないというのなら適当な色にされたってよくない?*1

もう少し言うとオリジナルの学び方というものを知らない。これはあれ、文化的資本って呼ばれているやつに近いと思う。 書籍を読むようになったのは大学院に入った時(24歳)でそれまではいわゆる教科書と読書感想文で嫌々読んだ本くらいしか読んだことがなかった。 なので、そもそも学び方を知らないので、人からコピるしかなかったわけです。 メタ学習(学び方を学ぶ)みたいな概念もなかった、メタ?なにそれ美味しいの?

今ではおかげさまで書籍を買う余裕があるのでいっぱい本読んでるけど。

もちろん私にもコピーして塗りつぶしたくない色や領域、そして大切にしているものがあると言えばあるが、それはそんなに多くないんですね。

大多数の物事に対するこだわりを捨てる、特定の領域にしぶとく深く興味を持ち続ける、その他は基本人の全コピーか脳死状態で行動できるよう仕組み化にしておく、それが俺のやり方。

ちなみに、このやり方を取った場合にみなさんがメリットと感じそうなこととしては”アンチ耐性の飛躍的な向上”があります。

すごい!RPGだと最後の方に出てきそうな高級なアクセサリー的装備が手に入ります。

例えば、最近おかげさまである程度書籍などが売れまして、インターネットにおいてもHOXO-Mアンチが沸いてきており、

  • 「くそっ、ホクソエムむかつく!」

とか

  • 「なんか〜ホクソエム苦手〜生理的に無理〜」

みたいな話がちょいちょいあるんだが、私はこの手の話はイチロー先生よろしくどんと来いだ。

www.youtube.com

何故どんとこいなのかというと、最近よく聞く「ネットの誹謗中傷で慰謝料が取れて儲けのチャンスだワハハ!」という点はおいておくとして、上述したように私に個性がないからですね。

もう少し言うと「皆さんが見ている・思っている私の個性的なものは大体私が他人から拝借したアイディア・やり方・技術・振る舞いであるので、 そもそも特に私自身がアンチにディスられているとは感じない」わけです。

「あっ、あの時助けていただいた鶴さんのアイディア・やり方・技術・振る舞いをディスってるんだなこいつらは」くらいのイメージです。 なので虚無。何も感じない。

もちろん私にもディスられたら嫌な興味関心のあるところがあるような気もするが、 それはそもそも大多数の社会を営んでいる皆様におかれましてはAlmost surely興味も関心もない話(特に広義の統計科学に関わるところ)なので、それがディスられることはまずないという状況なんですね、はい。

じゃぁ、貴方は誰からロックマン/星のカービィ戦略に基づいてコピーをするの?

ここまで何度か”コピーをする”と書いてきたが、その対象は何度か書いたようにある人の”アイディア・やり方・技術・振る舞い”です。 エンジニアリングにのみ対象を絞るとすると、コピーするするものは”技術(テクノロジー)”であって、”技能(スキル)”ではない、ここが重要です。

なんでかと言うと技能(スキル)はコピーできなくて技術(テクノロジー)のみがコピー(人から人へと伝達)可能だからですね。

「じゃぁ、技能と技術って何が違うんだ?」と言うと、昔、私も何度か調べてはいずれの回も厳密な定義にたどり着けずに力尽きているのだが、 ここではとりあえずWikipediaから”技術”と”技能”の違いについての定義を拝借するとしましょうか。

技術と技能(スキル)の違い
「技術」と「技能」という言葉は、違いを理解せずに混同して会話などで使用されがちであるが、以下のように明確な違いがある。
技術
    知識のことである。
    教科書のように文書化したり、会話などで他人に伝達可能である。
技能
    技術(知識)を使用し、作業を遂行する能力のことである。
    個人の中に熟成されるため、他人に伝達不可能である。

例えば、自動車の運転の仕方を知識として習得しただけでは、自動車を正確に運転できる確率は低い。訓練を行い、運転の技能を上げることにより、より正確に運転できるようになる。

技術 - Wikipedia

・・・とさ。 なるほど、他人に伝達可能か否かが違うぞと、そういうことです。

そもそもテクノロジー(技術)という語はギリシア語の「技(テクネ)を学問(XXXオロジー)のレベルにまで昇華したもの」という意味なので、ちゃんと原義に沿っていますね? なので、技術(テクノロジー)のみがコピー可能で技能(スキル)はコピー不可なわけです、人に宿る職人芸、強し。

じゃぁ技術者(エンジニア)ってのは何なのかと言うと同じくWikipediaを参考にすると

現代ではエンジニアとは、自然法と社会の必要性の制限の中でテクノロジーを創り出す人のことをいう。

技術 - Wikipedia

ということだそうだ*2

なんで、長いこと社会生活をしているとちょいちょい

  • 「いや〜XXXさんのCodeは良く高速に動きますが難解ですな、可読性0ですわいタハー(それメンテできなくね?)」

とか

  • 「俺の!コードは!読めば分かる!だから!ノーマニュアルや!(実際は難解&README.md書くくらいまではどう考えても仕事じゃね?)」

などと元気よく自慢してくるおじさんに出会うのだが、私は基本的にそれらの方々は無視。

だって、それエンジニア(技術者)じゃなくてテクニシャン(技能者)だもん、コピーできるもの持ってないんだもの(いや持ってるかもしれないけどその凄いポイントはコピー不可なんだもの)。

頑張ってチーフテクニシャンおじさん(CTO)、ゆくゆくは人間国宝を目指してもらいたい。 海老蔵の睨みばりのSomething技能を研ぎ澄ませて行ってほしい。 一方、優秀な技術者が優秀な技能者を兼ねている場合も当然多いので、その場合にはとても用があります。

そんなかんじ。

結論

長くなったが私のやり方をまとめると

  • 技術をたくさん持ってる/生み出してそうな優秀な技術者(エンジニア)に暗に陽に弟子入りしてたくさんの技術(Not技能)をコピーさせてもらおう!
  • 高度な技能(スキル)を持っている技能者(テクニシャン)さんを真似ようとする場合には「彼はコピー可能なもの(テクノロジーのレベルにまで至っているもの)を持っているのか?」を意識しよう
    • もしそうでないならばその人からコピーして学ぶことをやめよう、それは時間の無駄です
      • 技能(スキル)は定義からしてコピー不可能なものだから
      • このインターネット時代においては学ぶべきリソースは山ほど転がっているから

ということでした。

次回もまた、見てくれよな!

*1:実は一回全頭ドピンクにされたことがあって、そのときだけは鏡を見るたびになんか不安な気持ちになったので「ピンクだけはやめてくれ」と言うようになった。”髪の色がピンクなのは嫌だ”が私の個性かもしれない

*2:この辺の工学(者)・技術(者)の関係がWikipediaさんも曖昧で私も調べても曖昧なので誰か正しい定義を知ってたら教えて欲しい

TRI-AD(TOYOTAの自動運転のとこ)の服部圭悟さんにカジュアル面談してもらった

頭出し

前職の同僚(一時期私の真後ろの席に座っていた)で、今は「誰もが、安全に移動できる世界へ」を掲げるTRI-ADに勤めている服部圭悟さんとカジュアル面談したら面白かったのでまとめておきたい、そして彼のチームの採用へとつなげていきたい。

カジュアル面談中にメモった箇条書きを体裁整えただけなので、やや文が壊れているがご容赦&そこも含めて愛して欲しい。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?

このカジュアル面談では、会社の全容とかまるで聞かないで私の聞きたいことだけ聞いてきたんで、その質疑応答をまとめました。 本当に好き勝手に聞いたのにまじで全部真摯に答えてもらって大変ありがたかったです。

みんなが話を聞きに行くとまた違った結果になるかもしれない、カジュアル面談はここからカジュアルに応募することができる、お気軽に、応募、是非。

sites.google.com

まず、私は車はHONDA党(インテグラの96スペックTYPE-Rに乗っていた、当時Rはプログラミング言語じゃなくてRacing SpilitsのRだった)なのだが、トヨタイムズのファンでもあるので、冷やかしではなく本当にTRI-ADに興味がある人材です。

これとか最高に面白いから見て欲しい

・・・さて、服部圭悟さんは(最近ちょいちょい色んな人からディスられる)MLOpsやData Engineerをやってる人だ。 私の中ではUberのミケランジェロに異常なライバル心を燃やす機械学習プラットフォーム作りたがってるおじさんという認識だ。 彼は私と同僚だった時、国際的な政治事情に巻き込まれてチームごと爆散してしまったという悲しい過去を持つ男だ。でも、俺はそういう過去を背負った男、嫌いじゃないぜ。 しかし、今はラザロの如く蘇っている。素晴らしい。

ラザロとミケランジェロ、なんか語感が似てる。え?そういうこと?

トライアドって読んでたけどティーアールアイエーディーらしい知ったかぶりしてごめんなさい。 そして2021年からはウーブン・プラネット・ホールディングス株式会社になるらしい

質問とそれに対する返答

質問:ユー、今なにやっとん?

  • TRI(シリコンバレーにあるToyota Research Institute)と協力して機械学習基盤を作ってるよ
  • 自動車なんで莫大なセンサーデータを扱っています
  • 機械学習モデルとそのパイプラインつくるところまでがMISSION
  • 事故が起きた場合、機械学習モデルの説明性が求められるのでそこを頑張っています
    • 今までも自動車のアーキテクチャを15年間保存してきた
    • これらの情報整理(データ・モデルのVersion管理など)とシステム化もチームのMISSION
  • 俺はミケランジェロを作りたい!(アッハイ)

質問:大企業で自動運転ってやれるのですかね?まだまだトライアンドエラーの塊ぽい印象だから失敗を許容して高速改善回さないといけないだろうが、大企業という”型”が足かせになりそう

  • 割とやれる。
  • 今はリリースにとても時間がかかる(とても時間がかかる、大事なこと(ry )ので、リリースプロセスの最適化をやっている。

質問:顧客はどちらさまなのですか?ずっと研究開発してるだけでいいの?

  • 社内とトヨタグループ。個人的にはもっと広げていきたいと思っている。

質問:言うて、TOYOTA系列の自動運転研究開発してるぽいところたくさんあるし、系列で仕事被りまくってんじゃないの?

  • 意外と機械学習のタスクとしては被ってない(へぇ〜!!!)

質問:御チームの構成どうなっとんのや

  • MLOpsで3チームあるよ、そのうちの1つをKeigo HattoriがLead(4人)
  • 残りの2チーム
    • アノテーションチーム(各ベンダーに自動でタスクを投げる(1人))
    • 組み込み系チーム(ハードウェアへのデプロイ系とか(2人))
  • 大増員中です!!!(入社チャンス!!!)
  • 機械学習つよつよ人材はいるので、ソフトウェアつよつよ人材がほしい
  • チームに日本人が多いので日本語も使う。公用語は英語、ドキュメントも英語。

質問:R&D、不況に弱い印象だし、会社ちゃんともつ?爆散はないの?

その他

  • 車専用のOSも作っているよ
  • 会社の文化的にはAmazonから来ている人が多いのでそれっぽい
  • 1年後にTeslaにおいつけおいこせ!

法人としての価格設定問題からの、おじさんエンジニアの辛さと賃金の関係性

株式会社ホクソエム常務取締役のタカヤナギ=サンです、主にバックオフィス業務を担当しています。

自分メモに書き溜めていたポエムネタが溜まってきたので少しずつ放出していこうと思い筆をとりました。 「いや、そんなもん会社のBLOGに書くんじゃねーよ💢」という話があるかもしれないですが、ここは私の保有する会社なので何の問題もない、don't you?

これは何の話なの?

以前、社のお若い方が技術的に楽しそうな案件を持ってこられて、その価格設定をどうするかについて悩まれておられた時がありました。

その際に社内のSlackにいわゆる”おじさんの小言”のようなものをちらほら書いていたので、それを改めて文章にし、更に「あ、この話は私がちょいちょい感じているおじさんエンジニアの辛さと賃金の話にもつながってくるな」思い、そことも絡めて書いたものになります。

法人としての価格設定問題

既にご存じの方もいるかもしれないですが、弊社は全員が副業として回している会社で21世紀にふさわしい働き方改革ダイバージェンス2020を体現した会社となっております。 なので、OSSを開発するかの如く「うわぁ、この案件技術的に超楽しいナリ〜」と土日を潰してほぼ無料のご奉仕価格で開発しちゃうというのもありといえばあり、”ありよりのあり”になるのですがそれはやっていません。

なので、”ありよりのなし”、です。

「なんでじゃい、楽しければお安く引き受けてもいいんじゃないんかい💢(表現はもっとマイルドで低姿勢)」とお若い方がご本人の謙虚さからくる面もあってそう言われていたのですが、そこを諭す際によく引用させてもらっているのがこの「とんかつ屋の悲劇」という記事です。これを一部引用させていただくと

年金が形を変えた補助金に?  「何十年も変わらない値段と、チェーン店ではありえない品質の高さと格安さ」などとグルメサイトでも称賛されていることが多い。しかし、それを可能にしているのは、すでに減価償却の終わった古い設備、ローンを払い終えた自社店舗、そして年金をもらいながら夫婦で切り盛りしていることなどだ。  ある意味、年金が経営継続への補助金のようになっているわけだ。こうした経営を続けてきた場合、いよいよ世代交代の時期になると若い現役世代にはとても生活をしていけるだけの収入を得ることができない。  「そうなってから、急に値段を大幅に上げるなどはできないし、設備更新などに多額の費用がかかるので、後継者にとっては重荷になるでしょう。」商業関係の支援事業を行う行政職員は、そう話す。先の外食産業の社員も、「夫婦二人で一人分の給与しかなく、それでやっと可能になっているような低価格がウリでは、いくら有名でも、のれん代を出してまで買収する意味はあまりない」と言う。

news.yahoo.co.jp

という話です。

要するに「(ご本人らは年金で食えるので)善意から値上げをしていないのかもしれないが、結局はそれが過度な価格下落競争を生んで、近隣の似たような(この場合だととんかつ屋をやりたいお若い方の)店を強制的に廃業に追い込でいる」という話になるわけです。

これに絡めて「いいかい?俺たちはとんかつ屋の悲劇を起こしてはいけない。技術的に面白い(主にRの)話だからといってもめちゃ低価格で引き受けるのはやめよう!それは巡り巡ってお若いRユーザの芽🌱を摘むことになる(Rが儲からない言語になるのは悲しい😢)」という話をしていたのです。

うんうん、わかる、過度なダンピングよくない、それは強制的に海を赤く染める行為なわけです(しかもそこに流れる赤い血は自分のものではなく人のもの)。

おじさんエンジニアの辛さと賃金

これと同じ話が一個人のおじさんエンジニアの賃金にも当てはまってくるぞと、もうちょっと機械学習っぽくいうと”おじさんエンジニアの辛さと賃金”というトピックベクトルは良い感じに”法人の価格設定問題”空間に射影できるぞと、そういうことです。

え?何が関係あるのかって?ちょっと落ち着いて私の経験を聞いて欲しい。

私自身は社会人歴14年で転職を繰り替えしたりホクソエムったりで、合計6社ほどの経験があるのですが、転職の際に年収を2~300万円ほど下げて転職した経験が2度ほどあります。 その意図は「正直、自分を優秀な技術者と仮定するならば、そのうち成果出して給料もあがるだろうし昇給がだめなら転職/副業すればいいや〜」くらいの楽観論が根底にあります。 その一方で、更にガッツリ年収を下げる(年収300万円くらいになるイメージ)のはいくら副業で稼げてもNGです。

もともとの「法人としての価格設定問題」の考えてに至った原点も実はこのへんにあります。 以前、同僚(私の心の中のインフラクラウド師匠)だった方が

「某D社では技術的に凄いおじさんエンジニアがたくさんいるんだが、彼らは全然給料交渉をしない。そして僕らは彼ら技術的に凄いおじさんエンジニアと給料を比較されるので僕らが交渉しても給料がまるで上がらないんだ。だから転職した。」

という話をしていてこれが原点です、めっちゃ印象的でした(かれこれ4〜5年前です)。 それまでは社会を知らなすぎてこの視点が私にはまるでなかったのです(当時社会人8年目くらい)、 pip install 社会性 が実行された瞬間ですくぅぅ。

もちろん人事(あるいは採用コストを管理されてる方々)の側としては「いや、技術的にめっちゃすごいおじさんエンジニアXさんの年収がY円なのに、それより技術的に優れていない(私から見れば優れているんだけど&色々パクらせてもらった)君(インフラクラウド師匠)の給料を上げる必要なくね?」と主張できるわけです。 いや、人事殿らの意見わかる、実に筋が通っている。その通りや!

この場合、誰が(お若い方の給料を上げるという観点で)悪いのかというと私は技術的に凄いおじさんエンジニア氏らだと考えます。 じゃぁどうすべきなのか?犯人探しや批判は猿🐵でもできるので、私のこの状況における解決策とボヤキを書くと

「技術的に凄いおじさんエンジニアはお若い方のお賃金上昇に迷惑をかけないように、会社の売上を上げる(あるいはコストを下げる)ような成果を出して、ある程度の高給を交渉して取っていく必要がある。 はぁ、おじさんエンジニアは辛いな、技術的なことだけ考えていてぇ」

となるわけです。 技術的に凄いおじさんエンジニア、ヤッテイキたい。

そんなポエムでした。 ・・・次回もまた、見てくれよな!

【翻訳】機械学習の技術的負債の重箱の隅をつつく (後編)

ホクソエムサポーターの白井です。 今回は前回 【翻訳】機械学習の技術的負債の重箱の隅をつつく (前編) の続きを紹介します。

blog.hoxo-m.com

※この記事は、Matthew McAteer氏によるブログ記事Nitpicking Machine Learning Technical Debtの和訳です。原著者の許可取得済みです。

後編では、コードのアンチパターンなど、エンジニアには身近な話題で、前編と比較して実践しやすいコンテンツも多いと思います。

Nitpicking Machine Learning Technical Debt (機械学習の技術的負債の重箱の隅をつつく)

再注目されているNeurIPS2015の論文を再読する (そして、2020年のための、より関連した25のベストプラクティス)

Part5 MLコードにある共通のダメなパターン

技術的負債論文の 「アンチパターン」の章は前の章よりも実践可能でした。 このパートは前パート(フィードバックループ)よりもより高レベルな抽象的なパターンを扱います。

(これは実際は技術的負債論文の情報を表にしたもので、さらにその修正方法に関するアドバイスへのハイパーリンクをつけています。

この表は、著者らが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エンドポイントに依存性をリパッケージして修正すべきです。

f:id:sh111h:20200705190057p:plain
私たちはみな、自慢できない utils.py を持っているものです。

Best practice #10 汎用的な目的の依存関係は特定のAPIにリパッケージしよう

「パイプラインジャングル (pipeline jungles)」は少し厄介で、グルーコードが大量に蓄積された場所を指します。 すべてのちょっとした新しいデータに対する変換が、醜い混合物 (amalgam) として積み重なった場所です。 グルーコードとは異なり、著者らは、スクラッチからのコードベースを手放して、再設計するように強く薦めています。

今や他の選択肢があると言いたいところですが、グルーコードがパイプラインジャングルに変貌した時には、Uber's Michelangeloのようなツールですらむしろ問題の一部になりえます。

もちろん、著者らのアドバイスのメリットは、変換したコードを興奮するようなかっこいい名前の新しいプロジェクトにみせられるということです。 トルーキンの参照が義務付けられている「Balrog」のように。 (プロジェクトの名前の不幸な暗喩を無視しているのはPalantirのドメインだけではありません。 あなたもまた自由です。)

(訳注: Barlog(バルログ) も Palantir(パランティーア)もトルーキンの指輪物語 に登場する。 Palantirは物語において「見る石」であり、危険な道具である。)

f:id:sh111h:20200706083622j:plain
ソフトウェアの形

Best practice #11 トップダウンの再設計・再実装によってパイプラインジャングルを取り除こう

考えたくない話題、試験的コード (experimental code)について見ていきましょう。 あなたは後々のために試験的コードを保存しようと当然思うはずです。 使っていない関数や参照していないファイルにおいておけば、問題ないと思ったでしょう。 あいにく、このようなものは後方互換性の管理が頭痛のタネになる理由のひとつになります。

Tensorflowを深く掘り下げたことのある人であれば覚えがあるでしょう、一部だけ吸収されたフレームワークや、試験的コードのちの日に気が付くエンジニアのために残された 未完了の「TODO」 コードに。 Tensorflowコードの謎の失敗をデバッグしようとした時、あなたは偶然これらを見つけたのではないでしょうか。 これは間違いなく、Tensorflow 1.Xと2.Xの間にある互換性の遅れを浮き彫りにしています。

自分自身のためにも、5年もコードベースの剪定から逃げるのはやめましょう。 実験をつづけても、他のコードから実験的コードを隔離する基準を定めましょう。

f:id:sh111h:20200705190150p:plain Google Researchの散らかった (少なくとも上位5位に入る) githubレポジトリ、しかも公開されているものです。 そして、READMEは同じぐらい詳細とは程遠く、実際は真逆です。

Best practice #12 定期的な点検を定めて、コードを取り除くための基準をつくろう、もしくはビジネスに重大な影響を及ぼすコードとは程遠いディレクトリやディスクにMLコードをおこう

古いコードといえば、ソフトウェアエンジニアリングが以前からなにをやってきたか知っていますか? それは本当に素晴らしい抽象化です! 関係データベースの概念からウェブページの表示まで、ありとあらゆることです。 応用カテゴリ理論には、コードを整理するためのベストなやり方を発見することに執念を燃やしている分野があります。

応用カテゴリ理論が、何にいまだ悩みつづけているか知っていますか? そう、機械学習のコード整理です。 ソフトウェアエンジニアリングは壁に抽象的なスパゲティーを投げて、なにが刺さるかを見る経験がありました。

機械学習? Map-Reduce (これは関係データベースのように印象的な概念ではない) や Async Parameter servers (どうやってすべきなのか誰も同意できない) や sync allreduce (多くの場合大失敗している) は別として、私たちが見せられるものはありません。

f:id:sh111h:20200705190240p:plain 先ほど言及した、Sync AllReduce serverのアーキテクチャの大まかな概要 (かなり簡略化したバージョン)

f:id:sh111h:20200705190302p:plain 先ほど言及した、Async parameter serverのアーキテクチャの大まかな概要 (少なくともそのバージョンのひとつ)

実際、ランダムネットワークの研究をしているグループとPytorchがニューラルネットワークのノードがいかに流動的であるかを宣伝している間に、 機械学習は抽象的なスパゲティーを窓から綺麗に投げ捨ててしまったのです! 著者らは、この問題が時間と共にとても悪くなっていることに気づいていたとは思いません。 私のおすすめ? 大まかな抽象化について、有名な文献を読みましょう。あと、プロダクションのコードにはPytorchを使わないほうがいいと思います。

Best practice #13 時間と共に強固になる抽象化を最新の状態にしておこう

私は今まで、お気に入りのフレームワークがあってほとんどの問題に対してそれをつかうのを好むシニアな機械学習エンジニアとたくさん会ってきました。 私はまた、彼らのお気に入りのフレームワークを新しい問題に適用するとき、そのフレームワークが破綻したり機能的に区別できない他のフレームワークに変更されたりするのを目の当たりにした同じエンジニアを見てきました。

これは特に、分散させて計算するタイプの機械学習処理を行うチームに多く見られます。 私にははっきりさせておきたいことがあります。

MapReduceは別として、なにかひとつのフレームワークに執着しすぎるのは避けるべきです。

あなたの “シニア(先輩)” 機械学習エンジニアが「Micheloangeloが最高で、全てを解決できる」と信じているならば、彼らはそれほどシニアではないかもしれません。

MLエンジニアリングは成熟してきた一方で、まだ相対的には早熟なのです。

本当の “シニア” なシニアMLエンジニアは、フレームワークにとらわれないワークフローを重要視するでしょう、なぜなら、フレームワークの多くは寿命が長くないと知っているからです。 ⚰️

さて、前のセクションで機械学習における技術的負債の区別できるシナリオや質について言及しましたが、技術負債論文では機械学習開発のための大まかなアンチパターンの例も提供しています。

これを読んでいる人の多くは、コードの臭い (code-smells) というフレーズを聞いたことがあるでしょう。 good-smellPep8-auto-checking (さらにはみんながPythonコードに使っている、新しい自動フォーマッタ Black) のようなツールを使っているかもしれません。 正直に言うと、 「コードの臭い」という用語が好きではありません。

「臭い」は常に微妙なものを暗に意味しているようにみえますが、次のセクションに書かれたパターンは逆にとても露骨なものです。 それにもかかわらず、著者らは負債を大まかに示すコードの臭いの種類を少しだけ並べています (普通のコードの臭いの種類を超えて)。 なぜか、“コードの臭い”のセクションの途中からコードの臭いを記載し始めています。

「プレーンなデータ」の臭い (The "Plain data" smell)

numpy float形式のデータを大量に扱うコードをもっているかもしれません。 RNAの読み込み回数がベールヌイ分布からのサンプルを表現しているのかどうか、floatが数値の対数かどうかといった、データの性質を保持する情報がほとんどないことがあるかもしれません。

技術的負債論文では言及されていませんが、これはPythonのtypingが助けられる領域のひとつです。 不必要なfloatや、正確すぎるfloatの使用を避けることが大いに役に立ちます。 繰り返しますが、組み込みの DecimalTyping パッケージを使うことはとても助けになります (コードを誘導するだけでなく、CPUのスピード向上にもなります)。

Best practice #14 TypingDeimal のようなパッケージを使って、すべてのオブジェクトに対して 'float32' を使うのはやめよう

f:id:sh111h:20200705190349p:plain 過半数のハッカソンコード、そして残念なことにベンチャーキャピタルに資金提供されている「AI」スタートアップのコードの中身はこんな感じです。

「試作品」の臭い(The "Prototyping" smell)

ハッカソンに参加した人なら知っているでしょうが、24時間以内に寄せ集めでつくられたコードはこんな形をしています。 これは、先ほど言及した実際には誰にも使われない試験的コードともつながっています。 生物学データのための PHATE次元削減ツールを試したくて興奮するかもしれませんが、コードをきれいにするか、投げ捨てるかしてください。

Best practice #15 進行中のものを同じディレクトリに置かない。片付けるか、きれいにしよう。

「多言語」の臭い (The "Muitl-Language" smell)

プログラミング言語の種類について言うと、多言語のコードベースは技術的負債が複数積み重なるように、その積み重なりがより早くなるよう振る舞います。 もちろん、すべての言語にはそれぞれ利点があります。 Pythonはアイデアを素早く構築するためには良いです。 Javascriptはインタフェースに向いています。 C++はグラフィックに最適で、計算もはやいです。 PHPは、うーん、特にこれといったものはないです。 GolangはKubernetesを使う (もしくはGoogleで働く) には便利です。

しかし、これらの言語でお互いに会話するとして、エンドポイントが壊れたり、メモリリークによって、うまくいかない点がたくさんあるでしょう。 少なくとも機械学習では、言語の間で似たセマンティクスを持つSparkやTensorflowのようなツールキットは少ないです。 もし複数の言語をどうしても使わなくてはいけなくても、少なくとも2015年以降はそれが可能になってはいます。

f:id:sh111h:20200705190432p:plain C++のプログラマーがPythonに転向したら。この人が書いたレポジトリ全体のコードを思い描いてみてください

Best practice #16 エンドポイントが説明されていることを確認し、言語間で似たような抽象度を持つフレームワークを使用しよう

(これをコードの臭いと呼ぶのは奇妙な選択です、普通のコードの臭いを基準としても、これはとても露骨なパターンなので。)

Part6 構成の負債 (退屈だけど修正は簡単)

技術的負債論文の「構成の負債」の章はおそらく楽しいものではないですが、そこに記述されている問題は簡単に修正できます。

基本的に、機械学習のパイプラインについて、すべての調整可能で構成可能な情報を一箇所にまとめて、確かめる習慣であり、これはいうなればあなたの2番目のLSTMレイヤーのユニット数がどう設定されているか調べるために複数の辞書を探す必要のない状況のことです。 設定ファイルをつくる習慣を持っていても、すべてのパッケージと技術があなたを悩ませないわけではありません。

一般的な方針は別として、技術的負債論文のこの部分は詳細を十分に深堀りしていません。 技術的負債論文の著者らはCaffeのようなパッケージを使うのに慣れていたのではないかと疑っています (Caffeのプロトコルバッファーで設定ファイルを設定することは客観的に見てバグだらけで恐ろしいものでした)。

個人的には、もし設定ファイルを設定したいのであれば、tf.KerasChainer のようなフレームワークを使うことを提案します。 クラウドサービスはバージョンの構成管理機能をもっていますが、それ以外では、config.jsonか パラメーターフラグを使う準備をする必要があります。

Best practice #17 ファイルパスやハイパーパラメーター、レイヤーの種類、レイヤーの順番や他の設定が一つの場所から設定できるか確かめよう

f:id:sh111h:20200705190510p:plain 設定ファイルの素晴らしい例。 技術的負債論文はもっと例をあげたり、当時存在した設定ファイルのガイドについて指摘して欲しかったと切に思います。

もしコマンドラインをつかってこれらの設定を調整したいなら、ArgparseのかわりにClickのようなパッケージをつかってみましょう。

Part7 解決への夢を打ち砕く実世界

7章は、技術的負債の管理の多くが、現実世界の絶えない変化を扱っているという事実のために準備することであると認めています。 例えば、モデルの出力を分類に変換するために閾値決定が必要なモデルや、あるいは真(True)または偽(False)のBool値を直接選ぶことができるモデルがあるかもしれません。

生物学的もしくは健康データを扱うグループや企業は、診断の基準が急速に変化することをよく知っています。 特にベイズ機械学習をしているときは、あなたの扱っている閾値が永遠に続くとは仮定してはいけません。

f:id:sh111h:20200705190555j:plain オンライン学習アルゴリズムによる決定境界……デプロイから12分後

Best practice #18 モデルの現実世界でのパフォーマンスと、決定境界を常に監視しよう

このセクションではリアルタイムの監視の重要性を強調しています、私は間違いなくこれの良さがわかります。 どのようなことを監視するかについて、この論文は包括的なガイドではありませんが、いくつかの例を与えています。

ひとつは観測した観測したラベルの要約統計量と、予測ラベルの要約統計量を比較することです。 完璧ではありませんが、小さな動物の体重をチェックするようなものです。 何かがすごく間違っていれば、その問題をすぐに警告できます。

f:id:sh111h:20200705190849p:plain 数えきれないほどたくさんのツールがあります。 近頃はだれもが、その母親さえもが、監視ツールのスタートアップを作っています。 ここでは、他に比べてバグが少ないSageMakerとWeights & Biasesを例に挙げてみました。

(訳注: それぞれのリンクは、Amazon SageMaker Model MonitorWeights & Biases)

Best practice #19 予測ラベルの分布が、観測ラベルの分布と似ているか確認しよう

あなたのシステムが現実世界のなにかを意思決定しているなら、レート制限(何某かの呼び出し回数制限)をしたいと思うでしょう。 たとえシステムが株の入札のための数百万ドルを任せられているものではなく、細胞培養インキュベーターのなにかが正しくないと警告を出すだけのシステムであっても、 単位時間あたりの行動制限を設定していないことでのちのち後悔することになります。

f:id:sh111h:20200705190909p:plain ML製品の初期の頭痛のタネのいくつかは、レート制限をしていないシステムで起こるでしょう。

Best practice #20 機械学習システムによってもたらされる意思決定に制限を設けよう

MLパイプラインが消費している、データの上位生産者(データを提供してくれている人・会社たち)によるどんな変化も、心に留めておきたいところです。 例えば、人間の血液やDNAサンプルの機械学習を走らせているどんな企業も、当然ながら、それらのサンプルがすべて標準化された手順で収集されていることを確認したいと考えています。 もしサンプルの多くが特定の集団からきたものであれば、企業は分析がゆがんでいないか確かめなければいけません。 もし培養された人間の細胞のシングルセルシーケンシングを扱っているのであれば、薬剤が作用したために死滅したがん細胞と、インターンが誤って培養した細胞を脱水させてしまったために死滅したがん細胞を混同していないことを確認する必要があります。

著者らは、理想的には、人間が対応可能でない時も、これらの変化に反応できるシステム (例えばログをつける、調節を自ら止める、決定閾値を変更する、技術者や修復可能な人に警告する) が必要だと言っています。

f:id:sh111h:20200705190927p:plain 今は笑えるが、このようなミスは、思った以上に危険にさらされているのかもしれません。

(訳注: DNA採取で用いる綿棒が汚染されていたことで、複数の事件で同一のDNAが検出され、架空の犯人ハイルブロンの怪人として騒がれた事件のこと。 上記画像の記事リンク。)

Best practice #21 入力データの背後にある仮説を確かめよう

Part8 奇妙なメタセクション

技術的負債論文の最後から2番目の章は他の領域について言及しています。 著者らは以前、技術的負債の種類として抽象化の失敗について言及してました、そしてどうも、論文の最初の7章の中に技術的負債の種類に収められなかったことにまで及んでいるようです。

サニティーチェック (Sanity Checks)

データのサニティーチェック(訳注: プログラムのソースコードの整合性・正当性を検査すること)をすることは非常に重要です。 新しいモデルを学習しているとき、モデルが少なくともデータのカテゴリーの一つの種類に過学習する可能性があることを確かめたいでしょう。 もし学習が収束しないなら、ハイパーパラメーターを調整する前に、データはランダムなノイズでないか確認した方がいいかもしれません。 著者らはこのように具体的ではなかったですが、言及するのに良いテストだと考えました。

Best practice #22 モデルが過学習する可能性があることを確認し、データの全てがノイズであったり、無信号でないことを確かめよう

再現性 (Reproducibility)

再現性。研究チームにいるあなた方の多くがこれに遭遇すると思います。 seedが設定されていないのコードや、故障中のノートブック、パッケージバージョンなしのレポジトリをみたことがあるでしょう。 技術的負債論文以降、何人かが、再現性チェックリストをつくっています。 ここに、4ヶ月前 hacker newsが特集したかなり良いリストがあります。

f:id:sh111h:20200705190945p:plain これはとても素晴らしいチェックリストの一つで、私も使っており、共に働くチームにも使うよう勧めています。

(訳注: このチェックリストのpdfのリンクはここ)

Best practice #23 研究のコードを公開するときは、再現性に関するチェックリストを使おう

f:id:sh111h:20200705191004p:plain アカデミアからのコードを再現したことのある、産業側の人間なら、私の言っている意味がわかると思います。

プロセス管理 (Process management)

今まで議論してきた技術的負債の種類の多くは単一の機械学習モデルについて言及してきましたが、プロセス管理負債はおなじ時間にたくさんのモデルを実行した時に起こります、 1つの遅れたモデルが終わるのを待っている間に、すべてを止めようとは考えないでしょう。 システムレベルの臭いを無視しないのが重要で、また、モデルの実行時間をチェックすることが極めて重要です。 技術的負債論文が書かれて以降、機械学習エンジニアリングは少なくても大まかなシステム設計について考えて改良されてきています。

f:id:sh111h:20200705191020p:plain ボトルネックの同定に使われるチャートの一例

Best practice #24 機械学習モデルのために実行時間を確認・比較する習慣をつけよう

文化的負債 (Cultural debt)

文化的負債はとても厄介な種類の負債です。 著者らは、研究とエンジニアリングの間には時として格差があり、異種混合チームでは負債の返却行為は促しやすいと指摘しています。

個人的には、最後のこの部分は好きではありません。 私は、エンジニアディレクターとリサーチディレクターの両方に、最終的に報告する個人がいるチームをたくさん目撃しました。 エンジニアたちに、負債の修正に必要な変更のための権限のない異なる2つの部門にレポーティングさせることは技術的負債の解決策ではありません。 一部のエンジニアが、この技術的負債の矛先になるという意味では、これは解決策です。 そのようなエンジニアはNo Authority Gauntlet Syndrome (NAGS) (訳注: 権力なき挑戦症候群) になり、燃え尽き、最も同情的なマネージャーがバーニングマンに出ている間に、そのエンジニアが達成すべき作業を目標として持っていたマネージャーによって解雇されてしまうのです。 もし異種混合が助けになるなら、チームの 全体 に渡ることが必要です。

(訳注: バーニングマン は8~9月にアメリカで開催される大規模なイベントのこと)

それに加え、著者らは、チームや企業文化を語る時に、多くの人と同じ間違いをしていると思います。 特に、文化と価値観をごちゃまぜにしています。 企業やチームにとって向上心のある規則を列挙し、それを文化と呼ぶのは簡単です。 実行するのにMBAを持つ必要はないですが、それは実際の文化というより価値観です。 文化とは、二つの重みのある価値観のどちらかを選ぶよう要求した状況で、最終的に人々が実行することです。

これはUberが大きな問題に陥った原因です。 競争力と誠実さの両方が企業の価値観の一部でしたが、最終的には、文化は競争力がなによりも強調されることを要求しました、 例え人事部が絶対的に嫌なやつを会社に留めるために法律を破ることを意味しても。

(訳注: Uberの話はおそらく Reflecting on one very, very strange year at Uber で述べられている話)

f:id:sh111h:20200705191037p:plain 悪循環

技術的負債についてのこのイシューは似たような状況でも起こります。 どれぐらい“メインテイナブル(保守可能な)”コードが必要かについて話すのは簡単です。 しかし、誰もが締め切りで追われ、ドキュメントの執筆がJIRAボードの優先度の中で下がっていくなら、最善の努力をしていても負債は積み重なっていきます。

f:id:sh111h:20200705191051p:plain 技術的負債の様々な理由

(訳注: 「無鉄砲/慎重」と「意図的/不注意」の2つの基準を使った技術的負債の四象限)

Best practice #25 (それがどのような形であっても)技術的負債に対処するために、定期的に、交渉の余地のない時間を確保しておこう

Part9 技術的負債のリトマス試験

「負債」の一部は単なるメタファーだということを思い出してください。 どれだけ著者らがより厳格に見せようとしても、それにすぎません。

多くの負債と違って、機械学習の技術的負債は測るのが難しいものです。 与えられた時間であなたのチームがどれぐらいはやく動くかはどれぐらい負債をもっているかの指標にはなりません (新卒のプロダクトマネージャーが主張しているようにみえるにもかかわらず)。 指標にかかわらず、著者らは自身に問うための5つの質問を提案しています(ここではわかりやすく言い換えています)

  • 最大のデータ資源を実行する、任意のNeurIPS論文からアルゴリズムを取得するのにどれぐらいかかるか?
  • どのデータ依存性がコードの中で最も(もしくは全く)関わっているか?
  • システムの一部を変更した場合、結果の変化をどれぐらい予測できるか?
  • MLモデルの改良システムはゼロサムかpositive-sumか?
  • ドキュメントはあるか?新人のための立ち上げプロセスではとっかかりになるものがあるか?

もちろん、2015年以降、他の記事や論文がより正確なスコアリングメカニズムを考えようとしています (scoring rubicsのように)。 ツールのいくつかは、不正確であっても、一眼でわかるスコアリングメカニズムを作成可能で、技術的負債を追跡する助けになります。

また、技術的負債のいくつかの解決策になると称賛されている解釈可能なMLツールは多くの進歩を遂げています。 それを心に留めたうえで、改めて “Interpretable Machine Learning” by Christoph Molnar (オンラインで利用可能) をおすすめします。

The 25 Best Practices in one place

これまでに言及したベストプラクティスを以下にまとめます。 これよりたくさんあるでしょうが、技術的負債の解消のツールはパレートの法則に従います: 技術的な負債の救済策の20%は、あなたの問題の80%を修正することができます。

  1. 解釈可能・説明可能なツールを使おう
  2. 可能であれば、説明可能な種類のモデルを使おう
  3. 常に、順番に下流モデルを再学習しよう
  4. アクセス鍵、ディレクトリの権限、サービス水準合意をセットアップしよう
  5. データのバージョン管理ツールを使おう
  6. 使っていないファイル、膨大な関係する特徴量を削除し、因果関係推論ツールを使おう
  7. データの依存関係を追跡する、無数にあるDevOpsツールのいくつかを使おう
  8. モデルの背後にある独立性の仮定を確認しよう (セキュリティーエンジニアの近くで働こう)
  9. 定期的なコードレビューを使おう (そして・またはコードチェックツールを使おう)
  10. 汎用的な目的の依存関係は特定のAPIにリパッケージしよう
  11. トップダウンの再設計・再実装によってパイプラインジャングルを取り除こう
  12. 定期的な点検を定めて、コードを取り除くための基準をつくろう、もしくはディレクトリかビジネスの物から遠い場所にコードをおこう
  13. 時間と共に強固になる抽象化を最新の状態にしておこう
  14. TypingDeimal のようなパッケージを使って、すべてのオブジェクトに対して 'float32' を使うのはやめよう
  15. 進行中のものを同じディレクトリに置かない。片付けるか、きれいにしよう。
  16. エンドポイントが説明されていることを確認し、言語間で似たような抽象度を持つフレームワークを使用しよう
  17. ファイルパスやハイパーパラメーター、レイヤーの種類、レイヤーの順番や他の設定が一つの場所から設定できるか確かめよう
  18. モデルの現実世界でのパフォーマンスと、決定境界を常に監視しよう
  19. 予測ラベルの分布が、観測ラベルの分布と似ているか確認しよう
  20. 機械学習システムによってもたらされる意思決定に制限を設けよう
  21. 入力データの背後にある仮説を確かめよう
  22. モデルが過学習する可能性があることを確認し、データの全てがノイズであったり、無信号でないことを確かめよう
  23. 研究のコードを公開するときは、再現性に関するチェックリストを使おう
  24. 機械学習モデルのために実行時間を確認・比較する習慣をつけよう
  25. (それがどのような形であっても)技術的負債に対処するために、定期的に、交渉の余地のない時間を確保しておこう

おわりに・参考資料

今回翻訳する上で、ブログの元ネタとなる 技術的負債論文 と同じ著者で、技術的負債論文 が公開される1年前のNeurIPS2014で公開された論文 (Machine Learning:The High-Interest Credit Card of Technical Debt)について、日本語で解説している以下の情報を参考にさせていただきました。

ちなみに、日本語の情報としては「High-Interest Credit Card~」のほうが豊富ですが、前編で紹介した、機械学習の技術的負債を象徴する図 (下記) は技術的負債論文が初出です。

f:id:sh111h:20200622074908p:plain

改めて、翻訳の許可をしてくれた原著者のMatthew McAteer氏に感謝します。

仕事ではじめる機械学習

仕事ではじめる機械学習