给非ML专家的指南
您可以在这里找到补充本教程的代码。
运行:
./scripts/watch-demo demos/ml_beginners/ml_beginners.ts
然后访问http://localhost:8080/demos/ml_beginners/
。
或者直接点击这里观看我们的演示。
NDArrays,Tensors,以及numbers
数学张量
在数学上,“张量”是线性代数的最基本的对象,数字,向量和矩阵的泛化。 向量可以被认为是数字的一维列表,矩阵则是二维数字列表。 张量简单地将概念概括为n维数字列表。这是布置在任意多维矩形阵列中的任意的数字(或甚至字符串或其他数据类型)排列。
张量具有几个属性:
- 它有一个类型,它描述了每个元素的类型,例如整型,浮点等。deeplearn.js现在只支持32位浮点数。
- 它有一个形状,一个整数列表,它描述了矩形矩阵阵列的形状。例如当你说一个矩阵是“4乘4”时,你就在描述矩阵的形状。
- 它有一个等级,这是它的形状的长度,元素数组的维数。 矢量为1级,矩阵为2级。
示例张量 | 类型 | 形状 | 等级 |
---|---|---|---|
Scalar: 3.0 | float | [] | 0 |
Vector: (1, 5, -2) | int | [3] | 1 |
2x2 Matrix | int | [2, 2] | 2 |
number[],NDArray,Tensor:数学张量的三种数据类型
数学家称之为“张量”的相同对象在deeplearn.js中以三种不同的方式表示。上述讨论(等级,形状和类型)适用于所有这些,但它们不同,保持直线是很重要的:
-
number []
是与数组相对应的底层JavaScript类型。实际上number
是0级张量,number[]
是1级张量,number[][]
是二级张量,以此类推。你不会在deeplearn.js中使用太多,但这是你获得deeplearn.js之内或以外的内容的方法。 -
NDArray
是deeplearn.js更强大的实现。可以在客户端的GPU上执行涉及NDArray的计算,这是deeplearn.js的根本优点。这是计算最终发生时最实际的张量数据的格式。例如,当您调用Session.eval
时,这是您得到的返回值(以及FeedEntry
的输入)。您可以使用NDArray.new(number [])
和NDArray.get([indices])
在NDArray
和number []
之间进行转换。 -
Tensor
是一个空袋子,它里面没有实际的数据。当构建Graph
时,它是一个占位符,它记录最终将适合其中的数据的形状和类型。它的组件中不包含实际值。但是,只要知道形状和类型,在Graph
构建时就可以做到重要的错误捕获。如果您要将2x3矩阵乘以10x10矩阵,则在创建节点之前,该Graph
可以在您给出输入数据之前向提出警告。在Tensor
和NDArray
或number[]
之间直接转换是没有意义的。如果您发现您正在尝试这样做,以下其中之一可能是真的:- 您有一个静态的
NDArray
,并且想在一个Graph
中使用。您应该使用graph.constant()
创建一个常量Tensor
节点。 - 您有一个
NDArray
要作为Graph
输入。在Graph
中创建一个带有graph.placeholder
的占位符,然后将您的输入发送到FeedEntry
中的Graph
。 - 您有一个输出张量,您希望会话评估并返回其值。调用
Session.eval(tensor)
。
- 您有一个静态的
一般来说,您只需要在自动区分(训练)时使用Graph
。如果您只想使用库进行正向模式推理,或仅使用通用数值计算,则使用NDArrayMath
的NDArrays
就足够了。
如果您对训练感兴趣,您必须使用Graph
。构建Graph
时,您将使用Tensors
,当您使用Session.eval
执行时,结果将是NDArray
。
正向模式推理/数值计算
如果您只想对NDArray
执行数学运算,则可以简单地使用数据构建NDArray
,并使用NDArrayMath
对象对它们执行操作。
例如,如果要在GPU上计算矩阵乘以矢量:
const math = new NDArrayMathGPU();
math.scope((keep, track) => {
const matrixShape = [2, 3]; // 2行,3列。
const matrix = track(Array2D.new(matrixShape, [10, 20, 30, 40, 50, 60]));
const vector = track(Array1D.new([0, 1, 2]));
const result = math.matrixTimesVector(matrix, vector);
console.log("result shape:", result.shape);
console.log("result", result.getValues());
});
有关NDArrayMath,保持和跟踪的更多信息,请参阅简介和核心概念。
NDArray
/NDArrayMath
层可以被认为类似于NumPy。
训练:延迟执行,图形(Graphs)和会话(Sessions)
在deeplearn.js中了解训练(自动区分)的最重要的事情是它使用延迟执行模型。您的代码将包含两个独立的阶段:首先,您将构建一个Graph
,表示您要执行的计算的对象,然后执行Graph
并获取结果。
大多数情况下,您的Graph
将会将某些输入转换为某些输出。一般来说,Graph
的架构将保持固定,但它将包含将自动更新的参数。
执行Graph
时,有两种模式:训练和推断。
推断是提供Graph
输入以产生输出的动作。
训练Graph
涉及提供Graph
许多标记输入/输出对的示例,并自动更新Graph
的参数,以便在评估(推断)输入时Graph
的输出更接近标记的输出。给出表示对生成的输出的标签输出接近的Scalar
的函数称为“成本函数”(也称为“损失函数”)。当模型运行良好时,损失函数应该输出接近零。训练时必须提供成本函数。
deeplearn.js的结构非常类似于Google的基于python的机器学习语言Tensorflow。如果你知道TensorFlow,Tensor
,Graph
和Session
的概念几乎是一样的,但是我们假设在这里你没有TensorFlow的知识。
图形(graphs)作为函数
可以通过类比于常规JavaScript代码来理解差异。 对于本教程的其余部分,我们将使用这个二次方程:
// y = a * x^2 + b * x + c
const x = 4;
const a = Math.random();
const b = Math.random();
const c = Math.random();
const order2 = a * Math.pow(x, 2);
const order1 = b * x;
const y = order2 + order1 + c;
在这个原始代码中,数学计算在每一行被马上处理。
与以下代码相反,类似于deeplearn.jsGraph
推断的工作原理。
function graph(x, a, b, c) {
const order2 = a * Math.pow(x, 2);
const order1 = b * x;
return order2 + order1 + c;
}
const a = Math.random();
const b = Math.random();
const c = Math.random();
const y = graph(4, a, b, c);
这个代码有两个步骤:首先设置图形函数,然后调用它。 在最后一行调用函数之前,在前几行中设置图形函数的代码不会执行任何实际的数学运算。 在安装过程中,即使尚未执行计算,也可以捕获基本的编译器类型安全错误。
这完全类似于Graph
在deeplearn.js中的工作原理。 您的代码的第一部分将设置Graph
,描述如下:
- 输入,我们的例子为“x”。 输入表示为占位符(例如
graph.placeholder()
)。 - 输出,在我们的例子中是“order1”,“order2”,最终输出“y”。
- 产生输出的操作,在我们的例子中是二次(x ^ 2,乘法,加法)的分解函数。
- 可更新的参数,在我们的例子中是“a”,“b”,“c”。 可更新的参数表示为变量(例如
graph.variable()
)
然后,在代码的后续部分,您将在某些输入上“调用”(Session.eval
)图形的功能,您将学习“a”,“b”和“c”的值,即用Session.train
处理某些数据。
上述函数类比和deeplearn.jsGraph
之间的一个细微差别在于Graph
没有指定其输出。 相反,Graph
函数的调用者指定要返回哪些张量。 这允许对同一个Graph
的不同调用来执行它的不同部分。 只有获得呼叫者要求的结果所需的部分才能被评估。
Graph
的推理和训练由一个Session
对象驱动。 该对象包含运行时状态,权重,激活和渐变(派生),而Graph
对象只保存连接信息。
所以上面的功能将在deeplearn.js中实现,如下所示:
const graph = new Graph();
// 在graph中创建一个新的输入,称为“x”,其形状为[](标量)。
const x: Tensor = graph.placeholder('x', []);
// 在图形中创建新的变量'a','b','c',形状为[],并且随机初始值。
const a: Tensor = graph.variable('a', Scalar.new(Math.random()));
const b: Tensor = graph.variable('b', Scalar.new(Math.random()));
const c: Tensor = graph.variable('c', Scalar.new(Math.random()));
// 使新张量表示二次运算的输出。
const order2: Tensor = graph.multiply(a, graph.square(x));
const order1: Tensor = graph.multiply(b, x);
const y: Tensor = graph.add(graph.add(order2, order1), c);
// 训练时需要提供标签和成本函数。
const yLabel: Tensor = graph.placeholder('y label', []);
// 提供训练的平均成本函数。 cost =(y - yLabel)^ 2
const cost: Tensor = graph.meanSquaredCost(y, yLabel);
// 此时,图形(Graph)已设置,但尚未被评估。
// **deeplearn.js** 需要一个Session对象来评估一个图形。
const math = new NDArrayMathGPU();
const session = new Session(graph, math);
math.scope((keep, track) => {
/**
* 推断
*/
// 现在,我们要求图形来评估(推断),并在为“x”提供值4时给出结果。
// 注意:“a”,“b”和“c”被随机初始化,所以这将给我们一些随机的东西。
let result: NDArray =
session.eval(y, [{tensor: x, data: track(Scalar.new(4))}]);
console.log(result.shape);
console.log(result.getValues());
/**
* 训练
*/
// 现在让我们学习给出一些数据的这个二次方的系数。
// 为此,我们需要提供x和y的例子。
// 这里给出的值是值a = 3,b = 2,c = 1,随机噪声加到输出上,因此不是一个完美契合。
const xs: Scalar[] = [
track(Scalar.new(0)),
track(Scalar.new(1)),
track(Scalar.new(2)),
track(Scalar.new(3))
];
const ys: Scalar[] = [
track(Scalar.new(1.1)),
track(Scalar.new(5.9)),
track(Scalar.new(16.8)),
track(Scalar.new(33.9))
];
// 训练时,重要的是打乱你的数据!
const shuffledInputProviderBuilder =
new InCPUMemoryShuffledInputProviderBuilder([xs, ys]);
const [xProvider, yProvider] =
shuffledInputProviderBuilder.getInputProviders();
// 将训练分成各个批次。
const NUM_BATCHES = 20;
const BATCH_SIZE = xs.length;
// 在开始训练之前,我们需要提供一个优化器。
// 这是负责更新权重的对象。
// 学习率参数是一个值,表示更新权重时需要做的步骤。
// 如果这太大,你可能会超越和振荡。
// 如果太小,模型可能需要很长时间才能训练。
const LEARNING_RATE = .01;
const optimizer = new SGDOptimizer(LEARNING_RATE);
for (let i = 0; i < NUM_BATCHES; i++) {
// 训练需要一个成本张量来最小化;
// 此次调用训练一批数据,并将此批次的平均成本返回为标量(Scalar)。
const costValue = session.train(
cost,
// 将图表(Graph)上的输入提供者映射到Tensors。
[{tensor: x, data: xProvider}, {tensor: yLabel, data: yProvider}],
BATCH_SIZE, optimizer, CostReduction.MEAN);
console.log('average cost: ' + costValue.get());
}
// 现在打印x = 4的训练模型的值,应该是大约57.0。
result = session.eval(y, [{tensor: x, data: track(Scalar.new(4))}]);
console.log('result should be ~57.0:');
console.log(result.shape);
console.log(result.getValues());
});
在训练模型之后,您可以再次通过Graph
来推断给出“x”的“y”值。
当然,在实践中,你不会只想使用Scalar
值。deeplearn.js提供强大的硬件加速线性代数,可以用于从图像识别到文本生成的一切。 查看其他教程了解更多!