ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
PyTorchモデルをCore MLに変換する
PyTorchモデルをCore MLへ変換して、オンデバイスの機械学習をAppで活用する方法を理解しましょう。 PyTorchの機械学習のフレームワークは、複素ニューラルネットワークの作成やトレーニングを手助けします。モデルを構築した後にCore MLへと変換し、完全にオンデバイスで動かすことができ、CPUやGPU、ニューラルエンジンを最大限に活用できます。 Core ML ToolsパッケージがTorchScriptモデルを直接、変換させる方法を理解し、カスタマイズ操作と発生する変換エラーの扱いについて学びましょう。 Core MLコンバータについて更に学びたい場合は、WWDC20の "Get models on device using Core ML Converters"をご覧になるのをお勧めします。
リソース
- Composite Operations in Core ML Tools
- Core ML Tools PyTorch Conversion Documentation
- Introduction to TorchScript
関連ビデオ
WWDC23
WWDC22
WWDC21
WWDC20
-
ダウンロード
どうも 私はスティーブ Appleのエンジニアです 私はポール 同じくエンジニアです この動画では Core MLの新機能の1つを詳しく説明します PyTorchモデルのCore MLへの変換です
WWDC 2020では Core MLコンバータの見直しを発表しました 変換プロセスの多くの要素が改善されています
ディープラーニング業界でよく使われる ライブラリのサポートを拡張しました コンバータアーキテクチャを再設計することで 新しいインメモリ表現を活用し― ユーザー体験を向上させました またAPIを統一したため― どのモデルソースからでも変換するのに 1コールで済みます まだ見ていない方は― 下記の動画をぜひご覧ください 新しいコンバータアーキテクチャの詳細を 紹介しています
今回の動画はモデル変換が主題です まずはPyTorchのディープラーニングフレームワーク内で 構築されたモデルから始めましょう
例えばあなたが PyTorchを使ってモデルを訓練している― MLエンジニアだとしましょう あるいはApp開発者で オンラインで最適のPyTorchモデルを見つけて Appに導入したいとしましょう
問題は― PyTorchモデルを どうやってCore MLモデルに変換するかです 古いCore MLコンバータでは― モデルを一度 ONNXに エクスポートする必要がありました 使ったことがある人は― 制約を感じたかもしれません ONNXはオープン標準なので― 新機能の追加は遅くなりがちです その上 PyTorchのようなMLフレームワークは― 新機能をONNXにエクスポートするサポートの提供に 時間がかかります そのため古いコンバータでは― PyTorchモデルをONNXにエクスポートできず Core MLに変換できなかったこともあるでしょう
新しいCreate MLコンバータでは― この余計な障壁を取り除きました
この動画では― 新しいPyTorchモデル変換方法を詳しく説明します 実際の変換例を見せながら― PyTorchモデルをCore MLに変換する方法を いくつか紹介します そして最後に 途中トラブルが発生した時のために― いくつか役立つヒントを紹介します
それでは 新しい変換プロセスを見ていきましょう
変換したいPyTorchモデルから出発し― PyTorchのJITモジュールを使い TorchScriptという表現に変換します ちなみにJITとは Just In Timeを略した言葉です 次にTorchScriptモデルから― 新しいCore MLコンバータを使って MLモデルを生成しAppにドロップします 動画の後半で― TorchScript変換プロセスを詳しく説明します でも今は新しいコンバータの仕組みを 見てみましょう
コンバータはPythonで書かれているので― 起動するのに数行のコードしか必要ありません 用意するのはモデル TorchScriptオブジェクトか― ディスクに保存したパス それとモデルのインプット情報です モデルのアウトプット情報を含めてもいいですが そこは任意です
コンバータは TorchScriptグラフの演算を繰り返し― 1つずつ Core MLで同等のものに変換していきます 時には1つのTorchScript演算が― 複数のCore ML演算に変換されることもあります また時には グラフ最適化パスが未知のパターンを検出し― 複数演算を1つに融合させます
時にはモデルが コンバータが理解できないカスタム演算を― 含んでいることがあります でもコンバータは伸張性があるので― 新しい演算のために定義を加えるのは簡単です 多くの場合― 既存の演算の組み合わせとして表現できます これをComposite opと呼びます それで足りなければ― カスタムSwift実装を書き それをターゲットに変換することもできます この動画でその詳細は説明しません 例や解説はぜひ 我々のオンラインリソースをご覧ください
変換プロセスの全体像が分かったところで 最初に戻り― PyTorchモデルからTorchScriptモデルを得る方法を 詳しく見ましょう 方法は2つあります 1つは“トレーシング” もう1つは“スクリプティング”です
まずはモデルのトレースとは何か?
トレーシングは PyTorchの JITモジュールのトレースメソッドを使って行います こちらのコードです PyTorchモデルをインプット例と一緒に渡すと― TorchScript表現でモデルを返してくれます このコールは何をするのか? アクティブなトレーシングは インプット例をモデルのフォワードパスに走らせ― モデルの層を進む中インプットが起動する演算を キャプチャします これらの演算の集まりが― やがてモデルのTorchScript表現になります トレースに使うインプット例を選ぶ際― 一番いいのは 通常使用時にモデルが使うデータと似たものです 例えば検証データや― Appがモデルに提示する時と同じ方法で キャプチャされたデータがいいでしょう ランダムなデータでも可能です その際はインプット値の範囲と― テンソルの形が モデルが求めるものと一致するようにしましょう 理解を深めるため例を見ていきましょう 同僚のポールを紹介します PyTorchからCore MLへ分割モデルを変換するプロセスを 解説してもらいます ありがとう スティーブ ここに分割モデルがあり― オンデバイスで走らせるとします ちなみに分割モデルの役割は― 画像に対して ピクセルごとに クラス確率スコアを割り当てることです ではどうやって モデルをオンデバイスで走らせるか? モデルをCore MLモデルに変換します まずPyTorchモデルをトレースし― JITトレーシングモジュールを使って TorchScriptフォームにします それから 新しいCore MLコンバータを使って― TorchScriptモデルをCore MLモデルに変換します 最後に Core MLモデルが― Xcodeにいかにシームレスに統合するか 見せつけます ではコードを見ていきましょう
このJupyter Notebookで― スライドで言及したPyTorch分割モデルを Core MLモデルに変換します コードを試したい方は― 動画関連のコードスニペットをご覧ください まずデモ用にいくつか依存をインポートします
次にtorchvisionから ResNet-101分割モデルをロードします インプット例もです この場合は犬と猫の画像です
PyTorchモデルはPIL画像オブジェクトではなく テンソルを取り込みます そこで画像を transforms.ToTensorでテンソルに変換します モデルは テンソルで バッチサイズを示す追加次元を求めているので それも追加します
先述のように― Core MLモデルコンバータがTorchScriptモデルを 受け取ります Torch.JIモジュールのトレースメソッドを使い PyTorchモデルをTorchScriptモデルに変換します
おや? トレーシングで例外がスローされました 例外メソッドにあるように― テンソルかテンソルのタプルしか トレースした関数からアウトプットできません これはPyTorchのJITモジュールの制約です ここで問題なのは モデルが辞書を返していることです 解決のため モデルをPyTorchモジュールでラップし― アウトプット辞書からテンソル値のみを 抽出させます
ここでPyTorchのモジュールクラスから継承する クラスラッパーを宣言します
上にあるように ResNet-101を含むモデル属性を定義します
フォワードメソッドでは― “out”と名付けたキーで返された辞書を索引化し テンソルアウトプットのみを返します
これで辞書でなくテンソルを返すようになり― トレースも成功します
ここで新しいCore MLコンバータを活用します まずインプットとそのプリプロセスを定義します
インプットをImageTypeとして定義し プリプロセスで 画像をImageNetの統計で正規化し 値を0から1の間まで下げます このプリプロセスがResNet-101が求めるものです
次にCore ML Toolsの変換メソッドを呼び出し TorchScriptモデルとインプット定義を渡します
変換後 モデルのメタデータを設定し― Xcodeなど他のプログラムに理解できるようにします モデルの型を分割に設定し― モデルの順にクラスを列挙します
変換したモデルは機能するか? Xcodeを通じて モデルのアウトプットを簡単に視覚化できます まずモデルを保存します
次にFinderで保存したモデルをクリックすると Xcodeで開かれます ここでインプットの形と型を含む メタデータを見ることができます
モデルのアウトプットを視覚化するには― プレビュータブを開き 犬と猫のサンプル画像をドラッグ&ドロップします
うまくペットたちが分割されているようです
ResNet-101はトレースできましたが― トレースできないモデルもあります それらのモデルの変換方法を説明するため― スティーブに戻します
ありがとう ポール トレーシングを使った変換の仕組みは分かりました でもTorchScriptを得る方法はもう1つあります スクリプティングです スクリプティングは― PyTorchモデルを 直接TorchScript演算にコンパイルする方法です トレーシングでは データが流れる中モデルをキャプチャします モデルのスクリプティングも簡単です PyTorchのJITモジュールの スクリプトメソッドを起動し― モデルを提供するだけです ここまででTorchScript表現にする方法を 2つ紹介しましたが どう使い分ければいいのか? モデルが制御フローを含む場合は スクリプティングを使うべきです 例を見て理由を説明しましょう このモデルには枝やループがあり― スクリプティングならすべてキャプチャできます モデルを直接コンパイルするからです もしトレースすれば― インプットの通り道しか得られません それではモデル全体をキャプチャできません
モデルをスクリプトする時― どうするのがベストか? 可能な限りモデルをトレースし― 必要な個所だけスクリプトしましょう 基本的にトレーシングのほうが スクリプティングよりもシンプルな表現を― 生成できるからです コードを見ながら説明しましょう この例では― ループの中で モデルが固定の回数コードを走らせます そこでループ本体を隔離し― 簡単にトレースできるような形にしました その後モデル全体にスクリプティングを適用します 要するに必要な制御フローだけに限定して スクリプティングし― 残りはすべてトレーシングします すでにTorchScriptに変換済みのコードは スキップするので― この方法が可能になっています
次はスクリプティングを使う具体例を 見てみましょう ポールに戻します 言語モデルの変換を解説してもらいます どうも 文章完成モデルを― Core MLモデルに変換したいとしましょう 文章完成というのは― モデルを使って 文章の断片から次の言葉を予測するタスクです 演算ステップはどうなっているでしょうか? まずは文章の断片を エンコーダーと呼ばれるものに通して― モデルが理解できる表現に翻訳します この場合は整数トークンのシーケンスです 次にトークンのシーケンスをモデルに渡し― シーケンス内の次のトークンを予測させます そのまま断片的な文章をモデルに与え続け― モデルが文末トークンを予測するまで 末尾に新しいトークンを加えていきます つまり文章が完成するまでです 完成文のトークンが得られたので― デコーダーに渡し トークンを言葉に変換し直させます このダイアグラムの途中部分 トークンのリスト作成を― Core MLモデルに変換します エンコーダーとデコーダーは別々に扱われます 理解を深めるため疑似コードを見てみましょう モデルのコアは次のトークン予測です これにはHugging FaceのGPT2モデルを使います 予測子はトークンのリストをインプットとして 受け取り― 次のトークン予測をくれます 次に予測子に制御フローをラップし― 文末トークンが出るまで続けます
ループ内で 予測されたトークンを現行リストに加え― 以降それを 予測子のインプットとして使用し続けます 予測子が文末トークンを返したら― 完成文をデコーディングのために返します このプロセスをコードで見るため― Jupyter Notebookを使いましょう このノートブックで文章の断片から文章を完成させる 言語モデルを構築します インポートを片付けましょう
これがモデルのコードです
モデルはtorch.Moduleから継承し― 以下の属性を含みます 文末トークン 次のトークン予測モデル そして文頭を示すデフォルトトークン
フォワードメソッドには先述のように― トークンのリストから次を予測するループ本体を 書いています ループは文末トークンが生成されるまで続きます その後 完成文を返します
次のトークン予測子はGPT2で― ループ本体内にあります
先ほどのように モデル全体のスクリプティングと隔離して ループ本体をトレースします JITトレーサーを 次のトークン予測子にのみ走らせます トークンのリストをインプットとして受け取るので トレーシングには ランダムなトークンのリストを渡します
トレーサーが警告を発しました このトレースは 他のインプットに一般化されないと出ています 注意すべきは この警告がCoreMLでなく PyTorchのJITトレーサーから来ている点です 後ほどトラブルシューティングセクションで 説明しますが 問題ないので 今は警告を無視します
ループ本体の大部分をトレースしたので 文章完成モデルを例示化して JITスクリプターを適用しCoreMLへの変換に備えます
次にTorchScriptモデルを― 分割デモの時のようにCoreMLモデルに変換します
では文章を完成させられるか見てみましょう 文章の断片を作成します 今回は“The Manhattan Bridge is”です これをGPT2のエンコーダーで走らせ― 断片をエンコードします そしてトークンのリストを Torchテンソルに変換します
次にCoreMLモデルからインプットをまとめ― モデルを走らせ GPT2のデコーダーでアウトプットをデコードします
いいですね CoreMLモデルが文章を完成させました マンハッタン橋に関する発言を生成したようです
CoreMLフォーマットにするため モデルをトレースしスクリプトする中で― つまずくこともあるかもしれません スティーブから助言をもらいましょう
ではここでPyTorchモデルをCore MLに 変換する際に経験した問題を振り返り― トラブルシューティングのヒントや ベストプラクティスを紹介します
先ほどの分割デモで― トレーシング中にエラーに遭遇しました 理由はモデルが辞書を返したからです JITトレーサーはテンソルか テンソルのタプルしか扱えません
デモではモデルの周りにラッパーを作成し― 元のアウトプットを解凍して解決しました この例ではモデルが辞書を返しました だからここで 推定結果を表す辞書キーにアクセスし― そのテンソルを返しています もちろんこのやり方は 辞書から複数アイテムにアクセスして返すか 他のタイプのコンテナを解凍する時も使えます
言語モデルのデモでは― トレーサーの警告で 他のインプットに 一般化できない可能性が示されました 問題となるコードも表示されていました
では実際 何が起きているのか? モデルソースコードを見てみると― モデルが 別のテンソルのサイズに基づいて テンソルをスライスしているのが分かります テンソルのサイズは生のPython値になります つまりPyTorchテンソルではありません トレーサーは 生のPython値で行われる演算操作が― トレースできないと警告しています ただこのケースでは― トレーサーが少し警告を焦りすぎたようです 実際は問題ありません
生のPython値で操作されるコードを トレースする際は 経験則から言って― 組み込みのPython演算しか トレーサーは正しくキャプチャできません
いくつか例があります 正しくトレースできるかどうか― これらを見て確認しましょう 最初の例はデモとよく似通っており― 正しくトレースできます 組み込み演算である足し算が適用されているからです
2つ目の例も正しくトレースできます こちらも組み込みの余剰演算子を使っています
しかし3つ目は正しくトレースできません JITトレーサーが 辞書関数math.sqrtが何をするか理解できず トレースされたグラフは テンソルサイズと平方根を演算するのではなく― 一定値を記録します
しかしmath.sqrtを Python組み込みのべき乗演算に置き換えれば― 正しくトレースできます 次はスクリプティングの失敗例を見ましょう このモデルは空のリストから始めて― 継続的に固定の一連の整数を追加します これは有用なモデルではなく― 失敗例のためなので ご注意を このモデルをスクリプトすると― 型が一致しないというランタイムエラーが出ます JITスクリプターは モデルをTorchScriptにするのに型情報が必要で― 文脈からオブジェクト型を推測します でもそれができない時もあります オブジェクトの型が分からない場合― オブジェクトはテンソルだと見なします この例では 実際は整数のリストとして構築されているのに― テンソルのリストと見なしています ではどうすればいいか? 意味のある変数の初期化を含ませるか 型アノテーションを使うかです モデルを調整し 両方の例を示してみました
そして最後にもう1つ トレーシング前に 必ずモデルを評価モードにしてください これにより― すべての層が 訓練よりも推測向けの設定になります 大半の層では関係ありません ただ例として モデルにドロップアウト層がある場合― 評価モードにすれば無効になります コンバータは 無効になっている演算に遭遇すると― パススルー演算として扱います
この動画でいろいろお話ししましたが― 関連リンクからさらに詳しい情報が得られます 例えばCoreMLコンバータのドキュメント カスタムオペレータ変換の情報 TorchScriptの詳細な例もたくさんあります
PyTorchモデル変換の 最高水準のサポートをご提供できるのが楽しみです 新しいCoreMLコンバータが― PyTorchモデルに役立つことを願っています オンデバイスのモデル実行を最適化し― 変換を簡単にするため最大限のサポートを提供します ご視聴ありがとうございました
-
-
7:22 - Converting a Segmentation Model
# # Converting a Segmentation Model via CoreML # ### Imports import urllib import torch import torch.nn as nn import torchvision import json from torchvision import transforms import coremltools as ct from PIL import Image # ### Load Sample Model and Image # Load model model = torch.hub.load('pytorch/vision:v0.6.0', 'deeplabv3_resnet101', pretrained=True).eval() # Load sample image input_image = Image.open("dog_and_cat.jpg") display(input_image) # ### Image Preprocessing to_tensor = transforms.ToTensor() input_tensor = to_tensor(input_image) input_batch = input_tensor.unsqueeze(0) # ### Trace the Model with PyTorch # First attempt at tracing trace = torch.jit.trace(model, input_batch) # ### Wrap the Model to Allow Tracing class WrappedDeeplabv3Resnet101(nn.Module): def __init__(self): super(WrappedDeeplabv3Resnet101, self).__init__() self.model = torch.hub.load('pytorch/vision:v0.6.0', 'deeplabv3_resnet101', pretrained=True).eval() def forward(self, x): res = self.model(x) x = res["out"] return x # ### Trace the Wrapped Model traceable_model = WrappedDeeplabv3Resnet101().eval() trace = torch.jit.trace(traceable_model, input_batch) # ### Convert to Core ML # Define input _input = ct.ImageType( name="input_1", shape=input_batch.shape, bias=[-0.485/0.229,-0.456/0.224,-0.406/0.225], scale= 1./(255*0.226) ) # Convert model mlmodel = ct.convert( trace, inputs=[_input], ) # ### Set the Model Metadata labels_json = {"labels": ["background", "aeroplane", "bicycle", "bird", "board", "bottle", "bus", "car", "cat", "chair", "cow", "diningTable", "dog", "horse", "motorbike", "person", "pottedPlant", "sheep", "sofa", "train", "tvOrMonitor"]} mlmodel.type = 'imageSegmenter' mlmodel.user_defined_metadata['com.apple.coreml.model.preview.params'] = json.dumps(labels_json) # ### Save the Model for Visualization mlmodel.save("SegmentationModel.mlmodel")
-
16:32 - Converting a Language Model
# # Converting a Language Model via Core ML # ### Imports import torch import numpy as np from transformers import GPT2LMHeadModel, GPT2Tokenizer import coremltools as ct # ### Model class FinishMySentence(torch.nn.Module): def __init__(self, model=None, eos=198): super(FinishMySentence, self).__init__() self.eos = torch.tensor([eos]) self.next_token_predictor = model self.default_token = torch.tensor([0]) def forward(self, x): sentence = x token = self.default_token while token != self.eos: predictions, _ = self.next_token_predictor(sentence) token = torch.argmax(predictions[-1, :], dim=0, keepdim=True) sentence = torch.cat((sentence, token), 0) return sentence # ### Initialize the Token Predictor token_predictor = GPT2LMHeadModel.from_pretrained("gpt2", torchscript=True).eval() # ### Trace the Token Predictor # random_tokens = torch.randint(10000, (5,)) traced_token_predictor = torch.jit.trace(token_predictor, random_tokens) # ### Script the Outer Loop # model = FinishMySentence(model=traced_token_predictor) scripted_model = torch.jit.script(model) # ### Convert to Core ML # mlmodel = ct.convert( scripted_model, # Range for the sequence dimension to be between [1, 64] inputs=[ct.TensorType(name="context", shape=(ct.RangeDim(1, 64),), dtype=np.int32)], ) # ### Encode the Sentence Fragment # sentence_fragment = "The Manhattan bridge is" tokenizer = GPT2Tokenizer.from_pretrained("gpt2") context = torch.tensor(tokenizer.encode(sentence_fragment)) # ### Run the Model coreml_inputs = {"context": context.to(torch.int32).numpy()} prediction_dict = mlmodel.predict(coreml_inputs) generated_tensor = prediction_dict["sentence:2"] generated_text = tokenizer.decode(generated_tensor) print("Fragment: {}".format(sentence_fragment)) print("Completed: {}".format(generated_text))
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。