JayChiu's Blog

Tensorflow.js(三):基于 node 使用卷积神经网络进行文本识别

接着上期,本文将介绍基于 node 环境,通过 tensorflow.js 使用卷积神经网络进行分类任务处理,并将模型迁移至浏览器使用,在开始之前,我们需要再重新回忆一下相关概念。

点这里查看代码

A) Tensor Again

基本回顾

在开始前,我们需要再次回顾张量(tensor)的概念,因为张量是 tensorflow 中数据的中央单元。抛开复杂的概念来简单理解,张量就是我们用来描述和量化事物的载体。得以我们用标准化地通过数学与统计的方式探寻事物之间的联系。

试想,我们对于身边的事物,我们是如何将它们进行量化的,假设面前有这样一幅画?

  • 这幅画某一个像素点的颜色,我们可以用三个数字来描述,分别为 r, g, b三个通道,可以记录为 [255, 255, 255]

这是一维张量,但是事物并不总是以一个单位来呈现,有点,那么就会有线。

  • 多个点排列就变成了线,我们可以用若干个描述像素点的一阶张量来描述,即为

    [
        [255,, 255, 255], [0, 0, 0], ..., [100, 100, 100]
    ]
    

一维张量不足以描述的情况,那么可以用到二维张量,也就是包含了多个一维张量的张量,接下来我们来推导三维张量可以描述什么。

  • 从点到线,再到面,描述面就需要这样形状的张量:

    [
        [
            [255,, 255, 255], [0, 0, 0], ..., [100, 100, 100]
        ]
        [
            [255,, 255, 255], [0, 0, 0], ..., [100, 100, 100]
        ]
    ]
    

很明显,很多条线合到一起,就组合成了一幅画。这是一个三维张量

  • 如此一来,四维张量也就可以轻易想象出来了,包含多个图片的东西是什么呢? ---视频(想象下图每一个方块都是一个像素点)

张量的形状(shape)

如何计算形状?每一维所包含的元素个数,就是这一维的形状。

以下面的张量为例,他的形状是 [2, 4, 3],可以理解为高为2,宽为4,每个像素点有3个通道的图片

    [
        [
            [255,, 255, 255], [0, 0, 0], [100, 100, 100], [120, 80, 70]
        ]
        [
            [255,, 255, 255], [0, 0, 0], [100, 100, 100], [120, 80, 70]
        ]
    ]

卷积神经网络

在上一期中,其实已经对卷积神经网络进行了基本的介绍,这里再次回顾一下。

第一步:卷积层

卷积操作是一种图像到图像的转换,可以帮助我们提取图片的特征,如果输入图像是三维的图像张量,那么输出仍然是三维图像张量,只不过高度,宽度以及通道个数可能会有所不同。假设有以下灰度图

cnn

这是一个过滤器(filter),你可以把它想象成一个滤镜

cnn

过滤器会自上而下,从左到右扫描图像,每扫描一步,就会和与它重叠的输入图像部分相乘然后求和,得到输出像素。

多个过滤器各自负责提取特定的特征,不同的过滤器会对不同的视觉特征敏感。

下图可以直观地看到卷积操作的过程

第二步:引入非线性激活层

relu 激活函数将负值变为0,经过 relu 处理过的图像,参考下图

为什么要用relu?

首先,它引入了非线性。没有它,整个CNN就是一系列线性矩阵乘法和最大池化。同时,ReLU会使一部分神经元的输出为0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生

第三步:池化层

池化操作(polling)也是一种处理手段,它在保留了图片最重要的信息的同时,降低了维数。减少很多参数和不必要的运算,下图就是最大池化(Max polling)。

在上图的示例中,图像区块的宽高为 2 * 2,因为配置的 poolSize 是 [2, 2],区块同样会滑动,每次会得到 2 * 2 的区块,然后取这四个数字中的最大值,作为输出。这个操作缩小了图像尺寸,也为减少了后面的运算。

第四步:全连接层

经过多次卷积,池化后的图像会被flatten层拉平,并且与全连接层连接,此时会对特征图进行分类。只要有足量的带标签数据,就能增强特征与对应输出的联系。

由此可知我们为什么要进行池化,因为池化可以减少参数,减少参数可以减少过拟合的风险。也降低了计算量。

以下是一个经典的卷积神经网络的结构

在 tfjs-node 中实践

要识别验证码,我们需要上万张标注好的验证码图片作为训练样本,显然,犹豫在浏览器里无法访问文件系统,所以是很难完成这样的工作的,所以我们需要在 node 环境下进行训练。

模型设计

__________________________________________________________________________________________
Layer (type)                Input Shape               Output shape              Param #   
==========================================================================================
conv2d_Conv2D1 (Conv2D)     [[null,34,80,1]]          [null,32,78,8]            80        
__________________________________________________________________________________________
max_pooling2d_MaxPooling2D1 [[null,32,78,8]]          [null,16,39,8]            0         
__________________________________________________________________________________________
conv2d_Conv2D2 (Conv2D)     [[null,16,39,8]]          [null,14,37,16]           1168      
__________________________________________________________________________________________
max_pooling2d_MaxPooling2D2 [[null,14,37,16]]         [null,7,18,16]            0         
__________________________________________________________________________________________
conv2d_Conv2D3 (Conv2D)     [[null,7,18,16]]          [null,5,16,32]            4640      
__________________________________________________________________________________________
max_pooling2d_MaxPooling2D3 [[null,5,16,32]]          [null,2,8,32]             0         
__________________________________________________________________________________________
flatten_Flatten1 (Flatten)  [[null,2,8,32]]           [null,512]                0         
__________________________________________________________________________________________
dense_Dense1 (Dense)        [[null,512]]              [null,256]                131328    
__________________________________________________________________________________________
dense_Dense2 (Dense)        [[null,256]]              [null,144]                37008     
==========================================================================================
Total params: 174224
Trainable params: 174224
Non-trainable params: 0

由上面的summary可以看出,模型的参数量为 174224,参数量代表着模型的大小,参数量越大,模型越复杂,越能处理更复杂的事情。近期很火的chatgpt(GPT-3)是号称有1750亿个参数的模型,所以它能处理一些复杂的事情也就不足为奇了

onehot 编码

在这个项目中,笔者使用的是 onehot 编码,也就是将每个字符转换成一个长度为 36 * 4 的数组,每个数组中的每个元素都是 0,只有一个元素是 1,这个元素的下标就是这个字符的编码。

许多深度学习算法要求所有的输入输出变量都是数值(numeric)。那么如何将类别数据转换成数值数据?有两种方法:

  • 使用整数编码,比如将类别数据 猫、狗、鸡, 鸭 转换成 1、2、3, 4。
  • 使用onehot编码,比如将类别数据 猫、狗、鸡 转换成 [1,0,0]、[0,1,0]、[0,0,1]。

实际上,使用整数编码会让模型假设类别间存在自然的次序关系,从而导致结果有较大误差或得到意外的结果(猫 + 狗 为什么等于鸡?)

在浏览器内使用模型

在 node 环境中训练好模型后,我们可以将模型保存下来,然后在浏览器中使用。在本案例中,我们将在浏览器插件内加载模型,并使用模型对星纪sso出现的验证码进行预测。

在预测时,模型的输入依然要与训练时的输入保持一致,也就是 34 * 80 的图片,所以我们需要将验证码图片转换成 34 * 80 的图片。如果原来就是 34 * 80 的图片,那么就不需要转换了。

模型的输出是一个长度为 144 的数组,大多数元素都是 0,少数元素大于 0,我们将其切割成 4 个长度为 36 的数组,然后将每个数组中最大值的下标作为预测结果。

总结

卷积神经网络是一种非常强大的神经网络,它可以用来识别图片、音频、视频等。在目标检测,物体分类,自动驾驶等方面都有着广泛的应用。

回到 tfjs,基于 tfjs-node 可以高效训练出模型,然后在浏览器中使用模型,这就赋予了浏览器更多的能力。基于深度学习的应用值得进一步探索。

Reference