W3Cが推進しているWeb Machine Learning (WebML)という取り組みについて少し調べてみました。今回は解説記事というわけではなく個人用のメモに近いので正確性についてはあまり自信がありませんが。。
Web Machine Learning (WebML)とは
W3C Web Machine Learning Working Group standardizes Web APIs for in-device machine learning inference working together with the W3C ecosystem using well-received Community Group incubations as its seeds. Its sister Web Machine Learning Community Group incubates new proposals and is the place where new ideas are discussed and explored before formal standardization.
W3Cワーキンググループでは、デバイス上で動作する機械学習(DNN)アプリケーションやフレームワーク等が利用するAPI仕様の標準化に取り組んでいます。ターゲットとしては今のところWebブラウザ上での推論処理周りを対象としているようです。”Web API”と表記されているのでWeb系エンジニアは勘違いしやすいかもしれませんが、通信プロトコルの仕様を標準化するというわけではありません。
クライアントデバイス上で処理するメリットとしては以下の4つを謳っており、多少冗長に見えますがどれも一般化している内容かとは思います。
- Low Latency: クライアントのブラウザ上で処理するため低レイテンシで実行できる
- Privacy Preserving: 外部にデータ送信する必要がないためユーザーデータが守られる
- High Availability: ネットワーク接続に依存しないためオフライン実行できる
- Low Cost: サーバ環境が不要なので低コスト
WebNN API
WebML自体は特定の技術や製品を指すものではなく、前述の通りW3CではAPI仕様の標準化を推進しており、そのAPIをWebNN APIと呼んでいます。
Accelerating deep neural networks on the web
A new web standard that allows web apps and frameworks to accelerate deep neural networks with on-device hardware such as GPUs, CPUs, or purpose-built AI accelerators.
名前にNNとあるのでNeural Network用のAPIのようです。以下の図のようにフレームワークと各種OSネイティブのバックエンドやハードウェアとの橋渡しとなるAPIという位置付けとなっており、WebNNによる共通のインタフェースを使うことでクロスプラットフォームな実行環境を作ることができます。
TensorFlow.jsやONNX.jsなどの既存のフレームワークもWebNN対応するらしいですね。ちなみにONNX.jsについては後継プロダクトについて別記事で紹介していますので参考までに。
注意点
2021/11現在、WebNN APIの仕様はEditor’s Draftとなっているようでした。まだ草案段階なので仕様はどんどん変わるかもしれませんし、W3C勧告まではしばらく時間がかかりそうです。
この記事の投稿時点でDraftの日付は30 September 2021となっていました。これからも頻繁に更新されるかもしれないので細かい仕様自体の紹介は控えておきます。この後サンプルコードを載せますが、一部のAPIはEditor’s Draftにある仕様と異なっているものがありましたので留意ください。
使い方
前述の通り、現在は仕様がEditor’s Draft段階なので細かい使い方を紹介しても意味無いかもしれませんが、一応草案仕様を少しだけ見てみます。公式のHello World的なサンプルコードは以下になります。
こちらは簡単な行列計算を計算グラフとして定義、実行するサンプルになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
const context = navigator.ml.createContext({powerPreference: 'low-power'}); // The following code builds a graph as: // constant1 ---+ // +--- Add ---> intermediateOutput1 ---+ // input1 ---+ | // +--- Mul---> output // constant2 ---+ | // +--- Add ---> intermediateOutput2 ---+ // input2 ---+ // Use tensors in 4 dimensions. const TENSOR_DIMS = [1, 2, 2, 2]; const TENSOR_SIZE = 8; const builder = new MLGraphBuilder(context); // Create OperandDescriptor object. const desc = {type: 'float32', dimensions: TENSOR_DIMS}; // constant1 is a constant operand with the value 0.5. const constantBuffer1 = new Float32Array(TENSOR_SIZE).fill(0.5); const constant1 = builder.constant(desc, constantBuffer1); // input1 is one of the input operands. Its value will be set before execution. const input1 = builder.input('input1', desc); // constant2 is another constant operand with the value 0.5. const constantBuffer2 = new Float32Array(TENSOR_SIZE).fill(0.5); const constant2 = builder.constant(desc, constantBuffer2); // input2 is another input operand. Its value will be set before execution. const input2 = builder.input('input2', desc); // intermediateOutput1 is the output of the first Add operation. const intermediateOutput1 = builder.add(constant1, input1); // intermediateOutput2 is the output of the second Add operation. const intermediateOutput2 = builder.add(constant2, input2); // output is the output operand of the Mul operation. const output = builder.mul(intermediateOutput1, intermediateOutput2); // Build graph. const graph = await builder.build({'output': output}); // Setup the input buffers with value 1. const inputBuffer1 = new Float32Array(TENSOR_SIZE).fill(1); const inputBuffer2 = new Float32Array(TENSOR_SIZE).fill(1); // Asynchronously execute the built model with the specified inputs. const inputs = { 'input1': {data: inputBuffer1}, 'input2': {data: inputBuffer2}, }; const outputs = await graph.compute(inputs); // Log the shape and computed result of the output operand. console.log('Output shape: ' + outputs.output.dimensions); // Output shape: 1,2,2,2 console.log('Output value: ' + outputs.output.data); // Output value: 2.25,2.25,2.25,2.25,2.25,2.25,2.25,2.25 |
WebNNには計算グラフを構築するためのAPIが一通り定義されているようです。計算グラフについてはTensorFlowやPyTorchのようなフレームワークを使っている人には馴染み深いとは思いますが、JavaScriptの場合は演算子のオーバーロードができないので見た目はスマートとは言い難いですね。とはいえWebNN APIには抽象度の高さがそれほど求められているわけではないのでこれくらいがいいのかなとは思います。
このようにadd
とかmul
とか各種演算用の関数を使って計算グラフを構築していきます。その際にMLGraphBuilder
というものを使いますが、デザインパターンでいうところのBuilderパターンを踏襲しているわけではないようです。計算グラフのノードが都度吐き出される形になっているので、ユーザー側が各ノードの部品を正しく管理してグラフを組み立てていく必要があります。
また、MLGraphBuilder
に最初に渡されるcontextによって環境を指定する形になっています。このサンプルだと、{powerPreference: 'low-power'}
という指定がありますが、specとしては以下のMLPowerPreference
というものが定義されています。
1 2 3 4 5 6 7 8 |
enum MLPowerPreference { // Let the user agent select the most suitable behavior. "default", // Prioritizes execution speed over power consumption. "high-performance", // Prioritizes power consumption over other considerations such as execution speed. "low-power" }; |
消費電力を意識するのは大切ですね。エッジコンピューティング環境も想定されているのでしょう。また、上記サンプルには指定はありませんがデバイス環境を指定するMLDevicePreference
オプションもあります。
1 2 3 4 5 |
enum MLDevicePreference { "default", "gpu", "cpu" }; |
こちらはCPUかGPUを指定するだけなのでわかりやすいです。
データ型については、DNN系フレームワークだと基本的に提供されているTensorのようなものが別途用意されているわけではなく、TypedArray(Float32Arrayなど)をそのまま使うようです。以前紹介したONNX Runtime for WebでもデータバッファとしてTypedArrayを使っていたので特に違和感はないです。
個人的にはWebNN APIのレイヤーは抽象度は低くて良いのでハードウェアの性能を引き出しやすい薄いインタフェースだけ用意されていれば十分かなと思います。活性化関数などのAPI定義もあるようですが、そういうのは上位のフレームワーク側でやればいいのではないかという感想を持ちました。
、とはいえWebNNという名前が付いていますからニューラルネットワークに関するオペレーションは全てカバーする思想なのでしょう。例えばシグモイド関数を作る場合は以下みたいにMLGraphBuilder
を入れ子にして酷使すれば作れますが、さすがにこんなコーディングは生理的に嫌でしょうし、そもそもBuilderのインタフェース自体の改善も必要そうです。
1 |
builder.div(builder.constant(1),builder.add(builder.exp(builder.neg(x)), builder.constant(1))); |
他にも仕様をいろいろ妄想してみましたが、やはりAPI仕様の標準化作業というのは大変というか面倒くさそうだなと思いました。繰り返しになりますが、上記サンプルコードの一部のAPIはEditor’s Draftにある仕様と異なっているものがありましたので留意ください。
おわりに
WebMLについて表面的なところはいろいろ調べてはみたんですが、現場からの注目度としてはまだそれほど高くないという感じのようです(国内に至っては紹介記事がほぼ無い?)。というのもアプリケーション開発者はWebML(WebNN API)を直接利用する機会はほぼ無さそうというのと、仕様自体がまだ草案段階なので深堀りする動機はまだ薄いのでしょう。WebMLの構想自体は期待できる取り組みだと思うので動向は引き続き追っていきたいと思います。