前端机器学习入门笔记3:归一化

1,402 阅读6分钟

一、什么是归一化

归一化就是把大数量级特征转化到较小的数量级下,通常是[0,1]或[-1,1]。例如,对于身高体重预测、房价预测问题,需要把身高体重数据和房价及影响因素的数据压到[0,1]、[-1,1]之间,然后再去训练它。

1、为何要归一化?

1)绝大多数Tensorflow.js的模型都不是给特别大的数设计的。

2)将不同数量级的特征转换到同一数量级,防止某个特征影响过大。

2、操作步骤:

1)准备训练数据,并将它们归一化。

2)训练模型并预测,将结果反归一化为正常数据。(以便于结果的正常展示)

3、学习的前置条件:

1)要有最新版本的Chrome和有代码编辑器

2)要有基础的前端和神经网络预备知识

3)理解前面学习过的线性回归

二、归一化训练数据

1、步骤

1)准备身高体重训练数据

2)使用tfvis可视化训练数据

3)使用Tensorflow.js的API进行归一化

代码实现如下:

index.js

import * as tf from '@tensorflow/tfjs';
import * as tfvis from '@tensorflow/tfjs-vis';

window.onload = () => {
  const heights = [150, 160, 170];
  const weights = [40, 50, 60];

  tfvis.render.scatterplot(
    { name: '身高体重训练数据' },
    { values: heights.map((x, i) => ({ x, y: weights[i] }))}, // 每个点的坐标
    {
      xAxisDomain: [140, 180],
      yAxisDomain: [30, 70]
    } // x轴和y轴的显示区间
  );
}

执行parcel src/hei*/*html进行打包后,效果如下:

那么,我们从图上来看一看,如何把图中三个点的数据压缩到[0,1]区间之内呢?这是个很简单的数学问题:

1)对于三个点的x坐标来说,将x值最大的那个点的坐标减去x值最小的那个点的坐标,得到最大x差值。

2)(每个点的x坐标 - x值最小的点的坐标) / 最大x差值 就能把每个点的x坐标都压缩到[0,1]区间内。

3)类似地,也可以将y坐标的值压缩到[0,1]区间内。

TensorFlow.js提供了相应的API来比较简单地完成上述操作,即const inputs = tf.tensor(heights).sub(150).div(20);。其中,sub是做减法,div是做除法。于是,通过如下代码,我们可以完成身高和体重数据的归一化:

const inputs = tf.tensor(heights).sub(150).div(20);
inputs.print(); // 会log出来[0, 0.5, 1]
const labels = tf.tensor(weights).sub(40).div(20);
labels.print(); // 会log出来[0, 0.5, 1]

三、训练、预测和反归一化

操作步骤:

1)定义一个神经网络模型

2)将归一化的数据喂给神经网络模型去学习

3)预测后把结果反归一化为正常数据之后来显示

代码如下:

index.js

import * as tf from '@tensorflow/tfjs';
import * as tfvis from '@tensorflow/tfjs-vis';

window.onload = async () => {
  const heights = [150, 160, 170];
  const weights = [40, 50, 60];

  tfvis.render.scatterplot(
    { name: '身高体重训练数据' },
    { values: heights.map((x, i) => ({ x, y: weights[i] }))},
    {
      xAxisDomain: [140, 180],
      yAxisDomain: [30, 70]
    }
  );

  const inputs = tf.tensor(heights).sub(150).div(20);
  inputs.print(); // 会log出来[0, 0.5, 1]
  const labels = tf.tensor(weights).sub(40).div(20);
  labels.print(); // 会log出来[0, 0.5, 1]

  // 添加模型
  const model = tf.sequential(); // sequential方法会创建一个连续的模型,什么是连续的模型呢?就是这一层的输入一定是上一层的输出。
  // 其中tf.layers.dense()会生成一个全链接层。该层实现了如下操作:outputs = activation(dot(input, kernel) + bias),其中activation是作为activation参数传递的激活函数,input是输入,kernel是由层创建的权重矩阵,bias是由层创建的偏差向量(偏置)。
  model.add(tf.layers.dense({
    units: 1, // 神经元的个数
    inputShape: [1] // inputShape是不允许写空数组的,[1]表示是一维的数据并且长度是1(即特征数量是1)
  }));
  model.compile({
    loss: tf.losses.meanSquaredError, // 损失函数:均方误差
    optimizer: tf.train.sgd(0.1) // 优化器:随机梯度下降,括号内的参数为学习速率
  })

  await model.fit(inputs, labels, {
    batchSize: 3, // 小批量随机梯度下降中的小批量的批量样本数
    epochs: 200, // 迭代整个训练数据的次数,这个也是个超参数,需要不断调整得到一个合适值
    callbacks: tfvis.show.fitCallbacks({
        name: '训练过程'
      },
      ['loss'], // 度量单位,用于指定可视化想看什么,这是主要是想看损失情况
    )
  }); // 训练模型

  // 预测
  const output = model.predict(tf.tensor([180]).sub(150).div(20)); // 注意这里传入的参数需要归一化
  // 输出预测结果
  alert(`如果身高为180cm,那么预测体重为${output.mul(20).add(40).dataSync()[0]}kg`); // 注意这里需要做反归一化
}

注意一下代码的最后两句,分别做了预测数据输入前的归一化和预测结果输出前的反归一化。

最终预测结果为:

“如果身高为180cm,那么预测体重为69.82542419433594kg”。

至此,归一化就学习完了,还是蛮简单的。主要是理解一下概念就好。由于高考临近,今天看到高中老师发出的学弟学妹们高考出征仪式的照片,有人举着一句宣传标语:

“我易人也易,切莫大意;我男人也难,我不畏难”。

我觉得这句话也适合技术学习。在技术学习的路上,有时是一马平川、风和雨润,有时是荆棘夹道、泥泞满路,但若你心态好,不管如何道路如何,沿途总有别样的风景!

末了,在这个下午茶的时光,录美国诗人罗伯特·弗罗斯特的诗歌一首——The Road Not Taken,作为结尾,愿你悦纳和享受每一个不同的选择和独特的当下,因为不管情况如何,每一刻都是此生独一无二的时光,enjoy :)

未选择的路

黄色的树林里分出两条路,

可惜我不能同时去涉足,

我在那路口久久伫立,

我向着一条路极目望去,

直到它消失在丛林深处。

但我却选了另外一条路,

它荒草萋萋,十分幽寂,

显得更诱人、更美丽,

虽然在这两条小路上,

都很少留下旅人的足迹,

虽然那天清晨落叶满地,

两条路都未经脚印污染。

呵,留下一条路等改日再见!

但我知道路径延绵无尽头,

恐怕我难以再回返。

也许多少年后在某个地方,

我将轻声叹息把往事回顾,

一片树林里分出两条路,

而我选了人迹更少的一条,

从此决定了我一生的道路。