阅读 1807

前端如何学习机器学习之TensorFlow.js

TensorFlow.js简介

TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,其命名来源于本身的运行原理。Tensor(张量)意味着N维数组,Flow(流)意味着基于数据流图的计算,TensorFlow为张量从流图的一端流动到另一端计算过程。

TensorFlow是一个将复杂的数据结构传输至人工智能神经网中进行分析和处理过程的系统。目前被广泛的运用在语音识别或图像识别等多项机器学习和深度学习领域,它可在小到一部智能手机、大到数千台数据中心服务器的各种设备上运行。

TensorFlow的数据流运作模型如下图:

这里写图片描述

TensorFlow.js 是一个开源的用于开发机器学习项目的 WebGL-accelerated javascript 库。使用它可以在浏览器上创建CNN(卷积神经网络)、RNN(循环神经网络)等等,且可以使用终端的GPU处理能力训练这些模型。

#机器学习研究的范围 那么,学习机器学习之前,对于其中的一些概念我们需要有所了解。

我们解决一个问题有两种模式:一种叫做模型驱动(model driven),通过研究对象的物理、化学等机理模型,对对象进行建模,从而解决问题,比如我们熟知的牛顿三定律,对于上面那个公式就是我们已知输入x和机理模型f(),需要求解我们想要得到的y;而另外一种叫做数据驱动(data driven),随着人们遇到的问题越来越复杂,寻找对象机理模型的代价越来越大,反之数据获取的代价越来越小,于是科研工作者开始从另外角度思考问题,是否可以通过这些数据来分析得到我想要的东西,即我知道一些的样本(x,y)或者我只知道x,我想分析这些来得到对象的模型f(),进而当我再次拥有一个x的时候,我就可以得到我想要的y,如果不是那么严格的来讲,所有这种数据分析的方法都可以算作机器学习的范畴。

所以一个机器学习通常应该包括的基本要素有:训练数据,带参数的模型,损失函数,训练算法。训练数据作用自不必说;带参数的模型是用来逼近f();损失函数是衡量模型优劣的一个指标,比如模型识别分类的准确度;训练算法也可以叫做优化函数,用于不断更新模型的参数来最小化损失函数,得到一个较好的模型,或者叫做学习机。接下来将介绍一些机器学习中的基本概念,可能没有很强的连贯性。

模型

模型是对真实世界中问题域内的事物的描述,而不是对软件设计的描述。在机器学习中,模型的的具体还以有点类似于:带有一些待训练参数,用于逼近前文提到的f()的参数集合。在参数空间,f()只是一个点,而我提到的模型也是一个点,并且由于参数可以变,所以我要做的只是让我模型的这个点尽可能的接近真实f()的那个点。

器学习的模型算法有很多,但是比较常用的模型可以概括为三种:

  • 基于网络的模型:最典型的就是神经网络,模型有若干层,每一层都有若干个节点,每两个节点之间都有一个可以改变的参数,通过大量非线性的神经元,神经网络就可以逼近任何函数。
  • 基于核方法的模型:典型的是SVM和gaussian process,SVM把输入向量通过一个核映射到高维空间,然后找到几个超平面把数据分成若干个类别,SVM的核是可以调整。
  • 基于统计学习的模型:最简单的例子就是贝叶斯学习机,统计学习方法是利用数理统计的数学工具来实现学习机的训练,通常模型中的参数是一些均值方差等统计特征,最终使得预测正确概率的期望达到最大。

现实生活中,模型无处不在,如世界地图、图表等等都可以被认为是模型。为了说明模型是什么,我们举一个例子:Barcelona 房子价格随房间数的变化。

这里写图片描述
然后,我们将这两个数据使用一个 2D 图形展示,每个坐标轴对应一个参数。
这里写图片描述
现在,我们在此基础上预测下第 6 个或者以上房间的房子价格。 当然,这个模型还不够好,主要体现在以下方面:

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

对于第一个问题,我们可以添加样本数来解决,比如添加 100 万个数据。 对第二个问题,我们可以添加更多的坐标轴。在 2D 图形上我们可以画一条直线,在 3D 坐标轴里我们可以画一个平面。

这里写图片描述
对于如何处理 3D 以上的情形,比如 4D 甚至是 1000000D 呢,我们可以用数学和计算超平面来处理这种情况,而神经网络是一个很好的处理工具。

神经网络

在生物学中,一个典型的神经网络结构主要由:树状突、轴突和突触构成。

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

这里写图片描述

在机器学习中。简化后的神经则是下面这样的:

这里写图片描述

在机器学习中,以下部分是必须的:

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

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

这里写图片描述

训练模型

在上面的 2D 线性回归示例里,在图表中画条线就足以让我们开始预测新数据了。然而,“深度学习”的目的是要让我们的神经网络学着画这条线。可以参考下面的链接来学习:训练模型(Training Models)

画一条简单的线我们只需要包括一条神经的非常简单的神经网络对于训练模型来说是相对简单的情况,但其它的模型做的要复杂的多,比如归类两组数据就比较难了。例如,“训练学习”如何画出下面的图像:

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

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

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

这里写图片描述

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

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

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

这里写图片描述

TensorFlow.js使用

##1,创建神经网络 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
}
复制代码

该类的使用方法如下:

const model = new LinearModel();

// xs and ys -> array of numbers (x-axis and y-axis)
复制代码

##2,使用 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()
    )
  }
}
复制代码

然后我们调用该函数填入参数进行预测:

const prediction = model.predict(500); // Predict for the number 500
console.log(prediction) // => 420.423
复制代码

这里写图片描述
当然,我们提供了在线运行环境:stackblitz.com/edit/linear…

3,使用训练好的模型

有很多模型都可以在 TensorFlow.js 中使用,而且,你可以使用 TensorFlow 或 Keras 创建模型,然后导入到 TensorFlow.js。比如,你可以使用 posenet 模型 (实时人类姿态模拟) 来做些好玩的事情:

这里写图片描述
项目地址: github.com/aralroca/po… 它的使用也非常简单,首先导入这个模型:

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
      );
复制代码

然后为模型添加测试数据:

{
  "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
  ]
}
复制代码

下面是示例代码: github.com/aralroca/fi… ##4,从 Keras 导入模型 们可以从外部导入模型到 TensorFlow.js,在下面的例子里,我们将使用一个 Keras 的模型来进行数字识别 (文件格式为 h5)。为了达到目的,我们需要使用 tfjs_converter。

pip install tensorflowjs
复制代码

然后,使用转换工具:

tensorflowjs_converter --input_format keras keras/cnn.h5 src/assets
复制代码

现在,你可以将模型导入到 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);
复制代码

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

这里写图片描述
项目地址:github.com/aralroca/MN…

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

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

原文链接:aralroca.com/2018/08/24/… 参考:机器学习基本概念 TensorFlow.js基本概念