智能前端技术与实践:深度学习中的JavaScript

本节将介绍深度学习领域所涉及的前端知识,包括深度学习中的JavaScript数据类型和JavaScript异步编程。

2.6.1 JavaScript数据类型

深度学习中基本的数据结构是张量,高效的数据结构对任何一个深度学习项目都非常重要。在C/C++中,我们可以通过数组来高效地存储一些集合数据并实现快速访问;在 Python中,我们可以通过NumPy中的NDArray对象来实现此功能。NDArray对象是一系列同类型数据的集合,用于存放同类型元素的多维数组。

深度学习中支持的JavaScript数据类型是TypedArray,它是一种介于原始数组与NDArray之间的数据结构。在现代浏览器中有11种类型的TypedArray,如表2-8所示。

表2-8 TypedArray

类型

大小(单位是字节)

描述

Int8Array

1

8位二进制有符号整数

Uint8Array

1

8位无符号整数(超出范围后从另一边界循环)

Uint8ClampedArray

1

8位无符号整数(超出范围后为边界值)

Int16Array

2

16位二进制有符号整数

Uint16Array

2

16位无符号整数

Int32Array

4

32位二进制有符号整数

Uint32Array

4

32位无符号整数

Float32Array

4

32位IEEE浮点数(7位有效数字)

Float64Array

8

64位IEEE浮点数(16位有效数字)

BigInt64Array

8

64位二进制有符号整数

BigUint64Array

8

64位无符号整数

接下来,介绍ArrayBuffer及访问ArrayBuffer的两种方法——TypedArray和DataView。

1.ArrayBuffer

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区,它是一个字节数组。由于ArrayBuffer仅仅是内存上的二进制缓冲区,因此它并不提供任何操作数据(读取数据、写入数据)的方法,即我们并不能直接操作ArrayBuffer的内容,而要通过TypedArray或DataView对象来操作。它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区中的内容,如代码清单2-22所示。

代码清单2-22

const buffer = new ArrayBuffer(4)
console.log(buffer.byteLength) //4

2.TypedArray

TypedArray(类型化数组)对象描述了一个底层的二进制数据缓冲区的类数组视图。TypedArray定义了如何访问底层的ArrayBuffer,实际上用于存储数据的数据结构是ArrayBuffer,没有名为TypedArray的全局属性,也没有名为TypedArray的构造函数。示例代码如代码清单2-23所示。

代码清单2-23

const typedArray1 = new Int8Array(8);
typedArray1[0] = 128;
const typedArray2 = new Int8Array(typedArray1);
typedArray2[1] = 20;
console.log(typedArray1);
// 期望输出: Int8Array [-128, 0, 0, 0, 0, 0, 0, 0]
console.log(typedArray2);
// 期望输出: Int8Array [-128, 20, 0, 0, 0, 0, 0, 0]

由于Int8Array中单个元素值的范围是−128~127,因此当指定typedArray1数组的第一个元素为128时,该值超出范围,于是会从另一边界重新开始循环,即值为−128。

3.DataView

DataView是一个可以从二进制ArrayBuffer对象中读写多种数值类型的底层接口,在使用它时,无须考虑不同平台的字节序问题,如代码清单2-24所示。

代码清单2-24

var buffer = new ArrayBuffer(4)
new DataView(buffer).setInt16(0,42,true)
console.log(new Uint8Array(buffer))

我们首先在代码清单2-24中创建了4字节大小的ArrayBuffer,然后将已经创建的ArrayBuffer作为数据源并创建DataView对象,并通过setInt16()方法创建起始位置为0、值为42的16位整数。我们还指定setInt16()方法的参数littleEndian为true,即采用小端字节序(低位字节放在内存的低地址端,高位字节放在内存的高地址端)。该参数的默认值为false,即默认采用大端字节序。上述代码的运行结果如下。

Uint8Array [42, 0, 0, 0]

2.6.2 JavaScript异步编程

在 Web 浏览器或微信小程序中部署机器学习应用时,经常会采用从服务器端加载现成的JavaScript模型或转换TensorFlow模型这两种方法。JavaScript语言采用的是单线程模型,所以对于网络 I/O 请求等一些耗时较长的任务,通常会通过设置回调函数、使用 Promise对象、使用async/await函数来处理。本节将介绍JavaScript中的事件循环机制及异步任务的处理方法。

JavaScript语言最大的特点就是单线程。这意味着所有任务都必须同步执行,即前一个任务完成后,下一个任务才可以开始,但是如果前一个任务耗时较长,就会使后面的任务一直处于等待状态,从而造成主线程的阻塞,进而影响页面的渲染。

为了解决这个问题,我们一般将JavaScript中可以处理的任务分为同步任务和异步任务。同步任务是指在JavaScript主线程上排队执行的任务;异步任务是指在任务队列中执行的任务,异步执行的运行机制如下。

  • 所有同步任务在JavaScript主线程上执行,形成一个执行栈。
  • 所有异步任务在任务队列中执行,异步任务包括鼠标单击事件、网络请求事件等(任务队列又分为宏任务和微任务:宏任务包括script、setTimeout、setInterval、I/O、UI Rendering,微任务包括process.nextTick、Promise、MutationObserver等)。
  • 当执行栈中所有任务执行完毕时,系统会读取任务队列中的任务,并进入执行栈,由主线程开始执行。
  • 主线程不断重复上述过程。

具体运行过程如图2-29所示。

图2-29 异步编程运行过程
(图片来源:ruanyifeng 网站)

当JavaScript主线程运行的时候,会产生堆(heap)和栈(stack)。栈中的代码会调用外部API(WebAPI),它们会将所有异步任务(click事件、load事件、done事件等)加入任务队列(callback queue)中。当栈中的代码执行完毕时,主线程就会读取任务队列,并执行队列中异步任务对应事件的回调函数。主线程会不断从任务队列中读取事件,直至所有任务处理完成,如图2-30所示。

图2-30 异步编程运行过程

接着,介绍深度学习中常用的两种JavaScript异步任务解决方案。

1.使用Promise对象

对于传统异步任务,通常会采用回调函数处理,但是该方法会造成无限回调,从而使得程序结构混乱,不利于后期代码的维护。ECMAScript 6提供了一种新的异步任务解决方案,即Promise对象,它代表一个异步操作最终完成或者失败,共有如下3种状态。

  • 进行中(pending):初始状态,既没有成功,也没有失败。
  • 已成功(fulfilled):操作成功。
  • 已失败(rejected):操作失败。

Promise 对象状态的改变只有两种可能——由 Pending 变为 Fulfilled 和由 Pending 变为Rejected。当发生上述任何一种状态改变后(此时我们称为Resolved),用Promise对象的then()方法排列起来的相关处理程序就会被调用,该对象成功实现了用同步的方法编写异步的代码,避免了回调函数引发的无限回调问题,如图2-31所示。

图2-31 Promise对象
(图片来源:mozillademos网站 )

代码清单2-25展示了一段示例代码。

代码清单2-25

let myFirstPromise = new Promise(function(resolve, reject){
   setTimeout(function(){
      resolve("hahaCoder!"); 
   }, 250);
});
myFirstPromise.then(function(successMessage){
   console.log("Yay! " + successMessage);
});

代码清单 2-25 使用 setTimeout()来模拟异步代码。当异步代码执行成功时,才会调用resolve();当异步代码失败时,会调用reject()。其中successMessage的值是调用resolve()方法所传入的值。

2.async/await函数

async函数是使用async关键字声明的函数,是AsyncFunction构造函数的实例,并且允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无须刻意地链式调用Promise。

async函数可能包含0个或多个await表达式,await表达式会暂停整个async函数的执行进程并让出其控制权,只有当等待的基于Promise对象的异步操作成功或失败后才会恢复进程。示例代码如代码清单2-26所示。

代码清单2-26

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}
async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result);
  console.log("hahaCoder")
  // 期望输出: "resolved"
}
asyncCall();

运行结果如下。

"calling"
"resolved"
"hahaCoder"

代码清单2-26首先会输出calling字符串,接着会执行resolveAfter2Seconds()函数,由于该函数前有await关键字,故asyncCall函数的执行进程会中断,2s后,即异步操作执行完成后会恢复进程,从而输出resolved和hahaCoder。

不论是回调函数、Promise 还是 async/await 等其他异步任务解决方案,其本质都是通过JavaScript唯一的单线程执行所有任务。

本节最后介绍HTML5中新提出的概念——Web Worker,它可以帮助JavaScript创建多线程环境,即允许主线程创建worker线程,并将一些任务分配给worker线程。

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的解决方法。worker线程可以执行任务而不干扰用户界面,等到worker线程完成对应的计算任务,将结果返回主线程。示例代码如代码清单2-27所示。

代码清单2-27

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>web worker</title>
</head>
<body>
<script>
   var worker = new Worker("worker.js")
   worker.onmessage = (evt) => {
      console.log("Message posted from webworker: " + evt.data);
   }
   worker.postMessage("Hello,I'm hahaCoder from demo.html");
</script>
</body>
</html>

Worker.js中的代码如代码清单2-28所示。

代码清单2-28

postMessage("Hello,I'm shipudong from worker.js");
onmessage = (evt) => {
   postMessage("Worker received data: " + evt.data);
};

运行结果如图2-32所示。

图2-32 运行结果

本文摘自:《智能前端技术与实践》

石璞东,吴萌,王慧琴 著

  • TensorFlow前端开发技术案例教程,神经网络模型算法web深度学习
  • 适配新版本及多类型设备,提供大量案例分析
  • 助你深入了解智能前端开发!

本书旨在介绍智能前端开发和深度学习。本书首先介绍了相关的开发环境、前端开发基础知识、深度学习基础知识、前端智能框架和卷积神经网络,然后讲述了线性回归、logistical 回归、XOR 问题、人体姿态检测:目标检测、光学字符识别等方面的案例,最后讲解了前端智能化案例。
本书适合 Web 前端开发人员、人工智能开发人员阅读,也可作为计算机相关专业师生的参考用书。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值