Tensorflow.js 擬合曲線

2021-06-15 16:18 更新

這篇文章中,我們將使用TensorFlow.js來根據(jù)數(shù)據(jù)擬合曲線。即使用多項(xiàng)式產(chǎn)生數(shù)據(jù)然后再改變其中某些數(shù)據(jù)(點(diǎn)),然后我們會訓(xùn)練模型來找到用于產(chǎn)生這些數(shù)據(jù)的多項(xiàng)式的系數(shù)。簡單的說,就是給一些在二維坐標(biāo)中的散點(diǎn)圖,然后我們建立一個系數(shù)未知的多項(xiàng)式,通過TensorFlow.js來訓(xùn)練模型,最終找到這些未知的系數(shù),讓這個多項(xiàng)式和散點(diǎn)圖擬合。


先決條件

本教程假定您熟悉核心概念中介紹的TensorFlow.js的基本構(gòu)建塊:張量,變量和操作。 我們建議在完成本教程之前先完成核心概念的學(xué)習(xí)。


運(yùn)行代碼

這篇文章關(guān)注的是創(chuàng)建模型以及學(xué)習(xí)模型的系數(shù),完整的代碼在這里可以找到。為了在本地運(yùn)行,如下所示:

$ git clone https://github.com/tensorflow/tfjs-examples
$ cd tfjs-examples/polynomial-regression-core
$ yarn
$ yarn watch

即首先將核心代碼下載到本地,然后進(jìn)入polynomial-regression-core(即多項(xiàng)式回歸核心)部分,最后進(jìn)行yarn安裝并運(yùn)行。


輸入數(shù)據(jù)

我們的數(shù)據(jù)集由x坐標(biāo)和y坐標(biāo)組成,當(dāng)繪制在笛卡爾平面上時,其坐標(biāo)如下所示:

1


該數(shù)據(jù)是由三次方程 y = ax^{3}+ bx^{2} + cx + d 生成的。

我們的任務(wù)是學(xué)習(xí)這個函數(shù)的a,b,c和d系數(shù)以最好地?cái)M合數(shù)據(jù)。 我們來看看如何使用TensorFlow.js操作來學(xué)習(xí)這些值。


學(xué)習(xí)步驟

第1步:設(shè)置變量

首先,我們需要創(chuàng)建一些變量。即開始我們是不知道a、b、c、d的值的,所以先給他們一個隨機(jī)數(shù),如下所示:

const a = tf.variable(tf.scalar(Math.random()));
const b = tf.variable(tf.scalar(Math.random()));
const c = tf.variable(tf.scalar(Math.random()));
const d = tf.variable(tf.scalar(Math.random()));


第2步:建立模型

我們可以通過TensorFlow.js中的鏈?zhǔn)秸{(diào)用操作來實(shí)現(xiàn)這個多項(xiàng)式方程  y = ax3 + bx2 + cx + d,下面的代碼就創(chuàng)建了一個 predict 函數(shù),這個函數(shù)將x作為輸入,y作為輸出:

function predict(x) {
  // y = a * x ^ 3 + b * x ^ 2 + c * x + d
  return tf.tidy(() => {
    return a.mul(x.pow(tf.scalar(3))) // a * x^3
      .add(b.mul(x.square())) // + b * x ^ 2
      .add(c.mul(x)) // + c * x
      .add(d); // + d
  });
}

其中,在上一篇文章中,我們講到tf.tify函數(shù)用來清除中間張量,其他的都很好理解。

接著,讓我們把這個多項(xiàng)式函數(shù)的系數(shù)使用之前得到的隨機(jī)數(shù),可以看到,得到的圖應(yīng)該是這樣:

2


因?yàn)殚_始時,我們使用的系數(shù)是隨機(jī)數(shù),所以這個函數(shù)和給定的數(shù)據(jù)匹配的非常差,而我們寫的模型就是為了通過學(xué)習(xí)得到更精確的系數(shù)值。


第3步:訓(xùn)練模型

最后一步就是要訓(xùn)練這個模型使得系數(shù)和這些散點(diǎn)更加匹配,而為了訓(xùn)練模型,我們需要定義下面的三樣?xùn)|西:

  • 損失函數(shù)(loss function):這個損失函數(shù)代表了給定多項(xiàng)式和數(shù)據(jù)的匹配程度。 損失函數(shù)值越小,那么這個多項(xiàng)式和數(shù)據(jù)就跟匹配。
  • 優(yōu)化器(optimizer):這個優(yōu)化器實(shí)現(xiàn)了一個算法,它會基于損失函數(shù)的輸出來修正系數(shù)值。所以優(yōu)化器的目的就是盡可能的減小損失函數(shù)的值。
  • 訓(xùn)練迭代器(traing loop):即它會不斷地運(yùn)行這個優(yōu)化器來減少損失函數(shù)。

所以,上面這三樣?xùn)|西的 關(guān)系就非常清楚了: 訓(xùn)練迭代器使得優(yōu)化器不斷運(yùn)行,使得損失函數(shù)的值不斷減小,以達(dá)到多項(xiàng)式和數(shù)據(jù)盡可能匹配的目的。這樣,最終我們就可以得到a、b、c、d較為精確的值了。


定義損失函數(shù)

這篇文章中,我們使用MSE(均方誤差,mean squared error)作為我們的損失函數(shù)。MSE的計(jì)算非常簡單,就是先根據(jù)給定的x得到實(shí)際的y值與預(yù)測得到的y值之差 的平方,然后在對這些差的平方求平均數(shù)即可。

1044137-20180415120225981-2029551102

于是,我們可以這樣定義MSE損失函數(shù):

function loss(predictions, labels) {
  // 將labels(實(shí)際的值)進(jìn)行抽象
  // 然后獲取平均數(shù).
  const meanSquareError = predictions.sub(labels).square().mean();
  return meanSquareError;
}

即這個損失函數(shù)返回的就是一個均方差,如果這個損失函數(shù)的值越小,顯然數(shù)據(jù)和系數(shù)就擬合的越好。


定義優(yōu)化器

對于我們的優(yōu)化器而言,我們選用 SGD (Stochastic Gradient Descent)優(yōu)化器,即隨機(jī)梯度下降。SGD的工作原理就是利用數(shù)據(jù)中任意的點(diǎn)的梯度以及使用它們的值來決定增加或者減少我們模型中系數(shù)的值。

TensorFlow.js提供了一個很方便的函數(shù)用來實(shí)現(xiàn)SGD,所以你不需要擔(dān)心自己不會這些特別復(fù)雜的數(shù)學(xué)運(yùn)算。 即 tf.train.sdg 將一個學(xué)習(xí)率(learning rate)作為輸入,然后返回一個SGDOptimizer對象,它與優(yōu)化損失函數(shù)的值是有關(guān)的。

在提高它的預(yù)測能力時,學(xué)習(xí)率(learning rate)會控制模型調(diào)整幅度將會有多大。低的學(xué)習(xí)率會使得學(xué)習(xí)過程運(yùn)行的更慢一些(更多的訓(xùn)練迭代獲得更符合數(shù)據(jù)的系數(shù)),而高的學(xué)習(xí)率將會加速學(xué)習(xí)過程但是將會導(dǎo)致最終的模型可能在正確值周圍搖擺。簡單的說,你既想要學(xué)的快,又想要學(xué)的好,這是不可能的。

下面的代碼就創(chuàng)建了一個學(xué)習(xí)率為0.5的SGD優(yōu)化器。

const learningRate = 0.5;
const optimizer = tf.train.sgd(learningRate);


定義訓(xùn)練循環(huán)

既然我們已經(jīng)定義了損失函數(shù)和優(yōu)化器,那么現(xiàn)在我們就可以創(chuàng)建一個訓(xùn)練迭代器了,它會不斷地運(yùn)行SGD優(yōu)化器來使不斷修正、完善模型的系數(shù)來減小損失(MSE)。下面就是我們創(chuàng)建的訓(xùn)練迭代器:

function train(xs, ys, numIterations = 75) {

  const learningRate = 0.5;
  const optimizer = tf.train.sgd(learningRate);

  for (let iter = 0; iter < numIterations; iter++) {
    optimizer.minimize(() => {
      const predsYs = predict(xs);
      return loss(predsYs, ys);
    });
  }

現(xiàn)在,讓我們一步一步地仔細(xì)看看上面的代碼。首先,我們定義了訓(xùn)練函數(shù),并且以數(shù)據(jù)中x和y的值以及制定的迭代次數(shù)作為輸入:

function train(xs, ys, numIterations) {
...
}

接下來,我們定義了之前討論過的學(xué)習(xí)率(learning rate)以及SGD優(yōu)化器:

const learningRate = 0.5;
const optimizer = tf.train.sgd(learningRate);

最后,我們定義了一個for循環(huán),這個循環(huán)會運(yùn)行numIterations次訓(xùn)練。在每一次迭代中,我們都調(diào)用了optimizer優(yōu)化器的minimize函數(shù),這就是見證奇跡的地方:

for (let iter = 0; iter < numIterations; iter++) {
  optimizer.minimize(() => {
    const predsYs = predict(xs);
    return loss(predsYs, ys);
  });
}

minimize 接受了一個函數(shù)作為參數(shù),這個函數(shù)做了下面的兩件事情:

1、首先它對所有的x值通過我們在之前定義的pridict函數(shù)預(yù)測了y值。

2、然后它通過我們之前定義的損失函數(shù)返回了這些預(yù)測的均方誤差。

minimize函數(shù)之后會自動調(diào)整這些變量(即系數(shù)a、b、c、d)來使得損失函數(shù)更小。

在運(yùn)行訓(xùn)練迭代器之后,a、b、c以及d就會是通過模型75次SGD迭代之后學(xué)習(xí)到的結(jié)果了。 

查看結(jié)果!

一旦程序運(yùn)行結(jié)束,我們就可以得到最終的a、b、c和d的結(jié)果了,然后使用它們來繪制曲線,如下所示:

3

這個結(jié)果已經(jīng)比開始隨機(jī)分配系數(shù)的結(jié)果擬合的好得多了!

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號