2018 年 4 月 3 日 — 作者: Zaid Alyafeai
Tensorflow.js 是基于 deeplearn.js 构建的库,用于直接在浏览器中创建深度学习模块。使用它,您可以在浏览器上创建 CNN、RNN 等,并使用客户端的 GPU 处理能力训练这些模块。因此,训练 NN 不需要服务器 GPU。本教程首先解释 TensorFlow.js 的基本构建块及其操作…
<script src="https://cdn.jsdelivr.net.cn/npm/@tensorflow/tfjs@latest"> </script>
这将加载最新发布的捆绑包版本。const tensor = tf.scalar(2);
这创建了一个标量张量。我们也可以将数组转换为张量const input = tf.tensor([2,2]);
这将创建一个数组[2,2]
的常量张量。换句话说,我们通过应用张量函数将一维数组转换为张量。我们可以使用input.shape
来检索张量的大小
。const tensor_s = tf.tensor([2,2]).shape;
这具有形状[2]
。我们还可以创建具有特定大小的张量。例如,这里我们创建一个形状为[2,2]
的零张量。const input = tf.zeros([2,2]);
const a = tf.tensor([1,2,3]);
a.square().print();
x2
的值将为[4,9,16]
。TensorFlow.js 还允许链接操作。例如,要计算张量的 2 次方,我们使用const x = tf.tensor([1,2,3]);
const x2 = x.square().square();
x2
张量的值将为[1,16,81]
。x2
之后,我们不再需要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
的值后,我们不再需要它。[-0.5,0]
中。我们将使用一个优化器来找到确切的值。函数 f(x) 的图形 |
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
。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
}
然后,我们将模型拟合5000iterations
await model.fit(xs, ys, {
batchSize: 1,
epochs: 5000
})
最后,我们在训练集上进行预测model.predict(xs).print()
输出应该是[[0.0064339], [0.9836861], [0.9835356], [0.0208658]]
,这是预期的。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'
});
在这里,我们创建了一个conv
层,它接受大小为[28,28,1]
的输入。输入将是大小为28 x 28
的灰度图像。然后,我们应用大小为5x5
、步长等于1
的8
个内核,并使用VarianceScaling
初始化。之后,我们应用一个激活函数,该函数基本上会获取张量中的负值并将其替换为零。现在,我们可以将此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);
这将导致我们的结果为24
。回到我们的模型,我们意识到我们使用了flatten()
,它基本上将输入从形状[BATCH_SIZE,a,b,c]
转换为形状[BATCH_SIZE,axbxc]
。这很重要,因为在密集层中,我们不能应用2d
数组。最后,我们使用了具有输出单元10
的密集层,它代表了我们的识别系统中所需的类别数量。实际上,此模型用于识别所谓的MNIST 数据集中手写数字。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
函数的第二个变量表示模型的真实标签。最后,我们有配置参数,如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
的损失和准确率,它是fit()
函数的输入。//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]
,但模型采用的是四维向量。因此,我们需要使用expandDims
为张量添加一个额外的维度。const eTensor = tensor.expandDims(0);
因此,输出张量的大小将为[1,28,28,3]
,因为我们在索引0
处添加了一个维度。现在,为了预测,我们只需使用predict()
。model.predict(eTensor);
函数predict
将返回我们网络中最后一层的输出值,通常是一个softmax
激活函数。1,000
个不同的类别上训练的激活。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();
运行代码后,我得到了类别 = 21,它代表一个风筝。//The number of layers in the model '88'
const len = mobilenet.layers.length;
//this outputs the name of the 3rd layer 'conv1_relu'
const name3 = mobilenet.layers[3].name;
我们看到我们有88
层,如果要在另一个数据集上重新训练这些层,成本会非常高。因此,基本的技巧是仅使用此模型来评估激活(我们不会重新训练),但我们将创建可以训练到其他类别数量的密集层。2
的密集层来预测正确的类别。因此,mobilenet
模型将在某种程度上被“冻结”,我们只需训练密集层。81
,名称为conv_pw_13_relu
的层。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年4月3日 — 作者:Zaid Alyafeai
Tensorflow.js 是一个基于 deeplearn.js 的库,用于直接在浏览器上创建深度学习模块。使用它,您可以在浏览器上创建CNN、RNN等,并使用客户端的GPU处理能力来训练这些模块。因此,训练NN不需要服务器GPU。本教程从解释TensorFlow.js的基本构建块和操作开始…