カテゴリー
【tensorflowjs x kerasの使い方】tensorflowjsで初めてのLSTM入門
※ 当ページには【広告/PR】を含む場合があります。
2020/12/25
2022/08/18
データ処理は全部シェルで! tensorflowjsは解析だけに注力
実践的Awkの使い方ガイド AWK実践入門
実践例〜正弦波の推定
Awkでデータセットの生成
$ awk -v seed="${RANDOM}" '
function sin_with_noise(ampl_, arr_len_, period_) {
PI = 3.14159265359;
count = 0;
while(count < arr_len_) {
x1 = rand();
tmp_rand_val = 2.0 * (x1 - 0.5);
label_arr[count] = sin(2 * PI * count / period_) + ampl_ * tmp_rand_val;
count++;
}
}
BEGIN{
OFS=",";
srand(seed);
sin_with_noise(0.05, 200, 100);
arr_len = length(label_arr);
for (i = 0; i < arr_len; i++) {
print i, label_arr[i];
}
}
' > sin_rawdata.csv
sin_rawdata.csv
LSTM用のデータセット整形
ユニット
ステップ
シークエンス
sin_rawdata.csv
$ cat sin_rawdata.csv
0,0.0469849
1,0.0711396
2,0.0916004
3,0.170817
4,0.265518
5,0.268593
6,0.324074
#...中略
199,-0.022768
#ステップ0,ステップ1,ステップ2,ステップ3,ステップ4
0.0469849,0.0711396,0.0916004,0.170817,0.265518
0.0711396,0.0916004,0.170817,0.265518,0.268593
0.0916004,0.170817,0.265518,0.268593,0.324074
#...以下略
sin_rawdata.csv
$ cat sin_rawdata.csv | awk -v stepsize=25 -F "," '
BEGIN { OFS="," }
{ original_arr[$1] = $2; }
END {
arr_len = length(original_arr);
for (j = 0; j < arr_len - stepsize + 1; j++) {
rslt = original_arr[j];
for (i = 1 ; i < stepsize ; i++) {
rslt = rslt "," original_arr[j+i]
}
print rslt;
}
}
' > sin_lstm.csv
sin_lstm.csv
$ cat dist/sin_lstm.csv
0.0469849,0.0711396,0.0916004,0.170817,0.265518,0.268593,0.324074,0.396819,0.472588,0.57795,0.561532,0.620256,0.725495,0.715577,0.798626,0.777591,0.808541,0.88629,0.887139,0.907561,0.998604,0.94918,0.995441,0.979329,0.990558
0.0711396,0.0916004,0.170817,0.265518,0.268593,0.324074,0.396819,0.472588,0.57795,0.561532,0.620256,0.725495,0.715577,0.798626,0.777591,0.808541,0.88629,0.887139,0.907561,0.998604,0.94918,0.995441,0.979329,0.990558,0.954538
#...以下略
正解ラベル列のデータセット
fit
model.fit(
inputs, // [N x 25 x 1]
label, // [N x 1]
{
//...オプション以下略
入力:
[batchSize, timesteps, inputDim]
出力:
returnSequencesがfalse(デフォルト):
[batchSize, units]
returnSequencesがTrue:
[batch_size, timesteps, units]
ステップ数 + 1
学習用データの分ける意味
機械学習の基本と関連する技術をバランス良く学ぶ 図解即戦力 機械学習&ディープラーニングのしくみと技術がこれ1冊でしっかりわかる教科書
tensorflowjsの学習モデルによるデータ解析
calcLstm
import * as tf from '@tensorflow/tfjs';
async function calcLstm(
rawData: number[][],
testData: number[][]
) {
//①シーケンシャルモデルを構築
const model = tf.sequential();
model.add(tf.layers.lstm({
units: 32,
returnSequences: false,
inputShape: [25, 1],
}));
model.add(tf.layers.dense({ units: 128, activation: "relu" }));
model.add(tf.layers.dense({ units: 1, activation: "linear", useBias: true }));
model.summary();
model.compile({
optimizer: tf.train.adam(3e-4),
loss: tf.losses.meanSquaredError,
metrics: ['accuracy']
});
//②生のデータセット配列から入力データとラベルデータをテンソル形式で返す関数
//ついでにシャッフルも行う
//(実装は後述)
const {inputs, labels} = await convertToTensor(rawData);
//③モデルの訓練を開始
console.log('Training started...');
await model.fit(inputs, labels, {
epochs: 50,
shuffle: false,
callbacks: [
tf.callbacks.earlyStopping({
monitor: 'loss',
mode: 'auto',
patience: 20
}),
new tf.CustomCallback({
onEpochEnd: async(epoch: any, logs: any) => {
console.log(`Epoch#${epoch} : Loss(${logs.loss}) : acc(${logs.acc})`);
},
})
]
});
console.log('Training has done.');
//④訓練済みのモデルで予想データを計算(検証用)
//実装は後述
const inputs4eval = await convertToTensorForValidation(testData);
return await testModel(model, inputs4eval, 25);
}
convertToTensor
convertToTensorForValidation
testModel
// 元データの配列の列の長さは26で固定
// [0:24] > LSTM層の入力データ, [25] > ラベル列
async function convertToTensor(data: number[][], stepNum: number = 26): any {
return tf.tidy(() => {
tf.util.shuffle(data);
const [input2d, labelTensor] = tf.tensor2d(data, [data.length, stepNum]).split([stepNum - 1, 1], 1);
const inputTensor: any = (input2d as tf.Tensor).reshape([data.length, stepNum - 1, 1]);
const inputMax = inputTensor.max();
const inputMin = inputTensor.min();
const labelMax = labelTensor.max();
const labelMin = labelTensor.min();
// それぞれの最大・最小でデータ正規化(数値0から1の範囲へ変換)を行う
// =Leru等の非負数を出力にする活性化関数を適用させる目的
const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin));
return {
inputs: normalizedInputs,
labels: normalizedLabels,
inputMax,
inputMin,
labelMax,
labelMin
}
});
}
//こちらはシャッフル無しの検証用テンソルを返す
//こちらはLSTMモデルに入力するための検証用データセット(25列使用)を利用
async function convertToTensorForValidation(
data: number[][],
stepNum: number = 25
) {
return tf.tidy(() => {
const input2d = tf.tensor2d(postData, [postData.length, stepNum]);
const inputTensor = input2d.reshape([postData.length, stepNum, 1]);
const inputMax = inputTensor.max();
const inputMin = inputTensor.min();
inputMax.print();
inputMin.print();
// それぞれの最大・最小でデータ正規化(数値0から1の範囲へ変換)を行う
const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
return {
inputs: normalizedInputs,
inputMax,
inputMin
}
});
}
async function testModel(
model: any,
normalizationData: any, // 入力データは正規化した集合[0,1]^Nであることに注意
offset: number = 1 //x軸方向のオフセット(チャート描画用)
) : Array<{x: number, y: number}> {
const {
inputs, //正規化済みの集合I: [0,1]^N
inputMax,
inputMin
} = normalizationData;
const [preds] = tf.tidy(() => {
const preds = model.predict(inputs);
const unNormPreds = preds.mul(inputMax.sub(inputMin)).add(inputMin);
return [unNormPreds.dataSync()];
});
const predictedPoints = Array.from(preds).map((val, i) => {
return {
x: i + offset,
y: val as number
}
});
return validatedPoints;
}
calcLstm
import * as tf from '@tensorflow/tfjs';
const data4Train: number[][] = //...訓練用の時系列入力データ&ラベル列を持つcsvから配列を返す関数
const data4Test: number[][] = //...モデル評価用に時系列入力データ列を持つcsvから配列を返す関数
const result = await calcLstm(data4Train, data4Test); //検証用の予想データを得られる
ラグモデル(Lag model)
_________________________________________________________________
Layer (type) Output shape Param #
=================================================================
lstm_LSTM1 (LSTM) [null,32] 4352
_________________________________________________________________
dense_Dense1 (Dense) [null,128] 4224
_________________________________________________________________
dense_Dense2 (Dense) [null,1] 129
=================================================================
Total params: 8705
Trainable params: 8705
Non-trainable params: 0
_________________________________________________________________
Training started...
Epoch#0 : Loss(0.15472638607025146) : mse(0.30945494771003723)
Epoch#1 : Loss(0.11926285922527313) : mse(0.23852571845054626)
Epoch#2 : Loss(0.0869017168879509) : mse(0.1738034337759018)
#...中略
Epoch#49 : Loss(0.00028000862221233547) : mse(0.0005600172444246709)
Training has done.
done!
数周期先までの予想
+ LSTM層のユニット数はあまり増やし過ぎても精度は向上しない。
100個も以上はあまり意味がないように感じた
+ (入力データの正規化前提の話で)LSTM層以下の中間層にLeruを
活性化関数に仕込んでユニット数を増やした時が一番精度が良さそうだった
...何故でしょう
+ 最適化はADAMを使って、学習率を1e-4程度に持っていく方が
安定して損失関数が下がっていった
+ 損失関数は色々変えてみたがこの場合はmeanSquaredError
で弄らない方が良さそう
+ 過学習を抑えるためにearlyStoppingを仕込んでいるが、
とりあえずpatienceが20程度で落ち着いた
まとめ
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー