1. 始める前に
TensorFlow.js モデルの使用はここ数年で急激に増大し、多くの JavaScript デベロッパーは既存の最先端のモデルを使用し、独自のカスタムデータでモデルを再トレーニングすることを目指しています業種 既存のモデル(しばしば基本モデルと呼びます)を、似ているが別の領域で使用することは転移学習と呼ばれます。
転移学習は、完全に空のモデルから開始する場合に比べて多くの利点があります。以前のトレーニング済みモデルですでに学習している知識を再利用できます。また、分類する新しいアイテムの必要数も少なくて済みます。また、ネットワーク全体ではなく、モデル アーキテクチャの最後の数レイヤを再トレーニングするだけで済むため、トレーニングの速度が大幅に向上します。このため、転移学習は、実行デバイスごとに異なるリソースが表示されるウェブブラウザ環境に適していますが、センサーに直接アクセスしてデータを簡単に取得することもできます。
この Codelab では、空白のキャンバスからウェブアプリを作成して、Google で人気のある「Teachable Machine」ウェブサイトを作り直す方法を説明します。このウェブサイトでは、ウェブカメラからほんの数枚の画像でカスタム オブジェクトを認識できるように、機能的なウェブアプリを作成できます。ウェブサイトは意図的に最小化されているため、この Codelab の機械学習の側面に集中できます。しかしながら、元の Teachable Machine ウェブサイトと同様、UX を改善するために既存のウェブ デベロッパー エクスペリエンスを適用する余地が十分にあるのです。
要件
この Codelab は、事前に作成された TensorFlow.js モデルと基本的な API の使用方法にある程度精通しており、TensorFlow.js で転移学習を始めるウェブ デベロッパーを対象としています。
- このラボでは、TensorFlow.js、HTML5、CSS、JavaScript の基本的な知識があることを前提としています。
Tensorflow.js を初めて使用する場合は、まずこちらの無料ゼロ対コースを受講することをおすすめします。このコースでは機械学習や TensorFlow.js の背景知識はなく、必要な知識を段階的に習得できます。
学習内容
- TensorFlow.js の概要と、次のウェブアプリで使用する理由。
- Teachable Machine のユーザー エクスペリエンスを再現したシンプルな HTML/CSS /JS ウェブページを構築する方法。
- TensorFlow.js を使用して事前トレーニング済みのベースモデル(特に MobileNet)を読み込み、転移学習で使用できる画像特徴を生成する方法。
- 認識する複数のデータクラスについて、ユーザーのウェブカメラからデータを収集する方法。
- 画像特徴を取り、それらを使用して新しいオブジェクトを分類することを学習した多層パーセプトの作成方法と定義方法。
ハッキングを開始...
必要なもの
- Glitch.com アカウントをフォローすることをおすすめしますが、自分で編集して運用できる快適なウェブサービス環境を利用することもできます。
2. TensorFlow.js とは
TensorFlow.js は、JavaScript を実行できる任意の場所で動作するオープンソースの機械学習ライブラリです。Python で記述されたオリジナルの TensorFlow ライブラリに基づいており、この開発者エクスペリエンスと JavaScript エコシステム向けの API セットの再作成を目的としています。
利用できる場所
JavaScript は移植性が高いため、1 つの言語で書いて、以下のプラットフォームすべてで簡単に機械学習を実行できます。
- ウェブブラウザのクライアント側(vanilla JavaScript を使用)
- Node.js を使用したサーバー側や Raspberry Pi などの IoT デバイス
- Electron を使用するデスクトップ アプリ
- React Native を使用するネイティブ モバイルアプリ
TensorFlow.js では、各環境内で複数のバックエンドをサポートします(CPU や WebGL など、ハードウェア内で実行される実際の環境など)。ここでの「バックエンド」とは、サーバー側の環境を意味するものではなく、互換性を確保して実行を高速化するために、実行のバックエンドは WebGL のクライアント側などとなります。現在、TensorFlow.js は以下をサポートしています。
- デバイスのグラフィック カードで使用できる WebGL 実行(GPU) - GPU アクセラレーションを使用して、より大きいモデル(3 MB を超えるモデル)を最速で実行できます。
- CPU での Web Assembly(WASM)の実行 -古い世代のスマートフォンなど、デバイス全体で CPU のパフォーマンスを改善します。これは、グラフィック プロセッサにコンテンツをアップロードするオーバーヘッドにより、WebGL よりも WASM を使用した CPU で実際に高速に実行できる小さなモデル(3 MB 未満)に適しています。
- CPU の実行 - 他の環境を使用できない場合にフォールバックを使用します。これは 3 つのプロセスの中で最も遅いものですが、常に存在します。
注: 実行するデバイスの種類がわかっている場合は、これらのバックエンドのいずれかを強制的に使用することを選択できます。または、TensorFlow.js に判断させることもできます。これは指定しません。
クライアントサイドのスーパーパワー
クライアント コンピュータのウェブブラウザで TensorFlow.js を実行すると、検討すべきメリットがいくつかあります。
プライバシー
サードパーティのウェブサーバーにデータを送信せずに、クライアント コンピュータでデータをトレーニングして分類できます。GDPR などの地域の法律を遵守するための要件や、ユーザーがパソコンに保持して第三者に送信しないデータを処理する場合に、この要件が必要になることがあります。
速度
リモート サーバーにデータを送信する必要がないため、推論(データを分類する動作)にかかる時間を短縮できます。さらに、デバイスのセンサー(カメラ、マイク、GPS、加速度計など)に直接アクセスできれば、アプリに直接アクセスできます。
リーチと拡大
あなたが送信したリンクをワンクリックするだけで、誰でもブラウザ上でウェブページを開いて、あなたが編集したアプリを利用できます。サーバー側システム上の複雑な CUDA ドライバの設定などは必要ありません。機械学習システムを使用するだけで使用できます。
費用
サーバーは不要で、必要なのは、HTML、CSS、JS、モデルの各ファイルをホストするための CDN だけです。CDN の費用は、サーバーを(場合によってはグラフィック カードを接続した状態で)24 時間 365 日稼働し続けるよりもはるかに安価です。
サーバーサイドの機能
TensorFlow.js の Node.js 実装を活用すると、次の機能が有効になります。
CUDA によるフルサポート
グラフィック カード アクセラレーションのサーバー側では、NVIDIA CUDA ドライバをインストールして、TensorFlow がグラフィック カードと連携できるようにする必要があります(WebGL を使用するブラウザではインストールが不要です)。ただし、CUDA が完全にサポートされているため、グラフィック カードの下位レベルの機能を最大限に活用できるため、トレーニングと推論にかかる時間を短縮できます。どちらも同じ C++ バックエンドを共有しているため、パフォーマンスは Python TensorFlow 実装と同等です。
モデルサイズ
研究用の最先端のモデルの場合、非常に大きなモデル(数ギガバイト)を扱う可能性があります。現在のところ、これらのブラウザは、ブラウザタブごとのメモリ使用量の制限により、ウェブブラウザで実行できません。これらの大規模なモデルを実行するには、独自のモデルで Node.js を使用し、そのようなモデルを効率的に実行するために必要なハードウェア仕様を指定します。
IoT
Node.js は、Raspberry Pi のような一般的なシングルボード コンピュータでサポートされます。そのため、このようなデバイスでも TensorFlow.js モデルを実行できます。
速度
Node.js は JavaScript で記述されているため、短時間でコンパイルできます。このため、Node.js を使用すると実行時に改善されることがあり、特に前処理の場合はその時間が最適化されます。その好例が、このケーススタディをご覧ください。Hugging Face が Node.js を使用して自然言語処理モデルの 2 倍のパフォーマンスを向上させた様子を紹介しています。
ここまで、TensorFlow.js の基礎と実行できる場所、そのメリットについて学習しました。次は、実際に使ってみましょう。
3. 転移学習
転移学習とは、正確には
転移学習では、すでに学んだ知識をもとに、別の、または類似の事柄について学びます。
私たちは常にこれを実践しています。今まで経験したことのない、今まで経験したことのない新しいことを見つけることができる経験が大切なのです。ヤナギの木の例:
あなたがどこにいるかによっては、今までそのような種類の木を見たことがないかもしれません。
でも、たとえ下の画像で、ヤナギの木が何枚かある場合は、角度が少し違っていて、お見せしている元の画像とは少し違っていても、すぐに見つかるかもしれません。
脳には、木のような物体を識別する方法を認識しているニューロンや、長い直線を見つけるのに適した他のニューロンがすでに存在します。その知識を再利用してヤナギの木をすばやく分類できます。ヤシの木は、長い縦の枝がたくさんある木のようなオブジェクトです。
同様に、画像認識など、ドメイン上ですでにトレーニングされている機械学習モデルがある場合は、そのモデルを再利用して別のタスクを実行できます。
これは、1000 種類のオブジェクトに対して画像認識を実行できる、人気の高い調査モデル MobileNet のような高度なモデルにも同じことが言えます。犬から車まで、数百万ものラベル付き画像を含む ImageNet という巨大なデータセットでトレーニングを行いました。
このアニメーションでは、この MobileNet V1 モデルに膨大な数のレイヤがあります。
このモデルは、この 1,000 個のオブジェクトすべてに共通する特徴を抽出する方法をトレーニング中に学習しました。このようなオブジェクトの識別に使用する下位レベルの特徴の多くは、これまでに見たことのない新しいオブジェクトの検出に有用です。結局のところ、すべては線、テクスチャ、図形の組み合わせにすぎません。
従来の畳み込みニューラル ネットワーク(CNN)のアーキテクチャ(MobileNet に類似)を見て、転移学習がこのトレーニング済みネットワークを活用して、新しいことをどのように学習できるのかを見てみましょう。次の図は、CNN の典型的なモデル アーキテクチャを示しています。ここでは、0 から 9 までの手書きの数字を認識するようにトレーニングされています。
左に示すような既存のトレーニング済みモデルの事前トレーニング済み下位レベルレイヤを、右側にあるモデルの最後付近にある分類レイヤ(モデルの分類ヘッドとも呼ばれます)から切り離すことができる場合、下位レイヤで、トレーニング済みの元のデータに基づいて任意の画像の出力特徴を生成できます。分類ヘッドが削除された同じネットワークは次のとおりです。
認識しようとしている新しいものが、前のモデルが学習した出力機能を利用できると仮定すると、新しい目的に再利用できる可能性が高くなります。
上の図では、この架空のモデルは数字でトレーニングされています。したがって、数字について学んだことは、a、b、c などの文字にも適用できる可能性があります。
したがって、代わりに a、b、c を予測しようとする新しい分類ヘッドを次のように追加できます。
下位レベルのレイヤは凍結され、トレーニングは行われていません。新しい分類ヘッドのみが、事前トレーニング済みチョップド モデルで提供される特徴から学習するように自動的に更新されます。
このような行為は転移学習と呼ばれ、Teachable Machine がバックグラウンドで行う処理です。
また、ネットワークの最後で多層パーセプトをトレーニングするだけで、ネットワーク全体を最初からトレーニングする場合よりもはるかに速くトレーニングできます。
では、モデルの各サブパーツを入手するにはどうすればよいでしょうか。 次のセクションに進みましょう。
4.TensorFlow Hub - ベースモデル
使用すべき適切なベースモデルを見つける
MobileNet などのより一般的で一般的な研究モデルについては、TensorFlow Hub にアクセスし、MobileNet v3 アーキテクチャを使用する TensorFlow.js に適したモデルでフィルタして結果を確認します。 。
これらの結果のいくつかはタイプ「画像分類」(各モデルカードの結果の左上に示されます)のタイプで、他の結果は「画像特徴ベクトル」のタイプです。
これらの画像特徴ベクトルの結果は、基本的にはモバイル ネットの事前に細分化されたバージョンであり、最終的な分類ではなく画像特徴ベクトルの取得に使用できます。
このようなモデルは多くの場合「基本モデル」と呼ばれ、前のセクションで説明したように、新しい分類ヘッドを追加して独自のデータでトレーニングを行うことで、転移学習を行うことができます。
次に、特定のベースモデルについて、モデルがリリースされている TensorFlow.js の形式を確認します。これらの特徴ベクトル MobileNet v3 モデルのいずれかのページを開くと、JS ドキュメントから、tf.loadGraphModel()
を使用するドキュメントのコード スニペットに基づくグラフモデルの形式であることがわかります。
また、グラフ形式ではなくレイヤ形式でモデルが見つかった場合は、トレーニングのためにフリーズするレイヤとフリーズ解除するレイヤを選択できる点にも注意してください。これは「転送モデル」と呼ばれる新しいタスクのモデルを作成する際に非常に効果的です。 このチュートリアルでは、デフォルトのグラフのモデルタイプを使用します。これは、ほとんどの TF Hub モデルのデプロイに使用されます。レイヤモデルの操作について詳しくは、ゼロからヒーローへの TensorFlow.js に関するコースをご覧ください。
転移学習のメリット
モデル アーキテクチャ全体をゼロからトレーニングする代わりに、転移学習を使用するメリットは何ですか。
まず、構築済みのトレーニング済みベースモデルがあるので、トレーニング時間を移行学習アプローチで使用する主な利点とします。
また、すでに実施したトレーニングにより、分類しようとしている新しい事例がはるかに少なくなるというメリットもあります。
これは、分類したい対象のサンプルデータを収集するための時間とリソースが限られており、より堅牢なトレーニングのためにトレーニング データを収集する前に、プロトタイプをすばやく作成する必要がある場合に特に便利です。
必要なデータ量が少なく、トレーニングのスピードが小さいネットワークの場合、転移学習ではリソースの消費が少なくなります。このため、ブラウザ環境には非常に適しています。完全なモデル トレーニングに数時間、数日、数週間ではなく、数秒で完了できる最新のマシンです。
承知いたしました。転移学習の概要がわかったところで、今度は Teachable Machine の独自のバージョンを作成しましょう。では始めましょう。
5. コードの設定
必要なもの
- 最新のウェブブラウザ。
- HTML、CSS、JavaScript、Chrome DevTools(コンソール出力の表示)に関する基本的な知識がある。
コーディングを始めましょう
Glitch.com または Codepen.io 用にボイラープレート テンプレートが提供されています。この Codelab の基本状態として、いずれかのテンプレートをワンクリックで作成できます。
Glitch で [リミックス] ボタンをクリックすると、フォークして編集できる一連の新しいファイルが作成されます。
または、Codepen で、画面右下の [fork] をクリックします。
この非常にシンプルなスケルトンでは、次のファイルが提供されます。
- HTML ページ(index.html)
- スタイルシート(style.css)
- JavaScript コード(script.js)を書き込むファイル
便宜上、TensorFlow.js ライブラリの HTML ファイルにはインポートが追加されています。たとえば、次のようになります。
index.html
<!-- Import TensorFlow.js library -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js" type="text/javascript"></script>
別の方法: 任意のウェブエディタを使用するか、ローカルで使用する
コードをダウンロードしてローカルで動作させる場合、または別のオンライン エディタで作業する場合は、上記のディレクトリに 3 つのファイルを作成し、Glitch ボイラープレートからコードをコピーして、それぞれのファイルに貼り付けます。
6. アプリの HTML ボイラープレート
開始方法
すべてのプロトタイプには、検出結果をレンダリングできる基本的な HTML スキャフォールドが必要です。今すぐ設定してください。次を追加します:
- ページのタイトル。
- 説明文。
- ステータスです。
- ウェブカメラ フィードが表示されている動画(準備完了時)。
- カメラを起動したり、データを収集したり、エクスペリエンスをリセットしたりするためのボタン。
- 後でコーディングする TensorFlow.js ファイルと JS ファイルのインポート。
index.html
を開き、既存のコードで次のコードを貼り付けて上記の機能を設定します。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Transfer Learning - TensorFlow.js</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Import the webpage's stylesheet -->
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1>Make your own "Teachable Machine" using Transfer Learning with MobileNet v3 in TensorFlow.js using saved graph model from TFHub.</h1>
<p id="status">Awaiting TF.js load</p>
<video id="webcam" autoplay muted></video>
<button id="enableCam">Enable Webcam</button>
<button class="dataCollector" data-1hot="0" data-name="Class 1">Gather Class 1 Data</button>
<button class="dataCollector" data-1hot="1" data-name="Class 2">Gather Class 2 Data</button>
<button id="train">Train & Predict!</button>
<button id="reset">Reset</button>
<!-- Import TensorFlow.js library -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.11.0/dist/tf.min.js" type="text/javascript"></script>
<!-- Import the page's JavaScript to do some stuff -->
<script type="module" src="/script.js"></script>
</body>
</html>
分類する
上の主な HTML コードの明細を細かく見てみましょう。
- ページタイトルの
<h1>
タグと、ID が「status」の<p>
タグを追加しました。この ID は、システムのさまざまな部分を使って出力を表示するため、情報を出力する場所です。 - ID が「webcam」の
<video>
要素を追加し、このウェブカメラのストリームを後でレンダリングします。 <button>
要素を 5 個追加しました。1 つ目の ID は「enableCam」で、カメラを有効にします。次の 2 つのボタンには、「dataCollector」クラスがあり、認識するオブジェクトのサンプル画像を収集できます。後で記述するコードは、任意の数のボタンを追加して、意図したとおりに動作するよう設計されています。
これらのボタンには、data-1hot という特別なユーザー定義属性もあり、最初のクラスは 0 から始まる整数値になります。これは、特定のクラスのデータを表すために使用する数値インデックスです。ML モデルでは数値しか使用できないため、インデックスは文字列ではなく数値表現で出力クラスを正しくエンコードするために使用されます。
また、このクラスには人間が読み取れる形式の名前を使用する data-name 属性もあります。これにより、1 つのホット エンコーディングの数値インデックス値ではなく、よりわかりやすい名前をユーザーに提供できます。
最後に、データを収集したら、トレーニング プロセスを開始するか、アプリをリセットするためのトレーニング ボタンとリセットボタンを使用します。
- また、
<script>
インポートを 2 つ追加しました。1 つは TensorFlow.js 用、もう 1 つはまもなく定義する script.js 用です。
7. スタイルを追加する
要素のデフォルト
追加した HTML 要素のスタイルを追加して、正しく表示されるようにします。位置とサイズの要素に正しく追加されているスタイルは次のとおりです。特別なものはありません。授業に動画を追加しておいたところで、後から UX も改善できます。
style.css
body {
font-family: helvetica, arial, sans-serif;
margin: 2em;
}
h1 {
font-style: italic;
color: #FF6F00;
}
video {
clear: both;
display: block;
margin: 10px;
background: #000000;
width: 640px;
height: 480px;
}
button {
padding: 10px;
float: left;
margin: 5px 3px 5px 10px;
}
.removed {
display: none;
}
#status {
font-size:150%;
}
次の必要な処理はこれだけです。出力を今すぐプレビューすると、次のようになります。
8. JavaScript: キー定数とリスナー
キー定数を定義する
まず、アプリ全体で使用するいくつかのキー定数を追加します。まず、script.js
の内容を次の定数に置き換えます。
script.js
const STATUS = document.getElementById('status');
const VIDEO = document.getElementById('webcam');
const ENABLE_CAM_BUTTON = document.getElementById('enableCam');
const RESET_BUTTON = document.getElementById('reset');
const TRAIN_BUTTON = document.getElementById('train');
const MOBILE_NET_INPUT_WIDTH = 224;
const MOBILE_NET_INPUT_HEIGHT = 224;
const STOP_DATA_GATHER = -1;
const CLASS_NAMES = [];
それぞれの用途は次のとおりです。
STATUS
は、ステータスの更新を書き込む段落タグへの参照を保持するだけです。VIDEO
は、ウェブカメラ フィードをレンダリングする HTML 動画要素への参照を保持します。ENABLE_CAM_BUTTON
、RESET_BUTTON
、TRAIN_BUTTON
は、HTML ページにあるすべての主要なボタンへの DOM 参照を取得します。MOBILE_NET_INPUT_WIDTH
とMOBILE_NET_INPUT_HEIGHT
は、それぞれ MobileNet モデルの予想される入力幅と高さを定義します。このように、ファイルの先頭付近にある定数に保存することで、後で別のバージョンを使用することにしたほうが、さまざまな場所で値を置き換える必要がなくなり、値の更新が簡単になります。STOP_DATA_GATHER
は - 1 に設定されています。これにより、ユーザーがウェブカメラ フィードからのデータの収集を停止したときの状態を把握できるようになります。この番号にわかりやすい名前を付けると、コードが読みやすくなります。CLASS_NAMES
はルックアップとして機能し、人が読める形式のクラス予測名を保持します。この配列は後で入力されます。
主な要素への参照ができたので、次にイベント リスナーを関連付けます。
キーイベント リスナーを追加する
まず、次に示すように、ボタンにクリック イベント ハンドラを追加します。
script.js
ENABLE_CAM_BUTTON.addEventListener('click', enableCam);
TRAIN_BUTTON.addEventListener('click', trainAndPredict);
RESET_BUTTON.addEventListener('click', reset);
function enableCam() {
// TODO: Fill this out later in the codelab!
}
function trainAndPredict() {
// TODO: Fill this out later in the codelab!
}
function reset() {
// TODO: Fill this out later in the codelab!
}
ENABLE_CAM_BUTTON
- クリックすると、enableCam 関数が呼び出されます。
TRAIN_BUTTON
- クリックされたときに trainAndPredict を呼び出します。
RESET_BUTTON
- クリックすると通話がリセットされます。
最後に、このセクションを使用して、document.querySelectorAll()
を使用して「dataCollector」のクラスを持つすべてのボタンを探します。これは、ドキュメント内で見つかった次の要素の配列を返します。
script.js
let dataCollectorButtons = document.querySelectorAll('button.dataCollector');
for (let i = 0; i < dataCollectorButtons.length; i++) {
dataCollectorButtons[i].addEventListener('mousedown', gatherDataForClass);
dataCollectorButtons[i].addEventListener('mouseup', gatherDataForClass);
// Populate the human readable names for classes.
CLASS_NAMES.push(dataCollectorButtons[i].getAttribute('data-name'));
}
function gatherDataForClass() {
// TODO: Fill this out later in the codelab!
}
コードの説明:
そして、見つかったボタンを繰り返し処理し、それぞれに 2 つのイベント リスナーを関連付けます。1 つは「マウスダウン」用、もう 1 つは「マウスアップ」用です。これにより、ボタンが押されている限りサンプルを記録し続けることができ、データ収集に役立ちます。
どちらのイベントも、後で定義する gatherDataForClass
関数を呼び出します。
この時点で、HTML ボタン属性 data-name から、人が読める形式のクラス名を CLASS_NAMES
配列にプッシュすることもできます。
次に、後で使用するための重要なものを格納する変数を追加します。
script.js
let mobilenet = undefined;
let gatherDataState = STOP_DATA_GATHER;
let videoPlaying = false;
let trainingDataInputs = [];
let trainingDataOutputs = [];
let examplesCount = [];
let predict = false;
それぞれ見ていきましょう。
まず、読み込まれた mobilenet モデルを格納する変数 mobilenet
を用意します。最初は未定義に設定します。
次に、gatherDataState
という変数があります。「dataCollector」ボタンが押されると、HTML で定義されているように、そのボタンの 1 つのホット ID に変更され、その時点で収集しているデータのクラスを把握できます。最初は、STOP_DATA_GATHER
に設定されています。これにより、ボタンが押されていないときに、後で作成するデータ収集ループがデータを収集することはありません。
videoPlaying
は、ウェブカメラ ストリームが正常に読み込まれて再生可能かどうかを追跡し、使用可能かどうかを確認します。ウェブカメラは ENABLE_CAM_BUTTON.
を押すまでオンになっていないため、最初は false
に設定されています。
次に、2 つの配列(trainingDataInputs
と trainingDataOutputs
)を定義します。収集されたトレーニング データ値は、MobileNet 基本モデルで生成された入力特徴とサンプリングされた出力クラスの [dataCollector] ボタンをクリックするたびに保存されます。
1 つの最終配列 examplesCount,
を定義して、これらのクラスの追加を開始するたびに、各クラスに含まれる数を追跡します。
最後に、予測ループを制御する predict
という変数を作成します。最初は false
に設定されています。これを true
に設定するまで、予測は実行できません。
すべての重要な変数が定義されたので、分類の代わりに画像特徴ベクトルを提供する、事前に細分化された MobileNet v3 ベースモデルを読み込みます。
9. MobileNet ベースモデルを読み込む
まず、次のように loadMobileNetFeatureModel
という新しい関数を定義します。モデルを読み込む動作は非同期であるため、これは非同期関数である必要があります。
script.js
/**
* Loads the MobileNet model and warms it up so ready for use.
**/
async function loadMobileNetFeatureModel() {
const URL =
'https://tfhub.dev/google/tfjs-model/imagenet/mobilenet_v3_small_100_224/feature_vector/5/default/1';
mobilenet = await tf.loadGraphModel(URL, {fromTFHub: true});
STATUS.innerText = 'MobileNet v3 loaded successfully!';
// Warm up the model by passing zeros through it once.
tf.tidy(function () {
let answer = mobilenet.predict(tf.zeros([1, MOBILE_NET_INPUT_HEIGHT, MOBILE_NET_INPUT_WIDTH, 3]));
console.log(answer.shape);
});
}
// Call the function immediately to start loading.
loadMobileNetFeatureModel();
このコードでは、読み込むモデルが TFHub のドキュメントにある URL
を定義します。
次に、この Google ウェブサイトからモデルを読み込むときに、特別なプロパティ fromTFHub
を true
に設定して、await tf.loadGraphModel()
を使用してモデルを読み込みます。これは、TF Hub でホストされ、この追加プロパティを設定する必要があるモデルを使用する特殊なケースです。
読み込みが完了したら、STATUS
要素の innerText
にメッセージを設定します。これにより、読み込みが正しく行われ、データの収集を開始する準備が整っていることを確認できます。
あとはモデルをウォームアップすることだけです。このような大規模なモデルでは、モデルを初めて使用するときにすべてを設定するまでに少し時間がかかる場合があります。そのため、時間が最も重要となるような今後の待機を避けるため、モデルにゼロを渡すことをおすすめします。
tf.zeros()
を tf.tidy()
でラップすることで、テンソルが正しく廃棄され、バッチサイズが 1 で、開始時に定数として定義した高さと幅が正しく設定されていることがわかります。最後に、カラーチャンネルも指定します。この場合は RGB 画像が想定されている 3 です。
次に、answer.shape()
を使用して、返されたテンソルの形状をログに記録し、このモデルが生成する画像特徴のサイズを把握します。
この関数を定義したら、すぐに呼び出して、ページの読み込み時にモデルのダウンロードを開始できます。
ライブ プレビューを表示すると、しばらくするとステータス テキストが「TF.js の読み込みを待機中」から「MobileNet v3 が正常に読み込まれました」に変わります。 。続行する前に、これが機能することを確認してください。
このモデルで生成される出力特徴の出力サイズもコンソール出力で確認できます。MobileNet モデルでゼロを実行すると、[1, 1024]
の形が表示されます。最初の項目はバッチサイズ 1 ですが、実際には 1, 024 個の特徴が返され、これを使用して新しいオブジェクトを分類できます。
10. 新しいモデルヘッドを定義する
次に、モデルヘッドを定義します。これは基本的に、非常にシンプルなマルチレイヤ パーセプロンです。
script.js
let model = tf.sequential();
model.add(tf.layers.dense({inputShape: [1024], units: 128, activation: 'relu'}));
model.add(tf.layers.dense({units: CLASS_NAMES.length, activation: 'softmax'}));
model.summary();
// Compile the model with the defined optimizer and specify a loss function to use.
model.compile({
// Adam changes the learning rate over time which is useful.
optimizer: 'adam',
// Use the correct loss function. If 2 classes of data, must use binaryCrossentropy.
// Else categoricalCrossentropy is used if more than 2 classes.
loss: (CLASS_NAMES.length === 2) ? 'binaryCrossentropy': 'categoricalCrossentropy',
// As this is a classification problem you can record accuracy in the logs too!
metrics: ['accuracy']
});
では、このコードを見ていきましょう。まず、モデルレイヤを追加する tf.Sequence モデルを定義します。
次に、高密度レイヤをこのモデルの入力レイヤとして追加します。MobileNet v3 機能からの出力はこのサイズであるため、入力シェイプは 1024
になります。これは、前のステップでモデルを介して渡した後、気づきました。このレイヤには、ReLU アクティベーション関数を使用する 128 個のニューロンがあります。
活性化関数とモデルレイヤを初めて使用する場合は、このワークショップの開始時にコースを受講して、これらのプロパティが内部で何を行うかについて理解することを検討してください。
次に追加するレイヤは出力レイヤです。ニューロンの数は、予測しようとしているクラスの数と同じでなければなりません。CLASS_NAMES.length
を使用すると、分類するクラスの数を確認できます。これは、ユーザー インターフェースに表示されるデータ収集ボタンの数と同じです。これは分類問題であるため、この出力レイヤでは softmax
アクティベーションを使用します。これは、回帰ではなく分類問題を解決するためのモデルを作成する際に使用する必要があります。
model.summary()
を出力して、新しく定義されたモデルの概要をコンソールに出力します。
最後に、トレーニングの準備が整うようにモデルをコンパイルします。ここで、オプティマイザーは adam
に設定されています。CLASS_NAMES.length
が 2
に等しい場合は損失は binaryCrossentropy
になり、次のクラスが 3 つ以上ある場合には categoricalCrossentropy
が使用されます。分類 精度の指標もリクエストされるため、後でデバッグの際にログで確認することができます。
コンソールに次のように表示されます。
これには、トレーニング可能なパラメータが 13 万件あります。ただし、これは単純なニューロンの密集した層であるため、非常に速くトレーニングされます。
プロジェクトの完了後に行う作業として、最初のレイヤのニューロンの数を変更してみて、十分にパフォーマンスを維持しながら、それを減らすことができるレベルを確認してみましょう。多くの場合、機械学習には、リソース使用量と速度の最適なバランスを見つけるのに最適なパラメータ値を見つけるための試行錯誤があります。
11. ウェブカメラを有効にする
先ほど定義した enableCam()
関数を具体化します。 次のように hasGetUserMedia()
という名前の新しい関数を追加し、以前に定義した enableCam()
関数の内容を以下の対応するコードに置き換えます。
script.js
function hasGetUserMedia() {
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
}
function enableCam() {
if (hasGetUserMedia()) {
// getUsermedia parameters.
const constraints = {
video: true,
width: 640,
height: 480
};
// Activate the webcam stream.
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
VIDEO.srcObject = stream;
VIDEO.addEventListener('loadeddata', function() {
videoPlaying = true;
ENABLE_CAM_BUTTON.classList.add('removed');
});
});
} else {
console.warn('getUserMedia() is not supported by your browser');
}
}
まず、hasGetUserMedia()
という名前の関数を作成し、ブラウザの主な API プロパティが存在するかどうかをチェックして、ブラウザが getUserMedia()
をサポートしているかどうかを確認します。
enableCam()
関数では、上記で定義した hasGetUserMedia()
関数を使用して、サポートされているかどうかをチェックします。有効になっていない場合は、コンソールに警告を出力します。
サポートしている場合は、getUserMedia()
呼び出しに対していくつかの制約を定義します。たとえば、動画ストリームのみにして、動画の width
のサイズを 640
ピクセル、height
を 480
ピクセルにします。なぜなら、この場合、MobileNet モデルにフィードするには 224×224 ピクセルにサイズ変更する必要があるため、これより大きい動画を取得する意味はあまりありません。より小さい解像度をリクエストして、一部のコンピューティング リソースを節約することもできます。ほとんどのカメラはこのサイズの解像度に対応しています。
次に、上記の constraints
で navigator.mediaDevices.getUserMedia()
を呼び出し、stream
が返されるまで待ちます。stream
が返されたら、VIDEO
要素に srcObject
値を設定して stream
を再生させることができます。
また、VIDEO
要素に eventListener を追加して、stream
が読み込まれて正常に再生されていることを確認する必要があります。
スチームが読み込まれたら、videoPlaying
を true に設定し、クラスが「removed
」に設定されて再度クリックされないように ENABLE_CAM_BUTTON
を削除できます。
コードを実行し、[カメラを有効にする] ボタンをクリックして、ウェブカメラへのアクセスを許可します。この操作を初めて行う場合、ページの動画要素には次のように表示されます。
それでは、dataCollector
ボタンのクリックを処理する関数を追加します。
12. データ収集ボタンのイベント ハンドラ
次に、gatherDataForClass().
という空の関数を入力します。これは、Codelab の開始時に dataCollector
ボタンのイベント ハンドラ関数として割り当てたものです。
script.js
/**
* Handle Data Gather for button mouseup/mousedown.
**/
function gatherDataForClass() {
let classNumber = parseInt(this.getAttribute('data-1hot'));
gatherDataState = (gatherDataState === STOP_DATA_GATHER) ? classNumber : STOP_DATA_GATHER;
dataGatherLoop();
}
まず、属性名(この場合はパラメータとして data-1hot
)を指定して this.getAttribute()
を呼び出し、現在クリックされているボタンの data-1hot
属性を確認します。これは文字列なので、parseInt()
を使用して整数にキャストし、この結果を classNumber.
という名前の変数に割り当てることができます。
次に、それに応じて gatherDataState
変数を設定します。現在の gatherDataState
が STOP_DATA_GATHER
(-1 に設定)と等しい場合は、現在データを収集しておらず、イベントが発生した mousedown
イベントであることを意味します。gatherDataState
を先ほど見つかった classNumber
に設定します。
それ以外の場合は、現在データを収集していて、呼び出されたイベントが mouseup
イベントであり、そのクラスのデータの収集を停止します。すぐに定義するデータ収集ループを終了するには、これを STOP_DATA_GATHER
状態に戻します。
最後に、実際にクラスデータの記録を実行する dataGatherLoop(),
の呼び出しを開始します。
13. データ収集
ここで、dataGatherLoop()
関数を定義します。この関数は、ウェブカメラの動画から画像をサンプリングして MobileNet モデルに渡し、そのモデルの出力(1024 の特徴ベクトル)をキャプチャします。
次に、ボタンが現在押されているボタンの gatherDataState
ID と一緒に保存されるため、このデータが表すクラスを把握できます。
詳しく見ていきましょう。
script.js
function dataGatherLoop() {
if (videoPlaying && gatherDataState !== STOP_DATA_GATHER) {
let imageFeatures = tf.tidy(function() {
let videoFrameAsTensor = tf.browser.fromPixels(VIDEO);
let resizedTensorFrame = tf.image.resizeBilinear(videoFrameAsTensor, [MOBILE_NET_INPUT_HEIGHT,
MOBILE_NET_INPUT_WIDTH], true);
let normalizedTensorFrame = resizedTensorFrame.div(255);
return mobilenet.predict(normalizedTensorFrame.expandDims()).squeeze();
});
trainingDataInputs.push(imageFeatures);
trainingDataOutputs.push(gatherDataState);
// Intialize array index element if currently undefined.
if (examplesCount[gatherDataState] === undefined) {
examplesCount[gatherDataState] = 0;
}
examplesCount[gatherDataState]++;
STATUS.innerText = '';
for (let n = 0; n < CLASS_NAMES.length; n++) {
STATUS.innerText += CLASS_NAMES[n] + ' data count: ' + examplesCount[n] + '. ';
}
window.requestAnimationFrame(dataGatherLoop);
}
}
この関数の実行を続行するには、videoPlaying
が true である(ウェブカメラがアクティブであり、gatherDataState
が STOP_DATA_GATHER
と等しくなく、クラスデータ収集用のボタンが現在押されている)必要があります。
次に、コードを tf.tidy()
でラップし、後続のコードで作成されたテンソルを破棄します。この tf.tidy()
コードの実行結果は、imageFeatures
という変数に格納されます。
tf.browser.fromPixels()
を使用して、ウェブカメラ VIDEO
のフレームを取得できるようになりました。結果の画像データを含むテンソルが、videoFrameAsTensor
という変数に格納されます。
次に、videoFrameAsTensor
変数のサイズを変更して、MobileNet モデルの入力に合わせます。最初のパラメータとして再形成するテンソルと、前に作成した定数で定義された新しい高さと幅を定義するシェイプがある tf.image.resizeBilinear()
呼び出しを使用します。最後に、3 つ目のパラメータを渡すことで、アライメントの角を true に設定して、サイズ変更時にアライメントの問題が発生しないようにします。このサイズ変更の結果は、resizedTensorFrame
という変数に格納されます。
なお、ウェブカメラの画像のサイズは 640 x 480 ピクセル、モデルには 224 x 224 ピクセルの正方形の画像が必要なため、このプリミティブ サイズ変更によって画像が引き伸ばされます。
このデモでは、この方法で問題ありません。ただし、この Codelab を完了したら、このイメージから正方形を切り抜いて、後で作成する本番環境のシステムを改善することをおすすめします。
次に、画像データを正規化します。tf.browser.frompixels()
を使用する場合、画像データは常に 0 ~ 255 の範囲になります。したがって、resizeedTensorFrame を 255 で割って、すべての値が 0 ~ 1 になるようにします。これは MobileNet モデルが入力として想定している値です。
最後に、コードの tf.tidy()
セクションで、mobilenet.predict()
を呼び出して、正規化されたテンソルを読み込まれたモデルに push します。ここでは、expandDims()
を使用して normalizedTensorFrame
の拡張バージョンを渡して、モデルは 1 つのバッチを受け取ります。モデルは処理のために複数の入力バッチを想定しています。
結果が返されたらすぐに呼び出すことができます。squeeze()
それを 1D テンソルに縮小し、1 テンソルに戻してimageFeatures
結果をキャプチャする変数tf.tidy()
をご覧ください。
これでimageFeatures
MobileNet モデルtrainingDataInputs
配列を定義します。
現在の gatherDataState
を trainingDataOutputs
配列に push して、この入力が表す内容を記録することもできます。
gatherDataState
変数は、以前に定義した gatherDataForClass()
関数でボタンがクリックされたときにデータを記録する現在のクラスの数値 ID に設定されているはずです。
この時点で、特定のクラスのサンプル数を増やすこともできます。そのためには、まず examplesCount
配列内のインデックスが初期化済みかどうかを確認します。定義されていない場合は、0 に設定して特定のクラスの数値 ID 用のカウンタを初期化し、現在の gatherDataState
の examplesCount
をインクリメントします。
次に、キャプチャした各クラスの現在のカウントを表示するように、ウェブページ内の STATUS
要素のテキストを更新します。これを行うには、CLASS_NAMES
配列をループ処理し、人が読める形式の名前を examplesCount
の同じインデックスのデータ数と組み合わせて出力します。
最後に、dataGatherLoop
をパラメータとして渡して window.requestAnimationFrame()
を呼び出し、この関数を再帰的に呼び出します。ボタンの mouseup
が検出され、gatherDataState
が STOP_DATA_GATHER,
に設定されてデータの収集ループが終わるまで、動画のフレームのサンプリングが続行されます。
コードを実行すると、カメラの有効化ボタンをクリックし、ウェブカメラが読み込まれるのを待ってから、各データ収集ボタンを長押しして、各クラスのデータの例を収集します。ここには、携帯電話と、手それぞれに関するデータを収集しています。
上の画面キャプチャのように、すべてのテンソルがメモリに保存されているため、ステータス テキストが更新されます。
14. トレーニングと予測
次のステップでは、転移学習が行われる現在空の trainAndPredict()
関数のコードを実装します。コードを見てみましょう。
script.js
async function trainAndPredict() {
predict = false;
tf.util.shuffleCombo(trainingDataInputs, trainingDataOutputs);
let outputsAsTensor = tf.tensor1d(trainingDataOutputs, 'int32');
let oneHotOutputs = tf.oneHot(outputsAsTensor, CLASS_NAMES.length);
let inputsAsTensor = tf.stack(trainingDataInputs);
let results = await model.fit(inputsAsTensor, oneHotOutputs, {shuffle: true, batchSize: 5, epochs: 10,
callbacks: {onEpochEnd: logProgress} });
outputsAsTensor.dispose();
oneHotOutputs.dispose();
inputsAsTensor.dispose();
predict = true;
predictLoop();
}
function logProgress(epoch, logs) {
console.log('Data for epoch ' + epoch, logs);
}
まず、predict
を false
に設定して、現在の予測が行われないようにします。
次に、tf.util.shuffleCombo()
を使用して入力配列と出力配列をシャッフルし、順序がトレーニングの問題にならないようにします。
出力配列 trainingDataOutputs,
を int32 型の tensor1d に変換して、1 つのホット エンコーディングで使用できるようにします。これは、outputsAsTensor
という名前の変数に格納されています。
tf.oneHot()
関数とこの outputsAsTensor
変数を使用して、エンコードするクラスの最大数(CLASS_NAMES.length
のみ)を指定します。1 つのホット エンコード出力が oneHotOutputs
という新しいテンソルに格納されます。
現在 trainingDataInputs
は、記録されたテンソルの配列です。これらをトレーニングに使用するには、テンソルの配列を通常の 2D テンソルに変換する必要があります。
TensorFlow.js ライブラリには tf.stack()
と呼ばれる優れた関数があり、
テンソルの配列を取り、それを積み重ねて出力として高次元テンソルを生成します。この場合、テンソル 2D が返されます。これは、長さ 1,024 個の 1 次元入力のバッチです。各入力は、記録された特徴を含むトレーニング済みのものを含みます。
次に、await model.fit()
を使用して、カスタムモデル ヘッドをトレーニングします。ここでは、inputsAsTensor
変数を oneHotOutputs
とともに渡して、それぞれ入力とターゲット出力に使用するトレーニング データを表します。3 番目のパラメータの構成オブジェクトで次のように設定します。shuffle
~true
、batchSize
/5
、epochs
CANNOT TRANSLATE10
] を選択してから、callback
時間:onEpochEnd
をlogProgress
関数を定義します。
最後に、モデルのトレーニングが完了したら、作成したテンソルを破棄できます。次に、predict
を true
に戻して予測をやり直すことができ、predictLoop()
関数を呼び出してライブウェブカメラ画像の予測を開始できます。
トレーニングの状態をログに記録するように logProcess()
関数を定義することもできます。これは上記の model.fit()
で使用され、トレーニングのたびにコンソールに結果を出力します。
あと少しです。ここで、predictLoop()
関数を追加して予測を行います。
コア予測ループ
ここでは、ウェブカメラからフレームをサンプリングし、ブラウザでリアルタイムで結果を確認しながら各フレームの内容を予測するメイン予測ループを実装します。
コードを確認してみましょう。
script.js
function predictLoop() {
if (predict) {
tf.tidy(function() {
let videoFrameAsTensor = tf.browser.fromPixels(VIDEO).div(255);
let resizedTensorFrame = tf.image.resizeBilinear(videoFrameAsTensor,[MOBILE_NET_INPUT_HEIGHT,
MOBILE_NET_INPUT_WIDTH], true);
let imageFeatures = mobilenet.predict(resizedTensorFrame.expandDims());
let prediction = model.predict(imageFeatures).squeeze();
let highestIndex = prediction.argMax().arraySync();
let predictionArray = prediction.arraySync();
STATUS.innerText = 'Prediction: ' + CLASS_NAMES[highestIndex] + ' with ' + Math.floor(predictionArray[highestIndex] * 100) + '% confidence';
});
window.requestAnimationFrame(predictLoop);
}
}
まず、predict
が true になっていることを確認し、モデルのトレーニング後に使用可能になった場合にのみ予測を実行します。
次に、dataGatherLoop()
関数と同様に、現在の画像の画像機能を取得できます。基本的には、tf.browser.from pixels()
を使用してウェブカメラからフレームを取得し、正規化して、224 x 224 ピクセルにサイズ変更します。そして、そのデータを MobileNet モデルを介して渡して、画像特徴を取得します。
ただし、新しくトレーニングされたモデルヘッドを使用して、結果の imageFeatures
をトレーニング済みモデルの predict()
関数を介して渡すことで、実際に予測を実行できます。その後、結果のテンソルをスクイーズして 1 次元にし、prediction
という変数に代入します。
この prediction
では、argMax()
を使用して最も大きな値を持つインデックスを見つけ、そのテンソルを arraySync()
を使用して配列に変換して、JavaScript の基になるデータを取得して、最も価値の高い要素。この値は、highestIndex
という変数に格納されています。
prediction
テンソルで arraySync()
を直接呼び出しても、同じ方法で実際の予測信頼スコアを取得できます。
これで、STATUS
のテキストを prediction
のデータに更新する準備が整いました。人が読める形式のクラス文字列を取得するには、CLASS_NAMES
配列で highestIndex
を検索し、predictionArray
から信頼値を取得します。パーセンテージで読みやすくするには、100 を掛けて math.floor()
を掛けます。
最後に、準備ができたら window.requestAnimationFrame()
を使用して predictionLoop()
を再度呼び出して、動画ストリームのリアルタイム分類を取得できます。新しいデータで新しいモデルをトレーニングする場合は、predict
が false
に設定されるまで続行されます。
これがパズルの最終ピースになります。リセットボタンを実装する。
15. リセットボタンを実装する
もう少しで完了です!パズルの最後のピースは、リセットボタンからやり直すことです。現在空の reset()
関数のコードを以下に示します。次のように更新します。
script.js
/**
* Purge data and start over. Note this does not dispose of the loaded
* MobileNet model and MLP head tensors as you will need to reuse
* them to train a new model.
**/
function reset() {
predict = false;
examplesCount.length = 0;
for (let i = 0; i < trainingDataInputs.length; i++) {
trainingDataInputs[i].dispose();
}
trainingDataInputs.length = 0;
trainingDataOutputs.length = 0;
STATUS.innerText = 'No data collected';
console.log('Tensors in memory: ' + tf.memory().numTensors);
}
まず、predict
を false
に設定して、実行中の予測ループを停止します。次に、examplesCount
配列のすべての内容を長さを 0 に設定して削除します。これは、配列からすべてのコンテンツを消去するのに便利な方法です。
現在録音されている trainingDataInputs
をすべて調べ、それに含まれる各テンソルの dispose()
が再びメモリを解放できるようにします。テンソルは JavaScript ガベージ コレクタによってクリーンアップされません。
その後、trainingDataInputs
配列と trainingDataOutputs
配列の両方で配列の長さを 0 に安全に設定すれば、それらもクリアできます。
最後に、STATUS
テキストを適切なものに設定し、サニティ チェックとしてメモリに残っているテンソルを出力します。
MobileNet モデルと、定義した多層パーセプトは破棄されないため、まだ数百のテンソルがメモリ内に存在します。このリセット後に再度トレーニングする場合は、新しいトレーニング データを使用して再利用する必要があります。
16. 試してみましょう
今度は Teachable Machine のオリジナル バージョンを試しましょう!
ライブ プレビューに移動し、ウェブカメラを有効にして、教室内のオブジェクトについてクラス 1 用に少なくとも 30 個のサンプルを収集します。次に、別のオブジェクトに対してもクラス 2 について同じ操作を行い、[トレーニング] をクリックしてコンソールのログで進行状況を確認します。かなり速くトレーニングできます。
トレーニングが終了したら、オブジェクトをカメラに表示してライブ予測を取得します。この予測は、上部のウェブページのステータス テキスト領域に出力されます。問題が発生した場合は、完了した作業コードを確認して、コピーを見落としていないかどうかを確認してください。
17. 完了
これで、ブラウザでの TensorFlow.js の使用を開始して、最初の転移学習のサンプルを完了しました。
さまざまなオブジェクトで実際に試してみてください。特に他のオブジェクトと似ている場合は、他のものより認識しにくいものがあるかもしれません。クラスやトレーニング データを区別できるようにするには、追加する必要がある場合があります。
内容のまとめ
この Codelab では、以下のことを学びました。
- 転移学習の概要と利点は何ですか。
- TensorFlow Hub からモデルを再利用するための入手方法
- 転移学習に適したウェブアプリを設定する方法
- ベースモデルの読み込みと使用によって画像特徴を生成する方法。
- ウェブカメラの画像からカスタム オブジェクトを認識できる新しい予測ヘッドをトレーニングする方法。
- 結果のモデルを使用して、データをリアルタイムで分類する方法。
次のステップ
これで、実際に使える基礎的な知識を得ることができました。クリエイティブなアイデアを思いついたときは、この機械学習モデルをボイラープレートに実際に適用して応用できるものがないかと思います。 もし、あなたが現在働いている業界に革命を起こして、日常業務で重要なことを分類するよう社内の従業員がモデルをトレーニングできるようにしてもよいでしょうか。 できることは無限にあります。
さらに受講するには、こちらのコースを無料で受講することをおすすめします。この Codelab に現在参加している 2 つのモデルを 1 つのモデルにまとめ、効率化を図ることができます。
また、元の教育向けマシン アプリケーションの理論について詳しくは、こちらのチュートリアルをご覧ください。
作成したものを共有する
他のクリエイティブ ユースケースでも、今回作成した機能を簡単に拡張できます。また、既成概念にとらわれずに、引き続きハッキングを行うことをおすすめします。
ソーシャル メディアで #MadeWithTFJS ハッシュタグを使用すると、作成したプロジェクトが TensorFlow ブログや 今後のイベント。作品づくりを楽しみにしています。
参考になるウェブサイト
- TensorFlow.js の公式ウェブサイト
- TensorFlow.js の事前作成済みモデル
- TensorFlow.js の API
- TensorFlow.js の「Show & Tell」機能 - インスピレーションを得て、他のクリエイターの体験を見てみましょう。