比治山日記

比治山スカイウォーカーです

FLAMLで学習済みのモデルを保存する・読み込む方法

FLAMLでAutoMLしてモデルを作成したあと、そのモデルを保存して読み込む方法が分からず、調べたのでメモ。以下の2種類がありそう。

1. Pickle等でAutoMLのオブジェクトとしてそのまま保存する
2. 実際に使用された学習モデルのパッケージの重みとして保存する

学習部分
from flaml import AutoML
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
from sklearn.metrics import mean_squared_error

automl = AutoML()
automl_settings = {
    "time_budget": 10,  # in seconds
    "metric": 'r2',
    "task": 'regression',
    "log_file_name": "california.log",
}
X, y = fetch_california_housing(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)

automl.fit(X_train=X_train, y_train=y_train,**automl_settings)
print(automl.model.estimator)
Pickle等でAutoMLのオブジェクトとしてそのまま保存する

AutoMLのオブジェクトで読み出す場合にこちらを使用する。

import pickle
with open('automl.pkl', 'wb') as f:
    pickle.dump(automl, f, pickle.HIGHEST_PROTOCOL)

automl_pkl = pickle.load(open('automl.pkl', 'rb'))
y_pred = automl_pkl.predict(X_test)
print(mean_squared_error(y_pred, y_test, squared=False))
実際に使用された学習モデルのパッケージの重みとして保存する

推論時にはAutoMLを使用せずに、ベストな学習モデルのネイティブなパッケージを使用して推論したい場合にこちらを使用する。実際に何の学習モデルが選択されたかを、予め知る必要がある。

from catboost import CatBoostRegressor

automl.model.estimator.save_model("best_model.txt")

catboost = CatBoostRegressor()
catboost.load_model("best_model.txt")
y_pred = catboost.predict(X_test)
print(mean_squared_error(y_pred, y_test, squared=False))

KedroのModular Pipelineについて

本記事はMLOps Advent Calendar 2022の12/18の記事です。 (投稿遅れてしまい、申し訳ありません🙇‍♂。ご指摘・コメント等あればぜひお願いします。)

概要

  • Kedroでパイプラインを構成する場合に、モジュール化したパイプラインを定義することができる。
  • モジュールパイプラインとして定義することでそのパイプラインを他のプロジェクトにも移植しやすくなる。
  • 簡単な例で確かめる。

Modular Pipelineについて

Kedroで機械学習プロジェクトを実装していくと、プロジェクトが大きくなるに連れて、以下のような課題やニーズが発生するのではないでしょうか?

  • 一つのパイプラインがどんどん大きくなり、全体の見通しが悪くなり、テストも書きにくくなる
  • 同じような一連の処理を複数の場所で行っていて無駄が多くなる
  • パイプラインの中の特定の処理に関しては他のKedroを使ったプロジェクトになるべくそのまま利用したい

Kedroではパイプラインをモジュール化した状態で作成することで、パイプラインの肥大化を避け、再利用性を高める事が可能です。この記事ではModular Pipelineの作成方法と使用例を示したいと思います。

Modular Pipelineの作成方法

以下のコマンドで、パイプラインを作成します。

kedro pipeline create <pipeline_name>

コマンドを実行すると、プロジェクトトップディレクトリ直下に以下のboiler plateが作成されます。

  • src/<project_name>/pipelines/<pipeline_name>
    • パイプラインとパイプラインに含むノードを作成するディレクト
    • pipeline.py, nodes.pyが自動作成される
  • src/tests/pipelines/<pipeline_name>
    • パイプライン用のテストコードを格納するためのディレクト
  • conf/base/parameters/<pipeline_name>.yml
    • パイプライン用のパラメータのコンフィグファイル

作成したModular Pipelineの移植

モジュール化されているため、他のプロジェクトに簡単に移行することができますが、公式のドキュメントによれば他のKedroプロジェクトに移植するにあたっては以下の制約があります。

  • モジュールパイプラインはメイン側のパイプライン内のモジュールに依存できない
  • catalog.yml は移植先のプロジェクトで更新が必要
  • パラメータのコンフィグファイルについて移植先のプロジェクトの conf/以下に再配置が必要

Irisデータセットでの例

Starterにあるpandas-iris で動作を確認します。今回はpd.DataFrameのデータを  \mu=0, {\sigma}^2=1 に標準化するs処理をModular Pipelineとして追加してみます。

まずpandas-irisをスターターにしてプロジェクトを作成します。

kedro new --starter=pandas-iris

続いてプロジェクト内にあるsrc/requirements.txt`に記載された依存パッケージをインストールします。

pip install -r src/requirements.txt

ここでModular Pipelineを作成します。例としてmy_pipelineという名前で作成してみます。

kedro pipeline create my_pipeline

これでsrc/iris/pipelines/my_pipelineが作成されるので、my_pipelineの中のnodes.pypipeline.pyに処理を書いていきます。

# iris/pipelines/my_pipeline/nodes.py
import pandas as pd

def standardize(train_data: pd.DataFrame, test_data: pd.DataFrame) -> pd.DataFrame:
    def calc(data: pd.DataFrame) -> pd.DataFrame:
        return (data - data.mean()) / (data.std() + 1e-9)

    return calc(train_data), calc(test_data)
# iris/pipelines/my_pipeline/pipeline.py
from kedro.pipeline import Pipeline, node, pipeline
from .nodes import standardize


def create_pipeline(**kwargs) -> Pipeline:
    return Pipeline([
        node(
            func=standardize, 
            inputs=["input_train", "input_test"], 
            outputs=["standardized_train", "standardized_test"], 
            name="standardize_node"),
    ])

この状態でkedro vizしてパイプラインの構成を可視化すると以下のようになります。 (なおこのままだとモジュール側のパイプラインの入力となる"input_train"、"input_test"が宣言されていないため、catalog.ymlに"example_iris_data"と同じCSVを読み込むように追記しています。)

図から分かる通り、標準化を行うモジュールパイプラインは定義できていますが、メイン側のパイプラインとは独立して存在し、メインの処理に組み込まれていません。 この標準化のモジュールパイプラインの処理をX_trainX_testに適用したいと思います。そうするには、メイン側のパイプラインにつなぎ込む必要があり、メイン側のパイプラインを書き換えることで実現できます。

# iris/pipeline.py
from kedro.pipeline import Pipeline, node, pipeline
from .nodes import make_predictions, report_accuracy, split_data

def create_pipeline(**kwargs) -> Pipeline:
    return pipeline(
        [
            # node(
            #     func=split_data,
            #     inputs=["example_iris_data", "parameters"],
            #     outputs=["X_train", "X_test", "y_train", "y_test"],
            #     name="split",
            # ),
            # node(
            #     func=make_predictions,
            #     inputs=["X_train", "X_test", "y_train"],
            #     outputs="y_pred",
            #     name="make_predictions",
            # ),
            node(
                func=split_data,
                inputs=["example_iris_data", "parameters"],
                outputs=["input_train", "input_test", "y_train", "y_test"],
                name="split",
            ),
            node(
                func=make_predictions,
                inputs=["standardized_train", "standardized_test", "y_train"],
                outputs="y_pred",
                name="make_predictions",
            ),
            node(
                func=report_accuracy,
                inputs=["y_pred", "y_test"],
                outputs=None,
                name="report_accuracy",
            ),
        ]
    )

定義したモジュールパイプラインにデータを渡すメイン側のノードの出力名と、反対にモジュールパイプラインからデータを受け取るメイン側のノードの入力名を、モジュールパイプラインに合わせて変更しています。

これでモジュールパイプラインをメイン側のパイプラインにつなぎ込む事ができました。
再びkedro vizすると以下のようになり、無事定義したモジュールパイプラインがメイン側のパイプラインに接続していることがわかります。

これで、ノードを追加して一つのパイプラインとしてワークフローを定義するのではなく、モジュール化したパイプライン自体を定義して既存のパイプラインに結合することができました。モジュール化したパイプラインはそのまま他のプロジェクトに持っていくことができるので便利です。

この記事で書かなかったこと

この記事では時間の都合で以下の点について触れていません。後で追記できる範囲でしたいと思います。

  • モジュールパイプラインをメイン側のパイプラインに繋ぐ場合に、メイン側のノードの入出力を変えないままパイプして繋ぐ方法
  • モジュールパイプラインを他のプロジェクトに移植するためにmicro-packagingする方法

参考

公式のドキュメント

kedro.readthedocs.io

線画抽出の既存実装 - sketchKerasについて

概要

  • sketchKerasを調べた
  • Pytorchで再実装した

sketchKeras

アニメ絵から線画抽出する既存手法を調べていると,sketchKerasというレポジトリがありました.

github.com

sketchKerasの実装はありますが,解説や論文はなかったので公式実装を調べて, Pytorchで実装し直しました.

モデル構造

sketchKerasは単純なU-netベースのネットワークのようです.公式実装を読み込んでモデルをplot_modelで図にしたものとmodel.summary()して出力した結果を以下に示します.

f:id:exv:20200827011533p:plain:h600
f:id:exv:20200827011221p:plain:h600

再実装・テスト

これがわかれば他のフレームワークで書き直すのは難しくないので,自分でもPytorchで書き直してみました.重みもPytorch用に移植しました.

github.com

以下に実行結果を3例示します.左が元画像,真ん中が3x3 Sobelフィルタをかけた画像,右がsketchKerasの出力結果です.

f:id:exv:20200826204049j:plain
(Original image from https://safebooru.org/index.php?page=post&s=view&id=1234461)
f:id:exv:20200826204109j:plain
(Original image from https://www.metmuseum.org/art/collection/search/45434?searchField=All&sortBy=Relevance&ft=Katsushika+Hokusai&offset=0&rpp=20&pos=1)
f:id:exv:20200826204123g:plain
(Original image from https://safebooru.org/index.php?page=post&s=view&id=3066528)

微分フィルタの結果で認められるようなグラデーションの小さい不連続に起因するエッジが sketchKerasでは抑えられているように見えます.またフォントのエッジがきれいに出ているようにも見えます.

AdaINを実装して試す

Image style transferに興味を持ったのでArbitrary Style Transfer in Real-time with Adaptive Instance Normalizationを実装して試してみました.

実装 github.com

論文

Arbitrary Style Transfer in Real-time with Adaptive Instance Normalization (ICCV, 2019) https://arxiv.org/abs/1703.06868

NNベースのImage style transferで著名なGatysらの手法[1]はスタイル転送したい画像ペアごとに学習が必要なのがネックでした.この論文ではスタイルの特徴をコンテンツ画像とミックスするためのAdaptive Instance Nomalization(AdaIN)レイヤを導入し,一度の学習で任意のコンテンツ-スタイル変換を実現するアーキテクチャを提案しています.

AdaIN

AdaINはInstance Normalizationの拡張したものという位置づけです. コンテンツとスタイルの特徴量を入力とし,コンテンツの特徴の平均と分散がスタイルの特徴が持つ平均と分散のスケールに合うよう線形変換する処理です.このとき特徴のチャネルごとに変換をかけます. コンテンツ画像をx,スタイル画像をyとするとき,AdaINは次のような演算になります.

 \mathrm{AdaIN}(x, y) = \sigma(y) \  \left(\frac{x-\mu(x)}{\sigma(x)}\right) + \mu(y)

Pytorchで実装すると以下のようになるでしょうか.

class AdaptiveInstanceNorm2d(nn.Module):
    def __init__(self):
        super(AdaptiveInstanceNorm2d, self).__init__()
        self.eps = 1e-5

    def forward(self, x, y):
        mean_x, mean_y = torch.mean(x, dim=(2, 3), keepdim=True), torch.mean(y, dim=(2, 3), keepdim=True)
        std_x, std_y = torch.std(x, dim=(2, 3), keepdim=True) + self.eps, torch.std(y, dim=(2, 3), keepdim=True) + self.eps
        return std_y * (x - mean_x) / std_x + mean_y

AdaINがスタイル転送に有効な理由としては,例えばスタイル画像が明確なテクスチャの変化を持つときにはスタイル特徴も強い活性を示すはずなので,これによる平均,分散などの統計量の変化をコンテンツ特徴に反映すれば,スタイルに順応しやすくかつコンテンツの持つ幾何的な情報を保持した出力ができるからだとしています.

実験

コンテンツ,スタイルともに5000枚ずつ用意し,40エポックだけ学習しました.オプティマイザはデフォルトのAdamです.

結果は以下. f:id:exv:20200624000710p:plain

これ以外にも他の写真も貼り付けようと思っていたのですがなぜか横長の画像が貼り付けられませんでした.ただ他のはあまりうまく行っていません.まだ実装に間違いがあるかも.

参考文献

[1] L. A. Gatys, et al., "Image Style Transfer Using Convolutional Neural Networks," In CVPR, 2016

ImageMagickで画像の高さと幅のどちらか小さい方を基準にリサイズする

深層学習で使う画像データセットを予め表題のようにリサイズしておきたいことがあり,ImageMagickでのやり方を調べたのでメモしておきます.

やりたいことは例えばWxH=300x200の画像があるときに,この画像の高さか幅どちらか小さいほうが500になるようにアスペクト比を保ったままリサイズしたい,というものです. この場合ですと高さのほうが小さいので,高さが500となるようアスペクト比を保って750x500にリサイズ,といった感じです.

やりかたはかんたんで,convertコマンドの-resizeオプションの引数で縦横のサイズを指定するときに-resize WxH^のように^をサイズの後ろにつければOKです.

上げた例をコマンドとして書くと以下のようになります.

convert -resize 500x500^ input.jpg output.jpg

参考URL

www.imagemagick.org

IEEEの会議論文のlatexテンプレートファイルで著者がずれるときの対処

IEEE conference proceedingsのlatex用のテンプレートファイルを使っているときに、authorが複数段に渡ると2段目以降がauthorの欄が中央揃えにならずに、ずれてしまうことがあったので対処法についてメモ。

スタイルファイル👇 www.ieee.org

対処

以下のページを参考に段を変えるとき用の\andコマンドを定義して使えばいい。 tex.stackexchange.com