カテゴリー
【tensorflowjs x kerasの使い方】機能(Functional)モデルからソルバーを最適化する〜非線形回帰解析③
※ 当ページには【広告/PR】を含む場合があります。
2020/12/16
2022/08/18
fit無しでも最適化 ~ 機能モデルのススメ
機能モデル
Fitメソッド
Optimizer.minimizeメソッド
+ 学習モデルの作成:
機能モデルはtf.modelで作成する。
+ レイヤーの追加:
機能モデルはapplyによって直列でも並列でも如何様にも接続できる。
+ 最適化の手順:
機能モデルでの最適化とは、損失関数をtf.variableGradsから偏微分し、
optimizer.applyGradientsからモデルへ変分量を渡す作業を指す。
+ レイヤーの再構築:
学習済のレイヤだけを抽出・再構成して、ミニマルなソルバーモデル(完成品)だけを
別のモデルとして切り出し、そのモデルで予測させることもできる。
非線形回帰には機能モデルを使うべし
シーケンシャルモデル
tf.sequential
add
const model = tf.sequential();
model.add(tf.layers.dense({inputShape: [4], units: 8, activation: 'relu'}));
model.add(tf.layers.dense({units: 2, activation: 'softmax'}));
tf.sequential
layers
const model = tf.sequential({
layers: [
tf.layers.dense({inputShape: [3], units: 16}),
tf.layers.dense({units: 4, activation: 'tanh'}),
]
});
inputShape
[[19.1, 14.5, 13.0], [15.1, 11.1, 13.3], ...]
[100, 3]
inputShape: [3]
batchInputShape
batchSize
+ ウェブ検索で判例がたくさん出てくるので、tensorflow.js & kerasの初学者には便利
+ 公式のtensorflow.jsのAPIリファレンスにも詳しく解説してあるので用法が理解しやすい
+ 学習モデルのレイヤー構造が直感で分かりやすい
- 学習レイヤーを入力層から出力層まで直列にしか繋げない単純な構造しか作れない
- そのため複雑なアルゴリズムをもつ深層学習モデルを作るのが難しい
- 数学的なモデルを解くとき、解析的な手法での求解にしか向かない
機能モデル
機能モデル
tf.model
tf.model
const input = tf.input({shape: [3]});
const dense1 = tf.layers.dense({units: 16, activation: 'relu'}).apply(input);
const dense2 = tf.layers.dense({units: 3, activation: 'softmax'}).apply(dense1);
const model = tf.model({inputs: input, outputs: dense2});
apply
apply
tf.input
apply
const t = tf.tensor([-2, 1, 0, 5]);
const o = tf.layers.activation({activation: 'relu'}).apply(t);
o.print(); // [0, 1, 0, 5]
predict
const x = tf.input({shape: [32]});
const y = tf.layers.dense({units: 2, activation: 'softmax'}).apply(x);
const model = tf.model({inputs: x, outputs: y});
model.apply(tf.ones([3, 32])).print();
// model.predict(tf.ones([3, 32])).print();としても作用としては同じ
// [[0.0701714, 0.9298285], [0.0701714, 0.9298285], [0.0701714, 0.9298285]]
+ より複数で高度なモデルが構築できる
+ 偏微分方程式などのような数学的なモデルの数値解を求めるときに便利
- ウェブ検索ではあまりサンプルコードが見つからない
- 低レベルAPIのため公式のAPIリファレンスでもほとんど解説がない
- 複雑な学習モデルを作れるが、複雑にしすぎるとレイヤー間がサイクル接続してしまう恐れがある
機能モデルでの訓練
loss
const loss = () => tf.tidy(() => {
//👇trainingを有効にした変数がOptimizerに渡されるとモデル内の重みが補正・更新される
const predictedTensor = model.apply(inputTensor, {training: true});
return actualLabelTensor.sub(predictedTensor).square().mean().asScalar();
});
tf.variableGrads
// モデルを100回訓練する
for (let i = 0; i < 100; i++) {
const { value, grads } = tf.variableGrads(loss);
//👇optimizerがloss関数内で訓練可能な変数を見つけ、
// モデル内の重み係数を補正してれる
optimizer.applyGradients(grads);
}
{value: tf.Scalar, grads: {[name: string]: tf.Tensor}}
applyGradients
grads
minimize
機械学習の基本と関連する技術をバランス良く学ぶ 図解即戦力 機械学習&ディープラーニングのしくみと技術がこれ1冊でしっかりわかる教科書
線型回帰問題を解き直してみる
linearRegression
import * as tf from '@tensorflow/tfjs';
export async function linearRegression(rawData) {
//👇①機能モデルで書き換え
const inputTensor = tf.input({shape: [2], name: 'inputXY'});
const breeder = tf.layers.dense({units: 8, activation: 'sigmoid', name: 'breeder'}).apply(inputTensor);
const condenser = tf.layers.dense({units: 1, activation: 'linear', useBias: false, name: 'condenser'}).apply(breeder);
const trainee = tf.layers.dense({
units: 1,
activation: 'linear',
useBias: true,
name: 'trainee',
kernelInitializer: tf.initializers.constant({value: -1.0}),
biasInitializer: tf.initializers.constant({value: 0.8}),
}).apply(condenser);
const model = tf.model({inputs: inputTensor, outputs: trainee});
console.log('Training started.');
const originalData = [], labels = [];
for (const row in rawData) {
originalData.push({x: parseFloat(rawData[row][4]), y: parseFloat(rawData[row][0])});
labels.push(rawData[row][10]);
};
//👇②テンソルに変換+データの標準化
const tensorData = convertToTensor(originalData);
//👇③機能モデルのトレーニング
await trainModel(model, tensorData.xyInputs);
console.log('Training has done.');
model.summary();
//👇④訓練後のモデルでテスト
//traineeレイヤー > model.layers[3]
const predictedData = testModel(model.layers[3], tensorData);
console.log('Fitting has done.');
return { labels, originalData, predictedData };
}
///...以下略
//👇①機能モデルで書き換え
const inputTensor = tf.input({shape: [2], name: 'inputXY'});
const breeder = tf.layers.dense({units: 8, activation: 'sigmoid', name: 'breeder'}).apply(inputTensor);
const condenser = tf.layers.dense({units: 1, activation: 'linear', useBias: false, name: 'condenser'}).apply(breeder);
const trainee = tf.layers.dense({
units: 1,
activation: 'linear',
useBias: true,
name: 'trainee',
kernelInitializer: tf.initializers.constant({value: -1.0}),
biasInitializer: tf.initializers.constant({value: 0.8}),
}).apply(condenser);
const model = tf.model({inputs: inputTensor, outputs: trainee});
inputXY
[0,1]
breeder
condenser
[N x 1]
trainee
[N x 1]
convertToTensor
// 学習データをTensor型に変換する関数
function convertToTensor(data) {
return tf.tidy(() => {
tf.util.shuffle(data);
const inputs = data.map(d => d.x)
const labels = data.map(d => d.y);
const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]);
const labelTensor = tf.tensor2d(labels, [labels.length, 1]);
const inputMax = inputTensor.max();
const inputMin = inputTensor.min();
const labelMax = labelTensor.max();
const labelMin = labelTensor.min();
const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin));
//👇形状[N x 2]のマトリックス化
const normalizedXYInputs = tf.stack([normalizedInputs, normalizedLabels], -1);
return {
inputs: normalizedInputs,
labels: normalizedLabels,
inputMax,
inputMin,
labelMax,
labelMin,
xyInputs: normalizedXYInputs //👈新しいフィールドとして追加
}
});
}
[N x 2]
trainModel
async function trainModel(model, inputs) {
const learningRate = 0.01;
const optimizer = tf.train.sgd(learningRate);
//👇カスタム損失関数の定義: () => tf.Scalarの関数シグネチャに合わせる必要がある
const customLossFunction = () => tf.tidy(() => {
const qs = model.apply(inputs, {training: true});
//👇predictとしてもOK
//const qs = model.predict(inputs);
//形状[N x 2]の生データからy軸データを抽出
const labelList = [];
inputs.dataSync().forEach((e, i) => {
if(i%2 === 1) { labelList.push(e);}
});
//tf.lossesクラスの各損失関数に入力する際には形状[N x 1 x 1]
//に整形しておく必要がある
const labels = tf.tensor(labelList, [labelList.length, 1, 1]);
return tf.losses.meanSquaredError(labels, qs).asScalar();
});
// モデルを訓練する
const epochs = 1000;
for (let i = 0; i < epochs; i++) {
console.log(`TRAINING #${i}`);
//👇先程の損失関数を使ってvariableGradsから偏微分を実行する
const grads = tf.variableGrads(customLossFunction);
//👇偏微分量grads.gradsに従ってapplyGradientsメソッドを介して
// optimizerがmodel内の各ユニットを最適化してくれる
optimizer.applyGradients(grads.grads);
//gradsはもう要らないのでメモリをクリア
tf.dispose(grads);
}
}
testModel
function testModel(trainedLayer, normalizationData) {
const {inputMax, inputMin, labelMin, labelMax} = normalizationData;
const [xs, preds] = tf.tidy(() => {
const xs = tf.linspace(0, 1, 100);
//👇訓練済みレイヤーの名前
console.log(`Layer Name: ${trainedLayer.name}`);
//👇標準化前の情報
console.log(`Xmin ${inputMin.dataSync()}: Xmax ${inputMax.dataSync()}; Ymin ${labelMin.dataSync()}: Ymax ${labelMax.dataSync()}`);
const layerWeights = trainedLayer.getWeights();
for (let i = 0; i < layerWeights.length; i++) {
//👇y = ax + bのうち順にa、bの重み係数
layerWeights[i].print();
}
//👇訓練済みのレイヤーから出力を取り出す
const preds: any = trainedLayer.apply(xs.reshape([100, 1]));
const unNormXs = xs.mul(inputMax.sub(inputMin)).add(inputMin);
const unNormPreds = preds.mul(labelMax.sub(labelMin)).add(labelMin);
return [unNormXs.dataSync(), unNormPreds.dataSync()];
});
const predictedPoints = Array.from(xs).map((val, i) => {
return {
x: val,
y: preds[i]
}
});
return predictedPoints;
}
#...中略
TRAINING #999
Tensor
0.0363294892013073
Training has done.
_________________________________________________________________
Layer (type) Output shape Param #
=================================================================
inputXY (InputLayer) [null,2] 0
_________________________________________________________________
breeder (Dense) [null,8] 24
_________________________________________________________________
condenser (Dense) [null,1] 8
_________________________________________________________________
trainee (Dense) [null,1] 2
=================================================================
Total params: 34
Trainable params: 34
Non-trainable params: 0
_________________________________________________________________
Tensor
[[-1.0087168],]
Tensor
[0.7074651]
Layer Name: trainee
Xmin 1613: Xmax 5140; Ymin 9: Ymax 46.599998474121094
機械学習の基本と関連する技術をバランス良く学ぶ 図解即戦力 機械学習&ディープラーニングのしくみと技術がこれ1冊でしっかりわかる教科書
まとめ
+ 学習モデルの作成:
シークエンシャルモデルではtf.sequentialで作成し、
機能モデルはtf.modelで作成する
+ レイヤーの追加:
シークエンシャルモデルはaddでモデルに直列に追加するしかないが、
機能モデルはapplyによって直列でも並列でも自由に接続可能
+ 最適化の手順:
シークエンシャルモデルは損失関数やオプティマイザをcompileして、
model.fitかoptimizer.minimizeで最適化処理を行うが、
機能モデルでは損失関数をtf.variableGradsから偏微分し、
optimizer.applyGradientsからモデルへ変分量を渡す
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー