之所以想到读代码,是因为deeplearn.js报的这个错
Softmax backprop is not yet implemented
Softmax backprop is not yet implemented
Softmax backprop is not yet implemented
怎么办怎么办怎么办
没有枪没有炮,我们 (自己造||喝西北风)
只能依靠dl.math手写了自己需要的网络的bp,然后重新搭建了网络。
本以为自己数学基础可以,但是草稿纸还是写了很多张。
希望读了代码之后可以试着补上softmax的bp吧
语言表达能力有限,搜【实例】有惊喜
【解析】
我是希望能够先理解完整的架构设计的,所以先从deeplearn.js重写的tensorflow开始入手。
源码就不放上来了,毕竟不是我写的。
如果有必要,我会放一些例子上来辅助说明。
【关键词】
shape:形状。比如矩阵的形状是2,3,意味着矩阵是2行3列的。
【Tensor】
这个是架构中最重要的一环。Tensor只有两条属性:shape和id。构造函数接收Tensor的shape,id由Tensor实例化的顺序决定。
根据文档说明和我的理解,Tensor记录shape,保证网络结构正确,维护/保持网络结构。
【Node】
节点。Node是其他节点的父类。事实上,网络由Tensor构建,由Node记录/表现网络中的行为。比如向量a和向量b相加,或者用架构里的名字称呼他们,Tensor a和 Tensor b相加,是以AddNode的形式表现的,以一个node属性指向AddNode的Tensor构建。而Node就是诸如AddNode等节点的父类。
Node类的构造传入graph, name, inputs, output四个参数,graph是其所在的图模型,name是其名称,inputs用于记录该节点的输入,output用于指明该节点的输出。同样,节点拥有其id,由Node实例化的顺序决定。
最后,Node的output中指定node属性为当前节点。后面将会看到为output指定node的行为,实际是给代表这个节点的Tensor赋予了node属性并指向当前节点。
【Graph】
图。以图的形式构建网络。
Graph类的构造函数不接受任何参数,会在其内部构造记录该图内节点的数组nodes和记录该图内网络层的layers。
Graph内部提供的方法,其目的均为构建图内部的节点,也就是新增元素到nodes中。
【Graph.prototype.addNodeAndReturnOutput】
对图至关重要的一个方法,方法的作用根据名字直译就好了,添加节点并且返回节点的输出。
方法接收一个节点。首先将该节点添加至该图的nodes数组,再检查节点是否合法,最后,返回节点的输出。
疑问待解决:为啥先添加节点,然后再检查是否合法?
【Graph.prototype.variable】
这个在前面的《科研之路》里用到过,该方法创建的节点是图中的变量,也就是训练网络时需要调整的权重。在图中的话,称为变量节点比较合适。
方法接收name和data,实例一个变量节点VariableNode并调用addNodeAndReturnOutput,将该变量节点加入图的nodes数组,检查节点是否合法并返回该变量节点输出。
【节点说明:VariableNode】
在Graph.prototype.variable实例化变量节点的过程中,构造函数接收了graph,name,data三个参数。实例化的VariableNode属性如下:
节点所在图:传入的graph
节点名称:传入的name
节点输入:{}。VariableNode的inputs设置为一个空对象,和其本身的性质相符,因为本来就没有任何内容输入给它。
节点输出:一个Tensor。Tensor的shape为传入data的shape。注意此处,前文提到的在Node构造函数中有一步为"Node的output中指定node属性为当前节点",因此这个Tensor中有一个node属性指向当前VariableNode。
节点data:传入的data。
VariableNode合法性校验:data不能为空。
【实例】
var graph= new dl.Graph();
var W = graph.variable('W',dl.Array2D.randNormal([2,3]));
以上代码创建了图graph,并创建了权重矩阵W到图中。
此时W和graph.nodes见下图
直观地:
W是一个Tensor,其shape为[2,3],id为0,node指向由('W',dl.Array2D.randNormal([2,3]))构建的VariableNode。根据源码,W是VariableNode的输出,并且W的node属性指向该VariableNode。我们测试W.node.output === W,得到true。
graph的nodes数组中添加了构建得到的VariableNode,各属性也符合预期(VariableNode的inputs为一个空的对象)。
若此时再添加一个VariableNode。
var U = graph.variable('W',dl.Array2D.randNormal([6,7]));
结果显而易见。
【Graph.prototype.matmul】
这个在《科研之路》里也用到过,代表矩阵乘法。该方法为图添加了一个MatMulNode。由于和前文的VariableNode很相近,直接以实例来说明。
【实例】
var graph = new dl.Graph()
var St = graph.placeholder('St',[5,1]);
var W = graph.variable('W',dl.Array2D.randNormal([5,5]))
var mm = graph.matmul(W,St)
以上代码构建了名为graph的图,添加了shape为[5,1]的placeholder St和shape为[5,5]的variable W。然后调用matmul添加了一个MatMulNode。此时,graph中拥有三个节点:PlaceholderNode、VariableNode和MatMulNode。三个节点按其实例顺序添加至nodes数组。
St、W和mm分别为三个Tensor,其shape应分别为[5,1]、[5,5],最后的mm是W和St做矩阵乘法的结果,shape应为[5,1]。实际结果如下图,符合预期
mm的node指向由参数构建的MatMulNode。该节点构造函数如下(还是决定贴一下,要不太抽象了)
var MatMulNode = (function (_super) {
__extends(MatMulNode, _super);
function MatMulNode(graph, x1, x2) {
var _this = _super.call(this, graph, 'MatMul', {
x1: x1,
x2: x2
}, new Tensor(getMatMulOutputShape(x1.shape, x2.shape))) || this;
_this.x1 = x1;
_this.x2 = x2;
return _this;
}
MatMulNode.prototype.validate = function () {
if (this.x1.shape.length === 2 && this.x2.shape.length === 2) {
util.assert(this.x1.shape[1] === this.x2.shape[0], 'Error adding matmul op: inner shapes of matrices with shapes ' +
(this.x1.shape + " and " + this.x2.shape + " must match."));
} else if (this.x1.shape.length === 2 && this.x2.shape.length === 1) {
util.assert(this.x1.shape[1] === this.x2.shape[0], 'Error adding matmul op: second dimension of matrix with shape ' +
this.x1.shape.toString() +
(" must match size of vector with shape " + this.x2.shape + "."));
} else if (this.x1.shape.length === 1 && this.x2.shape.length === 2) {
util.assert(this.x1.shape[0] === this.x2.shape[0], "Error adding matmul op: size of vector with shape " + this.x1.shape +
" must match first dimension of matrix with " +
("shape " + this.x2.shape + "."));
} else {
throw new Error('Error adding matmul op: inputs must be vectors or matrices.');
}
};
MatMulNode.X1 = 'x1';
MatMulNode.X2 = 'x2';
return MatMulNode;
}(Node));
和前面VariableNode不同,MatMulNode将其输入定义为传入的形参x1和x2,在本实例中即为Tensor W和Tensor St。而构建输出Tensor的shape为输入的x1和x2做矩阵乘法结果的shape,getMatMulOutputShape(x1Shape, x2Shape)源码如下,很清晰的数学表达:
function getMatMulOutputShape(x1Shape, x2Shape) {
if (x1Shape.length === 1 && x2Shape.length === 1) {
return [1];
} else if (x1Shape.length === 1 && x2Shape.length === 2) {
return [x2Shape[1]];
} else if (x1Shape.length === 2 && x2Shape.length === 1) {
return [x1Shape[0]];
}
return [x1Shape[0], x2Shape[1]];
}
合法性校验方面,检验的是输入合法性(是否是矩阵向量)以及两个输入是否能做矩阵乘法运算。
具体验证见下图
【他们中出了一个奸细】
【写在最后】
大周末的读读源码陶冶一下情操蛮好。
架构的重头戏在session,下周读一下吧。
最后,我还是不知道为啥先添加节点,后校验合法性,希望接下来读的代码能解答这个疑惑。