Tensorflow.js介绍与例子

1,295 阅读10分钟

开源库TensorFlow.js大约在几年前推出。然而,直到现在我还没有设法去尝试它。在这篇文章中,我们将了解如何使用这项技术,并且我们将在一个真实世界的分类问题上做到这一点。我们的想法是利用TensorFlow.js的可能性,在浏览器或Node.js下构建和运行我们的机器学习和深度学习模式。说实话,我一开始是有点怀疑的。然而,这变成了一种很酷的方式,让网络开发者数据科学家更紧密地联系在一起。

在这篇文章中,我们介绍了:

  1. 为什么要考虑Tensorflow.js?
  2. 安装
  3. 葡萄酒质量分类问题
  4. 数据分析
  5. 用Tensorflow.js实现

1.为什么要考虑Tensorflow.js?

从本质上讲,在使用TensorFlow.js时,我们可以考虑几个好处。对我来说,主要的好处是,你可以直接在浏览器中建立模型。除此之外,你还可以从Python中导入现有的预训练模型并重新训练它们。想象一下,你正在使用基于NoSQL JSON的数据库(如MongoDB)的JavaScript栈工作。这当然是我们应该考虑使用TensorFlow.js的用例。

TensorFlow.js包含了Keras API,并将其作为高级API公开。这非常好,它简化了构建机器学习和深度学习模型的过程。它还包括一个低级别的API,以前称为deeplearn.js,可用于线性代数和自动微分*。也支持急切执行TensorFlow.jsWebGL驱动,这是一个JavaScript*API,用于在任何网络浏览器中渲染2D和3D图形,不需要插件。

在这篇文章中,我们将使用TensorFlow.js建立一个简单的神经网络,它将解决一个简单的分类问题。然而,在此之前,让我们看看如何安装TensorFlow.js。

2.安装

我们有几种方法可以使用TensorFlow.js。首先,当然是通过在我们的主HTML文件中添加 脚本 标签在我们的主HTML文件中使用。

<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0/dist/tf.min.js"></script>

你也可以用以下方式安装它 npm Node.js下进行设置。

npm install @tensorflow/tfjs
yarn add @tensorflow/tfjs

Tensorflow.js

TensorFlow有GPU支持,性能更高。你可以像这样安装它。

npm install @tensorflow/tfjs-node-gpu

npm install @tensorflow/tfjs-node-gpu

只有当你的系统有一个支持CUDA的NVIDIA® GPU并且你使用Linux时,才可以使用这个选项。如果不是这种情况,你仍然可以通过使用TensorFlow与naitiveC++绑定来获得更好的结果。

npm install @tensorflow/tfjs-node
yarn add @tensorflow/tfjs-node

3.葡萄酒质量分类问题

如果你读过我们以前的一些文章,你可能会注意到我们喜欢使用这个数据集。这是因为这个数据集对于简单的 分类分析来说确实很好,但它来自真实世界。我们的目标是根据提供的化学数据来预测葡萄酒的质量。数据本身是关于Vinho Verde,一种来自葡萄牙米尼奥地区的独特产品。这种产品占葡萄牙葡萄酒总产量的15%,葡萄牙是世界上第十大葡萄酒生产国。

Wine Dataset Visual

信息收集时间为2004年5月至2007年2月,由于隐私和物流问题,只提供了物理化学感官 变量。数据集中没有提供价格和产地的数据。数据集包含两个.csv文件,一个是红葡萄酒(1599个样本),一个是白葡萄酒(4898个样本)。在这篇文章中,我们将只使用白葡萄酒的样本。

每个样品都包含这些特征:

  • 固定酸度
  • 挥发性酸度
  • 柠檬酸
  • 残糖
  • 氯化物
  • 游离二氧化硫
  • 二氧化硫总量
  • 浓度
  • pH值
  • 硫酸盐
  • 酒精含量
  • 质量(0-10分之间)

4.数据分析

我知道在JavaScripuniverse中,一定有适合本章目的的库。但是,我懒得去找它们,所以数据的分析是在Python中完成的。如果你有建议,我可以为这些目的使用哪些JavaScript模块,请把信息发给我,我将非常感激。

数据分析是由几个子步骤本身组成的:

  • 单变量分析- 分析每个特征的类型和性质。
  • 缺失数据处理--检测缺失数据并制定相关策略。
  • 离群点检测--检测数据中的异常情况。离群点是指在某些数据中偏离整体模式的样本。
  • 相关分析--比较彼此之间的特征。

在单变量分析中,我们注意到输出数据的质量实际上是整数而不是类别。这将在实施过程中得到处理。除此以外,我们还注意到特征不在同一尺度上。这可能会导致一个问题,所以我们也需要在实施过程中处理这个问题。

在缺失数据处理阶段,我们注意到有些样品的 固定酸度 特征是空的。我们的策略是用该特征的平均值来替换这一信息。也有其他的选择,比如用最大特征值来改变缺失值,或者一些默认值。让我们检查一下质量分布,检测一下离群值。

从上图中,我们可以看到大部分的葡萄酒都属于5到6的类别。这意味着大多数的葡萄酒都是平均水平,我们只有少数质量高或低的葡萄酒。最后,让我们检查一下相关矩阵(correlationmatrix)。

正如你所看到的,我们无法检测到对质量影响太大的特征。唯一能引起我们怀疑的是 残留糖分 特征和 密度 特征之间的高相关性 。然而,我们将把这两个特征留在游戏中,看看我们会在哪里着陆。

5.用Tensorflow.js实现

5.1 作为JSON的数据

数据集本身是以*.csv文件格式出现的。所以我们要做的第一件事就是将其转换为JSON文件并上传。你可以在这里找到整个新创建的JSON文件。一般来说,.csv文件中的每个样本现在都是一个单独的JSON*节点。下面是它的样子。

 [
    {
       "fixed_acidity":7,
       "volatile_acidity":0.27,
       "citric_acid":0.36,
       "residual_sugar":20.7,
       "chlorides":0.045,
       "free_sulfur_dioxide":45,
       "total_sulfur_dioxide":170,
       "density":1.001,
       "pH":3,
       "sulphates":0.45,
       "alcohol":8.8,
       "quality":6
    },
    {
       "fixed_acidity":6.3,
       "volatile_acidity":0.3,
       "citric_acid":0.34,
       "residual_sugar":1.6,
       "chlorides":0.049,
       "free_sulfur_dioxide":14,
       "total_sulfur_dioxide":132,
       "density":0.994,
       "pH":3.3,
       "sulphates":0.49,
       "alcohol":9.5,
       "quality":6
    },
  ...

我们这样做是因为你知道,在JavaScript中处理JSON文件是很不方便的。

5.2 HTML文件

现在,让我们看看我们的index.html文件。

<!DOCTYPE html>
<html>
<head>
  <title>TensorFlow.js Wine Quality Classification</title>
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0/dist/tf.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis@1.0.2/dist/tfjs-vis.umd.min.js"></script>
</head>

<body>
  <script src="script.js"></script>
</body>
</html>

正如你所看到的,我们为TensorFlow.js添加了提到的脚本标签,并为tfjs-vis添加了附加标签。这是一个用于浏览器可视化的小库。

5.3 JavaScript文件

除此以外,你可以注意到我们定义了script.js。这个文件与index.html位于同一个文件夹中。要运行这整个过程,你所要做的就是在你的浏览器中打开index.html。下面是script.js文件中的主 运行 函数的样子。

async function run() {
  
  const data = await getData();
  displayData(data);
  
	const model = createModel();  
	tfvis.show.modelSummary({name: 'Model Summary'}, model);

	const tensorData = prepareData(data);
	const {inputs, outputs} = tensorData;
		
	await trainModel(model, inputs, outputs, 100);
	console.log('Done Training');
  
  await evaluateModel(model, inputs, outputs);
}

这个函数本质上揭示了我们的工作流程。首先,我们使用getDatamethod从这个位置获取数据。

/**
  * @desc retrieves data from defined location
  * @return wine data as json
*/
async function getDataFunction() {
  const wineDataReq = await fetch('https://raw.githubusercontent.com/NMZivkovic/file_hosting/master/wine_quality.json');  
  const wineData = await wineDataReq.json();  
  return wineData;
}

这可以通过使用fetch方法简单实现。之后,我们使用displayData来绘制一些有趣的图形。

/**
  * @desc plots one 
  * @param array values - array of values
  * @param string name - name of the plot
  * @param string xoutput - x name 
  * @param string youtput - y name
*/
function singlePlot(values, name, xoutput, youtput)
{
  tfvis.render.scatterplot(
    {name: name},
    {values}, 
    {
      xoutput: xoutput,
      youtput: youtput,
      height: 300
    }
  );
}

/**
  * @desc plots one 
  * @param json data - complete json that contains wine quality data 
*/
function displayDataFunction(data){
  let displayData = data.map(d => ({
    x: d.alcohol,
    y: d.quality,
  }));

  singlePlot(displayData, 'Alchocol v Quality', 'Alchocol', 'Quality')

  displayData = data.map(d => ({
    x: d.chlorides,
    y: d.quality,
  }));

  singlePlot(displayData, 'Chlorides v Quality', 'Chlorides', 'Quality')

  displayData = data.map(d => ({
    x: d.citric_acid,
    y: d.quality,
  }));

  singlePlot(displayData, 'Citric Acid v Quality', 'Citric Acid', 'Quality')
}

注意,在上面的gist中,singlePlot函数也被介绍了。这个方法包装了tfjs-vis功能,只显示一个图形。displayData函数利用这个方法绘制了三个图形。下面是它们。

这里我们可以看到不同特征的质量分布。一旦我们将数据可视化,我们就可以创建我们的模型。这是在函数createModel中完成的。

/**
  * @desc creates tensorflow graph
  * @return model
*/
function createModelFunction() {
  const model = tf.sequential(); 
  model.add(tf.layers.dense({inputShape: [11], units: 50, useBias: true, activation: 'relu'}));
  model.add(tf.layers.dense({units: 30, useBias: true, activation: 'tanh'}));
  model.add(tf.layers.dense({units: 20, useBias: true, activation: 'relu'}));
  model.add(tf.layers.dense({units: 10, useBias: true, activation: 'softmax'}));

  return model;
}

这里的主要目标不是为这个问题生成一个完美的模型,而是尝试一些TensorFlow.js的可能性。如果你熟悉用Keras构建神经网络模型,这个API将很容易理解。然而,如果你想了解更多关于神经网络的知识,你可以在这里查看我们的一套电子书。

让我们快速浏览一下。我们使用sequential来为我们的模型创建一个占位符。这样,我们就可以在其中添加不同的层。然后,我们利用密集连接(dense)来添加密集连接的神经元层。在第一次调用中,我们创建了一个由50个神经元组成的隐藏层,以及一个由11个神经元组成的输入层。我们有11个输入,因为在我们的数据集中我们有11个特征。然后我们再增加两个隐藏层,分别有30和20个神经元。最后,我们的输出层有10个神经元,因为我们有10个可能的酒的类别。一旦我们用tfjs-vis打印出模型的摘要,我们得到的结果就是这样。

棒极了!现在,我们为模型本身准备数据。我们的模型不能使用JSON对象,也不能使用数组。我们需要创建张量对象。这是在prepareData函数中完成的。

/**
  * @desc creates array of input data for every sample
  * @param json data - complete json that contains wine quality data 
  * @return array of input data
*/
function extractInputs(data)
{
  let inputs = []
  inputs = data.map(d => [d.fixed_acidity, d.volatile_acidity, d.citric_acid, d.residual_sugar, d.chlorides, d.free_sulfur_dioxide, d.total_sulfur_dioxide, d.density, d.pH, d.sulphates, d.alcohol])
	return inputs;
}

/**
  * @desc converts data from json format to tensors
  * @param json data - complete json that contains wine quality data 
  * @return tuple of converted data that can be used for training model
*/
function prepareDataFunction(data) {
  
  return tf.tidy(() => {
    tf.util.shuffle(data);
    
    const inputs = extractInputs(data);
    const outputs = data.map(d => d.quality);

    const inputTensor = tf.tensor2d(inputs, [inputs.length, inputs[0].length]);
    const outputTensor = tf.oneHot(tf.tensor1d(outputs, 'int32'), 10);

    const inputMax = inputTensor.max();
    const inputMin = inputTensor.min();  
    const outputMax = outputTensor.max();
    const outputMin = outputTensor.min();

    const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
    const normalizedoutputs = outputTensor.sub(outputMin).div(outputMax.sub(outputMin));

    return {
      inputs: normalizedInputs,
      outputs: normalizedoutputs,
      inputMax,
      inputMin,
      outputMax,
      outputMin,
    }
  });  
}

在这个函数中,我们首先将JSON对象转换成简单的数组。我们把数据分成输入和输出。在这个特殊的例子中,我们还没有把数据分成训练集和测试集,这是可以改进的地方。

一旦这样做了,我们就把它们转换成张量。最后,我们对数据进行归一化处理,也就是说,我们把它放在同一个尺度上。这是我们在数据分析阶段注意到的事情。另外,请注意,我们使用oneHotmethod将输出数据转换为分类变量。

所以,我们已经完成了所有的准备步骤,我们可以使用trainModel函数来训练我们的模型。

/**
  * @desc trains model
  * @return trained model
*/
async function trainModelFunction(model, inputs, outputs, epochs) {
  model.compile({
    optimizer: tf.train.adam(),
    loss: 'categoricalCrossentropy',
    metrics: ['accuracy'],
  });
  
  const batchSize = 64;
  
  return await model.fit(inputs, outputs, {
    batchSize,
    epochs,
    shuffle: true,
    callbacks: tfvis.show.fitCallbacks(
      { name: 'Training Performance' },
      ['loss', 'accuracy'], 
      { height: 200, callbacks: ['onEpochEnd'] }
    )
  });
}

再一次,你可以注意到TensorFlow.js保留了与Python中TensorFlowAPI相似的API。我们用Adam优化器和cathegorical crossentropy编译我们的模型。然后我们用fitmethod调用来运行训练过程。这个过程也是可视化的。

最后,我们使用evaluateModel方法来评估我们神经网络的准确性。这个方法只是对创建的模型的评估方法的一个封装。

/**
  * @desc evaluates the model
*/
async function evaluateModelFunction(model, inputs, outputs)
{
  const result = await model.evaluate(inputs, outputs, {batchSize: 64});
  console.log('Accuracy is:')
  result[1].print();
}

评估的输出被打印在控制台。

准确率是:0.7163332223892212

我们得到的准确率只有71%,这意味着我们的模型还有很多需要改进的地方。然而,我们能够在浏览器中完成这一切,这很了不起。

总结

在这篇文章中,我们学习了如何使用JavaScript进行机器学习和深度学习。我们弄清楚了如何使用TensorFlow.js制作基本模型并对其进行训练。在未来的文章中,我们将进一步研究这个库,并尝试用它来实现更复杂的架构。

谢谢你的阅读!