深圳幻海软件技术有限公司 欢迎您!

科普 | 从TensorFlow.js入手了解机器学习

2023-02-26

对前端开发者来说了解机器学习是一件有挑战性的事情。我学习机器学习没有多久,在这个领域是个新手,在本文里我将尝试用自己的理解去解释一些概念。不过,在使用已有的AI模型的时候我们并不需要很深的机器学习知识。我们可以使用现有的一些工具比如Keras、TensorFlow或TensorFlow.js。这里我

对前端开发者来说了解机器学习是一件有挑战性的事情。我学习机器学习没有多久,在这个领域是个新手,在本文里我将尝试用自己的理解去解释一些概念。

不过,在使用已有的 AI 模型的时候我们并不需要很深的机器学习知识。我们可以使用现有的一些工具比如 Keras、TensorFlow 或 TensorFlow.js。这里我们将看看如何创建 AI 模型并且使用 TensorFlow.js 中的一些复杂的模型。

虽说不需要很深的知识,不过还是让我来解释一些基本概念。

什么是模型?

或者更好的问题是:什么是现实?是的,这很难回答,我们必须简化问题以便理解。

表现部分简化版现实的一个方法就是使用模型。所以,你可以认为有无数个模型,如世界地图、图表等等。

没有机器参与其中的模型我们更易于理解。比如,如果我们想要创建一个模型来表示随房间数变化 Barcelona 房子价格的变化。

首先,我们需要收集一些数据:

Number of rooms Prices
3 131.000€
3 125.000€
4 235.000€
4 265.000€
5 535.000€

然后,我们将这两个数据使用一个 2D 图形展示,每个坐标轴对应一个参数。

然后...Duang! 我们现在可以画一条线并且预测有 6 个以上房间的房子价格。

这个模型叫做线性回归,这是机器学习里最简单的模型之一。

当然,这个模型还不够好:

  • 只有 5 个样本,结果不够可信
  • 只有两个参数,但其实影响房子价格有更多的因素,如地理位置、房子年龄等

对于***个问题,我们可以添加样本数来解决,比如添加 100 万个数据。

对第二个问题,我们可以添加更多的坐标轴。在 2D 图形上我们可以画一条直线,在 3D 坐标轴里我们可以画一个平面。

 

但是,如何处理 3D 以上的情形,比如 4D 甚至是 1000000D 呢? 

我们的大脑无法想象多维下的图表,不过好消息是,我们可以用数学和计算超平面来处理这种情况,而神经网络是一个很好的处理工具。 

顺便,使用 TensorFlow.js 不需要成为数学专家。 

什么是神经网络?

在理解神经网络之前,让我们先来看看什么是神经。 

现实世界里的神经差不多长这样:  

 

神经中最重要的部分包括: 

  • 树状突(Dendrites):数据输入的地方。 
  • 轴突(Axon):输出端。 
  • 突触(Synapse):神经之间进行交流的结构。它负责将电信号从神经轴突的末端传递到附近神经的树状突。这些突触结构是学习的关键,因为它们在使用中会增减电信号的活动。 

在机器学习中的神经则是 (简化后):  

  • 输入(Input):输入的参数。 
  • 权值(Weight):和突触一样,它们以增减来调整神经的活动来达成更好的线性回归。 
  • 线性函数 (Linear function):每个神经就像一个线性回归函数,目前为止一个线性回归函数只需要一个神经。 
  • 激活函数 (Activation function):我们能提供一些激活函数来改变从一个标量 (Scalar) 到另一个非线性的函数。比如:sigmoid、RELU、tanh。 
  • 输出 (Output):经过激活函数计算后的输出结果。 

激活函数的使用非常有用,它是神经网络的精髓所在。没有激活函数的话神经网络不可能很智能。原因是尽管在网络中你可能有很多神经,神经网络的输出总会是一个线性回归。我们需要一些机制来改变这个独立的线性回归为非线性的以解决非线性的问题。 

感谢这些激活函数,我们可以将这些线性函数转换到非线性函数: 

 

训练模型

在上面的 2D 线性回归示例里,在图表中画条线就足以让我们开始预测新数据了。然而,“深度学习”的概念是要让我们的神经网络学着画这条线。 

画一条简单的线我们只需要包括一条神经的非常简单的神经网络,但其它的模型做的要复杂的多,比如归类两组数据。在这种情况下,“训练”将会学习如何画出下面的图像:  

这还不算复杂的,因为这只是 2D 范畴内。 

每个模型都是一个世界,所有这些模型的训练的概念都差不多。首先是画一条随机的线,然后在一个循环算法中改进它,修复每个循环中的错误。这种优化算法又叫做梯度下降法 (Gradient Descent),还有更多复杂的算法如 SGD、ADAM,概念都类似。 

为了理解梯度下降法,我们需要知道每个算法 (线性回归、逻辑回归等) 有不同的代价函数 (cost function) 来度量这些错误。 

代价函数总会收敛于某个点,它可能是凸或非凸函数。***的收敛点将在 0% 错误时被发现,我们的目标就是到达这个点。  

 

但我们使用梯度下降算法时,我们开始于一个随机的点,但是我们不知道它在哪。想象一下你在一座山上,完全失明,然后你需要一步一步的下山,走到***的位置。如果地形复杂 (像非凸函数),下降过程将更加复杂。 

我不会深入的解释什么是梯度下降算法。你只需要记住它是一种优化算法,用来训练 AI 模型以最小化预测产生的错误。这个算法需要时间和 GPU 来计算矩阵乘法。收敛点通常在***轮执行中难以达到,所以我们需要对一些超参数 (hyperparameter) 如学习率(learning rate)进行调优,或者添加一些正则化 (regularization)。 

经过反复的梯度下降法,我们达到了离收敛点很近的地方,错误率也接近 0%。这时候,我们的模型就创建成功,可以开始进行预测了。   

使用 TensorFlow.js 来训练模型

TensorFlow.js 给我们提供了一个简单的办法来创建神经网络。 

首先,我们将先创建一个 LinearModel 类,添加trainModel方法。 

对这类模型我将使用一个序列模型 (sequential model),序列模型指的是某一层的输出是下一层的输入,比如当模型的拓扑结构是一个简单的栈,不包含分支和跳过。 

在trainModel方法里我们将定义层 (只需要使用一个,这对于线性回归问题来说足够了): 

 

import * as tf from '@tensorflow/tfjs'  
 
/**  
* Linear model class  
*/  
export default class LinearModel {  
  /**  
  * Train model  
  */  
  async trainModel(xs, ys){ 
    const layers = tf.layers.dense({  
      units: 1, // Dimensionality of the output space  
      inputShape: [1], // Only one param  
    });  
    const lossAndOptimizer = {  
      loss: 'meanSquaredError' 
      optimizer: 'sgd', // Stochastic gradient descent  
    }; 
 
    this.linearModel = tf.sequential();  
    this.linearModel.add(layers); // Add the layer  
    this.linearModel.compile(lossAndOptimizer);   
 
    // Start the model training!  
    await this.linearModel.fit(  
      tf.tensor1d(xs),  
      tf.tensor1d(ys),  
    );  
  }   
 
  ...more  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

 

该类的使用方法: 

 

const model = new LinearModel();   
// xs and ys -> array of numbers (x-axis and y-axis) 
await model.trainModel(xs, ys); 
  • 1.
  • 2.
  • 3.

 

训练结束后,我们可以开始进行预测了! 

使用 TensorFlow.js 进行预测

预测的部分通常会简单些。训练模型需要定义一些超参数,相比之下,进行预测很简单。我们将在 LinearRegressor 类里添加该方法: 

 

import * as tf from '@tensorflow/tfjs' 
export default class LinearModel {  
  ...trainingCode  
  predict(value){  
    return Array.from 
      this.linearModel  
      .predict(tf.tensor2d([value], [1, 1]))  
      .dataSync()  
    )  
  }  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

 

现在,我们在代码里使用预测方法 

 

const prediction = model.predict(500); // Predict for the number 500  
console.log(prediction) // => 420.423   
  • 1.
  • 2.

420.423" title="const prediction = model.predict(500); // Predict for the number 500 console.log(prediction) // => 420.423" width="auto" height="auto" border="0">

你可以在线运行一下这段代码: 

https://stackblitz.com/edit/linearmodel-tensorflowjs-react 

在 TensorFlow.js 中使用训练好的模型

学习如何创建模型是最难的部分,正常化训练数据,正确选择所有的超参数,等等。如果你是新手并且想使用某些模型玩玩,你可以使用训练好的模型。 

有很多模型都可以在 TensorFlow.js 中使用,而且,你可以使用 TensorFlow 或 Keras 创建模型,然后导入到 TensorFlow.js。 

比如,你可以使用 posenet 模型 (实时人类姿态模拟) 来做些好玩的事情:  

代码在: https://github.com/aralroca/posenet-d3 

它的使用非常简单: 

 

import * as posenet from '@tensorflow-models/posenet';   
// Constants  
const imageScaleFactor = 0.5;  
const outputStride = 16;  
const flipHorizontal = true 
const weight = 0.5;   
 
// Load the model  
const net = await posenet.load(weight);   
 
// Do predictions  
const poses = await net  
      .estimateSinglePose(  
          imageElement,   
          imageScaleFactor,   
          flipHorizontal,   
          outputStride  
      ); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

 

poses 变量在这个 JSON 文件里: 

 

 
  "score": 0.32371445304906,  
  "keypoints": [  
    {  
      "position": {  
        "y": 76.291801452637,  
        "x": 253.36747741699  
      },  
      "part""nose" 
      "score": 0.99539834260941  
    },  
    {  
      "position": {  
        "y": 71.10383605957,  
        "x": 253.54365539551  
      },  
      "part""leftEye" 
      "score": 0.98781454563141  
    },  
    // ...And for: rightEye, leftEar, rightEar, leftShoulder, rightShoulder  
    // leftElbow, rightElbow, leftWrist, rightWrist, leftHip, rightHip,  
    // leftKnee, rightKnee, leftAnkle, rightAnkle  
  ]  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

 

想象一下仅仅这个模型就可以做多少有趣的事情啊!  

上面这个示例代码在: https://github.com/aralroca/fishFollow-posenet-tfjs 

从 Keras 导入模型

我们可以从外部导入模型到 TensorFlow.js,在下面的例子里,我们将使用一个 Keras 的模型来进行数字识别 (文件格式为 h5)。为了达到目的,我们需要使用 tfjs_converter。 

 

pip install tensorflowjs 
  • 1.

然后,使用转换工具: 

 

tensorflowjs_converter --input_format keras keras/cnn.h5 src/assets 
  • 1.

现在,你可以将模型导入到 JS 代码里了。 

 

// Load model  
const model = await tf.loadModel('./assets/model.json');   
 
// Prepare image  
let img = tf.fromPixels(imageData, 1);  
img = img.reshape([1, 28, 28, 1]); 
img = tf.cast(img, 'float32');   
 
// Predict  
const output = model.predict(img); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

 

仅需几行代码,你就可以使用 Keras 中的数字识别模型。当然,我们还可以加入一些更好玩的逻辑,比如,添加一个 canvas 来画一个数字,然后捕捉图像来识别数字。  

代码: https://github.com/aralroca/MNIST_React_TensorFlowJS 

为何在浏览器运行 AI?

如果硬件不行,在浏览器上训练模型可能效率非常低下。TensorFlow.js 借助了 WebGL 的接口来加速训练,但即使这样它也比 TensorFlow Python 版本要慢 1.5-2 倍。 

但是,在 TensorFlow.js 之前,我们基本不可能不靠 API 交互在浏览器使用机器学习模型。现在我们可以在我们的应用里 离线的 训练和使用模型。并且,无需与服务端交互让预测变得更快。 

另一个好处是在浏览器执行这些计算可以降低服务器开销,节省成本。 

结论

  • 模型是我们用于展现现实某一部分的简化手段,可以用来进行预测。 
  • 创建模型的一个好方式是使用神经网络。 
  • 创建神经网络的简单易用方式是 TensorFlow.js。