SyntaxHighlighter

2022年5月5日木曜日

SQLAlchemy を用いた Snowflake へのアクセス

SQLAlchemy を用いた Snowflake へのアクセス

今回は、Snowflake SQLAlchemy ツールキットおよびPythonコネクターの使用にあるような、 SQLAlchemy を用いた Snowflake データベースへのアクセスについてまとめようと思います。

また、 Snowflake の JSON データへアクセスするのを容易にするために開発したライブラリである snowflake-sqlalchemy-json についても触れようと思います。

Snowflake と半構造化データ

Snowflake は、いわゆるクラウドでビッグデータを処理するためのデータプラットフォームです。
細かく最適化を考えなくても、それなりによいパフォーマンスでデータの保存/読み出しができ、 SQL でのアクセスもサポートされています。

2022年5月時点では、30日の無料トライアルを受け付けており、どんな雰囲気かは無料で試すことができます。

Snowflake は、一般的なデータ型をサポートしており、他のデータベースを使っていた人であれば、違和感なく使えるように思います。
なお、ビッグデータ向けのデータベースでよくあるように、 Unique 制約などは強制されないので、そこには注意が必要です。

少し特徴的な型として、半構造化データ型というものがあります。
これは、 VARIANT などの型をカラムに指定することで、カラム内に JSON, Avro などの半構造化データを入れておき、 SQL から直接アクセスできるようにするものです。

例えば SELECT column_name:key1 FROM table; のように記述することで、 table テーブルの column_name カラムにある {"key1": "value1", "key2": "value2"} の中の "value1" にアクセスすることができます。
WHERE の条件部分で指定することも可能で、通常のカラムと同様にアクセスすることができます。

SQLAlchemy による SQL の生成

話は変わりますが、 Python のライブラリで SQLAlchemy というものがあります。
このライブラリは ORM として使うこともできますが、生の SQL をラップし Python から扱いやすくするという、 core と呼ばれる使い方もあります。

これは、以下のように Python のコードとして SQL を組み立てられるものです。

SQLite の場合、上記の query は、以下のような SQL に変換されます。

このように SQLAlchemy を用いると、 Python のコードとして SQL を作ることができます。

他にも JOIN や CTE (Common Table Expression) を用いた例を書いておくと以下のようになります。
この例では、テーブルの定義に declarative_base を使っています。この場合は、 Table を使う場合と異なり、 .c の部分が不要になり、補完も効きやすくなるので、書きやすいかもしれません。
なお、例自体には特に意味はありません。

Python で記述することで、 CTE 部分を変数として保持でき、それなりに分かりやすく記述できているように思います。

上記の query からは、下記の SQL が生成されます。

snowflake-sqlalchemy

Snowflake でも、上述した SQLAlchemy を使うことができます。

公式のページで使い方がまとめられているので、こちらのページを参考にするのがよいと思います。

なお、 Snowflake の場合、テーブルを指定する際には DATABASE.SCHEMA.TABLE のように、データベース名とスキーマ名でテーブル名を修飾することで、一意にテーブルを指定することができます。
SQLAlchemy で、このようにデータベース名やスキーマ名も含めて修飾したテーブル名を指定する場合、以下のように quoted_name を用いてテーブル名を記述することで、実現できます。

snowflake-sqlalchemy-json

上述したように、 Snowflake では VARIANT 型のカラムに対しては、 JSON 形式などの値に対して SQL 内から所定の書式でアクセスすることができます。

snowflake-sqlalchemy では、その辺りの処理は 2022年5月時点では実装されていないようだったので、 snowflake-sqlalchemy-json でいくつかを実装してみました。

現時点では読み込みのみしかサポートしておらず、機能も限定的ですが、それなりに使えそうに思うので、このライブラリを使った例を挙げておきます。
ここでは、 Snowflake のアカウント作成時に例として提供されていた SNOWFLAKE_SAMPLE_DATA.WEATHER.DAILY_14_TOTAL テーブルに含まれているデータを読み込む際の例について挙げます。データのサンプルは、末尾に挙げておきます。

VARIANT 型を SQLAlchemy の JSON にマッピングしアクセス

上記の query で、 V カラムに入っているデータの中の city キーで取得できるオブジェクト内の要素にアクセスすることができます。
基本的に、 Python の dict と同様のアクセス方法になり、直感的に書けているのが分かると思います。

lateralflatten を用いたアクセス

Snowflake の VARIANT 型へのアクセスで重要な概念である lateral 結合を用いたアクセスについては、下記のように記述できます。
なお、 lateral 結合については結局 LATERAL とは何者なのか?の記事が分かりやすいように思います。

flatten は、あくまで通常の関数なので、 SQLAlchemy で関数を呼び出す際の形式である func.flatten(xxx) として記載します。
また、 lateral は SQLAlchemy がサポートしているので、公式のチュートリアルの通りに呼び出します。
lateral() で返されるテーブルの value カラムにアクセスすると、 flatten で展開された要素にアクセスすることができます。

なお、上記は FROM 句に FROM DAILY_14_TOTAL, LATERAL FLATTEN(xxx) のように lateral を記述する方法ですが、この書き方では Warning が出るかもしれません。
その場合、以下のように、 JOIN 句として独立させて書くこともできます。
私はこちらの方が分かりやすいように思います。

用いたデータの形式

最後に、 DAILY_14_TOTAL テーブルの V カラムに入っている JSON データの形式を挙げておきます。

以上のようにすることで、 Snowflake の VARIANT 型へのアクセスを SQLAlchemy を用いて記載することができるようになります。

2022年3月7日月曜日

Azure DevOps 向けの拡張機能 PublishMarkdownReports

Azure DevOps 向けの拡張機能 PublishMarkdownReports

Microsoft の統合開発環境である Azure DevOps 向けに、拡張機能 Publish Markdown Reports を作成したので、その紹介です。

拡張機能自体の作り方は、 Web 拡張機能の開発のページに詳しい方法が載っているので、こちらを参照してみてください。

やりたかったこと

ソースコードから自動生成した Markdown の API 仕様書を、 Azure DevOps の CI/CD である Azure Pipelines のページで閲覧できるようにしたい、というのがやりたかったことでした。
Azure Pipeline から Azure DevOps Wiki を更新する方法もあるのですが、こちらの記事のように Wiki 用の Git Repository を自動で更新する方法では、「同じぐらいのタイミングで CI/CD が走ったとき」や「CI/CD タスクが Re-queue され、古いコミットに対するタスクが後で実行されたとき」に、古い内容で上書きしてしまわないような仕組みが必要になります。

今回は、そういったことを考慮しなくてすむように、 Publish Markdown Reports では Azure Pipelines のビルド結果の画面に任意の Markdown ドキュメントを表示できるようにすることにしました。
このようにすることで、別の Wiki 用の Git Repository は不要になり、 CI/CD タスクの実行順序などを考えなくてもよくなります。
ただし、ビルド結果は設定によっては古い結果が削除されるので、正式なドキュメントとして残す場合は別途対応が必要です。

完成した表示画面は下記になります。
screenshot

Publish Markdown Reports について

まずは、 Publish Markdown Reports の概要について説明しようと思います。
なお、ソースコード全体は GitHub に置いてあります。

表示用 Markdown ファイルのアップロード

この拡張機能では、 Markdown として表示するファイルをビルドタスクの中でアップロードします。
下記のような YAML の記述を Azure Pipelines に加えることで、指定されたディレクトリのファイルをアップロードします。

上記の場合、ソースコードのディレクトリの下の markdown ディレクトリの下にあるファイルすべてをアップロードします。
また、既定のページとして main_page.md を指定しています。このファイルは contentPath で指定したディレクトリからの相対パスで指定します。
headingId は、 Markdown の # 見出し のような章のタイトル (<h1> などのタグで囲まれた部分) に対して、どのような ID を付与するかを指定します。
現時点では完全ではないのですが、 Python の Markdown の HTML 変換ライブラリである Python-Markdown や、 Doxygen の結果を Markdown に変換する Doxybook2 の ID のつけ方に対応しています。
なお、 Pydoc-Markdown などのツールでは、 <a> タグでリンク先の場所が定義されているので、 headingId の指定は不要です。

内部的には、これらのファイルはビルド中に Attachment として、ビルド結果に対する添付ファイルの形でアップロードされています。
このファイルは API でしか取得できないもので、ビルド生成物として登録する Artifact とは異なるものです。

Markdown ファイルの表示

この拡張機能では、アップロードされた Markdown ファイルを Marked で HTML 変換して表示しています。

ただし、 Azure DevOps の拡張機能の仕組み上、少し面倒ですが、下記のように表示を実現しています。

  1. 拡張機能に含まれる frame.html をビルド結果のタブ内に表示
  2. frame.html から Azure DevOps の SDK を用いて Attachment として保存されている Markdown を JavaScript で取得
  3. 取得した Markdown ファイルを Marked ライブラリを用いて HTML に変換
    その際に、リンクを処理する際には、 frame.html?page=/link-to-page.md のように、 frame.html に対して page パラメータとして Markdown のファイルのパスを指定するようにします。
    また、画像を表示するために、画像に対するファイル名に対して Attachment を取得し、得られたデータを Blob 変換した上で、 URL.createObjectURL で生成した URL に差し替えます。
  4. 生成された HTML をタブに表示

高速化

上述した方法では、表示されるまでに少しタイムラグが発生します。これは、 Azure DevOps の SDK の性質上、ロード時の初期化に 1~2 秒かかることが多いためです。
これを解消するために、少し高速化を行います。

上述した frame.html をラップする main.html を用意し、 frame.htmlmain.html の中で iframe として表示するようにします。

その上で、それぞれのファイルで以下のような処理を行うことで、 Azure DevOps の SDK の初期化処理がページ遷移のたびに動作しないようにしています。

main.html
  • Azure DevOps の SDK である VSS.SDK.js をロードし、初期化する。
  • onmessage ハンドラで frame.html からのメッセージを待ち受け、メッセージに応じて Attachment をダウンロードし、 frame.html に送る。
frame.html
  • 表示すべき Markdown ファイルを main.html に要望する。
  • main.html から送り返されたデータを処理し、 Marked ライブラリで HTML に変換する。

このようにすることで、画面が切り替わる際は frame.html だけがロードされ、 main.html 側は破棄されないため、 VSS.SDK.js による初期化処理は動作しないことになります。
そのため、 Markdown ファイルの切り替えによる画面遷移時には、 Markdown ファイルや画像のロードのみで動作が完結し、高速な動作が可能となります。

このようにして、それなりに実用に耐えうる拡張機能ができました。

2022年2月7日月曜日

Bash 向けの tcsh 同様な history-search

Bash 向けの tcsh 同様な history-search

Bash のシェルスクリプトの勉強がてら、 tcsh 同様な history-search-backward のようなものを実装してみました。

Bash の history-search-backward

Bash には、元々 history-search-backward というコマンドが用意されています。
しかし、このコマンドは tcsh のコマンドとは異なり、カーソル位置が行の末尾に移動しません。
私は Solaris の tcsh で Unix 系の環境に初めて触れたこともあり、 tcsh のようにカーソルが末尾に移動するという挙動の方が馴染みがあるので、そのようなコマンドを作ってみようと思いました。

なお、あくまで私が Bash のシェルスクリプトの練習をするというのも重要な目的の一つなので、例えば zsh をなど他の解は、今回は気にしていません。

ポイント

今回は、 Bash のシェルスクリプトの勉強を兼ねていたので、以下を守ることにしました。

  • Bash 以外での動作は気にしない。
  • Bash の組み込みコマンド以外は使わない。
  • Windows 版の Git に含まれている Bash でも動作させる。

コードの概要

コードは GitHub に置いてあります。

おおまかな流れとしては、以下のようになっています。

  1. fc コマンドでコマンド履歴の一覧を取ってくる。
  2. 一行ずつ read コマンドで読み込む。
  3. 特定の文字列で始まるコマンドを抜き出し、それを READLINE_LINE, READLINE_POINT に設定することで、ターミナルの文字列を置換する。

coproc の使い方

fc コマンドを実行し、その結果を read コマンドで処理する際に、 coproc コマンドを使うことにしました。
Bash の while でパイプから読み込んだテキストを一行ずつ処理をする場合、 Qiita の記事にあるように、 Process Substitution などを使えばよいようですが、 Windows 版の Git に含まれている Bash などでは実行することができません。
そのため、今回はこの方法は諦め、 coproc を使って読み込むことにしました。

概要

coproc に関連する部分をシンプルにして抜き出すと以下のようになっています。

coprocfc を実行するプロセスを作り、その出力を whileread で一行ずつ処理しています。

ちなみに、よく言われるように下記では while 内がサブシェルで実行されるので、 HISTORY_ARRAY に期待した通りに値が保存されません。

また、上述したように、下記のように Process Substitution を使う方法では、私の手元の Windows の bash.exe では動作しませんでした。

ジョブ情報の出力の抑制

そのまま使ってしまうと、[1] 1859[1]+ Done のような、ジョブの情報が出力されてしまうので、それらを抑制する必要がありました。

開始時は以下のようにします。
coproc の標準エラー出力を捨てればよいのですが、波括弧で囲ってリダイレクトすると指定しやすいです。

終了時の出力は、 coproc 呼び出し後に以下の disown を呼んでおくことで、抑制することができます。

coproc からの読み込み

coproc で返されるファイルディスクリプタから read コマンドでテキストを読み込むには、 -u オプションを使うことで実現できます。
この辺りは Web 上で情報がたくさん見つかるので省略します。

一方で、ただ coproc FC_FD { fc -lnr -${HISTSIZE} ; } として実行した coproc を read -u ${FC_FD[0]} のように読むだけでは、場合によってはうまくいきません。

Stack Overflow に書かれているように、 Bash の coproc では、 coproc で実行したプロセスが先に完了してしまうと、プロセスからの出力をすべて処理する前に出力が読めなくなります。

そのため、 coproc のプロセスの出力を読み終わるまで、プロセスが終了しないように工夫する必要があります。
また、 coproc のプロセスの出力をバッファリングせずに出力しきるために、以下のようにします。

このように、 coproc の中で read -s で入力を待ち、 exec 1>&- で出力のファイルディスクリプタを閉じることで出力を確実に処理させ、 coproc での出力をすべて処理できるようにします。

というような感じで、 Bash の組み込みコマンドのみで tcsh のような history-search-backward を実装することができました。
私はこれまでにほとんどシェルスクリプトを書く機会がなかったのですが、 Bash 4 以降は coproc や連想配列もあり、それなりにいろいろなことができるようです。

2022年1月28日金曜日

API による Microsoft Teams のステータスメッセージの取得/設定

API による Microsoft Teams のステータスメッセージの取得/設定

Microsoft Teams では、ステータスメッセージを設定することができます。

今回は、これをスクリプト経由で設定する方法について記載しようと思います。

Graph API でできる範囲

2022年1月現在、 Teams の Microsoft Graph REST API では、 ユーザーのプレゼンス情報の取得/設定ができます。
この API では、 availability, activity の取得/設定がサポートされており、 Python スクリプトなどで自由に設定することができます。

しかし、 beta 版も含め、ステータスメッセージの取得/設定は現状サポートされていないようです。

Teams アプリ、Teams Web アプリと同様の方法でのアクセス

そこで、公開されていない API を用いてアクセスすることになります。

結論としては、下記の情報で OAuth2 のアクセストークンを取得し、後述する URL に POST, PUT することで、ステータスメッセージの取得/設定することができます。
これは、原則として、 Teams アプリや Teams Web アプリが行っている方法と同様と思われます。

OAuth2 情報

OAuth2 のアクセストークンは、下記の情報で取得を行います。

Client ID
1fec8e78-bce4-4aaf-ab1b-5451cc387264
Client Secret
Public Client なので不要
Redirect URI
https://login.microsoftonline.com/common/oauth2/nativeclient
Scope
https://api.spaces.skype.com//.default

なお、上記の Client ID は、 Teams のデスクトップアプリの ID になっています。
また、 Client Secret, Redirect URI は、いずれも Public Client の設定になっています。
Scope の .default の前にスラッシュが二つありますが、 Microsoft のページによると、これで正しいようです。
(ただ、スラッシュが一つでも問題なく動いてはいるようでした。)

ステータスメッセージの取得

ステータスメッセージの取得は、下記の URL に JSON データを POST することで実現できます。

送信するデータの中の "mri" に設定している GUID は、 Graph API で取得できるユーザー情報の中の id のことです。
8:orgidPublicDirectoryParticipantPrefix と呼ばれるもののようで、固定値でよいようです。

URL
https://presence.teams.microsoft.com/v1/presence/getpresence/
HTTP Method
POST
Body として送信する JSON データ

上記を送ると、指定したユーザー分のステータスメッセージを含むユーザー情報が返ってきます。

ステータスメッセージの設定

自分のステータスメッセージの設定は、下記の URL に JSON データを PUT することで実現できます。

メッセージと有効期限を指定し、設定することができます。

URL
https://presence.teams.microsoft.com/v1/me/publishnote
HTTP Method
PUT
Body として送信する JSON データ

なお、 "message" の末尾に <pinnednote></pinnednote> を追加すると、「他のユーザーが自分にメッセージを送るときに表示する」にチェックした状態になるようです。

Python での実装例

最後に、 Python を用いてステータスメッセージを設定する例を書いておこうと思います。

Windows 上で簡易的に OAuth2 を扱えるようにした msal-interactive-token-acquirer と、 HTTP アクセスで広く使われている requests を用いた場合、以下のようになります。

なお、これは個人の Microsoft アカウントではなく、組織の Microsoft アカウントで行う必要があります。
個人で試す場合は、 Microsoft 365 開発者プログラムなどを活用するとよいと思います。

2021年8月16日月曜日

PyTorch における Deformable Convolution の ONNX 変換

PyTorch における Deformable Convolution の ONNX 変換

PyTorch では、何年か前から Deformable Convolution が DeformConv2d としてサポートされていました。

しかし、 ONNX 変換はサポートされておらず、 DeformConv2d を含むネットワークを ONNX にしようとするとエラーになっていました。

今回は、 DeformConv2d を PyTorch で ONNX に変換できるようにするモジュール deform_conv2d_onnx_exporter を作ったので、その説明です。

が、諸事情により、一旦ライブラリの公開は取りやめました。すみません。

Deformable Convolution の説明自体はここでは触れません。

ONNX 変換に必要なこと

DeformConv2d を ONNX に変換できないのは、内部で使われている deform_conv2d 関数を ONNX に変換する方法が定義されていないためです。

最終的に呼ばれる torch.ops.torchvision.deform_conv2d は、 CPU, CUDA 向けなどに C++ で実装されています。
そのため、 ONNX に変換するためには専用の変換関数を用意してやる必要があるのですが、現時点では実装されていません。

似たようなものとしては、 torch.ops.torchvision.roi_pool があるのですが、こちらは torchvision/ops/_register_onnx_ops.py の中で、以下のように ONNX 変換用の関数が登録されています。

こちらにあるように、 Graph object g を用いて、 ONNX の Operator である MaxRoiPool を使って ONNX のグラフを構築していくことになります。

ONNX Operator

ONNX の Operator 一覧は GitHub にあります。

今回やる必要があるのは、ここに定義されている Operator の一覧の中から適切なものを選び、組み合わせることで、 deform_conv2d の挙動を構築することになります。

なお、 ONNX の Operator は通常の手続き的な書き方ができるわけではなく、基本的には Tensor に対する処理の組み合わせで書いていくことになります。

deform_conv2d の ONNX Operator での実装

やることは、 Deformable ConvNets v2: More Deformable, Better Results の論文に書かれた内容を愚直に実装することになります。
注意点としては、 deform_conv2d の引数として渡されてくる Tensor のデータの格納順序が一部不明瞭なところがあるので、それを実動作ベースで確認しながら進めることになります。

大まかな流れとしては、下記になります。

  1. 各 Convolution の対象となる画素の位置を求める。
  2. その画素の位置座標が、画像外を指さないように丸める。
  3. 位置座標は小数になっているので、 Bilinear 補完により、周囲 4画素の値から重み付け平均を取り、画素の値とする。
    なお、このとき、画素の値は GatherND Operator を用いて取得をする。
  4. 得られた画素の値を基に、必要に応じて mask をかけ、 Convolution を行う。
  5. 必要に応じて bias を足す。

「愚直に実装する」と書きましたが、実際にはここはかなり苦労しました。
上記の各処理を ONNX の Operator のみで記述していくのは、 deform_conv2d の内部実装ドキュメントの少なさも相俟って、なかなか大変なものがありました。

気を付けたこと

Transpose を多用しない

上記の流れを実装する際に、 (論文に沿った愚直な方法では) GatherND を使って画素値を集めてくることになるかと思います。
似たような Operator の GatherElements でも実現はできますが、入力画像のチャネル数が大きくなった場合に、 (おそらく) とても非効率になります。

一方で、現時点の GatherND Operator の仕様上、複数のチャネルにまたがってデータを一括して集めてくる場合、そのチャネルがテンソルのランクの末尾の方に位置している必要があります。
つまり、 Tensor[batch, height, width, channel] のように、座標として指定される height, width よりも末尾に channel が位置している必要があります。

そのため、 Transpose Operator (PyTorch の Tensor の permute) で軸の順序を入れかえることになりますが、この処理は状況によっては負荷がかかるようです。

当初は Transpose を多用するコードになっていたのですが、なぜか速度がまったく出ず、 ONNX の profiling をすることで、無駄な Transpose が遅い要因の一つであることが分かりました。
そのため、必要最小限な部分に絞って Transpose を呼ぶようにすることで、効率化を図りました。

ただ、この辺りは ONNX のランタイムの実装にも強く依存する話なので、環境が変わると結果も異なるかもしれません。
不要な Operator を呼ばないようにするのはよいと思いますが。

GatherND で範囲外の座標値にアクセスしない

当然ですが、 GatherND では範囲外の座標値にアクセスしてはいけません。

当初は、これを防ぐために、範囲外なのかどうかを Greater, Less Operator を使って判定した mask を作り、これを活用することで範囲外へのアクセスを防いでいました。

しかし、この方法ではそれなりに時間がかかることから、 Clip Operator を使った方法に変更しました。

これは、まず入力画像の周囲に必ず 0 クリアされた Padding 領域が入るようにしておくことで、 Clip された座標値が Padding 領域を指すようにしておき、 Bilinear 補完の計算に入らないようにすることで実現しました。

これはそれなりに効率化がされたようで、処理速度も速くなりました。

これらを気を付けることで、 (遅いことは遅いのですが) それなりに使える範囲での実行速度を実現することができました。

よさそうなら PyTorch 本体にも Pull Request を出してみようかと思います。

2020年8月3日月曜日

nginx と Flask を使った Git LFS サーバー

nginx と Flask を使った Git LFS サーバー

Git LFS サーバーを自前で用意する必要が生じたのですが、手頃なものが見つからなかったので、手作りで作ってみることにしました。
Git LFS サーバーの仕様自体はシンプルで、とりあえず動作させるぐらいであればすぐに作れたので、今後のために記録を残しておこうと思います。

また、今さらですが Docker にも慣れておきたかったので、 Docker のイメージにしてみました。本来は docker-compose を使うようなケースのような気もしますが、とりあえず単一の Docker コンテナにしてあります。

なお、作成したサーバーのソースコードなどは、 gitlfs_with_nginx として GitHub 上に置いてあります。

Git LFS サーバー

Git LFS は、 Git で大きめのバイナリファイルを管理するために GitHub が中心となって策定した Git の拡張機能です。

概要としては、以下のようになるかと思います。

  • あらかじめ指定したファイルを、 Git リポジトリではなく、別に指定した Git LFS サーバーに保存する。
    ファイルは、ファイル固有の oid (現状ではファイルの SHA256 値) と紐付けて、 Git LFS サーバー内で Key-Value ストアのように保存される。
  • 元の Git リポジトリには、実体となるファイルの oid などが書かれたポインターファイルのみを保存しておき、 checkout などの際に、適宜 Git LFS サーバーからファイルの実体をダウンロードしてくる。
  • これらのファイルの保存やダウンロードの動作は、 Git のフックなどで実現されており、ユーザーはほとんど意識することなく、通常の Git リポジトリで作業する感覚で作業することができる。

ちなみに、私は Git LFS を最初に使ったとき、「Git LFS サーバーで管理されているファイルだけ、 Subversion のようなタイミングでダウンロードされるんだな」と感じました。

それはともかくとして、 Git LFS サーバーは比較的シンプルな仕様の HTTP サーバーです。
Batch APIBasic transfer をサポートするだけであれば、(どこまで異常系を考えるかによりますが) それほど悩まずに実装することができると思います。

Flask と nginx による Git LFS サーバー

そこで、 Git LFS サーバーの理解を深めるのを兼ねて、 Python の Flask フレームワークを使って実装してみました。
また、 nginx をリバースプロキシとして使うことで、パフォーマンスを向上させています。
以下に概要のみ、記します。 これらの処理は lfs_server.py に書かれています。

batch エンドポイント (batch メソッド)

Batch API の仕様に従って、 download, uploadoperation 要求に対し、指定された oid のファイルの実体をダウンロードもしくはアップロードするための URL を返します。

このとき、ファイルの実体をダウンロード/アップロードする際の GET/PUT リクエスト時に送信してもらうヘッダを同時に返します。
このヘッダには認証用のデータなどを含めることができます。
今回の実装でも、リポジトリ名や認証期限などを blake2b でハッシュをとったものをヘッダに追加しています。

ダウンロード用エンドポイント (download_file メソッド)

ダウンロードは、基本的に nginx に処理を任せます。
Flask 側では認証処理をしたあと、 X-Accel-Redirect ヘッダを指定し、 nginx 側で静的なファイルを配信するように構成することで、 Flask 側でファイル配信しなくてすむようにしています。

具体的には、 X-Accel-Redirect ヘッダに /repos/repository_name/oid のようなパスを指定することで、 nginx から静的なファイルを配信できるようにしています。

アップロード用エンドポイント (upload_file メソッド)

アップロードも、基本的に nginx に処理を任せます。
nginx 側で client_body_temp_path, client_body_in_file_only などを使うことで、クライアント側から PUT されてきたファイルを、 nginx がファイルに保存するようにします。
そして、そのファイル名を Flask 側に渡すことで、 Flask 側では渡されたファイルを oid で一意に決まる所定の場所に移動させます。
Flask 側はファイルの移動を行うだけなので、処理の負荷が少なくてすみます。

Docker での実装

上記のような仕組みの Flask サーバーの実装や nginx の設定を行い、 Docker イメージとして構築してみました。

env.list としていくつかのパラメータは変更できるようになっており、社内利用ぐらいであれば、 Basic 認証を追加するぐらいで使えなくはないのではないかと思います。

2019年12月31日火曜日

Windows 上の Python で OpenVINO を動かす

Windows 上の Python で OpenVINO を動かす

今回は、 Windows 上の Python で、 Intel からリリースされている AI 推論エンジン OpenVINO を動作させます。
といっても、OpenVINO ツールキットそのもののインストールは不要です。
Python の pip ライブラリをインストールするだけで試すことができます。

Intel OpenVINO

OpenVINO は Intel によって開発されている、エッジ端末上で AI 推論を行うためのライブラリです。
あくまで推論に特化しており、学習は別のツールで行うことが前提です。
また、推論エンジンは Intel CPU, GPU, GNA, VPU, FPGA などに対して高度に最適化されています。

事前準備

Python のインストール

Windows 上で、 Python 3.5, 3.6, 3.7 のいずれかをインストールしておきます。
64bit 版をインストールしておく必要があります。
現時点では、 Python 3.8 には対応していません。

OpenVINO の pip ライブラリのインストール

元々の OpenVINO は、現時点では pip ライブラリになっていないようです
そのため、私が Windows 向けに非公式な pip ライブラリ (wheel パッケージ) を作っておきました。
今回はこのライブラリをインストールします。
なお、後に公式ライブラリが公開される場合にややこしくなるので、公式の PyPI には登録していません。

PyPI に登録されていないので、下記のようにインストールします。

なお、 openvino-python をインストールすると、 cv2 モジュールも使えるようになります。そのため、別途 opencv-python などをインストールする必要はありません。

また、 numpy も必要なのでインストールしておきます。

パスの設定

openvino-python を動作させる際に必要な DLL は、 pip によるインストールの際に所定のディレクトリにダウンロードされています。
venv 環境の場合、 venv/Library/bin の下にダウンロードされます。
そのため、このディレクトリを PATH の先頭に追加しておきます。

venv の場合、 venv/Scripts/activate.bat の末尾を以下のように変更しておくとよいと思います。

動作確認

上記のように事前準備ができ、パスが通った状態であれば、下記のスクリプトがエラーなく実行できるはずです。

これで、 IENetwork など、種々の OpenVINO のクラスを使うことができます。

次回は、実際に Pre-trained なモデルを使い、簡単な動作サンプルを作ってみようと思います。