05
コードのポイント解説
初めて TensorFlow.js を触る方でも理解しやすいよう、重要な部分をステップ別に解説します。
データの正規化(Min-Max スケーリング)
// 時系列データを 0〜1 の範囲に変換する
function normalize(data) {
const min = Math.min(...data);
const max = Math.max(...data);
return {
normalized: data.map(v => (v - min) / (max - min)),
min, max
};
}
// 予測結果を元のスケールに戻す(逆正規化)
function denormalize(value, min, max) {
return value * (max - min) + min;
}
ニューラルネットワークは 0〜1 のような小さな数値を扱うのが得意です。
生の数値(例:株価 3万円台)をそのまま入れると学習が不安定になるため、まずスケーリングを行います。
AutoEncoder モデルの構築
function buildAutoEncoder(windowSize) {
const model = tf.sequential();
// ── Encoder(圧縮側)──
model.add(tf.layers.dense({
inputShape: [windowSize],
units: 8, // 20次元 → 8次元に圧縮
activation: ‘relu’
}));
model.add(tf.layers.dense({
units: 4, // 8次元 → 4次元にさらに圧縮(ボトルネック)
activation: ‘relu’
}));
// ── Decoder(復元側)──
model.add(tf.layers.dense({
units: 8, // 4次元 → 8次元に展開
activation: ‘relu’
}));
model.add(tf.layers.dense({
units: windowSize, // 元のサイズに戻す
activation: ‘sigmoid’
}));
model.compile({
optimizer: ‘adam’,
loss: ‘meanSquaredError’ // 入力と復元の差を最小化
});
return model;
}
Encoder 部分で次元を絞ることで、データの「本質的なパターン」だけを抽出します。
この圧縮されたボトルネック(4次元)が、正常パターンの記憶として機能します。
meanSquaredError(平均二乗誤差)が学習の目標で、これが最終的にアノマリースコアにもなります。
学習の実行(非同期・進捗表示付き)
await model.fit(trainTensor, trainTensor, {
// 入力と正解が同じ(= 自己符号化)
epochs: epochCount,
batchSize: 32,
validationSplit: 0.1,
callbacks: {
onEpochEnd: async (epoch, logs) => {
// エポックごとに損失を表示(学習の進み具合を確認できる)
setStatus(`学習中... Epoch ${epoch + 1} / Loss: ${logs.loss.toFixed(6)}`);
await tf.nextFrame(); // UIがフリーズしないよう制御を返す
}
}
});
tf.nextFrame()が重要です。
JavaScriptはシングルスレッドなので、重い計算中はUIが固まります。
各エポックの終わりにメインスレッドに制御を返すことで、進捗表示がリアルタイムに更新されます。
再構成誤差の計算とアノマリー判定
async function computeErrors(model, windows) {
const inputTensor = tf.tensor2d(windows);
const outputTensor = model.predict(inputTensor);
const input = await inputTensor.array();
const output = await outputTensor.array();
// 各ウィンドウの MSE(平均二乗誤差)を計算
return input.map((win, i) => {
const mse = win.reduce((sum, v, j) =>
sum + (v - output[i][j]) ** 2, 0
) / win.length;
return mse;
});
}
// 閾値を超えたらアノマリー
const anomalies = errors.map(e => e > threshold);
MSE(Mean Squared Error)は「入力と復元の差の二乗平均」です。
正常データは小さく、異常データは大きくなります。
スライダーで閾値を調整することで、どこからを「異常」と見なすかをリアルタイムに変えられます。