20210418のTensorFlowに関する記事は2件です。

TensorFlow.js学習メモ② 線形回帰(Linear Regression)で車の燃費を予測

はじめに k近傍法に引き続き、線形回帰モデルを使って車の燃費を予測してみました。 学習メモなので基本用語の詳しい解説などは書いていません。あしからず。 前の記事は以下 前提知識 実装に際して前提となる超基本知識をまとめました。 線形回帰 (Linear regression) ある変数が与えられたときに、それと相関関係のある値を予測することを回帰分析と呼びます。 多数のデータをプロットし、直線を引いてモデルをつくることで、変数の相関値を予測できるようになります。 つまり、予測の精度をできるだけ高くできるように直線を引くこと(= 適切な傾きと切片を与えること)がとても大事になります。 平均二乗誤差(Mean Square Error) 平均二乗誤差とは、線形回帰モデルの性能を数値化する効果的な手法の一つで、実際の値とモデルによる予測値との誤差の平均値のことをいいます。 勾配降下法 (Gradient Descent) MSEができるだけ小さくなるような直線の傾き(m)と切片(b)を設定するために、勾配降下法を使用します。 MSEをb(もしくはm)で微分したときの値が最小になったときに、MSEを最小にするb(もしくはm)を導出するといった手法になります。 導出手順は以下のようになります。 1. MSEをbで微分 2. 微分値が最小であるか判定 -> 最小であればbを決定(ここで終了) 3. 最小でなければ学習率(Learning Rate)をかける 4. b = b - (3で導出した値)とする 5. 2に戻る 実装 horsepower(馬力), weight(重量), displacement(エンジンの大きさ)を入力値として、mpg(燃費)を予測します。 メソッドの構成 線形回帰を実装するにあたり、LinearRegressionクラスを作成し、各メソッドに機能をわけました。 gradientDescent(): 勾配降下処理の実行とm, bの更新 train(): 最適なm, bがでるまでgradientDescent()を実行 test(): Test dataから導出したm, bの精度を評価 predict(): 導出したm, bで値を予測 processFeatures(): 計算に使えるようにfeaturesを加工 standardize(): データの標準化 recordMSE(): 学習率の調整に使えるようにMSEを記録 updateLearningRate(): 学習率の更新 コード① linear-regression.js コードにするとこのような感じになりました。 linear-regression.js //ライブラリの読込 const tf = require('@tensorflow/tfjs'); const _ = require('lodash'); //クラス class LinearRegression { constructor(features, labels, options) { this.features = this.processFeatures(features); // this.labels = tf.tensor(labels); // this.mseHistory = []; this.options = Object.assign( { learningRate: 0.1, iterations: 100 }, options ); this.weights = tf.zeros([this.features.shape[1], 1]); //m, bの初期値 } gradientDescent(features, labels) { const currentGuesses = features.matMul(this.weights); //features * weights const differences = currentGuesses.sub(labels); // features * weights - labels // dMSE/dmとdMSE/dbのテンソル // (features * (features * weights - labels))/n const slopes = features .transpose() .matMul(differences) .div(features.shape[0]); //列の個数で割る this.weights = this.weights.sub(slopes.mul(this.options.learningRate)); //m, bのテンソル } train() { //バッチの個数(データ個数/1回のバッチサイズ) const batchQuantity = Math.floor( this.features.shape[0] / this.options.batchSize ); // 学習率を最適化しながらMSEを繰り返し実行 for (let i = 0; i < this.options.iterations; i++) { for (let j = 0; j < batchQuantity; j++) { const startIndex = j * this.options.batchSize; //バッチ開始位置 const { batchSize } = this.options; //1回のバッチサイズ //各バッチのfeatures const featureSlice = this.features.slice( [startIndex, 0], [batchSize, -1] ); //各バッチのlabels const labelSlice = this.labels.slice([startIndex, 0], [batchSize, -1]); //バッチごとにgradientDescentを実行 this.gradientDescent(featureSlice, labelSlice); } this.recordMSE(); this.updateLearningRate(); } } predict(observations) { return this.processFeatures(observations).matMul(this.weights); } //決定係数の導出 test(testFeatures, testLabels) { testFeatures = this.processFeatures(testFeatures); testLabels = tf.tensor(testLabels); const predictions = testFeatures.matMul(this.weights); const res = testLabels.sub(predictions).pow(2).sum().get(); //Sres const tot = testLabels.sub(testLabels.mean()).pow(2).sum().get(); //Stot return 1 - res / tot; //coefficient of determination(決定係数) } //計算に使えるようにfeaturesを加工 processFeatures(features) { features = tf.tensor(features); features = tf.ones([features.shape[0], 1]).concat(features, 1); if (this.mean && this.variance) { features = features.sub(this.mean).div(this.variance.pow(0.5)); //正規化 } else { features = this.standardize(features); //標準化 } return features; } //標準化 standardize(features) { const { mean, variance } = tf.moments(features, 0); this.mean = mean; this.variance = variance; return features.sub(mean).div(variance.pow(0.5)); } //学習率(Learning Rate)調整のためにMSEを記録 recordMSE() { const mse = this.features .matMul(this.weights) .sub(this.labels) .pow(2) .sum() .div(this.features.shape[0]) .get(); this.mseHistory.unshift(mse); } //学習率(Learning Rate)の更新 updateLearningRate() { if (this.mseHistory.length < 2) { return; } if (this.mseHistory[0] > this.mseHistory[1]) { this.options.learningRate /= 2; } else { this.options.learningRate *= 1.05; } } } module.exports = LinearRegression; 決定係数(Coefficient of Determination) test()メソッドで出力する予測精度として、決定係数というものを考えました。 決定係数は、回帰によって導いたモデルで予測した値が実際の値とどの程度一致しているかを表現する評価指標です。 決定係数はR2として表現され、値が大きいほどモデルが適切にデータを表現できていること(予測値の精度が高いこと)を意味します。 バッチ勾配降下法 勾配降下法はパラメータの更新のたびにすべてのTraining dataで勾配を計算します。 Training dataの量が増加するにつれて計算を行うのが難しくなるため、その場合はバッチ勾配降下法や確率的勾配降下法を利用します。 バッチ勾配降下法では、一度に利用するTraining dataの量が勾配降下法と比較して少なくなります。 Training dataの中からいくつかのデータを取り出し、そのデータで計算した勾配にもとづいてパラメータを更新します。 勾配降下法と比較すると計算に必要なメモリ量が少なく、確率的勾配降下法と比較すると外れ値の影響を受けにくく学習が比較的安定して進むという利点があります。 train()メソッドではbatchQuantityでバッチの個数(回数)を定義し、1回のiterationごとにその回数gradientDescent()を実行しています。 学習率(Learning Rate)の最適化 勾配降下法(gradientDescent)で使用する学習率(Learning Rate)は、MSEの導出ごと(1回のiterationごと)に最適化を行います。 MSEの導出と記録 1回前のMSEと新しいMSEの値の比較 MSEが大きくなっていたら学習率を1/2にする MSEが小さくなっていたら学習率を5%増やす コード② index.js index.jsでlinear-regression.jsを読み込んで、決定係数(R2)とm, bを出力してみました。 index.js require('@tensorflow/tfjs-node'); //ルートフォルダで1回だけ呼ぶ const tf = require('@tensorflow/tfjs'); const loadCSV = require('../load-csv'); const LinearRegression = require('./linear-regression'); const plot = require('node-remote-plot'); let { features, labels, testFeatures, testLabels } = loadCSV( '../data/cars.csv', { shuffle: true, splitTest: 50, dataColumns: ['horsepower', 'weight', 'displacement'], labelColumns: ['mpg'], //Miles Per Gallon } ); const regression = new LinearRegression(features, labels, { learningRate: 0.1, iterations: 100, batchSize: 10, }); regression.train(); const r2 = regression.test(testFeatures, testLabels); plot({ x: regression.mseHistory.reverse(), xLabel: 'Iteration #', yLabel: 'Mean Squared Error', }); console.log('R2 is', r2); console.log( 'Updated M is:', regression.weights.get(1, 0), 'Updated B is:', regression.weights.get(0, 0) ); regression.predict([[120, 2, 380]]).print(); 結果 R2 is 0.6592028145756478 Updated M is: -1.645716905593872 Updated B is: -23.4530029296875 また、iterationごとのMSEの変遷をplotでグラフにすると以下のようになりました。 おわりに 確率的勾配降下法を使うとどんな結果になるのかも試してみたいです。 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TensorFlow.js学習メモ① k近傍法(k-nearlest neighbor)で座標から家の価格を予測

はじめに 仕事の関係で機械学習について学習する必要性がでてきたので、アルゴリズムを何個か軽く勉強することにしました。 Pythonが絡むと学習負荷が一気に高くなってしまう気がしたので、学習はJavaScript(TensorFlow.js)で行いました。 まずは、座標や床面積を入力してk近傍法で家の価格を予測するようなアルゴリズムを実装しました。 ほぼ学習メモみたいな感じなので、深い内容をお求めの方はその点ご了承ください。 TensorFlow.js TensorFlow.jsはPythonのnumpyみたいな操作ができるライブラリです。 JSで簡単に行列計算をすることができます。 k近傍法(k-nearllest neighbor) 入力値に対する予測値を出すアルゴリズムの一つです。 指定した座標(lat, long)から家の価格(price)を予測する場合を考えると、予測まで以下のような流れになります。 座標と家の価格に関する実際のデータをたくさん集める(Training data) 価格を知りたい座標を指定して前ステップで集めた座標データとの差分をそれぞれとっていく 差分が小さい順にデータをソートする 差分が小さい順に指定した個数(k)分のデータを取得する 取得したデータの価格の平均値をとる(予測値) 理想的なkの値を探すために、Training dataとTest dataをそれぞれ用意します。 knnでTest dataの座標から価格を出し、Test dataの価格との差が小さくなるようにkを調整していきます。 また、座標だけではなく床面積(sqft_lot)やリビング面積(sqft_living)などの複数の要素を考慮すると、より精度の高い価格予測ができそうです。 前提知識 実装に際して前提となる超基本知識をまとめました。 分類と回帰 値予測のために分類(Classification)と回帰(Regression)について理解しておく必要があります。 入力値に対してその値が合格か不合格かを予測したい場合には分類、どのような値になるかを予測したい場合には回帰を使います。 正規化(Normalization)と標準化(Standardization) データを最大値が1、最小値が0のデータとなるように変換することを正規化、元のデータの平均を0、標準偏差が1のものへと変換することを標準化と呼びます。 最大値及び最小値が決まっている場合(画像処理とか)などには正規化を利用します。 一方、最大値及び最小値が決まっていない場合や外れ値が存在する場合には、重みを学習しやすくするために標準化を利用します。 正規化の式 標準化の式 (μ: 平均, σ: 標準偏差) 実装 実装の流れをまとめました。 データの準備 予測に使うデータ(kc_house_data.csv)を用意します。 Training dataとTest dataをこの中から抽出します。 index.jsの作成 この中にデータの予測処理を作成します。 ライブラリの読み込み index.js require('@tensorflow/tfjs-node'); const tf = require('@tensorflow/tfjs'); const loadCSV = require('./load-csv'); knn処理 index.js //一番近いpriceを探索するknn function knn(features, labels, predictionPoint, k) { const { mean, variance } = tf.moments(features, 0); //平均値と分散の取得、第2引数でrowかcolumnを指定 const scaledPrediction = predictionPoint.sub(mean).div(variance.pow(0.5)); //入力値の標準化 (入力値-平均値)/標準偏差 return ( features .sub(mean) .div(variance.pow(0.5)) //Training dataの標準化 .sub(scaledPrediction) //入力値(標準化済)との差 .pow(2) //各要素を2乗 .sum(1) //各要素の和 .pow(0.5) //ルートをとる(distance算出) .expandDims(1) //連結するためにdimを操作 .concat(labels, 1) //labels(price)と連結 .unstack() //sort, sliceの操作を行うためにobj化 .sort((a, b) => (a.get(0) > b.get(0) ? 1 : -1)) //distance小さい順にソート .slice(0, k) //k個データを取得 .reduce((acc, pair) => acc + pair.get(1), 0) / k //priceの平均値(knnの結果) ); } データの取得 splitTest:10でTest data(testFeatures, testLabels)をランダムに取得し、それ以外をTraining data(features, labels)として取得します。 index.js let { features, labels, testFeatures, testLabels } = loadCSV( 'kc_house_data.csv', { shuffle: true, splitTest: 10, //testFeatures, testLabelsの数を指定 dataColumns: ['lat', 'long', 'sqft_lot', 'sqft_living'], //featureカラムを指定 指定数増やすと精度上がる labelColumns: ['price'], //labelカラムを指定 } ); features = tf.tensor(features); labels = tf.tensor(labels); 予測値の出力 10個のTest data(testFeatures)をknnで処理した結果(Guess)とtestLabelsとの乖離度合(Error)を出力しました。 index.js testFeatures.forEach((testPoint, i) => { //testFeature10個それぞれのerrをみる const result = knn(features, labels, tf.tensor(testPoint), 10); //knnの結果(予測値) const err = (testLabels[i][0] - result) / testLabels[i][0]; //Test dataのpriceと予測値の乖離度合 console.log('Error', err * 100); console.log('Guess', result, testLabels[i][0]); }); 出力結果 Error -15.323502304147466 Guess 1251260 1085000 Error -11.344580119965723 Guess 519756.5 466800 Error -2.047058823529412 Guess 433700 425000 Error 19.327433628318584 Guess 455800 565000 Error 7.806324110671936 Guess 699750 759000 Error -14.106372465729613 Guess 584260 512031 Error -8.782552083333334 Guess 835450 768000 Error 13.227406199021207 Guess 1329790 1532500 Error -36.336911441815076 Guess 279422.5 204950 Error 7.381578947368421 Guess 228767.5 247000 おわりに ほんとに触りだけなので、数式がわかればそこまで実装は難しくないという印象。 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む