TensorFlow.js 的基本构建块及其操作,如何创建一些复杂的模型

这创造了一个标量张量。我们还可以将数组转换为张量。
tensorflow.js 是一个基于 deeplearn.js 构建的库,可直接在浏览器环境中创建深度学习模型。使用它可以在浏览器上创建 cnns,rnns 等,并使用客户端的 gpu 处理能力训练这些模型。因此,训练 nn 并不一定需要服务器级别的 gpu。本教程首先解释 tensorflow.js 的基本构建块及其操作。然后,我们描述了如何创建一些复杂的模型。
我在 observable 上创建了一个交互式编码会话,可用于代码演示。此外,我创建了许多迷你项目,包括简单分类,样式转换,姿势评估和 pix2pix 翻译。
入门
由于 tensorflow.js 在浏览器上运行,您只需将以下脚本包含在 html 文件的 header 中即可:
1
以上会自动加载最新版本的 tensorflow.js。
张量(tensor)
如果您熟悉 tensorflow 等深度学习平台,您应该能够认识到张量是 operators 使用的 n 维数组。因此,它们代表了任何深度学习应用程序的构建块。让我们创建一个张量:
1const tensor = tf.scalar(2);
以上创建了一个张量。我们还可以将数组转换为张量:
1const input = tf.tensor([2,2]);
这会创建一个恒定的数组张量[2,2]。换句话说,我们通过应用张量函数将一维数组转换为张量。我们可以使用 input.shape 获取张量大小。
1const tensor_s = tf.tensor([2,2]).shape;
我们还可以创建具有特定大小的张量:
1const input = tf.zeros([2,2]);
操作(operators)
为了使用张量,我们需要创建操作。如下所示,可以获取到张量的平方:
1const a = tf.tensor([1,2,3]);
2a.square().print();
tensorflow.js 还允许链接操作。例如,要评估我们使用的张量的二次幂:
1const x = tf.tensor([1,2,3]);
2const x2 = x.square().square();
tensor disposal
通常我们会生成大量的中间张量。例如,在前面的例子中,我们不需要生成 const x。为了做到这一点,我们可以调用 dispose():
1const x = tf.tensor([1,2,3]);
2x.dispose();
请注意,我们在以后的操作中不再使用张量 x。现在,对于每个张量来说,这可能有点不方便。
tensorflow.js 提供了一个特殊的操作 tidy() 来自动处理中间张量:
1function f(x)
2{
3return tf.tidy(()=>{
4const y = x.square();
5const z = x.mul(y);
6return z
7});
8}
请注意,张量 y 的值将被处理,因为我们在评估 z 的值之后不再需要它。
优化问题
在这里,我们将学习如何解决优化问题。给定函数 f(x),基于 x = a 评估最小化 f(x)。为此,我们需要一个优化器。优化器是一种通过渐变来最小化函数的算法。文献中有许多优化器,如 sgd,adam 等......这些优化器的速度和准确性各不相同。tensorflowjs 支持最重要的优化器。
我们将举一个简单的例子:f(x)=x⁶+2x⁴+3x²+ x + 1。函数图如下所示。我们看到函数的最小值在区间内 [-0.5,0] 。我们将使用优化器来查找确切的值。
首先,我们定义需要最小化的函数
1function f(x)
2{
3const f1 = x.pow(tf.scalar(6, 'int32')) //x^6
4const f2 = x.pow(tf.scalar(4, 'int32')).mul(tf.scalar(2)) //2x^4
5const f3 = x.pow(tf.scalar(2, 'int32')).mul(tf.scalar(3)) //3x^2
6const f4 = tf.scalar(1) //1
7return f1.add(f2).add(f3).add(x).add(f4)
8}
现在我们可以迭代该函数以找到最小值。我们将以 a = 2 的初始值开始。学习速率定义了我们达到最小值的速度。我们将使用 adam 优化器
1function minimize(epochs , lr)
2{
3let y = tf.variable(tf.scalar(2)) //initial value
4const optim = tf.train.adam(lr); //gadient descent algorithm
5for(let i = 0 ; i f(y));
7return y
8}
当学习速率为 0.9 时,迭代 200 次之后找到最小值 -0.16092407703399658。
一个简单的神经网络
现在我们学习如何创建一个神经网络来学习 xor,这是一个非线性操作。代码类似于 keras 实现。我们首先创建了两个输入和一个输出的训练集。
1xs = tf.tensor2d([[0,0],[0,1],[1,0],[1,1]])
2ys = tf.tensor2d([[0],[1],[1],[0]])
然后我们创建两个具有不同非线性激活函数的密集层。我们使用具有交叉熵损失的随机梯度下降。学习速率是 0.1
1function createmodel()
2{
3var model = tf.sequential()
4model.add(tf.layers.dense({units:8, inputshape:2, activation: 'tanh'}))
5model.add(tf.layers.dense({units:1, activation: 'sigmoid'}))
6model.compile({optimizer: 'sgd', loss: 'binarycrossentropy', lr:0.1})
7return model
8}
然后我们对模型进行 5000 次迭代
1await model.fit(xs, ys, {
2batchsize: 1,
3epochs: 5000
4})
最后我们预测训练集
1model.predict(xs).print()
输出预期应该是 [[0.0064339], [0.9836861], [0.9835356], [0.0208658]]。
cnn 模型
tensorflow.js 使用计算图来自动区分。我们只需要创建图层,优化器并编译模型。让我们创建一个顺序模型:
1model = tf.sequential();
现在我们可以为模型添加不同的图层。让我们添加带输入的第一个卷积层 [28,28,1]
1const convlayer = tf.layers.conv2d({
2inputshape: [28,28,1],
3kernelsize: 5,
4filters: 8,
5strides: 1,
6activation: 'relu',
7kernelinitializer: 'variancescaling'
8});
在这里,我们创建了一个 convlayer 接受输入图层为 [28,28,1]。输入将是大小为 28 x 28 的灰色图像。然后我们对其进行初始化。之后,我们应用一个激活函数,它基本上取张量中的负值并用零替换它们。现在我们可以将此 convlayer 添加到模型中:
1model.add(convlayer);
使用 tensorflow.js 我们不需要为下一层指定输入大小,因为在编译模型后它将自动评估。我们还可以添加最大池,密集层等。这是一个简单的模型:
1const model = tf.sequential();
2
3//create the first layer
4model.add(tf.layers.conv2d({
5inputshape: [28, 28, 1],
6kernelsize: 5,
7filters: 8,
8strides: 1,
9activation: 'relu',
10kernelinitializer: 'variancescaling'
11}));
12
13//create a max pooling layer
14model.add(tf.layers.maxpooling2d({
15poolsize: [2, 2],
16strides: [2, 2]
17}));
18
19//create the second conv layer
20model.add(tf.layers.conv2d({
21kernelsize: 5,
22filters: 16,
23strides: 1,
24activation: 'relu',
25kernelinitializer: 'variancescaling'
26}));
27
28//create a max pooling layer
29model.add(tf.layers.maxpooling2d({
30poolsize: [2, 2],
31strides: [2, 2]
32}));
33
34//flatten the layers to use it for the dense layers
35model.add(tf.layers.flatten());
36
37 //dense layer with output 10 units
38model.add(tf.layers.dense({
39units: 10,
40kernelinitializer: 'variancescaling',
41activation: 'softmax'
42}));
为了检查输出张量,我们可以为任何层应用张量。但是这里的输入需要的是一个形状为[batch_size,28,28,1],其中batch_size表示我们一次应用于模型的数据集元素的数量。以下是如何评估卷积层的示例:
1const convlayer = tf.layers.conv2d({
2inputshape: [28, 28, 1],
3kernelsize: 5,
4filters: 8,
5strides: 1,
6activation: 'relu',
7kernelinitializer: 'variancescaling'
8});
9
10const input = tf.zeros([1,28,28,1]);
11const output = convlayer.apply(input);
在检查 output 张量的形状后,我们看到它的形状为 [1,24,24,8]。使用公式评估:
1const outputsize = math.floor((inputsize-kernelsize)/stride +1);
回到我们的模型,我们意识到我们使用的 flatten() 基本上将输入从形状 [batch_size,a,b,c] 转换为形状 [batch_size,axbxc]。这很重要,因为在密集层中我们不能应用 2d 数组。最后,我们使用了带有输出单元的密集层,10 代表了我们识别系统中所需的类别。实际上,该模型用于识别所谓的 mnist 数据集中的手写数字。
优化和编译
创建模型后,我们需要一种优化参数的方法。像 sgd 和 adam 优化器都有不同的方法。如下所示创建优化器:
1const learning_rate = 0.0001;
2const optimizer = tf.train.adam(learning_rate);
这将使用指定的学习速率创建 adam 优化器。现在,我们已准备好编译模型
1model.compile({
2optimizer: optimizer,
3loss: 'categoricalcrossentropy',
4metrics: ['accuracy'],
5});
在这里,我们创建了使用 adam 来优化损失函数的模型,该函数评估预测输出和真实标签的交叉熵。
训练
在编译模型之后,我们准备在数据集上训练模型。我们需要使用 fit() 函数:
1const batch = tf.zeros([batch_size,28,28,1]);
2const labels = tf.zeros([batch_size, num_classes]);
3
4const h = await model.fit(batch, labels,
5{
6batchsize: batch_size,
7validationdata: validationdata,
8epochs: batch_epochs
9});
10
请注意,我们正在为一组训练集提供 fit 函数。fit 函数的第二个变量表示模型的真实标签。最后,我们有配置参数,如 batchsize 和 epochs。请注意,它 epochs 表示我们迭代当前批次而不是整个数据集的次数。因此,我们可以将该代码包装在 for 循环中,该循环遍历训练集的所有批次。
注意我们使用了特殊的关键字 await,它阻塞并等待函数执行完成。这就像运行另一个线程,主线程正在等待拟合函数完成执行。
热编码
通常给定的标签是代表该类的数字。例如,假设我们有两个类,一个橙色类和一个苹果类。然后我们将给出橙色类标签 0 和苹果类标签 1。但是,我们的网络接受一个大小为 [batch_size,num_classes] 的张量。因此,我们需要使用热编码:
1const output = tf.onehot(tf.tensor1d([0,1,0]), 2);
2
3//the output will be [[1, 0],[0, 1],[1, 0]]
4
5
因此,我们将 1d 张量标签转换为张量形状[batch_size,num_classes]。
损失和准确性
为了检查我们模型的性能,我们需要知道损失和准确性。为此,我们需要使用历史模型获取结果。
1//h is the output of the fitting module
2const loss = h.history.loss[0];
3const accuracy = h.history.acc[0];
请注意,我们正在评估 validationdata 的损失性和准确性。
预测
假设我们完成了对模型的训练,并且给出了良好的损失和准确性。是时候预测看不见的数据元素的结果了。假设我们的浏览器中有一个图像,或者我们直接从我们的网络摄像头拍摄,那么我们就可以使用我们训练有素的模型来预测它的类。首先,我们需要将图像转换为张量
1//retrieve the canvas
2const canvas = document.getelementbyid(mycanvas);
3const ctx = canvas.getcontext(2d);
4
5//get image data
6imagedata = ctx.getimagedata(0, 0, 28, 28);
7
8//convert to tensor
9const tensor = tf.frompixels(imagedata);
在这里我们创建了一个 canvas 并从中获取imagedata,然后我们转换为张量。现在张量大小为 [28,28,3] 但模型采用 4 维向量。因此,利用 expanddims 我们可以为张量添加额外的维度。
1const etensor = tensor.expanddims(0);
因此,输出张量大小为 [1,28,28,3] 因为我们在索引 0 处添加了维度。现在我们使用 predict() 进行预测。
1model.predict(etensor);
函数 predict 将返回我们最后一层的值。
转移学习
在前面的部分中,我们必须从头开始训练我们的模型。然而,这是一项昂贵的操作,因为它需要更多的训练迭代。因此,我们使用称为 mobilenet 的预训练模型。它是一款轻巧的 cnn,经过优化可在移动应用中运行。mobilenet 受过 imagenet 训练。
要加载模型,如下所示:
1const mobilenet = await tf.loadmodel(
2 'https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_0.25_224/model.json');
我们可以使用输入,输出来检查模型的结构:
1//the input size is [null, 224, 224, 3]
2const input_s = mobilenet.inputs[0].shape;
3
4//the output size is [null, 1000]
5const output_s = mobilenet.outputs[0].shape;
6
因此,我们需要大小为 [1,224,224,3] 的图像,输出将是一个大小为 [1,1000] 的张量,它保存 imagenet 数据集中每个类的概率。
为了简便起见,我们将采取零值数组,并试图预测出 1,000 种类别。
1var pred = mobilenet.predict(tf.zeros([1, 224, 224, 3]));
2pred.argmax().print();
运行代码后,我得到 class = 21:
现在我们需要检查模型的内容。为此,我们可以获得模型图层和名称:
1//the number of layers in the model '88'
2const len = mobilenet.layers.length;
3
4//this outputs the name of the 3rd layer 'conv1_relu'
5const name3 = mobilenet.layers[3].name;
当基于另一个数据集再次训练具有 88 个图层的模型时,是非常昂贵的。因此,基本的技巧是使用这个模型来评估激活(我们不会重新训练)。
假设我们需要一个模型来区分胡萝卜和黄瓜。我们将使用 mobilene tmodel 来计算我们选择的某个层的激活。然后我们使用具有输出大小为 2 的密集层来预测正确的类。因此,我们只需要训练密集层。
首先,我们需要摆脱模型的密集层。
1const layer = mobilenet.getlayer('conv_pw_13_relu');
现在让我们更新我们的模型,让这个图层成为一个输出
1mobilenet = tf.model({inputs: mobilenet.inputs, outputs: layer.output});
最后,我们创建了可训练模型,但我们需要知道最后一层输出形状:
1//this outputs a layer of size [null, 7, 7, 256]
2const layeroutput = layer.output.shape;
我们看到形状为 [null,7,7,256],现在我们可以将它输入到我们的密集层:
1trainablemodel = tf.sequential({
2layers: [
3tf.layers.flatten({inputshape: [7, 7, 256]}),
4tf.layers.dense({
5units: 100,
6activation: 'relu',
7kernelinitializer: 'variancescaling',
8usebias: true
9}),
10tf.layers.dense({
11units: 2,
12kernelinitializer: 'variancescaling',
13usebias: false,
14activation: 'softmax'
15})
16]
17});
如您所见,我们创建了一个带有 100 个神经元的密集层和带有大小为 2 的输出层。
1const activation = mobilenet.predict(input);
2const predictions = trainablemodel.predict(activation);
我们可以使用前面的部分来使用某个优化器训练模型。

用雷达给道路做“B超”
Cypress推出新型软件工具CyClockWizard
意法半导体LED电视 200W数字电源解决方案满足严格的生态设计标准
倾斜传感器的三种类型说明
软压缩与硬压缩的区别
TensorFlow.js 的基本构建块及其操作,如何创建一些复杂的模型
在平板计算机上实现1080p画质
三星利用LCD扩大中低价市场 苹果推出iPhone新机型
晶闸管的工作过程是什么 晶闸管可控整流电路工作原理
中国家电业市场的现状 现“10年翻一倍”表现
高增益LC谐振放大器的设计
IGBT 的结构及主要参数
使用基于Eclipse开发Android应用程序的好处
用CPLD支持多个SD器件
什么是少儿编程?全方位科普,有疑惑的家长看过来!
AMD针对显卡软件进行了稳定性的审查
【科普】干货!带你从0了解移动机器人(六) (底盘结构类型)
自制无线充电线圈,简易无线充电器教程
基于 NOVATEK NT98530 Multiview Stitching 应用解决方案
小米5c详细体验, 是否值得购买?