TensorFlow.js、迁移学习与AI产品创新之道

2018-04-12 11:23:11 浏览数 (1)

TensorFlow 的 JS 版本终于出啦,deeplearn.js 正式收编至 TensorFlow 项目,并改名为 TensorFlow.js :

采用 WebGL 加速的基于浏览器的 JS 机器学习库。

摘要:

本文涉及 TensorFlow 基本概念的理解,迁移学习技术的实践应用,全文从技术聊到产品的玩法,设计师/产品经理只有懂得技术的新特性,才能为产品融入新的玩法。设计师也应该关注新技术带来的新的交互方式的变化,研究怎么样的交互方式才适合基于浏览器的深度学习应用。

阅读本文需要有 tensorflow ,及 javascript 、nodejs 的相关知识基础,以下为正文:

为了快速练习代码,我们可以使用 chrome 直接打开官网:

代码语言:javascript复制
https://js.tensorflow.org/

然后打开 console 面板,直接输入代码即可使用 TensorFlow.js 。官方指南写得非常清楚,对 TensorFlow.js 核心概念的介绍,包括 tensors , operations , models , layers 以及 training ,都有简洁的代码示例。这里配合动手环节,加深对这几个概念的理解。

1 概念篇

1.1 tensors

TensorFlow.js 把 N 维数组都统称为 tensor ,为方便理解,见下图。

动手实践下代码:

代码语言:javascript复制
var shape=[2,3];

var a=tf.tensor([1,2,3,4,4,5],shape);

a.print()

也可以改写为:

代码语言:javascript复制
var a=tf.tensor([[1,2,3],[4,4,5]]);

a.print()

还可以写成 tf.scalar, tf.tensor1d , tf.tensor2d , tf.tensor3d 和 tf.tensor4d,以提高代码的可读性。

代码语言:javascript复制
var a=tf.scalar(4);
a.print();

var b=tf.tensor1d([0,2,3,4]);
b.print();

var c=tf.tensor2d([[0,3],[4,5]]);
c.print();

TensorFlow.js还提供了直接创建所有值为0或者1的张量( tensor ),实验下:

代码语言:javascript复制
tf.zeros([10]).print();
tf.zeros([2,8]).print();
tf.zeros([2,2,3]).print();
tf.ones([10]).print();
tf.ones([2,8]).print();
tf.ones([2,2,3]).print();

1.2 Variables

Tensors 是不可变的,一旦创建,不能改变其值;而 variables 则可以动态改变其值,主要用于在模型训练期间存储和更新值。

代码语言:javascript复制
var initalValues=tf.ones([8]);
initalValues.print();

//biases变量,通过assign方法更新其值

var biases=tf.variable(initalValues);
biases.print();

var updatedValues=tf.tensor1d([0,1,2,3,4,5,6,7]);
updatedValues.print();

biases.assign(updatedValues);

biases.print();

1.3 Operations ( Ops )

一些数学的运算,矩阵变换,卷积操作,逻辑操作等。官方 api 文档很齐全,写得很清楚( https://js.tensorflow.org/api/0.6.1/ ),下面练习下 square 和 add :

代码语言:javascript复制
//square

var d=tf.tensor2d([[1,2,3],[4,5,6]]);
d.print();

var d_squared=d.square();
d_squared.print();

//add

var a=tf.tensor2d([[1,2,3],[4,5,6]]);
var b=tf.tensor2d([[3,1,9],[14,25,16]]);
a.print();
b.print();

var c=a.add(b);
c.print();

d.add(b).square().print();

1.4 Models and Layers

Models 相当于 JS 函数的概念,给定一些输入,使用 Ops 来表示模型所做的工作,产生一些期望的输出。 TensorFLow.js 有 2 种创建模型的方法。

代码语言:javascript复制
// 定义一个 predict 函数

function predict(input) {

// 实现一个 数学函数的计算 y = a * x ^ 2   b * x   c
  
  return tf.tidy(() => {
    const x = tf.scalar(input);

    const ax2 = a.mul(x.square());
    const bx = b.mul(x);
    const y = ax2.add(bx).add(c);

    return y;
  });
}

// 定义常量

var a = tf.scalar(2),
    b = tf.scalar(4),
    c = tf.scalar(8);
  
// 测试下 predict 函数

predict(1999993).print();

由于 TensorFlow.js 是使用 GPU 来运算的,所以需要管理 GPU 的内存,当使用 tensors 和 variables 时。其中, tf.tidy 的方法有助于避免内存泄漏(避免程序崩溃),试下 tidy :

代码语言:javascript复制
// y = 3 ^ 2   1

var y = tf.tidy(() => {
   // a, b, 以及 one 将会被清空当 tidy 结束时。
   const one = tf.scalar(1);
   const a = tf.scalar(3);
   const b = a.square();
   console.log('tensors 的数量 (in tidy): '   tf.memory().numTensors);
   return b.add(one);
});

console.log('tensors 的数量 (outside tidy): '   tf.memory().numTensors);
y.print();

除了 tidy 外,还有 dispose 可以用来手动管理 GPU 内存。试验下:

代码语言:javascript复制
const x=tf.tensor2d([[0,2,3],[1,2,3]]);
const x_squared=x.square();

x.print();
x_squared.print();
console.log(tf.memory().numTensors);

x.dispose();
console.log(tf.memory().numTensors);

x_squared.dispose();
console.log(tf.memory().numTensors);

tensorFlow.js 还内置了一些 model 的抽象,可以使用 tf.model 来构造一个不含 layer 的模型。 tf 包含的 layer 有 tf.layers.simpleRNN , tf.layers.gru 和 tf.layers.lstm 等。这里得通过几个小型项目来实践了。

2 官方示例

我们可以下载官方示例,在本地运行查看效果。官方 tensorFlow.js 项目,使用 yarn 作为包管理工具,使用 Parcel 作为 Web 应用的打包工具。

代码语言:javascript复制
http://www.parceljs.io

如何使用官方示例,只要解压后,进入项目目录,终端输入 yarn ,安装完依赖包后再输入 yarn watch 即可运行项目。

官网有几个示例,第一个简单的是从头开始构建一个小型的模型,用于拟合曲线。第二个示范了 CNN 识别手写数字。第三个使用了迁移学习,训练一个神经网络来预测摄像头的数据。第四个介绍如何将 Keras 或 TensorFlow 训练好的模型导入 TensorFlow.js 来使用。有兴趣可以详细学习下。

3 webcam-transfer-learning

其中官方的游戏示例 webcam-transfer-learning ,建议玩一玩,是基于 MobileNet 的一个迁移学习的例子。

3.1 MobileNet

MobileNets:

Efficient Convolutional Neural Networks for Mobile Vision Applications

这是谷歌的一篇论文提出的,可以极大的压缩模型文件大小,非常适合移动端使用。本文使用 Keras 预训练的图像分类模型 MobileNet_25_224 。通过加载训练好的 keras 模型,可以直接在浏览器使用或再次在浏览器中使用迁移学习,训练新的模型。

先下载训练好的模型:

代码语言:javascript复制
https://github.com/fchollet/deep-learning-models/releases/download/v0.6/mobilenet_2_5_224_tf.h5

然后终端运行:

代码语言:javascript复制
pip install tensorflowjs

然后运行:

代码语言:javascript复制
tensorflowjs_converter --input_format keras mobilenet_2_5_224_tf.h5 model

转成 tensorFlow.js 可调用的 model 后,我们需要把 model 放置在一个服务器上,并设置允许跨域请求,这边可以使用一个 nodejs 的库:

代码语言:javascript复制
npm install http-server -g

进入model文件夹内运行:

代码语言:javascript复制
http-server -p 3000 --cors

加载 model 可以使用:

代码语言:javascript复制
const model = await tf.loadModel(‘https://localhost:3000/model.json');

官方也很贴心的把模型放到 https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_0.25_224/model.json 供调用了。

这里我测试了下 MobileNet 的效果:

3.2 Transfer Learning

webcam-transfer-learning 是一个图像分类问题,将摄像头拍摄的照片与上下左右的动作做关联。主要是训练数据收集:摄像头拍摄,每张图片归一化处理成 shape 为 [1,244,244,3] 的张量,作为训练数据;为此 tensorFlow.js 特地封装了调用 webcam 的相关方法,以方便直接对接到 tensorFLow.js 中使用。并使用 Transfer Learning 迁移学习来减少训练数据的量,达到分类的目的。

3.2.1 预处理 加载预训练模型 MoblieNet ,并截取合适的层作为输出。上文已经介绍过如何把 keras 训练的模型转成 tensorFlow.js 的模型格式了,这里我们直接从谷歌提供的模型服务中获取。

代码:

代码语言:javascript复制
async function loadMobilenet() {
  const mobilenet = await tf.loadModel(
    'https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_0.25_224/model.json');

  console.log(mobilenet.layers)
  const layer = mobilenet.getLayer(‘conv_pw_13_relu');

  console.log(layer.output.shape)

  return tf.model({
    inputs: mobilenet.inputs,
    outputs: layer.output
  });
}

通过调用 getLayer(‘conv_pw_13_relu’) ,我们进入了预训练的 MobileNet 模型的内部层,并构建了一个新的模型,其中输入是与 MobileNet 相同的输入,但输出的是 MobileNet 中间层名为 conv_pw_13_relu 的层。我们凭经验选择了这一层( 它对我们的任务很有效 )。一般来说,接近预训练模型结束的层将在传输学习任务中表现更好,因为它包含输入的更高级语义特征。尝试选择另一个图层,看看它是如何影响模型质量的!可以使用 model.layers 打印模型的图层查看。

在代码中加入:

代码语言:javascript复制
console.log(layer.output.shape)

打印出来是 [ null , 7 , 7 , 256 ] ,每次用户拍摄照片,都会马上调用 MobileNet 输出 conv_pw_13_relu 层,作为 Model 的输入数据(上图的红色框)。

3.2.2 迁移学习 我们将把 MobileNet 的这一层输出作为我们新创建的模型的输入,新创建的模型输出为 4 个类别的预测。(上图的红色框)

代码语言:javascript复制
model = tf.sequential({
    layers: [   
      tf.layers.flatten({
        inputShape: [7, 7, 256]
      }),
      tf.layers.dense({
        units: ui.getDenseUnits(),
        activation: 'relu',
        kernelInitializer: 'varianceScaling',
        useBias: true
      }),
      tf.layers.dense({
        units: NUM_CLASSES,
        kernelInitializer: 'varianceScaling',
        useBias: false,
        activation: 'softmax'
      })
    ]
  });

创建2个全连接层的模型,独立于 mobilenet 模型。根据用户拍摄的4个图片,训练此新模型。

这里使用了 tf.layers.flatten 。关于 tf.layers.flatten 的使用,可以实践下:

代码语言:javascript复制
model=tf.sequential();
model.add(tf.layers.flatten({inputShape:[12,4]}));
nn=model.predict(tf.ones([99,12,4]));
console.log(nn.shape);
nn.print()

输入是一个shape 为[99,12,4]的三维张量,最后输出的是一个shape 为 [99, 48] 的二维张量,flatten 把 [12 , 4] ,压缩为 [ 12X4 ] 。

4 基于用户个性化数据的产品

webcam-transfer-learning 游戏给我们提供了一个基于用户个性化数据的玩法。用户可以非常低成本的训练属于自己的图像分类模型,用于各种分类问题。我们可以拓展下,比如识别用户的手势动作,来控制游戏中的人物;识别用户的表情,控制3d人物的表情;识别图像中的人脸数量,自动隐藏所浏览的内容,防止被窥视……甚至 autodraw 、ui2code 、手写字识别等这些应用都可以尝试融入用户个性化的数据再训练的玩法,给予用户掌控权。

我认为新技术都会有一种很自然的新的交互方式与之匹配。基于浏览器的用户个性化数据再训练,可以提炼出以下基本的交互流程:

设定类别

—> 采集数据

—> 开始训练

—> 使用用户数据

—> 核心功能

—> 完成任务/得到某个结果。

用户使用自己的数据,应用更符合用户个性化特征,是一种不同于个性化推荐的“个性化”产品设计方法。

以上为全文内容,本文同时在微信公众号:Design-AI-Lab 发表。最近我在思考把文章当成产品来打磨,定了个小基调:一篇文章尽量涉及2个不同领域的内容,跨界思考之间的关联性。欢迎读者在微信群交流,入群方式留言获取。

0 人点赞