前言
最近公司向员工搜集公司杂志的文章,刚好最近学习了机器学习相关课程。为了赚取购买课程的费用,所以写了如下文章投稿赚取稿费。
如下文章可能涉及一些我所购买课程的内容,所以不便将所有资源进行展示。
当初写这篇文章的目的除了赚取公司的稿费外,还有就是给现有web开发的同事提供一些新的开发方向,认识新的开发领域。
一、人工智能、机器学习、Tensorflow三者关系
人工智能是近几年火热的话题,同时人工智能也是一门很庞大的学科。那么如何实现人工智能?机器学习就是其中一种实现方式。
机器学习又是什么?“机器学习”如同字面意思,让计算机自己学习提供的数据特征,最终达到识别相应的类似数据。
那么如何以编程的形式使程序拥有机器学习能力?Tensorflow库是一款很好的机器学习的编程库。而本文的Tensorflow.js是Tensorflow的JS版本。除了JS版本,Tensorflow还有Python、C++、Java等多种语言版本。
综上所述可以下图总结这三者关系:
二、为什么要学习机器学习
学习机器学习并不是为了蹭人工智能的热度,而是为了更好的迎接未来的发展趋势。现有绝大多数开发人员面向的是规则编程。根据规则写固定规则程序。但是需求的复杂性提高就无法再通过编写规则来编程。
比如产品需求要让程序识别猫的图片筛选出来做专题,那么程序猿不可能将所有猫的图片进行编写规则来识别,因为每新增一张图片就意味着要修改代码增加一个判断规则。这就是传统编程的短板。
学习人工智能,学习机器学习编程就是为了弥补这个短板,让程序能更好的适应未来复杂多变的需求。
三、为什么选择Tensorflow.js
本文主要是针对web开发者认识机器学习这个领域,而web开发者必备的语言技能就是Javascript。Tensorflow.js就是以纯Javascript来编写机器学习程序的开发库,而且运行环境可以选择浏览器,可以很好的可视化程序的训练过程。这就是本文选择Tensorflow.js的最重要原因。
除了这个重要原因,Tensorflow.js还有如下优势:
1、Tensorflow.js是开箱即用的开发库,开发者无需花精力去编写基础复杂的数学问题。
2、由于可运行于浏览器,减少服务器的运算,提高服务器资源利用,增强客户端响应运算结果的速度。
3、使用语言就是Javascript,前端工程师不需要学习其他后端语言,降低入门门槛。
4、由于浏览器的WebGL可调用GPU,所以Tensorflow.js会使用GPU加速模型的运算,提高运算效率。
5、由于Node和Python一样都是使用C++编写的环境,所以在Node环境进行运算的速度目前与Python速度不相上下。
6、Tensorflow.js的模型可以跟Python等其他语言模型进行互转。就是js写了一个机器模型可以转换模型到Python环境下使用。
7、浏览器可以很好可视化机器训练过程,同时浏览器可调用设备的摄像头、麦克风等增加机器学习的应用场景,让机器学习跟接近用户。
有优势比如有它的劣势:
1、部署在浏览器,js就是公开的,那么训练模型就是公开的,商业保密性低。
2、浏览器端不适合部署体积过大的训练模型,不然用户加载页面会相当耗时。
3、在Node环境目前无法做到分布式训练,使用多台服务器对一个模型进行大规模训练。
4、Tensorflow.js的社区活跃度、资源等方面都不如Python社区,但是可以从Python社区去寻找资源运用到js平台。
综合上面优劣,选择Tensorflow.js是为了进入机器学习领域,让web开发者体验到机器学习编程和传统的规则编程两者之间的不同。深入学习之后可以平滑过度到其他人工智能开发的开发库。
四、体验Tensorflow.js开箱即用编程
(一)安装和引用Tensorflow.js
在html页面中可直接引用一个js文件即可。cdn地址是:
https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js
npm安装使用:npm i @tensorflow/tfjs即可。
以上是浏览器端的安装方式,如果运行在node环境可选择安装node版本,
npm安装:npm i @tensorflow/tfjs-node。因为node版本是运行c++环境,所以在执行这个命令前需要下载很多c++环境的安装包。
(二)第一个TF程序:
就是这么简单,跟使用jquery这些库是一样的。
(三)体验机器学习编程思想,初识Tensorflow.js的API
上图代码:
const model = tf.sequential(); model.add(tf.layers.dense({ units: 4, inputShape: [2], activation: 'relu' })); model.add(tf.layers.dense({ units: 1, activation: 'sigmoid' })); model.compile({ loss: tf.losses.logLoss, optimizer: tf.train.adam(0.1) }); const inputs = tf.tensor(data.map(p => [p.x, p.y])); const labels = tf.tensor(data.map(p => p.label)); await model.fit(inputs, labels, { epochs: 10 }); window.predict = (form) => { const pred = model.predict(tf.tensor([[form.x.value * 1, form.y.value * 1]])); alert(`预测结果:${pred.dataSync()[0]}`); };
上面代码是使用Tensorflow.js分析XOR数据集的部分代码。代码描述了从创建神经网络到训练神经网络最终进行预测。
从代码中可以看出,没有写一句IF判断语句,全部都是在使用Tensorflow.js提供的API进行构建神经网络。所以机器学习的编程是通过构建神经网络来实现程序,而不是通过规则判断来编写程序。
于此同时可以看出使用Tensorflow.js构建神经网络相当容易,只需要调用API设置你想要的构建元素即可完成。无需编写过多的数学理论方法。比如激活函数sigmoid,你无需重新编写实现sigmoid数学函数,TF已经提供了该函数,设置调用即可。
途中的注释中描述了很多名词,比如:神经元、损失函数、优化器、神经层、激活函数等等,这些名词都是学习机器学习的一些基础知识。本文不对这些基础知识做一一详解,如有机会再详细对这些基础知识做一次整合讲解。
五、使用Tensorflow.js解决问题
下面我们看下Tensorflow.js如何进行机器学习编程的实例,除了上面介绍需要npm安装@tensorflow/tfjs这个包外,还需要额外安装@tensorflow/tfjs-vis可视化包。这个包不影响机器训练,功能是为了将训练过程可视化到浏览器当中。
(一)线性回归问题:
已知x轴值1、2、3、4对应y轴值为1、3、5、7,可得如下坐标图:
传统的规则编程也可轻松完成这样的线性回归函数,并且还能准确预测,比方说x轴为100.5时对应的y值是多少。下面我们看下在Tensorflow.js中如何用机器学习的编程方式实现。
import * as tf from '@tensorflow/tfjs'; import * as tfvis from '@tensorflow/tfjs-vis'; window.onload = async () => { const xs = [1, 2, 3, 4]; const ys = [1, 3, 5, 7]; tfvis.render.scatterplot( { name: '线性回归训练集' }, { values: xs.map((x, i) => ({ x, y: ys[i] })) }, { xAxisDomain: [0, 5], yAxisDomain: [0, 8] } ); const model = tf.sequential(); model.add(tf.layers.dense({ units: 1, inputShape: [1] })); model.compile({ loss: tf.losses.meanSquaredError, optimizer: tf.train.sgd(0.1) }); const inputs = tf.tensor(xs); const labels = tf.tensor(ys); await model.fit(inputs, labels, { batchSize: 4, epochs: 200, callbacks: tfvis.show.fitCallbacks( { name: '训练过程' }, ['loss'] ) }); const output = model.predict(tf.tensor([5])); alert(`如果 x 为 5,那么预测 y 为 ${output.dataSync()[0]}`); };
以上代码将输入值[1,2,3,4]和输出结果[1,3,5,7]作为训练集,这个训练集只有4组个数据,但是通过机器反复学习这4个数据集,就能预测出很接近的最终值。
这里使用sequential模型构建学习的神经网络,模型损失函数采用均方误差即代码中的tf.losses.meanSquaredError,优化器使用随机梯度下降即tf.train.sgd(0.1)。
最后将训练数据集给model进行训练,也就是调用了model.fit方法。batchSize是指每次训练数据个数,epochs是指训练次数。代码中表示每次训练4个数据,一共训练200次。
运行程序,浏览器中会显示整个训练过程:
由图可以看出loss也就是训练损失已平滑的曲线不断降低,损失越低表示训练结果越接近真实结果。
最终训练完毕会执行model.predict方法进行预测,代码中预测x为5时y值是多少,根据人类的大脑对这个简单函数的预测应该是为9,而程序实际输出是:
无限接近于9。这虽然没有完全等于9,但是已经相当有参考价值,因为训练损失是很难降低到0,而且这段机器学习程序只重复训练4个数据集。
(二)解决逻辑回归问题:
如图,我们要输入xy的坐标轴,然后预测该坐标属于黄色还是蓝色。这张图中,黄色和蓝色之间有少许的杂音而且还有很多空白地区没有显示出对应数据,图表也不可能显示完全部数据,这个时候就无法使用传统的规则编程进行书写。
我们看下Tensorflow.js如何解决这逻辑回归问题:
import * as tf from '@tensorflow/tfjs'; import * as tfvis from '@tensorflow/tfjs-vis'; import { getData } from './data.js'; window.onload = async () => { const data = getData(400); tfvis.render.scatterplot( { name: '逻辑回归训练数据' }, { values: [ data.filter(p => p.label === 1), data.filter(p => p.label === 0), ] } ); const model = tf.sequential(); model.add(tf.layers.dense({ units: 1, inputShape: [2], activation: 'sigmoid' })); model.compile({ loss: tf.losses.logLoss, optimizer: tf.train.adam(0.1) }); const inputs = tf.tensor(data.map(p => [p.x, p.y])); const labels = tf.tensor(data.map(p => p.label)); await model.fit(inputs, labels, { batchSize: 40, epochs: 20, callbacks: tfvis.show.fitCallbacks( { name: '训练效果' }, ['loss'] ) }); window.predict = (form) => { const pred = model.predict(tf.tensor([[form.x.value * 1, form.y.value * 1]])); alert(`预测结果:${pred.dataSync()[0]}`); }; };
这种逻辑回归问题比上面的线性回归问题明显复杂很多,但是使用Tensorflow.js进行编程却没有复杂多少,用的API是一样的。只是将损失函数从均方误差meanSquaredError变成了对数损失函数logLoss,因为逻辑回归问题无法使用均方误差进行很好的训练(这是一个数学问题,需要理解对数函数)。优化器采用了adam,当然优化器也可以使用随机梯度下降(至于优化器函数的选择也是一个数学问题)。
除了这两块有明显的区别,其他代码基本和线性回归的使用的API没有差别。
执行代码后我们可以看到整个训练过程的损失变化:
在页面输入框可以输入我们想要预测的坐标:
此例中预测结果接近0的是黄点,接近于1的是蓝点,如果预测值是0.5左右,那么所预测结果应该是两块区域之间的临界点。
(三)卷积神经网络的编写
卷积神经网络是一个伟大的发明,它可以让机器更加高效的学习图片、声音这些文件特征。
我们来看下Tensorflow.js如何构建卷积神经网络来识别手写数字的。在编写之前需要在网络上找到mnist数据集,这是经典的手写数字数据集,为我们节约了收集手写数字的图片集。下面看下代码的具体实现:
import * as tf from '@tensorflow/tfjs'; import * as tfvis from '@tensorflow/tfjs-vis'; import { MnistData } from './data'; window.onload = async () => { const data = new MnistData(); await data.load(); const examples = data.nextTestBatch(20); const surface = tfvis.visor().surface({ name: '输入示例' }); for (let i = 0; i < 20; i += 1) { const imageTensor = tf.tidy(() => { return examples.xs .slice([i, 0], [1, 784]) .reshape([28, 28, 1]); }); const canvas = document.createElement('canvas'); canvas.width = 28; canvas.height = 28; canvas.style = 'margin: 4px'; await tf.browser.toPixels(imageTensor, canvas); surface.drawArea.appendChild(canvas); } const model = tf.sequential(); model.add(tf.layers.conv2d({ inputShape: [28, 28, 1], kernelSize: 5, filters: 8, strides: 1, activation: 'relu', kernelInitializer: 'varianceScaling' })); model.add(tf.layers.maxPool2d({ poolSize: [2, 2], strides: [2, 2] })); model.add(tf.layers.conv2d({ kernelSize: 5, filters: 16, strides: 1, activation: 'relu', kernelInitializer: 'varianceScaling' })); model.add(tf.layers.maxPool2d({ poolSize: [2, 2], strides: [2, 2] })); model.add(tf.layers.flatten()); model.add(tf.layers.dense({ units: 10, activation: 'softmax', kernelInitializer: 'varianceScaling' })); model.compile({ loss: 'categoricalCrossentropy', optimizer: tf.train.adam(), metrics: ['accuracy'] }); const [trainXs, trainYs] = tf.tidy(() => { const d = data.nextTrainBatch(1000); return [ d.xs.reshape([1000, 28, 28, 1]), d.labels ]; }); const [testXs, testYs] = tf.tidy(() => { const d = data.nextTestBatch(200); return [ d.xs.reshape([200, 28, 28, 1]), d.labels ]; }); await model.fit(trainXs, trainYs, { validationData: [testXs, testYs], batchSize: 500, epochs: 20, callbacks: tfvis.show.fitCallbacks( { name: '训练效果' }, ['loss', 'val_loss', 'acc', 'val_acc'], { callbacks: ['onEpochEnd'] } ) }); const canvas = document.querySelector('canvas'); canvas.addEventListener('mousemove', (e) => { if (e.buttons === 1) { const ctx = canvas.getContext('2d'); ctx.fillStyle = 'rgb(255,255,255)'; ctx.fillRect(e.offsetX, e.offsetY, 25, 25); } }); window.clear = () => { const ctx = canvas.getContext('2d'); ctx.fillStyle = 'rgb(0,0,0)'; ctx.fillRect(0, 0, 300, 300); }; clear(); window.predict = () => { const input = tf.tidy(() => { return tf.image.resizeBilinear( tf.browser.fromPixels(canvas), [28, 28], true ).slice([0, 0, 0], [28, 28, 1]) .toFloat() .div(255) .reshape([1, 28, 28, 1]); }); const pred = model.predict(input).argMax(1); alert(`预测结果为 ${pred.dataSync()[0]}`); }; };
上面代码中,有一大半是canvas画布的操作代码,可以不进行研究。在创建模型model之前的代码是将20张实例图片挂载到页面显示,也可以不进行研究。而Tensorflow.js的实际书写代码比线性回归相比之多了十来行。我们来解析下这部分代码。
这个卷积神经网络构建一共就分了5层,2层2D卷积层,2层池化层,1层输出层。tf.layers.conv2d创建的是2d卷积层,卷积层之后创建一个tf.layers.maxPool2d最大池化层对上一次卷积特征进行优化。进行两次卷积提取特征和池化后使用了tf.layers.flatten将立体数据平铺,最后根据平铺数据通过tf.layers.dense全链接层作为预测输出。这样一个简单的卷积神经网络就构建完成。
后面是将图片数据读取1000张图片转换成Tensorflow.js可运算的数据Tensor(张量),以及随机读取这1000张以外的200张验证数据。
model.fit方法进行模型的训练,这里将训练集和验证集都放入训练过程。验证集的目的是为了验证模型训练效果是否偏离了轨道,也就是是否出现过拟合或者欠拟合的情况。
运行代码后的结果如图:
我们可以看到loss训练损失在平滑下降,acc是训练准确度。val_loss和val_acc是验证集的损失值和准确度。这里的模型我们只训练了1000张图片,然后只训练了20次。在如此小的数据集以及训练次数的情况下已经可以达到惊人的效果。
我们在页面画布上可以随意写数字0-9,模型就可以进行判断我们写的是什么数字。如图效果:
案例使用小结:
上面例子只是让大家通过具体案例和代码实现来更加深入了解机器学习是如何编程的,以及Tensorflow.js的强大。
机器学习的编程和传统编程有着思维上的不同。传统编程要求程序逻辑缜密,对条件判断做出人为认定。而机器学习编程不在拘泥于规则编写,而是构建神经网络让计算机进行特征的学习。
而Tensorflow给我们程序猿封装了很多构建神经网络、训练模型的API。
六、本篇小结
上述简要介绍了人工智能、机器学习这些AI领域的概念,以及让大家感受了一下Tensorflow.js在机器学习上是如何编程的。本篇文章主要目的是带大家认识人工智能编程的世界。
Tensorflow.js并不是最热门最高效的机器学习框架,但是由于它使用的语言是Javascript以及开箱即用的API,这些条件可以让WEB开发者们用比较低的学习成本进行人工智能编程领域的学习。
如今很多大厂对人工智能的研发都投入了大量的人力资金,Tensorflow.js在移动端也出现很多应用,最近比较有名的实时彩妆就是使用Tensorflow.js进行编写的小程序。至于未来AI的发展是迅猛的,人工智能编程未来很可能是每个程序猿的必修课。所以在此勉励程序猿任重而道远,大家共勉之。
七、其他拓展
人工智能是一门庞大的学科,机器学习只是它的一个分支,它的分支可以用网上的下面这张图来描述:
从上图中可以看到机器学习也有庞大的分支,其中Deep Learning(深度学习)是近年来最火热的课题,深度学习训练的效果会更加智能。Tensorflow可以进行深度学习的编程。不过深度学习需要庞大的数据量以及强大的计算量,传统的机器学习不需要庞大的数据量。所以两者有着不可分割的关系。
下面是一些有关人工智能的一些学习网站,这些网站不仅限于Tensorflow.js,更多是关于机器学习的基础知识:
Tensorflow模拟:http://playground.tensorflow.org
谷歌机器学习速成教程:https://developers.google.cn/machine-learning/crash-course
机器学习算法课程:http://wiki.fast.ai/index.php/Main_Page
卷积神经网络原理:https://setosa.io/ev/image-kernels/
以上就是在公司投稿有关tensorflow.js的文章,希望对人工智能有兴趣的朋友有所帮助。