ホーム » deeplearn.js

deeplearn.js」カテゴリーアーカイブ

deeplearn.js : サンプル : 補色を予想する

deeplearn.js : サンプル : 補色を予想する

作成 : (株)クラスキャット セールスインフォメーション
日時 : 08/25/2017

* 本ページは、github.io の deeplearn.js サイトの Example: Predicting Complementary Colors を翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、適宜、追加改変している場合もあります。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

このチュートリアルは補色を予想するモデルのコーディングを読者に体験してもらいます。このモデルのハイパーパラメータは完全には最適化されないかもしれませんが、モデルの構築は deeplearn.js の重要な概念を通り抜けるでしょう。実際に、より多くの層の追加は補色のより近い予想を生成するようです。私たちはハイパーパラメータの最適化に本質的な時間を使いませんでした – その目的に向かっては pull リクエストが望ましいでしょう。

訳注: 補色 (complementary color) とは、色相環で正反対に位置する関係の色の組合せ。相補的な色のことでもあります。 ( Wikipedia )

読者は既に Introduction とおそらくは Guide for non-ML Experts を読み終えているでしょう。このチュートリアルは TypeScript を使用します、JavaScript の知識で十分ですが。

このチュートリアルのためのすべてのコードは demos/complementary-color-predictions ディレクトリにあります。

Stack Overflow 上の Edd の回答 が示すように、補色を計算することはほんの少しのロジックを使います。小さな 順伝播ニューラルネットワーク がそのロジックをどのように上手く学習できるのかを見てみましょう。

(訳注: 以下の画像はデモの訓練開始直後のスナップショットです。)

(そして以下は 4,000 ステップほど訓練した後のスナップショットです。補色の予想が実際の補色に漸近しています。)

 

入力サンプルを作成する

最初に訓練データを生成します: RGB 空間でランダム色を生成します、

const rawInputs = new Array(exampleCount);
for (let i = 0; i < exampleCount; i++) {
  rawInputs[i] = [
    this.generateRandomChannelValue(), this.generateRandomChannelValue(),
    this.generateRandomChannelValue()
  ];
}

そしてそれから Edd の役に立つ解法を通してそれらの補色を計算します。

/**
 * This implementation of computing the complementary color came from an
 * answer by Edd https://stackoverflow.com/a/37657940
 */
computeComplementaryColor(rgbColor: number[]): number[] {
  let r = rgbColor[0];
  let g = rgbColor[1];
  let b = rgbColor[2];

  // Convert RGB to HSL
  // Adapted from answer by 0x000f http://stackoverflow.com/a/34946092/4939630
  r /= 255.0;
  g /= 255.0;
  b /= 255.0;
  const max = Math.max(r, g, b);
  const min = Math.min(r, g, b);
  let h = (max + min) / 2.0;
  let s = h;
  const l = h;

  if (max === min) {
    h = s = 0;  // achromatic
  } else {
    const d = max - min;
    s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));

    if (max === r && g >= b) {
      h = 1.0472 * (g - b) / d;
    } else if (max === r && g < b) {
      h = 1.0472 * (g - b) / d + 6.2832;
    } else if (max === g) {
      h = 1.0472 * (b - r) / d + 2.0944;
    } else if (max === b) {
      h = 1.0472 * (r - g) / d + 4.1888;
    }
  }

  h = h / 6.2832 * 360.0 + 0;

  // Shift hue to opposite side of wheel and convert to [0-1] value
  h += 180;
  if (h > 360) {
    h -= 360;
  }
  h /= 360;

  // Convert h s and l values into r g and b values
  // Adapted from answer by Mohsen http://stackoverflow.com/a/9493060/4939630
  if (s === 0) {
    r = g = b = l;  // achromatic
  } else {
    const hue2rgb = (p: number, q: number, t: number) => {
      if (t < 0) t += 1;
      if (t > 1) t -= 1;
      if (t < 1 / 6) return p + (q - p) * 6 * t;
      if (t < 1 / 2) return q;
      if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
      return p;
    };

    const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    const p = 2 * l - q;

    r = hue2rgb(p, q, h + 1 / 3);
    g = hue2rgb(p, q, h);
    b = hue2rgb(p, q, h - 1 / 3);
  }

  return [r, g, b].map(v => Math.round(v * 255));
}

各カラー・チャネルを 255 で除算して入力を正規化します。正規化はしばしば訓練プロセスを助けることができます。

normalizeColor(rgbColor: number[]): number[] {
  return rgbColor.map(v => v / 255);
}

各入力 (現在 0 と 1 の間の3つの値のリスト) を Array1D 内にストアします、deeplearn.js は GPU 上にデータを置く場所を構築します。inputArray と targetArray は両者とも Array1D のリストです。

const inputArray: Array1D[] =
    rawInputs.map(c => Array1D.new(this.normalizeColor(c)));
const targetArray: Array1D[] = rawInputs.map(
    c => Array1D.new(
        this.normalizeColor(this.computeComplementaryColor(c))));

その後で、ShuffledInputProvider をビルドします、これは入力データ (これは 2 リストから成ります) をシャッフルします。入力データをシャッフリングする間、ShuffledInputProvider は入力とターゲットの間の関係を維持します (従って両者の配列の要素は同じインデックスにシャッフルされます)。

const shuffledInputProviderBuilder =
    new InCPUMemoryShuffledInputProviderBuilder(
        [inputArray, targetArray]);
const [inputProvider, targetProvider] =
    shuffledInputProviderBuilder.getInputProviders();

provider を使用して、モデルにデータを渡す feed entries を作成します。

this.feedEntries = [
  {tensor: this.inputTensor, data: inputProvider},
  {tensor: this.targetTensor, data: targetProvider}
];

 

Graph のセットアップ

このパートはエキサイティングです、何故ならモデルを構築するからです。TensorFlow のように、deeplearn.js は graph-ベースの API です: モデルを実行するために session を使用する前に最初にモデルをデザインします。

Graph オブジェクトと 2 tensor を作成します: 一つは入力カラーのためで一つはターゲット・カラーのためです。ターゲット・カラーは訓練 (そして推論でない時) の間だけ生息して (= populated)、推論の間は、入力カラーが与えられるのみでターゲットを予想します。

上記により、tensor はモデルにデータを渡すために feed entries 内で使用されます。

const graph = new Graph();

// This tensor contains the input. In this case, it is a scalar.
this.inputTensor = graph.placeholder('input RGB value', [3]);

// This tensor contains the target.
this.targetTensor = graph.placeholder('output RGB value', [3]);

graph.layers.dense を使用して完全結合層を作成する関数をコーディングします。

private createFullyConnectedLayer(
    graph: Graph, inputLayer: Tensor, layerIndex: number,
    sizeOfThisLayer: number, includeRelu = true, includeBias = true) {
  return graph.layers.dense(
      'fully_connected_' + layerIndex, inputLayer, sizeOfThisLayer,
      includeRelu ? (x) => graph.relu(x) : undefined, includeBias);
}

その関数を使用して、64, 32, そして 16 ノードを持つ3つの完全結合層を作成します。

// Create 3 fully connected layers, each with half the number of nodes of
// the previous layer. The first one has 16 nodes.
let fullyConnectedLayer =
    this.createFullyConnectedLayer(graph, this.inputTensor, 0, 64);

// Create fully connected layer 1, which has 8 nodes.
fullyConnectedLayer =
    this.createFullyConnectedLayer(graph, fullyConnectedLayer, 1, 32);

// Create fully connected layer 2, which has 4 nodes.
fullyConnectedLayer =
    this.createFullyConnectedLayer(graph, fullyConnectedLayer, 2, 16);

正規化された予想された補色を出力する層を作成します。それは3つの出力を持ちます、各チャネルのために1つです。

this.predictionTensor =
    this.createFullyConnectedLayer(graph, fullyConnectedLayer, 3, 3);

損失関数 (mean squared) を指定する cost tensor も追加します。

this.costTensor =
    graph.meanSquaredCost(this.predictionTensor, this.targetTensor);

最後に、訓練を実行して推論するための session を作成します。

this.session = new Session(graph, this.math);

 

訓練して予想する

モデルを訓練するために、optimizer を (0.042 の初期学習率で) 構築します、

this.optimizer = new SGDOptimizer(this.initialLearningRate);

そしてカラーのバッチ上で訓練する関数を書きます。訓練の session の呼出しを math.scope コールバック内にラップすることに注意してください。math.scope の使用はここでは (そしてコードの他のパートで) 必須です、何故ならばそれらが必要でなくなればそれは deeplearn.js に (GPU 上のデータのような) リソースを刈り取る (= reap) ことを可能にするからです。

train1Batch メソッドが shouldFetchCost パラメータを受け取ることにも注意してください。これは (train1Batch を呼び出す) 外側のループに損失の値をあるステップだけ取得することを可能にします。GPU から損失の値を取得することは遅延を負います何故ならば GPU からのデータ転送を伴うからです、従って時々そのようにするだけです。

train1Batch(shouldFetchCost: boolean): number {
  // Every 42 steps, lower the learning rate by 15%.
  const learningRate =
      this.initialLearningRate * Math.pow(0.85, Math.floor(step / 42));
  this.optimizer.setLearningRate(learningRate);

  // Train 1 batch.
  let costValue = -1;
  this.math.scope(() => {
    const cost = this.session.train(
        this.costTensor, this.feedEntries, this.batchSize, this.optimizer,
        shouldFetchCost ? CostReduction.MEAN : CostReduction.NONE);

    if (!shouldFetchCost) {
      // We only train. We do not compute the cost.
      return;
    }

    // Compute the cost (by calling get), which requires transferring data
    // from the GPU.
    costValue = cost.get();
  });
  return costValue;
}

加えて、任意の与えられたカラー上で推論を実行するためのメソッドを書きます。入力カラーをモデルに渡す mapping と呼ばれる FeedEntry を作成します。

predict(rgbColor: number[]): number[] {
  let complementColor: number[] = [];
  this.math.scope((keep, track) => {
    const mapping = [{
      tensor: this.inputTensor,
      data: Array1D.new(this.normalizeColor(rgbColor)),
    }];
    const evalOutput = this.session.eval(this.predictionTensor, mapping);
    const values = evalOutput.getValues();
    const colors = this.denormalizeColor(Array.prototype.slice.call(values));

    // Make sure the values are within range.
    complementColor = colors.map(
        v => Math.round(Math.max(Math.min(v, 255), 0)));
  });
  return complementColor;
}

 

UI を更新する

.ts ファイル内のロジックの残りは殆どは UI を管理します。trainAndMaybeRender メソッドの呼出しは訓練を実行してブラウザの viewport のリフレッシュレートと同期した方法でレンダリングします (requestAnimationFrame によります)。4242 ステップ後に訓練を停止します。コンソールに損失をログ出力もします。

幾つかのサンプルカラーに基づけば、64 + 32 + 16 = 112 中間層ノードの私たちのモデルはまずまず良いように見えます。

重みの初期化は重要です

時々、予想される補色のチャネルは訓練を通して 0 にとどまるかもしれません。例えば、下のスクリーンショットでは、青のチャネルが 0 に固着されています。

 
この挙動は訓練が発生するかに向けた重みの初期化がどのくらい重要であるかに不幸なことに由来します。時々、0 に固着されたチャネルは時間とともに解決するかもしれません。他の時は、ページのリフレッシュが必要かもしれません。

最後に

多分、コードとその内のコメントの精読が learnjs がどのように動作するかの単純なサンプルを提供するでしょう。貴方が追跡する興味深いプロジェクト上で post し続けてください。

 

以上



deeplearn.js : チュートリアル : TensorFlow モデルの移植


deeplearn.js : チュートリアル : TensorFlow モデルの移植

作成 : (株)クラスキャット セールスインフォメーション
日時 : 08/23/2017

* 本ページは、github.io の deeplearn.js サイトの Tutorials – Port TensorFlow models を翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、適宜、追加改変している場合もあります。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

本文

このチュートリアルでは訓練と TensorFlow モデルの deeplearn.js への移植をデモします。このチュートリアルで使用されるコードとすべての必要なリソースは demos/mnist にストアされています。

(訳注: 画像はデモ実行時のブラウザ画面のスナップショットです)


MNIST データセットから手書き数字を予想する完全結合ニューラルネットワークを使用します。コードは公式 TensorFlow MNIST チュートリアル から fork されています。

NOTE : deeplearn.js repo のベース・ディレクトリを $BASE として参照します。

最初に、deeplearn.js レポジトリを clone して TensorFlow がインストールされていることを確認します。$BASE 内に cd して以下を実行することでモデルを訓練します :

python demos/mnist/fully_connected_feed.py

訓練は ~1 分かかりそしてモデル・チェックポイントを /tmp/tensorflow/mnist/tensorflow/mnist/logs/fully_connected_feed/ にストアするでしょう。

次に、TensorFlow チェックポイントから deeplearn.js へ重みを移植する必要があります。これを行なうスクリプトを提供しています。それを $BASE ディレクトリから実行します :

python scripts/dump_checkpoint_vars.py --output_dir=demos/mnist/ \
  --checkpoint_file=/tmp/tensorflow/mnist/logs/fully_connected_feed/model.ckpt-1999

(訳注: 以下は dump_checkpoint_vars.py 実行時のコンソールログです : )

Writing variable softmax_linear/biases...
Writing variable softmax_linear/weights...
Writing variable hidden2/weights...
Writing variable hidden1/weights...
Writing variable hidden2/biases...
Writing variable hidden1/biases...
Ignoring global_step
Writing manifest to demos/mnist/manifest.json
Done!

スクリプトはファイルのセット (variable 毎に一つのファイル、そして manifest.json) を demos/mnist ディレクトリに保存します。manifest.json は単なる辞書で variable 名をファイルにそして shape を map します :

{
  ...,
  "hidden1/weights": {
    "filename": "hidden1_weights",
    "shape": [784, 128]
  },
  ...
}

コーディングを開始する前の最後の一つのこと – $BASE ディレクトリから静的 HTTP サーバを実行する必要があります :

npm run prep
./node_modules/.bin/http-server
>> Starting up http-server, serving ./
>> Available on:
>>   http://127.0.0.1:8080
>> Hit CTRL-C to stop the server

ブラウザで http://localhost:8080/demos/mnist/manifest.json を見ることにより HTTP 経由で manifest.json にアクセスできることを確認してください。

幾つかの deeplearn.js コードを書くための準備ができました!

NOTE : TypeScript で書くことを選択した場合、コードを JavaScript にコンパイルして静的 HTTP サーバ経由でそれをサーブすることを確認しましょう。

重みを読みためには、CheckpointLoader を作成してそれが manifest ファイルを指すようにする必要があります。そして loader.getAllVariables() を呼び出します、これは variable 名を NDArray にマップする辞書を返します。その時点で、私たちのモデルを書く準備ができます。CheckpointLoader の使用方法を示すスニペットがここにあります :

import {CheckpointLoader, Graph} from 'deeplearnjs';
// manifest.json is in the same dir as index.html.
const reader = new CheckpointReader('.');  // 訳注: 原文のコード行は誤り、正しくは new CheckpointLoader('.');
reader.getAllVariables().then(vars => {
  // Write your model here.
  const g = new Graph();
  const input = g.placeholder('input', [784]);
  const hidden1W = g.constant(vars['hidden1/weights']);
  const hidden1B = g.constant(vars['hidden1/biases']);
  const hidden1 = g.relu(g.add(g.matmul(input, hidden1W), hidden1B));
  ...
  ...
  const math = new NDArrayMathGPU();
  const sess = new Session(g, math);
  math.scope(() => {
    const result = sess.eval(...);
    console.log(result.getValues());
  });
});

完全なモデル・コードについての詳細は demos/mnist/mnist.ts を見てください。デモは、3つの異なる API を使用する MNIST モデルの正確な実装を提供します :

  • buildModelGraphAPI() は Graph API を使用します、これは TensorFlow API を模倣していて、feed と fetch による遅延実行 (lazy execution) を提供します。ユーザは入力データ以外の GPU 関連のメモリリークを心配する必要はありません。
  • buildModelLayerAPI() は Graph.layers と連動する Graph API を使用します、これは Keras layers API を模倣しています。
  • buildModelMathAPI() は Math API を使用します。これは deeplearn.js の低位 API でユーザに最大限の制御を与えます。Math コマンドは直ちに実行されます、numpy のように。Math コマンドは math.scope() でラップされその結果、中間的な math コマンドで作成された NDArray は自動的にクリーンアップされます。

mnist デモを実行するために、watch-demo スクリプトを提供しています、これは typescript コードを監視してそれが変更された時にリコンパイルします。更に、スクリプトは単純な HTTP サーバを 8080 上で実行します、これは静的な html/js ファイルをサーブします。watch-demo を実行する前に、チュートリアルの前の方で起動した HTTP サーバを 8080 ポートを開放するために必ず kill してください。それから $BASE から web app デモのエントリ・ポイント、demos/mnist/mnist.ts を指すように watch-demo を実行します :

./scripts/watch-demo demos/mnist/mnist.ts
>> Starting up http-server, serving ./
>> Available on:
>>   http://127.0.0.1:8080
>>   http://192.168.1.5:8080
>> Hit CTRL-C to stop the server
>> 1410084 bytes written to demos/mnist/bundle.js (0.91 seconds) at 5:17:45 PM

http://localhost:8080/demos/mnist/ を見てください、demos/mnist/sample_data.json にストアされた 50 mnist 画像のテストセットを使用して計測されたテスト精度 ~90% を示す単純なページを見るはずです。デモで自由に遊んでください (e.g. それを対話的にするとか)、そして pull リクエストを私たちに送ってください!

(訳注 : demos/mnist の実行結果のスナップショットです : )
 

 

以上



deeplearn.js : チュートリアル : ML 非専門家のためのガイド


deeplearn.js : チュートリアル : ML 非専門家のためのガイド

作成 : (株)クラスキャット セールスインフォメーション
日時 : 08/22/2017

* 本ページは、github.io の deeplearn.js サイトの Tutorials – Guide for non-ML experts を翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

NDArray, Tensor, そして number

数学的な tensor

数学的には、”tensor” は線形代数の最も基本的なオブジェクトで、数字、ベクトル、そして行列の一般化です。ベクトルは数字の 1-次元リストとして考えられます; 行列は数字の 2-次元リストです。tensor は単純にその概念を数字の n-次元リストに一般化します。それは任意の多次元矩形配列に配置された構成要素の数字 (あるいは文字列や他のデータ型でさえ) の任意の配置です。

tensor は幾つかの属性を持ちます :

  • それは type (型) を持ちます、それはその構成要素、例えば integer, float, etc の各々の型を表します。deeplearn.js は今のところは float32 のみをサポートしています。
  • それは shape (形状) を持ちます、整数のリストでこれはコンポーネントの矩形 (= rectangular) 配列の形状を表します。行列が “4 by 4 ” と言う時、行列の shape を表しています。
  • それは rank (ランク) を持ちます、それはその shape の長さです; 構成要素の配列の次元です。ベクトルは rank 1 です; 行列は rank 2 を持ちます。
サンプル tensor Type Shape Rank
Scalar: 3.0 float [] 0
Vector: (1, 5, -2) int [3] 1
2×2 Matrix int [2, 2] 2

number[], NDArray, Tensor: 数学的な tensor のための3つのデータ型

数学者が “tensor” と呼ぶこの同じオブジェクトは deeplearn.js では3つの異なる方法で表現されます。上の議論 (rank, shape, そして type)、これらのすべてに適用されますが、しかしこれらは異なりそれらをストレートに保持することが重要です :

  • number[] は数字の配列に相当する基本的な javascript 型です。実際に 0-rank tensor のためにはそれは number で、1-rank tensor のためには number[]、2-rank のためには number[][]、etc. deeplearn.js 内ではそれをそれほど使わないでしょう、しかし deeplearn.js へ何かを出し入れする方法です。
  • NDArray は deeplearn.js のよりパワフルな実装です。NDArray を伴う計算はクライアント GPU 上で実行可能で、これは deeplearn.js の基本的な優位点です。最終的に計算が発生する時これがもっとも実際的な tensor データが内在するフォーマットです。Session.eval を呼び出す時、例えばですが、これが返されるものです (そして FeedEntry 入力が在るべきものです)。NDArray.new(number[]) と NDArray.get([indices]) を使用して NDArray と number[] 間を変換できます。
  • Tensor は空のバッグです; それは実際のデータを内部に持ちません。Graph が構築される時に使用されるのは placeholder で、これは最終的に内部に収まるデータの shape と型を記録します。それはその構成要素に実際の値は含みません。ちょうど shape と型を知ることにより、けれども、それは Graph 構築時に重要なエラー・キャッシングを行なうことができます ; 2×3 行列に 10×10 行列を乗算することを望む場合、入力データを与える前に、ノードを作成する時に graph は貴方に大声をあげる (= yell) ことができます。Tensor と NDArray または number[] の間で直接変換することは意味がありません; 貴方がそれを行なうことを試みることを見出す場合、次の一つが多分真実です :
    • 静的な NDArray を持っています、貴方はそれをグラフで使用することを望みます; constant Tensor ノードを作成するために graph.constant() を使用しましょう。
    • NDArray を持っています、貴方はそれをグラフへの入力として供給することを望みます; graph 内で graph.placeholder により Placeholder を作成しましょう、そして FeedEntry で graph に入力を送ります。
    • 出力 tensor を持っています、そして貴方は session にその値を評価して返すことを望みます。Session.eval(tensor) を呼び出しましょう。

一般的に、自動微分 (訓練) を望む時には Graph のみを使用すべきです。forward モード推論、または一般的な数値計算だけのためにライブラリを使用することを望む場合には、NDArrayMath で NDArray を使うことで十分でしょう。

もし訓練に興味があるのであれば、Graph を使用しなければなりません。graph を構築した時には Tensor と共同作業するでしょう、そして Session.eval でそれを実行する時、結果は NDArray になるでしょう。

 

Forward モード推論 / 数値計算

NDArray 上で数値演算を実行することを望むだけならば、単に貴方のデータで NDArray を構築して、そして NDArrayMath オブジェクトでその上で演算を実行できます。

例として、GPU 上で行列にベクトルを掛ける計算をすることを望む場合 :

const math = new NDArrayMathGPU();

math.scope((keep, track) => {
  const matrixShape = [2, 3];  // 2 rows, 3 columns.
  const matrix = track(Array2D.new(matrixShape, [10, 20, 30, 40, 50, 60]));
  const vector = track(Array1D.new([0, 1, 2]));
  const result = math.matrixTimesVector(matrix, vector);

  console.log("result shape:", result.shape);
  console.log("result", result.getValues());
});

NDArrayMath, keep, そして track 上の更なる情報は、チュートリアル : 序説 を参照してください。

NDArray/NDArrayMath 層は NumPy への相似として考えることができます。

 

訓練: 遅延実行 (delayed execution), Graph, そして Session

deeplearn.js における訓練 (自動微分) について理解すべき最も重要なことはそれは遅延実行モデル (delayed execution model) を使用することです。貴方のコードは2つの分離した段階を含むでしょう : 最初に Graph を構築します、実行したい計算を表すオブジェクトです、それから後で graph を実行して結果を得るでしょう。

殆どの時間、貴方の Graph はある入力(群)をある出力(群)に変換します。一般的に、Graph のアーキテクチャは固定されたままですが、自動的に更新されるパラメータを含むでしょう。

Graph を実行する時、2つのモードがあります: 訓練、そして推論です。

推論は Graph に出力を生成するために入力を提供する行為です。

graph を訓練することは Graph にラベル付けされた入力/出力のペアの多くのサンプルを提供することを伴い、そして自動的に Graph のパラメータを更新してその結果、入力を評価 (推論) する時 Graph の出力がラベル付けされた出力に近くなります。

ラベル付けされた出力が生成された出力にどのくらい近いかを表す Scalar を与える関数は “目的関数 (cost function)” と呼ばれます (“損失関数 (loss function)” としてもまた知られます)。モデルが上手く動作する時、損失関数はゼロ近くを出力すべきです。訓練する時には目的関数を提供しなければなりません。

deeplearn.js は TensorFlow に非常に類似して構造化されています、これは Google の python ベースの機械学習言語です。もし TensorFlow を知っているのであれば、Tensor, Graph, そして Session の概念は殆どすべて同じです、けれどもここでは TensorFlow の知識は仮定しません。

関数としての Graph

違いは標準の JavaScript コードへの類推により理解されます。このチュートリアルの残りについて、この二次方程式を扱っていきます :

// y = a * x^2 + b * x + c
const x = 4;
const a = Math.random();
const b = Math.random();
const c = Math.random();

const order2 = a * Math.pow(x, 2);
const order1 = b * x;
const y = order2 + order1 + c;

この元のコードでは、数値計算は各行でそれが処理される時に直ちに評価されます。

次のコードでは、deeplearn.js Graph 推論がどのように動作するかへの類推で、対照的です。

function graph(x, a, b, c) {
  const order2 = a * Math.pow(x, 2);
  const order1 = b * x;
  return order2 + order1 + c;
}

const a = Math.random();
const b = Math.random();
const c = Math.random();
const y = graph(4, a, b, c);

このコードは2つのブロックから成ります: 最初に graph 関数をセットアップし、それからそれを呼び出します。最初の数行で graph 関数をセットアップするコードは、関数が最後の行で呼び出されるまでどのような実際の数値演算も行ないません。セットアップの間、計算はまだ実行されていなくても基本的なコンパイラ type-safety エラーは捕らえることができます。

これは Graph が deeplearn.js でどのように動作するかへの正確な類推です。貴方のコードの最初のパートは graph のセットアップで、以下を記述します :

  • 入力、私たちのケースでは “x” です。入力は placeholder として表されます (e.g. graph.placeholder())。
  • 出力、私たちのケースでは “order1”, “order2”, そして最後の出力 “y” です。
  • 出力を生成する演算子、私たちのケースでは二次の分解された関数です (x^2, 乗算, 加算)。
  • 更新可能なパラメータ、私たちのケースでは “a”, “b”, “c” です。更新可能なパラメータは variable として表されます (e.g. graph.variable())。

そしてコードの後半のパートで幾つかの入力上で graph の関数を “call” (Session.eval) して、そして Session.train で幾つかのデータに対して “a”, “b”, そして “c” のための値を学習するでしょう。

上の関数類推と deeplearn.js Graph の間の一つの小さな違いは Graph はその出力を指定しないことです。代わりに、Graph の呼出し側はどの tensor を返すことを望むかを指定します。これは同じ Graph にその異なるパートを実行させるという異なる呼出しを可能にします。呼出し側により要求された結果を得るために必要なパートだけが評価されます。

Graph の推論と訓練は Session オブジェクトにより駆動されます。このオブジェクトはランライム状態、重み、活性化、そして勾配 (導関数) を含み、その一方で Graph オブジェクトは接続性情報のみを保持しています。

従って上の関数は deeplearn.js では以下のように実装されるでしょう :

const graph = new Graph();
// Make a new input in the graph, called 'x', with shape [] (a Scalar).
const x: Tensor = graph.placeholder('x', []);
// Make new variables in the graph, 'a', 'b', 'c' with shape [] and random
// initial values.
const a: Tensor = graph.variable('a', Scalar.new(Math.random()));
const b: Tensor = graph.variable('b', Scalar.new(Math.random()));
const c: Tensor = graph.variable('c', Scalar.new(Math.random()));
// Make new tensors representing the output of the operations of the quadratic.
const order2: Tensor = graph.multiply(a, graph.square(x));
const order1: Tensor = graph.multiply(b, x);
const y: Tensor = graph.add(graph.add(order2, order1), c);

// When training, we need to provide a label and a cost function.
const yLabel: Tensor = graph.placeholder('y label', []);
// Provide a mean squared cost function for training. cost = (y - yLabel)^2
const cost: Tensor = graph.meanSquaredCost(y, yLabel);

// At this point the graph is set up, but has not yet been evaluated.
// **deeplearn.js** needs a Session object to evaluate a graph.
const math = new NDArrayMathGPU();
const session = new Session(graph, math);

math.scope((keep, track) => {
  /**
   * Inference
   */
  // Now we ask the graph to evaluate (infer) and give us the result when
  // providing a value 4 for "x".
  // NOTE: "a", "b", and "c" are randomly initialized, so this will give us
  // something random.
  const result: NDArray =
      session.eval(y, [{tensor: x, data: track(Scalar.new(4))}]);

  /**
   * Training
   */
  // Now let's learn the coefficients of this quadratic given some data.
  // To do this, we need to provide examples of x and y.
  // The values given here are for values a = 3, b = 2, c = 1, with random
  // noise added to the output so it's not a perfect fit.
  const xs: Scalar[] = [
    track(Scalar.new(0)),
    track(Scalar.new(1)),
    track(Scalar.new(2)),
    track(Scalar.new(3))
  ];
  const ys: Scalar[] = [
    track(Scalar.new(1.1)),
    track(Scalar.new(5.9)),
    track(Scalar.new(16.8)),
    track(Scalar.new(33.9))
  ];
  // When training, it's important to shuffle your data!
  const shuffledInputProviderBuilder =
      new InCPUMemoryShuffledInputProviderBuilder([xs, ys]);
  const [xProvider, yProvider] =
      shuffledInputProviderBuilder.getInputProviders();

  // Training is broken up into batches.
  const NUM_BATCHES = 5;
  const BATCH_SIZE = xs.length;
  // Before we start training, we need to provide an optimizer. This is the
  // object that is responsible for updating weights. The learning rate param
  // is a value that represents how large of a step to make when updating
  // weights. If this is too big, you may overstep and oscillate. If it is too
  // small, the model may take a long time to train.
  const LEARNING_RATE = .001;
  const optimizer = new SGDOptimizer(LEARNING_RATE);
  for (let i = 0; i < NUM_BATCHES; i++) {
    // Train takes a cost tensor to minimize; this call trains one batch and
    // returns the average cost of the batch as a Scalar.
    const costValue = session.train(
        cost,
        // Map input providers to Tensors on the graph.
        [{tensor: x, data: xProvider}, {tensor: y, data: yProvider}],
        BATCH_SIZE, optimizer, CostReduction.MEAN);

    console.log('average cost: ' + costValue.get());
  }
});

モデルを訓練した後、与えられた "x" に対して "y" の値を得るために graph を通して再度推論できます。

もちろん、実際には、Scalar 値を使うことだけを望むのではないでしょう。deeplearn.js はパワフルな hardware-accelerated 線形代数を提供します、これは画像認識、テキスト生成等あらゆるものに利用できます。より詳しくは他のチュートリアルを見てください。

 

以上


deeplearn.js : チュートリアル : 序説


deeplearn.js : チュートリアル : 序説

作成 : (株)クラスキャット セールスインフォメーション
日時 : 08/21/2017

* 本ページは、github.io の deeplearn.js サイトの Tutorials – Introduction を翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

deeplearn.js はオープンソースの機械知能のための WebGL-accelerated JavaScript ライブラリです。deeplearn.js は高性能な機械学習ビルディング・ブロックを完全にもたらし、ブラウザでニューラルネットワークを訓練したり推論モードで事前訓練されたモデルを実行することを可能にします。それは、直接利用可能な数学関数のセットに加えて微分可能なデータフロー・グラフを構築するための API を提供します。

このチュートリアルを補足するコードは ここで 見つけられます。

それを貴方自身で次のように実行しましょう :

./scripts/watch-demo demos/intro/intro.ts

そして http://localhost:8080/demos/intro/ を訪れましょう。

(訳注: 以下の画像は intro.ts 実行後のブラウザのスナップショットです : )

このドキュメントのためには、TypeScript コード・サンプルを使用します。vanilla JavaScript のためには、const, let, あるいは他の型定義を取り除く必要があるでしょう。

 

核となる概念

NDArray

deeplearn.js のデータの中心的なユニットは NDArray です。NDArray は任意の次元数の配列に shape された浮動小数点のセットで構成されます。NDArray はそれらの shape を定義する shape 属性を持ちます。ライブラリは低 rank NDArray のための sugar サブクラスを提供します : Scalar, Array1D, Array2D, Array3D and Array4D.

2×3 matrix のサンプル使用方法 :

const shape = [2, 3];  // 2 rows, 3 columns
const a = Array2D.new(shape, [1.0, 2.0, 3.0, 10.0, 20.0, 30.0]);

NDArray はデータを GPU 上 WebGLTexture として、ここでは各ピクセルが浮動小数点値をストアし、あるいは CPU 上 vanilla JavaScript TypedArray としてストアします。ほとんどの場合、ユーザはストレージについて考えるべきではありません、それは実装の詳細ですので。

NDArray データが CPU 上にストアされている場合、GPU 数値演算が最初に呼び出された時にデータは texture に自動的にアップロードされます。GPU-resident NDArray 上で NDArray.getValues() を呼び出す場合は、ライブラリは texture を CPU にダウンロードして texture を削除します。

NDArrayMath

ライブラリは NDArrayMath 基底クラスを提供します、これは NDArray 上で動作する数値関数のセットを定義します。

NDArrayMathGPU

NDArrayMathGPU 実装を使用する時、これらの数値演算は shader プログラムを GPU 上で実行されるようにキューに入れます。NDArrayMathCPU とは異なり、これらの演算子はブロックしません、しかし詳細は後述するように、ユーザはNDArray 上で get() または getValues() を呼び出すことにより cpu を gpu と同期することができます。

これらの shader は NDArray により所有される WebGLTexture から読み書きをします。
数値演算を連鎖する時、(演算の間に CPU へダウンロードされずに) texture は GPU メモリに常駐できます 、これはパフォーマンスにとって致命的です。

2つの matrices の間の mean squared difference を取るサンプルです (math.scope, keep, と track の詳細は後述) :

const math = new NDArrayMathGPU();

math.scope((keep, track) => {
  const a = track(Array2D.new([2, 2], [1.0, 2.0, 3.0, 4.0]));
  const b = track(Array2D.new([2, 2], [0.0, 2.0, 4.0, 6.0]));

  // Non-blocking math calls.
  const diff = math.sub(a, b);
  const squaredDiff = math.elementWiseMul(diff, diff);
  const sum = math.sum(squaredDiff);
  const size = Scalar.new(a.size);
  const average = math.divide(sum, size);

  // Blocking call to actually read the values from average. Waits until the
  // GPU has finished executing the operations before returning values.
  // average is a Scalar so we use .get()
  console.log(average.get());
});

NOTE : NDArray.get() と NDArray.getValues() はブロッキング・コールです。連鎖した数値関数を実行する後に callback を登録する必要はありません、CPU & GPU を同期するためには getValues() を呼び出すだけです。

TIP : デバッグしているのでない限り数値 GPU 演算の間に get() や getValues() を呼び出すことは回避してください。これは texture を強制的にダウンロードさせ、そして続く NDArrayMathGPU 呼び出しはデータを新しい texture に再アップロードしなければならないでしょう。

 

math.scope()

math 演算子を使用する時、上のサンプルで示されるようにそれらを math.scope() 関数クロージャでラップしなければなりません。このスコープの math 演算子の結果はそれらがスコープ内で返される値でない限りはスコープの最後で処理されるでしょう。

2つの関数が関数クロージャに渡されます、keep() と track() です。

keep() は保持するために渡された NDArray がスコープが終わるときに自動的にクリーンアップされないことを保証します。

track() はスコープの内側で直接 construct するかもしれない任意の NDArray を追跡します。スコープが終わる時、任意の手動で track された NDArray はクリーンアップされます。

すべての math.method() 関数の結果、更には多くの他のコアライブラリ関数の結果は自動的にクリーンアップされます、従ってそれらを手動で追跡しなくてよいです。

const math = new NDArrayMathGPU();

let output;

// You must have an outer scope, but don't worry, the library will throw an
// error if you don't have one.
math.scope((keep, track) => {
  // CORRECT: By default, math wont track NDArrays that are constructed
  // directly. You can call track() on the NDArray for it to get tracked and
  // cleaned up at the end of the scope.
  const a = track(Scalar.new(2));

  // INCORRECT: This is a texture leak!!
  // math doesn't know about b, so it can't track it. When the scope ends, the
  // GPU-resident NDArray will not get cleaned up, even though b goes out of
  // scope. Make sure you call track() on NDArrays you create.
  const b = Scalar.new(2);

  // CORRECT: By default, math tracks all outputs of math functions.
  const c = math.neg(math.exp(a));

  // CORRECT: d is tracked by the parent scope.
  const d = math.scope(() => {
    // CORRECT: e will get cleaned up when this inner scope ends.
    const e = track(Scalar.new(3));

    // CORRECT: The result of this math function is tracked. Since it is the
    // return value of this scope, it will not get cleaned up with this inner
    // scope. However, the result will be tracked automatically in the parent
    // scope.
    return math.elementWiseMul(e, e);
  });

  // CORRECT, BUT BE CAREFUL: The output of math.tanh will be tracked
  // automatically, however we can call keep() on it so that it will be kept
  // when the scope ends. That means if you are not careful about calling
  // output.dispose() some time later, you might introduce a texture memory
  // leak. A better way to do this would be to return this value as a return
  // value of a scope so that it gets tracked in a parent scope.
  output = keep(math.tanh(d));
});

より技術的な詳細 : WebGL texture が JavaScript のスコープの外へ出るとき、ブラウザのガベージコレクション機構では自動的にクリーンアップされません。これは、GPU-resident な NDArray の扱いが終わった時いつか後で手動で処分されなければなならないことを意味します。NDArray の扱いが終わった時に ndarray.dispose() を手動で呼び出すのを忘れた場合、texture メモリリークをもたらし、これは深刻なパフォーマンス問題を引き起こすでしょう。もし math.scope() を使用する場合、math.method() とスコープを通して結果を返す任意の他のメソッドで作成された任意の NDArray は自動的にクリーンアップされます。

もし手動のメモリ管理を望み math.scope() を使用しないのであれば、NDArrayMath オブジェクトを safeMode = false で construct できます。これは推奨されませんが、NDArrayMathCPU に対しては有用です、何故ならば CPU-resident メモリは JavaScript ガベージコレクタで自動的にクリーンアップされるからです。

NDArrayMathCPU

CPU 実装を使用する時は、これらの数値演算はブロックされて vanilla JavaScript により基礎となる TypedArray 上で直ちに実行されます。

訓練

deeplearn.js の微分可能なデータフロー・グラフはちょうど TensorFlow におけるように、遅延実行モデル (delayed execution model) を使用します。ユーザはグラフを構築してそして FeedEntry を通して入力 NDArray を提供することでその上で訓練して推論します。

NOTE : 推論モードのためには NDArrayMath と NDArray で十分です。訓練を望む場合にのみグラフが必要です。

Graph と Tensor

Graph オブジェクトはデータフロー・グラフを構築するための核となるクラスです。Graph オブジェクトは実際には NDArray データを保持しません、演算間の接続性のみです。

Graph クラスはトップレベルなメンバー関数として微分可能な演算子を持ちます。演算を追加するために graph メソッドを呼び出す時、Tensor オブジェクトが返されます、これは接続性と shape 情報だけを保持します。

入力に variable を乗算するサンプル graph です :

const g = new Graph();

// Placeholders are input containers. This is the container for where we will
// feed an input NDArray when we execute the graph.
const inputShape = [3];
const inputTensor = g.placeholder('input', inputShape);

const labelShape = [1];
const labelTensor = g.placeholder('label', labelShape);

// Variables are containers that hold a value that can be updated from
// training.
// Here we initialize the multiplier variable randomly.
const multiplier = g.variable('multiplier', Array2D.randNormal([1, 3]));

// Top level graph methods take Tensors and return Tensors.
const outputTensor = g.matmul(multiplier, inputTensor);
const costTensor = g.meanSquaredCost(outputTensor, labelTensor);

// Tensors, like NDArrays, have a shape attribute.
console.log(outputTensor.shape);

Session と FeedEntry

Session オブジェクトは Graph の実行を駆動するものです。FeedEntry (TensorFlow feed_dict に類似) は実行のためのデータを供給するもので、与えられた NDArray から Tensor に値を供給します。

バッチング上のクイックノート : deeplearn.js は演算のための outer dimension としてのバッチングをまだ実装してません。このことはすべてのトップレベル graph op、更には math 関数が単一のサンプル上で動作することを意味します。けれども、バッチングは重要でその結果重み更新はバッチに渡る勾配平均上で動作します。deeplearn.js は入力を提供するために NDArray 直接よりも、訓練 FeedEntry 内の InputProvider を使用することによりバッチングをシミュレートします。InputProvider はバッチで各アイテムのために呼び出されます。入力セットをシャッフルしてそれらを同期的に保持するために InMemoryShuffledInputProviderBuilder を提供します。

上記からの Graph オブジェクトによる訓練 :

const learningRate = .00001;
const batchSize = 3;
const math = new NDArrayMathGPU();

const session = new Session(g, math);
const optimizer = new SGDOptimizer(learningRate);

const inputs: Array1D[] = [
  Array1D.new([1.0, 2.0, 3.0]),
  Array1D.new([10.0, 20.0, 30.0]),
  Array1D.new([100.0, 200.0, 300.0])
];

const labels: Array1D[] = [
  Array1D.new([4.0]),
  Array1D.new([40.0]),
  Array1D.new([400.0])
];

// Shuffles inputs and labels and keeps them mutually in sync.
const shuffledInputProviderBuilder =
  new InCPUMemoryShuffledInputProviderBuilder([inputs, labels]);
const [inputProvider, labelProvider] =
  shuffledInputProviderBuilder.getInputProviders();

// Maps tensors to InputProviders.
const feedEntries: FeedEntry[] = [
  {tensor: inputTensor, data: inputProvider},
  {tensor: labelTensor, data: labelProvider}
];

const NUM_BATCHES = 10;
for (let i = 0; i < NUM_BATCHES; i++) {
  // Wrap session.train in a scope so the cost gets cleaned up automatically.
  math.scope(() => {
    // Train takes a cost tensor to minimize. Trains one batch. Returns the
    // average cost as a Scalar.
    const cost = session.train(
        costTensor, feedEntries, batchSize, optimizer, CostReduction.MEAN);

    console.log('last average cost (' + i + '): ' + cost.get());
  });
}

訓練後、graph を通して推論できます :

// Wrap session.eval in a scope so the intermediate values get cleaned up
// automatically.
math.scope((keep, track) => {
  const testInput = track(Array1D.new([0.1, 0.2, 0.3]));

  // session.eval can take NDArrays as input data.
  const testFeedEntries: FeedEntry[] = [
    {tensor: inputTensor, data: testInput}
  ];

  const testOutput = session.eval(outputTensor, testFeedEntries);

  console.log('---inference output---');
  console.log('shape: ' + testOutput.shape);
  console.log('value: ' + testOutput.get(0));
});

// Cleanup training data.
inputs.forEach(input => input.dispose());
labels.forEach(label => label.dispose());
 

以上


AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com