前端AI实战——告诉世界前端也能作AI

我想大多数人和我同样,第一次听见“人工智能”这个词的时候都会以为是一个很高大上、高不可攀的概念,特别像我这样一个平凡的前端,和大部分人同样,都以为人工智能其实离咱们很遥远,咱们对它的印象老是停留在各类各样神奇而又复杂的算法,这些仿佛都是那些技术专家或者海归博士才有能力去作的工做。我也曾一度觉得本身和这个行业没有太多缘分,但自从Tensorflow发布了JS版本以后,这一领域又引发了个人注意。在python垄断的时代,发布JS工具库不就是意味着咱们前端工程师也能够参与其中?html

当我决定开始投身这片领域作一些本身感兴趣的事情的时候,却发现身边的人投来的都是鄙夷的目光,他们对前端的印象,还老是停留在上个年代那些只会写写页面脚本的切图仔,只有身处这片领域的咱们才知道大前端时代早已发生了翻天覆地的变革。前端

今天,我就带领你们从原理开始,尽量用最通俗易懂的方式,让JS的爱好者们快速上手人工智能。python

具体项目可参照:github.com/jerryOnlyZR…git

本文就单拿人工智能下的一块小领域——“图像识别”做一些简单介绍和实战指引,固然这些都只是这片大领域下的冰山一角,还有不少不少知识等着你去发掘。github

1.CNN卷积神经网络原理剖析

若是我不讲解这部份内容,而是直接教大家怎么使用一个现成的库,那这篇文章就没什么价值了,看完以后给大家留下的也必定都会是“开局一张图,过程全靠编”的错觉。所以,要真正了解人工智能,就应该进入这个黑盒,里面的思想才是精华。算法

1.1.图像灰度级与灰度图

1.1.1.基本概念

要作图像识别,咱们确定要先从图像下手,你们先理解一个概念——图像灰度级。后端

众所周知,咱们的图片都是由许多像素点组成的,就好像一张100*100像素的图片,就表示它是由10000个像素点呈现的。但你可曾想过,这些像素点能够由一系列的数字表示嘛?数组

就先不拿彩色的图片吧,彩色太复杂了,咱们就先拿一张黑白的图片为例,假设咱们以黑色像素的深浅为基准,将白色到黑色的渐变过程分为不一样的等级,这样,图片上每个像素点都能用一个最为临近的等级数字表示出来:网络

若是咱们用1表示白色,用0表示黑色,将图像二值化,最后以矢量(数字)的形式呈现出来,结果大概就是这样:(下图是一张5*5的二值化图像,没有具体表示含义,只做示例)前端工程师

同理,若是是彩色的图像,那咱们是否是能够把R、G、B三个维度的像素单独提取出来分别处理呢?这样,每个维度不就能够单独视为一张灰度图。

1.1.2.平滑图像与特征点

若是一张图像没有什么像素突变,好比一张全白的图片,若是以数字表示,天然都是0,那咱们能够称这张图片的像素点是平滑的。再好比这张全白的图片上有一个黑点,天然,灰度图上就会有一个突兀的数值,咱们就把它称做特征点,一般来讲,图像的特征点有多是噪声、边缘或者图片的实际特征。

1.2.神经网络与模型训练

tensorflow在发布了JS版本的工具库后,也同时制做了一个Tensorflow游乐场,打开以后,引入眼帘的网页中央这个东西即是神经网络:

从图中,咱们能够看到神经网络有不少不一样的层级,就是图中的Layers,每一层都是前一层通过滤波器计算后的结果,越多的层级以及越多的“神经元”通过一次计算过程计算出来的结果偏差越小,同时,计算的时间也会增长。神经网络正是模仿了咱们人类脑壳里的神经元通过了一系列计算而后学习事物的过程。这里推荐阮一峰的《神经网络入门》这篇文章,可以帮助你们更加浅显地了解神经网络是什么。

在咱们的卷积神经网络中,这些层级都有不一样的名字:输入层、卷积层、池化层以及输出层。

  • 输入层:咱们输入的矢量化以后的图像
  • 卷积层:通过滤波器卷积计算以后的图像
  • 池化层:通过池化滤波器卷积计算以后的图像
  • 输出层:输出数据

Features就是咱们的算子,也称为滤波器,可是每种不一样的滤波器对最后的输出结果都会有不一样的影响,进过训练以后,机器会经过咱们赋予的算法(好比激活函数等等)计算出哪些滤波器会对输出结果形成较大的偏差,哪些滤波器对输出结果压根没有影响(原理很简单,第一次计算使用全部滤波器,第二次计算拿掉某一个滤波器,而后观察偏差值(Training loss)就能够知道这个被拿掉的滤波器所起到的做用了),机器会为比较重要的滤波器赋予较高的权重,咱们将这样一个过程称为“训练”。最终,咱们将获得的整个带有权重的神经网络称为咱们经过机器训练出的“模型”,咱们能够拿着这个模型去让机器学习事物。

这就是机器学习中“训练模型”的过程,Tensorflow.js就是为咱们提供的训练模型的工具库,当你真正掌握了模型训练的奥义以后,Tensorflow对你而言就像JQuery用起来通常简单。

你们看完这些介绍以后确定仍是一脸茫然,什么是滤波器?什么又是卷积计算?不着急,下一个版块的内容将会为你们揭开全部谜题。

1.3.卷积算法揭秘

1.3.1.卷积算法

还记得咱们在1.1.1里说到一张图片能够用矢量的形式表示每一个像素点嘛?卷积计算就是在这基础上,使用某些算子对这些像素点进行处理,而这些算子,就是咱们刚刚提到的滤波器(好比左边,就是一张通过二值化处理的5*5的图片,中间的就是咱们的滤波器):

那计算的过程又是怎样的呢?卷积这东西听起来感受很复杂,但实际上就是把咱们的滤波器套到图像上,乘积求和,而后将图像上位于滤波器中心的值用计算结果替换,大概的效果就是下面这张动图这样:

对,所谓高大上的卷积就是这样一个过程,咱们的滤波器每次计算以后就向右移动一个像素,因此咱们能够称滤波器的步长为1,以此类推。不过咱们发现,通过滤波器处理后的图像,好像“变小了”!原来是5*5的图片这下变成了3*3,这是卷积运算带来的必然反作用,若是不想让图片变小,咱们能够为原图像加上必定像素且值均为0的边界(padding)去抵消反作用,就像下面这样:

1.3.2.池化算法

其实在平时训练模型的过程当中,咱们输入的图像确定不仅有5*5像素这么小,咱们最常常见到的图片许多都是100*100像素以上的,这样使用咱们的机器去计算起来确定是比较复杂的,所以,咱们经常会使用池化算法进行特征提取或者图像平滑处理,池化的过程其实就是按照某种规律将图片等比缩小,过程就像下面这样:

而池化算法最经常使用的有两大类:取均值算法和取最大值算法,顾名思义,取均值算法就是取滤波器中的平均值做为结果,取最大值算法就是取滤波器中的最大值做为输出结果:

上图就是取最大值算法的处理过程,你们也能很直观的看出,在池化层中,滤波器的步长大都是等于滤波器自身大小的(比较方便控制缩放比例)。而且,取最大值算法确定是很容易取到滤波器中的特征点(还记得特征点嘛?忘记的话快回去1.1.2看看哦~),因此咱们能够讲取最大值算法的池化处理称为特征提取;同理,取均值算法由于把全部的像素点的灰度级都平均了,因此咱们能够称之为平滑处理。

关于卷积神经网络的知识,能够具体参照这篇文章:《卷积神经网络(1)卷积层和池化层学习》。了解了这些知识以后,就能够开始咱们的实战啦~

2.图像识别实战

说了那么多理论,也不比实操来得有感受。在你们了解了卷积神经网络的基本原理以后,就可使用咱们的工具库来帮助咱们完成相关工做,这里我推荐ConvNetJS。这款工具库的本质就是咱们在1.2中提到的别人训练好的模型,咱们只须要拿来“学习”便可。

2.1.使用ConvNetJS

咱们能够看到在ConvNetJS的README里有这样一段官方demo,具体的含义我已经用注释在代码里标注:

// 定义一个神经网络
var layer_defs = [];
// 输入层:便是32*32*3的图像
layer_defs.push({type:'input', out_sx:32, out_sy:32, out_depth:3}); 
// 卷积层 
// filter:用16个5*5的滤波器去卷积
// stride:卷积步长为1
// padding:填充宽度为2(为保证输出的图像大小不会发生变化)
// activation:激活函数为relu(还有Tanh、Sigmoid等等函数,功能不一样)
layer_defs.push({type:'conv', sx:5, filters:16, stride:1, pad:2, activation:'relu'});
// 池化层
// 池化滤波器的大小为2*2
// stride:步长为2
// 在这里咱们没法看出这个框架池化是使用的Avy Pooling仍是Max Pooling算法,先视为后者
layer_defs.push({type:'pool', sx:2, stride:2});
// 反复卷积和池化减少模型偏差
layer_defs.push({type:'conv', sx:5, filters:20, stride:1, pad:2, activation:'relu'});
layer_defs.push({type:'pool', sx:2, stride:2});
layer_defs.push({type:'conv', sx:5, filters:20, stride:1, pad:2, activation:'relu'});
layer_defs.push({type:'pool', sx:2, stride:2});
// 输出层
// 分类器:输出10中不一样的类别
layer_defs.push({type:'softmax', num_classes:10});

// 实例化一个神经网络
net = new convnetjs.Net();
net.makeLayers(layer_defs);

// 模型训练
const trainer = new convnetjs.SGDTrainer(net, { learning_rate: 0.01, momentum: 0.9, batch_size: 5, l2_decay: 0.0 });
trainer.train(imgVol, classIndex);

// 使用训练好的模型进行图像识别
var x = convnetjs.img_to_vol(document.getElementById('some_image'))
var output_probabilities_vol = net.forward(x)
复制代码

若是想要更形象点,上述过程能够用这样一幅图表示:

中间的“卷积-池化-卷积-池化……“就是咱们定义并训练的神经网络,咱们输入矢量化处理后的图像后,先进行卷积运算,不一样的滤波器获得了不一样的结果,官方demo里是使用了16个不一样的滤波器(PS:这里给你们留一个思考的问题,一个3*3的二值化滤波器,能写出多少种可能?),天然能卷积出16种不一样的结果,再拿着这些结果池化处理,不断重复这个过程,最终得出图像识别结果:

2.2.实战项目解析

来,咱们一块儿详细梳理一下使用ConvNetJS这个工具库完成整个图像识别的具体流程,

(PS:项目代码具体参照:github.com/jerryOnlyZR…

首先,咱们必须先有数据供咱们的模型去学习,至少你该让这个模型知道啥是啥对吧,在项目里的 net 文件夹里的 car.js 文件,存放的就是咱们的学习数据,若是大家感兴趣能够打开看看,里面的数据就是告诉机器什么样的车标对应的是车的什么品牌。

在咱们的项目里,是经过这样一段代码完成机器学习的:

const trainer = new convnetjs.SGDTrainer(net, { learning_rate: 0.01, momentum: 0.9, batch_size: 5, l2_decay: 0.0 });
let imageList = [];
const loadData = i => {
    return function () {
        return new Promise(function (resolve, reject) {
    		let image = new Image();
		    image.crossOrigin = "anonymous";
 		    image.src = carList[i].url;
		    image.onload = function () {
        		let vol = convnetjs.img_to_vol(image);
                // 逐张训练图片
        		trainer.train(vol, i);
       		 	resolve();
    		};
   		  	image.onerror = reject;
		})
    }
}
// 遍历图片资源
for (let j = 0; j < carList.length; j++) {
    imageList.push(loadData(j));
}
var testBtn = document.getElementById("test")
function training(){
    testBtn.disabled = true
    return new Promise((resolve, reject) => {
        Promise.all(imageList.map(imageContainer => imageContainer())).then(() => {
    		console.log("模型训练好了!!!👌")
    		testBtn.disabled = false
    		resolve()
		})
    })
}
复制代码

咱们试着去打印一下图像识别的输出结果,获得的是这样一个东西:

从识别结果中咱们能够看到,咱们获得的是一个数组,这就是通过分类器分类的10个不一样类别,对应的天然是咱们的车的品牌,值就是每一个类别对应的几率。因此,咱们只要拿到几率的最大值,就是预测得出的最倾向的结果。

3.结语

随着JS引擎的计算能力不断加强,人工智能领域的不断发展,能够预见的是,在不久的未来,确定能有一些简单的算法能够被移植到用户前端执行,这样既能减小请求,又能分担后端压力。这一切并非无稽之谈,为何tensorflow.js会应运而生,正是由于JS的社区在不断壮大,JS这款便捷的语言也在获得更为广泛的使用。因此,请对你所从事的这份前端事业,有足够的信心!

仍是那句老话:

技术历来不会受限于语言,受限你的,永远只是思想。

我并非什么算法工程师,我也不是CS专业出来的科班生,我只是一枚普普统统的前端,和绝大多数人同样,没有多深厚的基础,但我愿意去学,我享受克服困难的过程,而那份对人工智能的执着,只是来源于那份不知足于现状的倔性和对这片领域一成不变的初心。

若是您以为这篇文章对您有帮助,还请麻烦您为文章提供的示例demo项目点个star;若是您对个人其余项目感兴趣,也欢迎follow哦~

4.鸣谢

本文项目资源大部分来自京程一灯,感谢京程一灯袁志佳老师对本文以及我我的提供的支持和帮助,若是你也在前端前进路上感到迷茫,京程一灯也许是你不错的选择。

相关文章
相关标签/搜索