deeplearn.js 源码解析(一)

【写在最前】

之所以想到读代码,是因为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,下周读一下吧。
最后,我还是不知道为啥先添加节点,后校验合法性,希望接下来读的代码能解答这个疑惑。










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值