- 投稿日:2019-05-12T16:27:50+09:00
TensorFlow.jsでDeepLearning(Making Predictions from 2D Data)
こんにちわ。Electric Blue Industries Ltd.という、ITで美を追求するファンキーでマニアックなIT企業のマッツと申します。TensorFlow.jsでDeepLearningのチュートリアル「Making Predictions from 2D Data」の詳細解説の前半です。
これはTensorFlow.JSの公式サイトにある「TensorFlow.js — Making Predictions from 2D Data」をコードの中に記載したコメントで詳細に解説したものです。解説の利便性によりコードの部分の位置関係は変更してありますが、内容に変化はありません。実際に動作するデモはこちらで見られます。
1. コード
1.1. html
ライブラリを読み込んでDeep LearningのためのJavaScriptを実行するHTMLです。
展開してコードを見る
index.html<html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TensorFlow.js Tutorial - Making Predictions from 2D Data</title> <!-- Import TensorFlow.js (TensorFlow.jsライブラリ本体を読み込みます) --> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0/dist/tf.js"></script> <!-- Import tfjs-vis (TensorFlow.js向けの可視化ライブラリを読み込みます) --> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis@1.0.2/dist/tfjs-vis.umd.min.js"></script> <!-- Import the main script file (下記に解説するJavaScriptを読み込みます)--> <script type="text/javascript" src="script.js"></script> </head> <body> </body> </html>1.2. JavaScript
上記のHTMLにインクルードされてDeep Learning処理を行うJavaScriptです。
展開してコードを見る
script.js/****************************************************************** TensorFlow.js — Making Predictions from 2D Data url: https://codelabs.developers.google.com/codelabs/tfjs-training-regression/index.html#0 filename: script.js copyrighted to: tensorflow.org composed by: Mats (Electric Blue Industries Ltd.) description: 車のスペック情報から「燃費」「馬力」のデータを学習し、それらの相関を学習させる ******************************************************************/ async function run() { //****************************************************************** // 1. getData: 元データをgetDataを使って読み込み対象アイテムのみフィルター(元データプロット用) //****************************************************************** // 非同期で元データを取得(getDataは下で関数として定義) // なお、取得するデータは [{0:a, 1:b, 2:c}, {0:d, 1:e, 2:f}]のように、各クルマのスペック情報を持つオブジェクトが要素になった配列データ const data = await getData(); // 読み込んだ元データであるdataから項目抽出して、座標情報オブジェクトを各要素とする配列として新たに格納 const values = data.map(d => ({ x: d.horsepower, y: d.mpg, })); // TensorFlowJSの可視化ライブラリで元データの位置をプロットする tfvis.render.scatterplot( // 表のタイトルの指定 {name: 'Horsepower vs Miles Per Gallon'}, // 上記で生成した座標情報オブジェクトを各要素とする配列を指定 {values}, // 表のx軸y軸のタイトルおよび表の高さを指定 { xLabel: 'Horsepower', yLabel: 'Miles Per Gallon', height: 300 } ); // 元データを非同期で読み込む関数 async function getData() { // 非同期でクルマのスペックデータ(内容はオブジェクトを要素に持つ配列のフォーマットをしている)を取得しcarsDaraに格納 const carsDataReq = await fetch('https://storage.googleapis.com/tfjs-tutorials/carsData.json'); // 読み込んだデータをJSON形式として読んで各クルマのスペック(オブジェクト)を要素として持つ配列carsDataとして格納 const carsData = await carsDataReq.json(); // 格納した配列carsDataデータから元データとして用いる2アイテムのみを取得しcleanedとして格納 // map関数は配列の各要素に繰り返し指定された処理を行うので、cleanedはmpgとhorsepowerの情報のみを含むオブジェクトを要素とする配列 // なお、mpgとhorsepowerのどちらかでも空文字の場合はデータから削除しておくfilterをかける const cleaned = carsData.map(car => ({ mpg: car.Miles_per_Gallon, horsepower: car.Horsepower, })) .filter(car => (car.mpg != null && car.horsepower != null)); return cleaned; } //****************************************************************** // 2. createModel: モデルの枠組みの作成 //****************************************************************** // モデルを作成(createModelは下で関数として定義) const model = createModel(); // 上記で作成したモデルの要約情報(Layer Name, Output Shape, # Of Params, Trainable)表示 tfvis.show.modelSummary({name: 'Model Summary'}, model); // モデル作成の関数定義 function createModel() { // シーケンシャルモデル(線形回帰モデル)の枠組みの作成 // これはモデルが全体として線形回帰モデルになるという意味でなく、各ニューロンの入出力の関係が y=Σ(wx)+b と書ける線形回帰モデルであるということ const model = tf.sequential(); // 入力層を追加 model.add(tf.layers.dense({ // 入力は 1x1 のテンソル(=スカラー) inputShape: [1], // ユニット(別名:ノード)は1個だけ units: 1, // y=Σ(wx)+b となる定数項bであるバイアスを使用する useBias: true })); // ここに中間層を追加した場合にはどうなるのかは別途に言及する // 出力層を追加 model.add(tf.layers.dense({ // ユニット(別名:ノード)は1個だけ units: 1, // y=Σ(wx)+b となる定数項bであるバイアスを使用する useBias: true })); return model; } //****************************************************************** // 3. convertToTensor: 学習データを上記モデルに流し込めるようテンソルに変換する //****************************************************************** // getDataで取得したclean済み配列データを下記で定義したconvertToTensor関数でテンソルに変換(不要なアイテムは同時にフィルター) const tensorData = convertToTensor(data); const {inputs, labels} = tensorData; // 学習データをテンソルに変換する function convertToTensor(data) { // tidyを使って計算することで、計算経過で生成される変数をメモリから削除してメモリにゴミを残さない return tf.tidy(() => { // (ステップ1) データをシャッフルする tf.util.shuffle(data); // (ステップ2) データを配列に格納してから展開してテンソルに変換 // 変数inputに馬力に関するデータ(実際は配列)を格納 const inputs = data.map(d => d.horsepower) // 変数labelsに燃費に関するデータ(実際は配列)を格納 const labels = data.map(d => d.mpg); // 上記で作成したインプット値(馬力)の配列を使って Nx1 の行列を生成 const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]); // 上記で作成される2次元テンソルは下記のような縦長の形 // [[馬力の値1], // [馬力の値2], // : // [馬力の値N]] // 上記で作成したラベル値(燃費)配列を使って Nx1 の行列を生成 const labelTensor = tf.tensor2d(labels, [labels.length, 1]); // 上記で作成される2次元テンソルは下記のような縦長の形 // [[燃費の値1], // [燃費の値2], // : // [燃費の値N]] // (ステップ3) 入力データの値を0から1の間に正規化 // 入力とラベルを最大値と最小値を調べて取得 // 入力である馬力の最大値を取得 const inputMax = inputTensor.max(); // 入力である馬力の最小値を取得 const inputMin = inputTensor.min(); // 出力である燃費の最大値を取得 const labelMax = labelTensor.max(); // 出力である燃費の最小値を取得 const labelMin = labelTensor.min(); // 入力とラベルを正規化 // inputTensor.sub(inputMin)で各入値から最小入力値をひく(=最小値をゼロに落とす) >> (これをAとする) // inputMax.sub(inputMin)で最大入力値から最小入力値をひく >> (これをBとする) // 上記の各(A)の値を(B)で割ることで、最大値が1で最小値が0になるよう正規化する const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin)); // labelTensor.sub(labelMin)で各入値から最小入力値をひく(=最小値をゼロに落とす) >> (これをA'とする) // labelMax.sub(labelMin)で最大入力値から最小入力値をひく >> (これをB'とする) // 上記の各(A')の値を(B')で割ることで、最大値が1で最小値が0になるよう正規化する const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin)); return { // 正規化された値を要素に持つ入力テンソルと出力(ラベル)テンソルを返す inputs: normalizedInputs, labels: normalizedLabels, // 入力と出力(ラベル)の最大値最小値もあとで逆正規化できるよう返す inputMax, inputMin, labelMax, labelMin, } }); } //****************************************************************** // trainModel: モデルの学習 //****************************************************************** // モデルの学習(trainModelは下で関数として定義) // awaitとすることでtrainModel関数からreturnがあるまで待機する await trainModel(model, inputs, labels); // モデル・入力(インプット)テンソル・出力(ラベル)テンソルを指定してモデルの学習を行う関数 async function trainModel(model, inputs, labels) { // 学習実行のため、学習方法を指定してモデルをコンパイル model.compile({ // 最適化法をアダム(=適応モーメント推定法)に指定 optimizer: tf.train.adam(), // 損失関数をMSE(=平均二乗誤差)に指定 loss: tf.losses.meanSquaredError, // 学習とテストに用いる指標(この場合は平均二乗誤差)を表す表現を決める metrics: ['mse'], }); // バッチサイズ(小分けにグループ分けした学習データに含まれるデータの個数)を28個とする const batchSize = 28; // 学習の1手順の回数を50回とする const epochs = 50; // epochsで指定した回数の学習手順回数(エポック)になるまで学習を実行する return await model.fit(inputs, labels, { batchSize, epochs, shuffle: true, // 学習結果の随時の描画用にTFVISに、コンパイル時に指定した指標の値をコールバックする指定 callbacks: tfvis.show.fitCallbacks( // 描画する表のタイトル { name: 'Training Performance' }, // 描画する指標(ここではlossとmseを改めて指定) ['loss', 'mse'], { // 表の高さ height: 200, // コールバックのタイミング callbacks: ['onEpochEnd'] } ) }); } // trainModel関数に返り値があった時点で規定したエポックが終了したことになるので「学習が終わった」とコンソールに出力 console.log('Done Training'); //****************************************************************** // testModel: 学習ずみモデルに入力を与えて出力を得て、元データのプロットと重ねて違いを視覚的に見せる //****************************************************************** // モデルのテスト(testModelは下で関数として定義) testModel(model, data, tensorData); function testModel(model, inputData, normalizationData) { const {inputMax, inputMin, labelMin, labelMax} = normalizationData; // Generate predictions for a uniform range of numbers between 0 and 1; // We un-normalize the data by doing the inverse of the min-max scaling // that we did earlier. const [xs, preds] = tf.tidy(() => { // tf.linespaceで0から1までの間に等間隔となる100個の値を生成(0, 0.01, 0.02, 0.03,・・, 0.98, 0.99)し格納 // なお、tf.linspace()によって生成されるのは配列ではなく「Array.from()で配列化できるオブジェクト」である const xs = tf.linspace(0, 1, 100); // 上記で生成した100個の数値を要素にもつ行列を生成し、学習したモデルに予測値として出力させる const preds = model.predict(xs.reshape([100, 1])); // モデルの入出力は共に正規化されているので、これを元に戻す計算を行う const unNormXs = xs.mul(inputMax.sub(inputMin)).add(inputMin); const unNormPreds = preds.mul(labelMax.sub(labelMin)).add(labelMin); // 上記で非正規化されたデータは100行1列の行列になっているので、これらを単なる配列の形にする return [unNormXs.dataSync(), unNormPreds.dataSync()]; }); // 学習データのポイント(座標)を配列として格納 // mapは配列を受け取って、指定した処理を行う // ここでは入力値inputDataとして「座標情報を持ったオブジェクト」を要素にもつdataを代入しているので、各要素から馬力と燃費のデータを読んで // ポイント(座標)をオブジェクト形式で表す要素を持つ配列の各要素として持たせている const originalPoints = inputData.map(d => ( { x: d.horsepower, y: d.mpg, } )); // 学習させたモデルを使って算出した予測値のポイント(座標)をオブジェクト形式で表す要素を持つ配列として格納 const predictedPoints = Array.from(xs).map((val, i) => { return { x: val, y: preds[i] } }); // 上記で得た予測値および学習データ値のポイントをTFVISに渡してプロット表示 tfvis.render.scatterplot( { // 表のタイトル name: 'Model Predictions vs Original Data' },{ // originalPointsとpredictedPointsはポイントの座標をJSONで表現した文字列を要素に持つ配列 values: [originalPoints, predictedPoints], series: ['original', 'predicted'] },{ // 縦軸と横軸の名称、表の高さ xLabel: 'Horsepower', yLabel: 'Miles Per Gallon', height: 300 } ); } } document.addEventListener('DOMContentLoaded', run);2. 実行結果
TensorFlow.jsの処々の情報は可視化ライブラリであるTF-VISを使って表やグラフで可視化することができます。下記は上記のコードを実行した際に表示された情報です。
2.1. 学習データのプロット
学習データの「馬力(横軸:Horsepower)」と「燃費(縦軸:Miles Per Gallon)」の関係をプロットしたプロットチャートです。これを見ると、学習すべき馬力と燃費の関係は互いに反比例する関係にあることが伺えますが、直線的なの反比例関係ではなく、穏やかなカーブを描くような反比例であることが伺えます。
2.2. 学習の進行状況
下記は損失関数の値です。損失関数をmse(= Mean Squared Error = 平均二乗誤差)に指定し、学習のエポックごとにその値が降下していっていることがわかります。このコードでは学習を50エポックまでと指定したので横軸は50までとなっています。損失関数の値がこれで十分なのかどうかと言う話は別途に言及します。
2.3. 学習したモデルによる出力予測
学習させたモデルに入力として「馬力」の値を与え、その出力として「予測(predict)される燃費」を得ました。その関係を先ほどの学習データのプロットチャートに重ねて表示したものがこれです。学習したモデルが出力したオレンジ色の点が直線的に並んでいます。
3. モデルの精度を高める
TensorFlow.orgのチュートリアルは上記で終わりとなっているのですが、上記で記述したコードと作成したモデルについて、「これで良いのでしたっけ?」と言う視点で考察と改造を行います。
と言うのは、学習データの「馬力(横軸:Horsepower)」と「燃費(縦軸:Miles Per Gallon)」の関係は直線的なの反比例関係ではなく、穏やかなカーブを描くような反比例であることが伺えます。しかしながら、学習させたモデルが予測した関係は直線的な正比例の傾向を示しています。この相違によってモデルの予測精度が十分ではなくなっていると推測できます。もっと、正確に入力と出力の関係を学習させるにはどうすればイイのか?
なお、上記で作成したモデルによる予測の損失関数の値(MSE: 平均二乗誤差)は0.7から0.8でした。
3.1. 出力層に非線形活性化関数を適用する
// 出力層を追加 model.add(tf.layers.dense({ // ユニット(別名:ノード)は1個だけ units: 1, // y=Σ(wx)+b となる定数項bであるバイアスを使用する useBias: true, // 出力を線形にしないために非線形活性化関数を被せる(データに対してsoftplusが最適) activation: 'softplus' }));そうすると、MSEの値は0.0189程度まで下がり、回帰の精度が向上しました。
回帰を表す予測値の描くオレンジの線も曲線になり、非線形回帰ができています。
3.2. 中間層を追加する
しかしながら、回帰曲線の曲がり具合が「硬い」ように見受けられ、この曲線をもっと柔らかくしたいように思います。そこで、モデルに中間層を加えます。中間層を加える行為というのは、イメージで言って「回帰曲線に急激な曲がりを加えられるようにすることを可能にする」行為です。また、ユニット(=ノード)の数は回帰曲線の曲げ具合を制御する「コントロールポイント」の数のイメージで、コントロールポイントが多いと回帰曲線を細かく曲げられます。ベジェ曲線のコントロールポイントと似ています。
// ここに中間層を追加した場合にはどうなるのかは別途に言及する model.add(tf.layers.dense({ // ユニット(別名:ノード)は16個 units: 16, // y=Σ(wx)+b となる定数項bであるバイアスを使用する useBias: true }));そうすると、MSEの値は0.0136程度まで下がり、回帰の精度が向上しました。
回帰曲線の曲がり具合も強くなりました。
3.3. 中間層のノード数を増やす
回帰曲線の曲がり具合が柔軟になったので、更にユニットを増やして32個にします。
// ここに中間層を追加した場合にはどうなるのかは別途に言及する model.add(tf.layers.dense({ // ユニット(別名:ノード)は32個 units: 32, // y=Σ(wx)+b となる定数項bであるバイアスを使用する useBias: true }));そうすると、MSEの値は0.0136程度でユニット数16の時とほぼ変わらず、回帰の精度は向上しません。
描かれる回帰曲線もほぼ変わっていません。
これは、「ユニットを増やすことで回帰曲線を細かく曲げられるようになったが、実際に細かく曲げても『右に曲げれば左から遠くなり、左に曲げれば右から遠くなり』という収束点に既に到達したから」と考えられます。このように、ユニット数を増やすのは予測精度を高めるには有効ですが、無駄に増やすと計算処理量ばかりが増えて精度は向上しないので、学習させるデータの特性を理解しながら学習させることが肝要です。
追伸: Machine Learning Tokyoと言うMachine Learningの日本最大のグループに参加しています。作業系の少人数会合を中心に顔を出しています。基本的に英語でのコミュニケーションとなっていますが、能力的にも人間的にもトップレベルの素晴らしい方々が参加されておられるので、機会がありましたら参加されることをオススメします。
- 投稿日:2019-05-12T16:27:50+09:00
TensorFlow.jsでDeepLearning(Making Predictions from 2D Data - 前半)
こんにちわ。Electric Blue Industries Ltd.という、ITで美を追求するファンキーでマニアックなIT企業のマッツと申します。TensorFlow.jsでDeepLearningのチュートリアル「Making Predictions from 2D Data」の詳細解説の前半です。
これはTensorFlow.JSの公式サイトにある「TensorFlow.js — Making Predictions from 2D Data」をコードの中に記載したコメントで詳細に解説したものです。解説の利便性によりコードの部分の位置関係は変更してありますが、内容に変化はありません。実際に動作するデモはこちらで見られます。
1. コード
1.1. html
ライブラリを読み込んでDeep LearningのためのJavaScriptを実行するHTMLです。
index.html<html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TensorFlow.js Tutorial - Making Predictions from 2D Data</title> <!-- Import TensorFlow.js (TensorFlow.jsライブラリ本体を読み込みます) --> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0/dist/tf.js"></script> <!-- Import tfjs-vis (TensorFlow.js向けの可視化ライブラリを読み込みます) --> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis@1.0.2/dist/tfjs-vis.umd.min.js"></script> <!-- Import the main script file (下記に解説するJavaScriptを読み込みます)--> <script type="text/javascript" src="script.js"></script> </head> <body> </body> </html>1.2. JavaScript
上記のHTMLにインクルードされてDeep Learning処理を行うJavaScriptです。
script.js/****************************************************************** TensorFlow.js — Making Predictions from 2D Data url: https://codelabs.developers.google.com/codelabs/tfjs-training-regression/index.html#0 filename: script.js copyrighted to: tensorflow.org composed by: Mats (Electric Blue Industries Ltd.) description: 車のスペック情報から「燃費」「馬力」のデータを学習し、それらの相関を学習させる ******************************************************************/ async function run() { //****************************************************************** // 1. getData: 元データをgetDataを使って読み込み対象アイテムのみフィルター(元データプロット用) //****************************************************************** // 非同期で元データを取得(getDataは下で関数として定義) // なお、取得するデータは [{0:a, 1:b, 2:c}, {0:d, 1:e, 2:f}]のように、各クルマのスペック情報を持つオブジェクトが要素になった配列データ const data = await getData(); // 読み込んだ元データであるdataから項目抽出して、座標情報オブジェクトを各要素とする配列として新たに格納 const values = data.map(d => ({ x: d.horsepower, y: d.mpg, })); // TensorFlowJSの可視化ライブラリで元データの位置をプロットする tfvis.render.scatterplot( // 表のタイトルの指定 {name: 'Horsepower vs Miles Per Gallon'}, // 上記で生成した座標情報オブジェクトを各要素とする配列を指定 {values}, // 表のx軸y軸のタイトルおよび表の高さを指定 { xLabel: 'Horsepower', yLabel: 'Miles Per Gallon', height: 300 } ); // 元データを非同期で読み込む関数 async function getData() { // 非同期でクルマのスペックデータ(内容はオブジェクトを要素に持つ配列のフォーマットをしている)を取得しcarsDaraに格納 const carsDataReq = await fetch('https://storage.googleapis.com/tfjs-tutorials/carsData.json'); // 読み込んだデータをJSON形式として読んで各クルマのスペック(オブジェクト)を要素として持つ配列carsDataとして格納 const carsData = await carsDataReq.json(); // 格納した配列carsDataデータから元データとして用いる2アイテムのみを取得しcleanedとして格納 // map関数は配列の各要素に繰り返し指定された処理を行うので、cleanedはmpgとhorsepowerの情報のみを含むオブジェクトを要素とする配列 // なお、mpgとhorsepowerのどちらかでも空文字の場合はデータから削除しておくfilterをかける const cleaned = carsData.map(car => ({ mpg: car.Miles_per_Gallon, horsepower: car.Horsepower, })) .filter(car => (car.mpg != null && car.horsepower != null)); return cleaned; } //****************************************************************** // 2. createModel: モデルの枠組みの作成 //****************************************************************** // モデルを作成(createModelは下で関数として定義) const model = createModel(); // 上記で作成したモデルの要約情報(Layer Name, Output Shape, # Of Params, Trainable)表示 tfvis.show.modelSummary({name: 'Model Summary'}, model); // モデル作成の関数定義 function createModel() { // シーケンシャルモデル(線形回帰モデル)の枠組みの作成 // これはモデルが全体として線形回帰モデルになるという意味でなく、各ニューロンの入出力の関係が y=Σ(wx)+b と書ける線形回帰モデルであるということ const model = tf.sequential(); // 入力層を追加 model.add(tf.layers.dense({ // 入力は 1x1 のテンソル(=スカラー) inputShape: [1], // ユニット(別名:ノード)は1個だけ units: 1, // y=Σ(wx)+b となる定数項bであるバイアスを使用する useBias: true })); // ここに中間層を追加した場合にはどうなるのかは別途に言及する // 出力層を追加 model.add(tf.layers.dense({ // ユニット(別名:ノード)は1個だけ units: 1, // y=Σ(wx)+b となる定数項bであるバイアスを使用する useBias: true })); return model; } //****************************************************************** // 3. convertToTensor: 学習データを上記モデルに流し込めるようテンソルに変換する //****************************************************************** // getDataで取得したclean済み配列データを下記で定義したconvertToTensor関数でテンソルに変換(不要なアイテムは同時にフィルター) const tensorData = convertToTensor(data); const {inputs, labels} = tensorData; // 学習データをテンソルに変換する function convertToTensor(data) { // tidyを使って計算することで、計算経過で生成される変数をメモリから削除してメモリにゴミを残さない return tf.tidy(() => { // (ステップ1) データをシャッフルする tf.util.shuffle(data); // (ステップ2) データを配列に格納してから展開してテンソルに変換 // 変数inputに馬力に関するデータ(実際は配列)を格納 const inputs = data.map(d => d.horsepower) // 変数labelsに燃費に関するデータ(実際は配列)を格納 const labels = data.map(d => d.mpg); // 上記で作成したインプット値(馬力)の配列を使って Nx1 の行列を生成 const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]); // 上記で作成される2次元テンソルは下記のような縦長の形 // [[馬力の値1], // [馬力の値2], // : // [馬力の値N]] // 上記で作成したラベル値(燃費)配列を使って Nx1 の行列を生成 const labelTensor = tf.tensor2d(labels, [labels.length, 1]); // 上記で作成される2次元テンソルは下記のような縦長の形 // [[燃費の値1], // [燃費の値2], // : // [燃費の値N]] // (ステップ3) 入力データの値を0から1の間に正規化 // 入力とラベルを最大値と最小値を調べて取得 // 入力である馬力の最大値を取得 const inputMax = inputTensor.max(); // 入力である馬力の最小値を取得 const inputMin = inputTensor.min(); // 出力である燃費の最大値を取得 const labelMax = labelTensor.max(); // 出力である燃費の最小値を取得 const labelMin = labelTensor.min(); // 入力とラベルを正規化 // inputTensor.sub(inputMin)で各入値から最小入力値をひく(=最小値をゼロに落とす) >> (これをAとする) // inputMax.sub(inputMin)で最大入力値から最小入力値をひく >> (これをBとする) // 上記の各(A)の値を(B)で割ることで、最大値が1で最小値が0になるよう正規化する const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin)); // labelTensor.sub(labelMin)で各入値から最小入力値をひく(=最小値をゼロに落とす) >> (これをA'とする) // labelMax.sub(labelMin)で最大入力値から最小入力値をひく >> (これをB'とする) // 上記の各(A')の値を(B')で割ることで、最大値が1で最小値が0になるよう正規化する const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin)); return { // 正規化された値を要素に持つ入力テンソルと出力(ラベル)テンソルを返す inputs: normalizedInputs, labels: normalizedLabels, // 入力と出力(ラベル)の最大値最小値もあとで逆正規化できるよう返す inputMax, inputMin, labelMax, labelMin, } }); } //****************************************************************** // trainModel: モデルの学習 //****************************************************************** // モデルの学習(trainModelは下で関数として定義) // awaitとすることでtrainModel関数からreturnがあるまで待機する await trainModel(model, inputs, labels); // モデル・入力(インプット)テンソル・出力(ラベル)テンソルを指定してモデルの学習を行う関数 async function trainModel(model, inputs, labels) { // 学習実行のため、学習方法を指定してモデルをコンパイル model.compile({ // 最適化法をアダム(=適応モーメント推定法)に指定 optimizer: tf.train.adam(), // 損失関数をMSE(=平均二乗誤差)に指定 loss: tf.losses.meanSquaredError, // 学習とテストに用いる指標(この場合は平均二乗誤差)を表す表現を決める metrics: ['mse'], }); // バッチサイズ(小分けにグループ分けした学習データに含まれるデータの個数)を28個とする const batchSize = 28; // 学習の1手順の回数を50回とする const epochs = 50; // epochsで指定した回数の学習手順回数(エポック)になるまで学習を実行する return await model.fit(inputs, labels, { batchSize, epochs, shuffle: true, // 学習結果の随時の描画用にTFVISに、コンパイル時に指定した指標の値をコールバックする指定 callbacks: tfvis.show.fitCallbacks( // 描画する表のタイトル { name: 'Training Performance' }, // 描画する指標(ここではlossとmseを改めて指定) ['loss', 'mse'], { // 表の高さ height: 200, // コールバックのタイミング callbacks: ['onEpochEnd'] } ) }); } // trainModel関数に返り値があった時点で規定したエポックが終了したことになるので「学習が終わった」とコンソールに出力 console.log('Done Training'); //****************************************************************** // testModel: 学習ずみモデルに入力を与えて出力を得て、元データのプロットと重ねて違いを視覚的に見せる //****************************************************************** // モデルのテスト(testModelは下で関数として定義) testModel(model, data, tensorData); function testModel(model, inputData, normalizationData) { const {inputMax, inputMin, labelMin, labelMax} = normalizationData; // Generate predictions for a uniform range of numbers between 0 and 1; // We un-normalize the data by doing the inverse of the min-max scaling // that we did earlier. const [xs, preds] = tf.tidy(() => { // tf.linespaceで0から1までの間に等間隔となる100個の値を生成(0, 0.01, 0.02, 0.03,・・, 0.98, 0.99)し格納 // なお、tf.linspace()によって生成されるのは配列ではなく「Array.from()で配列化できるオブジェクト」である const xs = tf.linspace(0, 1, 100); // 上記で生成した100個の数値を要素にもつ行列を生成し、学習したモデルに予測値として出力させる const preds = model.predict(xs.reshape([100, 1])); // モデルの入出力は共に正規化されているので、これを元に戻す計算を行う const unNormXs = xs.mul(inputMax.sub(inputMin)).add(inputMin); const unNormPreds = preds.mul(labelMax.sub(labelMin)).add(labelMin); // 上記で非正規化されたデータは100行1列の行列になっているので、これらを単なる配列の形にする return [unNormXs.dataSync(), unNormPreds.dataSync()]; }); // 学習データのポイント(座標)を配列として格納 // mapは配列を受け取って、指定した処理を行う // ここでは入力値inputDataとして「座標情報を持ったオブジェクト」を要素にもつdataを代入しているので、各要素から馬力と燃費のデータを読んで // ポイント(座標)をオブジェクト形式で表す要素を持つ配列の各要素として持たせている const originalPoints = inputData.map(d => ( { x: d.horsepower, y: d.mpg, } )); // 学習させたモデルを使って算出した予測値のポイント(座標)をオブジェクト形式で表す要素を持つ配列として格納 const predictedPoints = Array.from(xs).map((val, i) => { return { x: val, y: preds[i] } }); // 上記で得た予測値および学習データ値のポイントをTFVISに渡してプロット表示 tfvis.render.scatterplot( { // 表のタイトル name: 'Model Predictions vs Original Data' },{ // originalPointsとpredictedPointsはポイントの座標をJSONで表現した文字列を要素に持つ配列 values: [originalPoints, predictedPoints], series: ['original', 'predicted'] },{ // 縦軸と横軸の名称、表の高さ xLabel: 'Horsepower', yLabel: 'Miles Per Gallon', height: 300 } ); } } document.addEventListener('DOMContentLoaded', run);2. 実行結果
TensorFlow.jsの処々の情報は可視化ライブラリであるTF-VISを使って表やグラフで可視化することができます。下記は上記のコードを実行した際に表示された情報です。
2.1. 学習データのプロット
学習データの「馬力(横軸:Horsepower)」と「燃費(縦軸:Miles Per Gallon)」の関係をプロットしたプロットチャートです。これを見ると、学習すべき馬力と燃費の関係は互いに反比例する関係にあることが伺えますが、直線的なの反比例関係ではなく、穏やかなカーブを描くような反比例であることが伺えます。
2.2. 学習の進行状況
下記は損失関数の値です。損失関数をmse(= Mean Squared Error = 平均二乗誤差)に指定し、学習のエポックごとにその値が降下していっていることがわかります。このコードでは学習を50エポックまでと指定したので横軸は50までとなっています。損失関数の値がこれで十分なのかどうかと言う話は別途に言及します。
2.3. 学習したモデルによる出力予測
学習させたモデルに入力として「馬力」の値を与え、その出力として「予測(predict)される燃費」を得ました。その関係を先ほどの学習データのプロットチャートに重ねて表示したものがこれです。学習したモデルが出力したオレンジ色の点が直線的に並んでいます。
3. 次回の話
TensorFlow.orgのチュートリアルは上記で終わりとなっているのですが、次回は上記で記述したコードと作成したモデルについて、「これで良いのでしたっけ?」と言う視点で考察と改造を行います。
と言うのは、学習データの「馬力(横軸:Horsepower)」と「燃費(縦軸:Miles Per Gallon)」の関係は直線的なの反比例関係ではなく、穏やかなカーブを描くような反比例であることが伺えます。しかしながら、学習させたモデルが予測した関係は直線的な正比例の傾向を示しています。もっと、正確に入力と出力の関係を学習させるにはどうすればイイのか?次回の投稿で考えていきます。
追伸: Machine Learning Tokyoと言うMachine Learningの日本最大のグループに参加しています。作業系の少人数会合を中心に顔を出しています。基本的に英語でのコミュニケーションとなっていますが、能力的にも人間的にもトップレベルの素晴らしい方々が参加されておられるので、機会がありましたら参加されることをオススメします。
- 投稿日:2019-05-12T11:53:06+09:00
TensorFlow.jsでDeepLearning(初めの整理)
1. 初めに
こんにちわ。Electric Blue Industries Ltd.という、ITで美を追求するファンキーでマニアックなIT企業のマッツと申します。このところ投稿が疎かになったので、何かお役に立てることをと思い、TensorFlow.jsの解説を不定期することにしました。
Deep Learningをおこなううえで最もメジャーな方法のひとつが「仮想環境やコンテナを作ってTensorFlow & Python」かと思います。Google Colabと言う素晴らしい環境もありますが、「はじめの一歩」にはまだまだ敷居が高いところがあり、初学者には気軽に取り組める状況ではないように思います。
そんな中で特に初学者にオススメしたいのが「TensorFlow.js」です。TensorFlow.jsはJavaScriptでDeep Learningをおこなうためのライブラリです。機能的にはPython版のTensorFlowと基本的に足並みを揃えています(TFGANのような目新しい関数はTensorFlow.jsにはなかったりしますが)。学習に際しての敷居が低く、教育現場での利用にも有効と認識しています。メリット・デメリットは下記かと思います。
メリット
- Webブラウザがあれば即に動作させられます
- Webページを作成する際に慣れ親しんだ人も多いJavaScriptでの記述
- PythonとJavaScriptは記述に類似性があるので思考的に同時並行が楽
- GPUの使用もブラウザのWebGL経由で行える
- ウェブサイトへの組み込みが容易でウェブコンテンツ化がしやすい
- デバイスのカメラやマイクとの連動が簡単
- TensorBoardに相当するような可視化ライブラリ「TF-VIS」もあり視覚化できる
- Pythonとの相互変換(コンバーター)も使用可能
- モデルのインポートとエクスポートも可能
デメリット
- 日進月歩のDeepLearning業界の進化ペースに微妙にキャッチアップし遅れている
- 大規模な学習処理にはやっぱりちょっと重いかも
2. この投稿の目的
今後に複数回の投稿に分けて、TensorFlow.orgから公開されているTensorFlow.jsを用いたチュートリアルを、非常に基本的な事項についてまで細かく詳細に説明し、何をしているのか流れを理解するお手伝いをします。狙いとしては、どのようなデータ(配列・オブジェクト・テンソル等)がどのように処理されて何が行われるのかを理解できるようになることです。
3. Deep Learningで頻出するJavaScriptのおさらい
次回からの各チュートリアルの解説をする前に、ウェブサイトでのJavaScriptではあまり登場しない処理について言及します。これらを理解していることが解説を読む上での前提となるので、不明事項はあらかじめ学習をお願いしたいです。
3.1. 変数と宣言(var, let, const)
JavaScriptでは変数を用いる際には使い始めに変数を宣言してから用います。宣言のタイプにはいくつかあり、ウェブサイト内で記述する場合はほぼvarしか使いませんが、Deep Learningではletやconstが多用されます。
var : 通常の変数宣言(上書き可能)
let : ifやforといったブロックスコープ内でのみ有効(上書き可能)
const : ifやforといったブロックスコープ内でのみ有効(上書き不可能)3.2. 非同期処理(async, await)
asyncは指定された処理を非同期で行うことを宣言し、awaitはasyncで指定された処理が実行結果を返す(コールバック)するまで待つことを宣言します。これもウェブサイト作成では滅多に出てこないものです。
3.3. データのマッピング(map)
JavaScriptのmapは配列データに使うメソッドであり、配列データの各要素1つずつに対し指定されたコールバック処理を実行し、その結果を新しい配列として返すことが出来ます(見かけからはピンと来ないが繰り返し処理をしている状況)。なお、mapと類似するメソッドにfilterとreduceがあるが、ここでは割愛する。
map_1.js// 1から5までの5つの整数を格納した、要素が5つの配列をitemsと言う名称で作成。 const items = [1, 2, 3, 4, 5]; // 上記の配列itemsの各要素について、各配列要素をvalueとしてfunctionで規定された処理をおこなう。 const result = items.map(function(value) { //配列の各要素を2倍 return value*2; }); // ブラウザの「開発者ツール(inspect)」のconsole画面にresultの内容が出力されます。 console.log(result); // resultは配列となり、下記の要素が格納される // [2, 4, 6, 8, 10]さらに、インデックス(配列の先頭要素(0番)から何番目か)の番号も使って下記のようにオブジェクトを要素に持たせることもでき、この形式はTensorFlow.jsでのデータ処理に頻出するものです。
map_2.jsconst items = [1, 2, 3, 4, 5]; const result = items.map(function(value, index) { //valueとindexを含むオブジェクトを要素とする配列を生成 return { value, index }; }); console.log(result); // resultは配列となり、下記のvalueとindexを含むオブジェクトを各要素が格納される // [{value: 1, index: 0}, {value: 2, index: 1}, {value: 3, index: 2}, {value: 4, index: 3}, {value: 5, index: 4}]3.4 配列の生成(Array.from)
Array.from(x)は下記のように「配列っぽい(インデックス、要素、要素数が記録された)」オブジェクトから正真正銘の配列を生成するメソッドです。
arrayFrom_1.jsconst items = {0: 11, 1: 22, 2: 33, length: 3}; const result = Array.from(items); console.log(result); // resultには下記の配列が格納される // [11, 22, 33]配列の要素にオブジェクトを格納させるべく、上記で述べたmapと併用して用いる場合は
arrayFrom_2.jsconst items = {0: 11, 1: 22, 2: 33, length: 3}; const result = Array.from(items).map((value, index) => { return { x: value, y: index } }); console.log(result); // resultには下記のオブジェクトを要素とする配列が格納される // [{x: 11, y: 0}, {x: 22, y: 1}, {x: 33, y: 2}]のように記述します。この処理はDeep Learningの学習データとテストデータの生成に頻出する処理です。
さらに、オブジェクトの中にオブジェクトが入ったような形式の配列もどきの場合は、配列もどきオブジェクトの各要素に対して処理を行わせるべくmapと合わせると下記のようにハンドルできます。
arrayFrom_3.jsconst items = {0: {alpha: 11, bravo:111}, 1: {alpha: 22, bravo: 222} , 2: {alpha: 33, bravo: 333}, length: 3}; const result = Array.from(items).map(d => { return { x: d.alpha, y: d.bravo } }); console.log(result); // resultは下記のオブジェクトを要素とする配列になる。 // [{x: 11, y: 111}, {x: 22, y: 222}, {x: 33, y: 333}]Deep Learningの学習で最も難しく感じるのは、このように「配列」「オブジェクト」が入れ子になり、かつ多次元行列の要素となってデータが扱われるところかと思います。データを処理する際には「このデータはどのような構造になっているか」を確認しながら処理を理解することが、その後の汎用的なDeep Learning実施に肝要です。
4. TensorFlow.jsで行列を扱う
4.1. ブラウザでTensorFlow.jsが動くようにする
ブラウザ(Google Chromeの最新版を推奨)でTensorFlow.jsが動くようにするには、下記のHTMLファイルを作成し、それと同じフォルダに置いた script.js というJavaScriptを書いたファイルにJaveScriptでコードを記述することで行います(もちろんファイル名は別の名称でも良いです)。実行結果は「開発者ツール(inspect)」のコンソール画面に表示されます。
index.html<!DOCTYPE html> <html> <head> <title>TensorFlow.js Tutorial</title> <!-- TensorFlow.jsのインポート --> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0/dist/tf.js"></script> <!-- JavaScriptを書くscript.jsのインポート --> <script type="text/javascript" src="script.js"></script> </head> <body></body> </html>4.2. 行列を作る
下記の方法1から3は全て同じ
a = \begin{bmatrix} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{bmatrix}という行列aを作ります。
方法1:各行ごとにまとめて指定することを機械的に繰り返す。
cretaeMatrix_1.jsconst a = tf.tensor([[1, 2], [3, 4], [5, 6]]);方法2:行列の要素を左上から機械的に続けて指定し、行列の形(m行 x n列)を指定する。
cretaeMatrix_2.jsconst shape = [3, 2]; const a = tf.tensor([1, 2, 3, 4, 5, 6], shape);方法3:方法1と方法2の合わせ技(なお、画像や音声の学習ではデータが浮動小数になるのでint32などと明示的に指定することも多いので、これは意外と使います)。
cretaeMatrix_3.jsconst a = tf.tensor([[1, 2], [3, 4], [5, 6]], [3, 2], 'int32');4.3. 行列の変形
- reshape: 上記で作成した行列aに対して、shapeでTensorの再形成を行い行列bを生成
reshape_1.js// 2行3列に変形 const b = a.reshape([2, 3]);b = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}
- transpose: 上記で作成した行列aに対して、transposeで行列の行と列を転置して転置行列bを生成する
transpose_1.js// 行と列を対角線を軸としてくるっとひっくり返し(転置) const b = a.transpose();b = \begin{bmatrix} 1 & 3 & 5 \\ 2 & 4 & 6 \end{bmatrix}4.4. 行列の要素を得る
- arraySyncで行列aの各行を要素とする配列を得る
arraySync_1.jsb = a.arraySync();b = \begin{bmatrix} \begin{bmatrix} 1 & 2 \end{bmatrix} , \begin{bmatrix} 3 & 4 \end{bmatrix} , \begin{bmatrix} 5 & 6 \end{bmatrix} \end{bmatrix}
- dataSyncで行列aの各値を要素とする配列を得る
dataSync_1.jsb = a.dataSync();b = \begin{bmatrix} 1, 2, 3, 4, 5, 6 \end{bmatrix}4.5. 行列の算術計算
- 行列xの各要素の二乗を得る
square_1.jsconst x = tf.tensor([1, 2, 3, 4]); const y = x.square();y = \begin{bmatrix} 1, 4, 9, 16 \end{bmatrix}
ということで、今後のTensorFlow.jsの詳細解説を前に、今回はベースとなるJavaScriptのおさらいまでをしました。空いた時間をうまく使って、シュウイチくらいでは続きを投稿していく予定です。どうぞよろしくお願いします。
追伸: Machine Learning Tokyoと言うMachine Learningの日本最大のグループに参加しています。作業系の少人数会合を中心に顔を出しています。基本的に英語でのコミュニケーションとなっていますが、能力的にも人間的にもトップレベルの素晴らしい方々が参加されておられるので、機会がありましたら参加されることをオススメします。
- 投稿日:2019-05-12T09:02:58+09:00
[環境構築]TensorFlow動かすと「ImportError: DLL load failed:~」が出る
背景
Interface 2019年5月号(CQ出版)の記事の中に、Tensorflow Object Detection APIを使用した物体検知についてのサンプルがあり、面白そうなので動かしてみたいと思いました。しかし、環境構築でかなり躓いてしまったので記録を残しておきます。
環境
- windows 10
- python=3.6.5
やったこと
まずは雑誌の記事を参考に環境構築をしましたが、エラーが出てうまくいきませんでした。そこで、以下の記事を参考に改めて環境構築をしました。
・Anaconda PromptでのTensorFlowの導入に苦戦した話
・TensorFlow Object Detection APIをWindowsで使ってみたしかし、うまくいかず、tensorflowをimportしようとすると、以下のようなエラーがでてきました。
Traceback (most recent call last): File "C:\Users\grin\Anaconda3\envs\study\lib\site-packages\tensorflow\python\pywrap_tensorflow.py", line 58, in <module> from tensorflow.python.pywrap_tensorflow_internal import * File "C:\Users\grin\Anaconda3\envs\study\lib\site-packages\tensorflow\python\pywrap_tensorflow_internal.py", line 28, in <module> _pywrap_tensorflow_internal = swig_import_helper() File "C:\Users\grin\Anaconda3\envs\study\lib\site-packages\tensorflow\python\pywrap_tensorflow_internal.py", line 24, in swig_import_helper _mod = imp.load_module('_pywrap_tensorflow_internal', fp, pathname, description) File "C:\Users\grin\Anaconda3\envs\study\lib\imp.py", line 243, in load_module return load_dynamic(name, filename, file) File "C:\Users\grin\Anaconda3\envs\study\lib\imp.py", line 343, in load_dynamic return _load(spec) ImportError: DLL load failed: 指定されたモジュールが見つかりません。 During handling of the above exception, another exception occurred: Traceback (most recent call last): File "detect_cat.py", line 2, in <module> import tensorflow as tf File "C:\Users\grin\Anaconda3\envs\study\lib\site-packages\tensorflow\__init__.py", line 24, in <module> from tensorflow.python import pywrap_tensorflow # pylint: disable=unused-import File "C:\Users\grin\Anaconda3\envs\study\lib\site-packages\tensorflow\python\__init__.py", line 49, in <module> from tensorflow.python import pywrap_tensorflow File "C:\Users\grin\Anaconda3\envs\study\lib\site-packages\tensorflow\python\pywrap_tensorflow.py", line 74, in <module> raise ImportError(msg) ImportError: Traceback (most recent call last): File "C:\Users\grin\Anaconda3\envs\study\lib\site-packages\tensorflow\python\pywrap_tensorflow.py", line 58, in <module> from tensorflow.python.pywrap_tensorflow_internal import * File "C:\Users\grin\Anaconda3\envs\study\lib\site-packages\tensorflow\python\pywrap_tensorflow_internal.py", line 28, in <module> _pywrap_tensorflow_internal = swig_import_helper() File "C:\Users\grin\Anaconda3\envs\study\lib\site-packages\tensorflow\python\pywrap_tensorflow_internal.py", line 24, in swig_import_helper _mod = imp.load_module('_pywrap_tensorflow_internal', fp, pathname, description) File "C:\Users\grin\Anaconda3\envs\study\lib\imp.py", line 243, in load_module return load_dynamic(name, filename, file) File "C:\Users\grin\Anaconda3\envs\study\lib\imp.py", line 343, in load_dynamic return _load(spec) ImportError: DLL load failed: 指定されたモジュールが見つかりません。 Failed to load the native TensorFlow runtime. See https://www.tensorflow.org/install/errors for some common reasons and solutions. Include the entire stack trace above this error message when asking for help. exit status 1そこからは、「何かわからんがくらえッ!」精神で、ここやここなんかを参考に、地獄のようにpip install ~, pip uninstall ~, conda install ~, conda uninstall ~, ... を繰り返しました。
結果・・・こうすればできました
新しく環境を作ります。
$conda create -n test python=3.6.5 anaconda環境をactivateしたら、
$pip install tensorflow==1.12.0
これだけでした。。。tensorflowのバージョンを指定しないと、私の環境だとデフォルトで1.13.1がインストールされ、これがどうも相性が悪いみたいでした。
物体検知のサンプルも無事、実行できました。
画像出典(https://www.pakutaso.com/20190201044post-19435.html)「何かわからんがクラ・・・」
いや、、tie...ネクタイ?person...人??
まあ、ネクタイした人にも見え・・・ないですね









