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