TensorFlow.js Layers APIでカスタム画像分類CNNを定義・学習する:Python Kerasとの比較
TensorFlow.jsは、WebブラウザやNode.js環境で機械学習モデルの実行や学習を可能にするJavaScriptライブラリです。PythonでTensorFlowやKerasを使って機械学習モデルを開発されている方にとって、TensorFlow.jsのLayers APIは非常に馴染みやすく設計されています。このLayers APIを利用することで、PythonのKerasで行うのと同様のアプローチで、ブラウザやNode.js上で新しいモデルを定義し、データを使って学習させることができます。
本記事では、TensorFlow.jsのLayers APIを用いてカスタムの畳み込みニューラルネットワーク(CNN)モデルを定義し、ブラウザ上で簡単なデータセットを使って学習させる具体的なコード例とその解説を提供します。Python Kerasでのコードと比較しながら説明を進めることで、Pythonでの経験をTensorFlow.jsにスムーズに応用するための手助けとなることを目指します。
TensorFlow.js Layers APIとは
TensorFlow.jsには、モデルの構築やトレーニングを行うための高レベルAPIとしてLayers APIが提供されています。このAPIは、Pythonのtf.keras.layers
モジュールやtf.keras.Model
クラスにインスパイアされており、ニューラルネットワークを層(Layer)を積み重ねて構築するという、Kerasと同様の直感的で使いやすい方法を提供します。
Layers APIを使うことで、以下のような操作を容易に行うことができます。
- モデルの定義:
tf.sequential()
やtf.model()
を使って、層を積み重ねる形式(Sequentialモデル)や、より複雑なグラフ構造を持つモデルを定義できます。 - 層の追加:
tf.layers
モジュールから利用できる様々な層(conv2d
,dense
,maxPooling2d
,batchNormalization
など)をモデルに追加できます。 - モデルのコンパイル: オプティマイザ、損失関数、評価指標を指定してモデルをコンパイルし、学習の準備を行います。
- モデルの学習: データセットを使ってモデルを学習させます。
- モデルの評価: 学習済みモデルの性能を評価します。
- 推論の実行: 新しい入力データに対して推論を実行し、予測結果を得ます。
PythonでKerasを使った経験があれば、Layers APIの概念や使い方はすぐに理解できるでしょう。
Python KerasでのカスタムCNN定義例
まず、読者の皆様に馴染みのあるPython Kerasで、簡単な画像分類用のCNNモデルを定義するコードを見てみましょう。
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, InputLayer
# モデルの定義
model = Sequential([
InputLayer(input_shape=(28, 28, 1)), # 入力層 (例: 28x28画素のグレースケール画像)
Conv2D(filters=32, kernel_size=3, activation='relu', padding='same'),
MaxPooling2D(pool_size=2),
Conv2D(filters=64, kernel_size=3, activation='relu', padding='same'),
MaxPooling2D(pool_size=2),
Flatten(),
Dense(units=128, activation='relu'),
Dense(units=10, activation='softmax') # 出力層 (例: 10クラス分類)
])
# モデルのコンパイル
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'])
# モデルのサマリー表示
model.summary()
このコードは、畳み込み層、プーリング層、全結合層を積み重ねた典型的なCNNモデルを定義しています。Sequential
モデルを使用し、add
メソッド(ここではリストで渡していますが、model.add()
と同等です)を使って層を順に追加しています。最後に、オプティマイザ、損失関数、評価指標を指定してcompile
しています。
TensorFlow.js Layers APIでのカスタムCNN定義
それでは、上記のPython Kerasのモデルと同じ構造を持つCNNモデルを、TensorFlow.jsのLayers APIを使って定義してみましょう。
まず、TensorFlow.jsライブラリをインポートします。HTMLの<script>
タグで読み込むか、npmでインストールしてimport文を使用します。
// HTMLのScriptタグで読み込む場合
// <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest"></script>
// npmでインストールする場合
// npm install @tensorflow/tfjs
// import * as tf from '@tensorflow/tfjs';
Layers APIを使ったモデル定義のコードは以下のようになります。
import * as tf from '@tensorflow/tfjs';
// モデルの定義
const model = tf.sequential(); // Sequentialモデルを作成
// 層の追加
model.add(tf.layers.conv2d({ // 畳み込み層1
inputShape: [28, 28, 1], // 入力形状 (高さ, 幅, チャンネル)
filters: 32,
kernelSize: 3,
activation: 'relu',
padding: 'same' // Python Kerasの'same'パディングに対応
}));
model.add(tf.layers.maxPooling2d({ // プーリング層1
poolSize: 2
}));
model.add(tf.layers.conv2d({ // 畳み込み層2
filters: 64,
kernelSize: 3,
activation: 'relu',
padding: 'same'
}));
model.add(tf.layers.maxPooling2d({ // プーリング層2
poolSize: 2
}));
model.add(tf.layers.flatten()); // データを平坦化
model.add(tf.layers.dense({ // 全結合層1
units: 128,
activation: 'relu'
}));
model.add(tf.layers.dense({ // 全結合層2 (出力層)
units: 10, // クラス数
activation: 'softmax' // 多クラス分類の場合
}));
// モデルのコンパイル
model.compile({
optimizer: tf.train.adam(), // tf.trainモジュールからオプティマイザを取得
loss: 'categoricalCrossentropy', // 損失関数 (Pythonのcategorical_crossentropyに対応)
metrics: ['accuracy'] // 評価指標
});
// モデルのサマリーを表示 (Node.js環境や一部ブラウザ環境のコンソールで確認可能)
model.summary();
Python KerasのコードとTensorFlow.jsのコードを見比べていただくと、その構造が非常に似ていることがわかります。
tf.keras.models.Sequential
がtf.sequential()
に対応しています。model.add()
メソッドを使って層を追加する点も同様です。- 各層(
Conv2D
,MaxPooling2D
,Flatten
,Dense
)は、tf.layers
モジュールから取得し、ほぼ同じ引数名で設定できます。例えば、filters
,kernel_size
/kernelSize
,activation
,pool_size
/poolSize
,units
などです。 input_shape
は TensorFlow.js ではinputShape
となり、配列形式[高さ, 幅, チャンネル]
で指定します。- オプティマイザは
tf.train
モジュールから取得します(例:tf.train.adam()
)。Python Kerasのように文字列で指定できる場合もありますが、インスタンスを渡すのがより一般的です。 - 損失関数や評価指標も文字列で指定できますが、Python Kerasとは若干命名規則が異なる場合があります(例:
categorical_crossentropy
がcategoricalCrossentropy
に)。tf.lossesやtf.metricsモジュールから関数を取得して渡すことも可能です。
このように、Python Kerasでのモデル構築の知識があれば、TensorFlow.js Layers APIを使ったモデル定義も直感的に行うことができます。
データの準備
モデルを学習させるためにはデータが必要です。TensorFlow.jsでは、tf.Tensor
という形式でデータを扱います。画像データの場合、一般的には [サンプル数, 高さ, 幅, チャンネル数]
の形状を持つ4次元テンソルとして準備します。
ここでは、簡単なダミーデータを使って学習の準備をする例を示します。実際には、ImageDataオブジェクトからテンソルを作成したり、tf.data.csv
などの関数を使ってデータをロードしたりします。
import * as tf from '@tensorflow/tfjs';
// ダミーデータの生成 (例: 100枚の28x28グレースケール画像、10クラス分類)
const numSamples = 100;
const imgHeight = 28;
const imgWidth = 28;
const numChannels = 1;
const numClasses = 10;
// 特徴量データ (ランダムな値)
// 形状: [100, 28, 28, 1]
const xTrain = tf.randomNormal([numSamples, imgHeight, imgWidth, numChannels]);
// ラベルデータ (one-hot encoding形式)
// 形状: [100, 10]
// ランダムなクラスIDを生成し、one-hotに変換する例
const labels = tf.randomUniform([numSamples], 0, numClasses, 'int32'); // 0-9のランダムな整数
const yTrain = tf.oneHot(labels, numClasses); // one-hotエンコーディング
// バリデーションデータも同様に準備
const numValidationSamples = 20;
const xVal = tf.randomNormal([numValidationSamples, imgHeight, imgWidth, numChannels]);
const valLabels = tf.randomUniform([numValidationSamples], 0, numClasses, 'int32');
const yVal = tf.oneHot(valLabels, numClasses);
console.log('xTrain shape:', xTrain.shape);
console.log('yTrain shape:', yTrain.shape);
console.log('xVal shape:', xVal.shape);
console.log('yVal shape:', yVal.shape);
// データの解放(不要になったテンソルは解放してメモリを節約することが重要)
xTrain.dispose();
yTrain.dispose();
xVal.dispose();
yVal.dispose();
labels.dispose();
valLabels.dispose();
TensorFlow.jsでは、テンソルはデフォルトでガベージコレクションされません。不要になったテンソルはtensor.dispose()
メソッドを使って明示的に解放するか、tf.tidy()
を使ってスコープ内のテンソルを自動解放する必要があります。特に学習など大量のテンソルを扱う処理では、メモリ管理が重要になります。
モデルの学習(Fitting)
データの準備ができたら、model.fit()
メソッドを使ってモデルを学習させます。これはPython Kerasのmodel.fit()
とほぼ同じように動作します。
import * as tf from '@tensorflow/tfjs';
// 前のステップで定義・コンパイルしたmodelと準備したデータ (xTrain, yTrain, xVal, yVal) があるとする
async function trainModel(model, xTrain, yTrain, xVal, yVal) {
const history = await model.fit(xTrain, yTrain, {
epochs: 10, // エポック数
batchSize: 32, // バッチサイズ
validationData: [xVal, yVal], // バリデーションデータ
callbacks: tf.callbacks.earlyStopping({ // コールバックの例: 早期終了
monitor: 'val_loss', // 監視する評価指標
patience: 3 // 改善が見られないエポック数
}),
// 学習の進捗をコンソールに表示
callbacks: {
onEpochEnd: (epoch, log) => {
console.log(`Epoch ${epoch + 1}: loss = ${log.loss.toFixed(4)}, accuracy = ${log.acc.toFixed(4)}, val_loss = ${log.val_loss.toFixed(4)}, val_acc = ${log.val_acc.toFixed(4)}`);
}
}
});
console.log('学習完了');
// historyオブジェクトには、エポックごとの損失や評価指標の値が含まれる
// console.log(history.history);
}
// モデルの定義、コンパイル、データの準備コード (前述のコードをここに記述)
// ...
// ダミーデータを作成し直す例 (メモリ管理のためdisposeも行う)
const numSamples = 100;
const imgHeight = 28;
const imgWidth = 28;
const numChannels = 1;
const numClasses = 10;
const xTrain = tf.randomNormal([numSamples, imgHeight, imgWidth, numChannels]);
const labels = tf.randomUniform([numSamples], 0, numClasses, 'int32');
const yTrain = tf.oneHot(labels, numClasses);
const numValidationSamples = 20;
const xVal = tf.randomNormal([numValidationSamples, imgHeight, imgWidth, numChannels]);
const valLabels = tf.randomUniform([numValidationSamples], 0, numClasses, 'int32');
const yVal = tf.oneHot(valLabels, numClasses);
// モデル定義・コンパイルコード (前述のコードをここに記述)
const model = tf.sequential();
model.add(tf.layers.conv2d({ inputShape: [28, 28, 1], filters: 32, kernelSize: 3, activation: 'relu', padding: 'same' }));
model.add(tf.layers.maxPooling2d({ poolSize: 2 }));
model.add(tf.layers.conv2d({ filters: 64, kernelSize: 3, activation: 'relu', padding: 'same' }));
model.add(tf.layers.maxPooling2d({ poolSize: 2 }));
model.add(tf.layers.flatten());
model.add(tf.layers.dense({ units: 128, activation: 'relu' }));
model.add(tf.layers.dense({ units: 10, activation: 'softmax' }));
model.compile({
optimizer: tf.train.adam(),
loss: 'categoricalCrossentropy',
metrics: ['accuracy']
});
// 学習を実行
trainModel(model, xTrain, yTrain, xVal, yVal).then(() => {
console.log('トレーニングプロセス終了');
// 不要になったテンソルやモデルを解放
xTrain.dispose();
yTrain.dispose();
xVal.dispose();
yVal.dispose();
labels.dispose();
valLabels.dispose();
model.dispose(); // モデル自体もテンソルを保持しているので解放が必要
});
model.fit()
は非同期処理であるため、async/await
を使用するか、Promiseとして扱います。これはJavaScriptでの非同期プログラミングの一般的なパターンです。PythonのKerasとは異なり、学習の進捗表示などもコールバックを使って実装する必要があります。callbacks
オプションには、学習中の様々なイベントで実行される関数を指定できます。
Python Kerasとの違い・実践的な注意点
TensorFlow.js Layers APIはPython Kerasに酷似していますが、JavaScript環境特有の違いや注意点があります。
- 非同期処理: JavaScriptはシングルスレッドで非同期処理が基本です。
model.fit()
やmodel.predict()
、モデルのロード/保存など、時間のかかる処理は非同期で行われます。Promiseやasync/await
によるコーディングが必須となります。 - メモリ管理: 前述の通り、TensorFlow.jsではテンソルの明示的な解放が必要です。
dispose()
やtf.tidy()
を適切に使用しないと、メモリリークにつながる可能性があります。 - データ形式: PythonではNumPy配列が一般的ですが、TensorFlow.jsでは
tf.Tensor
を使用します。JavaScriptの通常の配列やTypedArrayからtf.Tensor
への変換(tf.tensor()
,tf.browser.fromPixels()
など)が必要です。 - 環境: ブラウザ環境では、計算はデフォルトでWebGLを利用してGPUで行われますが、環境によってはCPUにフォールバックします。Node.js環境では、デフォルトはCPUですが、
@tensorflow/tfjs-node-gpu
をインストールすればGPUを利用できます。期待通りのパフォーマンスが出ない場合は、利用されているバックエンド(tf.getBackend()
で確認可能)を確認し、必要に応じてtf.setBackend()
で変更を試みてください。 - デバッグ: Pythonのデバッガのような詳細なステップ実行は難しい場合がありますが、ブラウザの開発者ツールやNode.jsのデバッグ機能、そして
console.log
を使ったテンソルの値や形状の確認が基本的なデバッグ手法となります。
これらの違いを理解しておくことで、よりスムーズに開発を進めることができます。
まとめ
本記事では、TensorFlow.jsのLayers APIを使用して、カスタムの画像分類CNNモデルをJavaScriptで定義し、学習させる方法を解説しました。Python Kerasのコードと比較することで、Layers APIがKerasの概念を忠実に引き継いでおり、Python経験者にとって学習コストが低いことを示しました。
TensorFlow.js Layers APIを使えば、既存のPython Kerasモデルを変換するだけでなく、ブラウザやNode.js環境で直接新しいモデルを構築・実験することも可能です。これにより、ユーザーのデバイス上でのオンライントレーニングや、Python環境なしでのモデル開発といった、新たな可能性が開かれます。
今後は、より複雑なモデルの定義、実際のデータセットを使った学習、モデルの保存・ロード方法の詳細、そしてNode.js環境での学習といったトピックにも触れていくことで、TensorFlow.js Layers APIの活用範囲をさらに広げていくことができるでしょう。
この記事が、Python Kerasの知識を活かしてTensorFlow.jsでのモデル構築・学習に取り組む皆様の一助となれば幸いです。