[KANMARS原创]-DUBBO源码解析(6)-summarize

DUBBO源码解析(6)-summarize

一、总结

为期六期的Dubbo源码解析,这是最后一期《总结》

1、性能:我一直觉得讨论性能,是程序员界的俗人才做的事情。

더보기

[KANMARS原创]-DUBBO源码解析(5)-consumerCall

DUBBO源码解析(5)-consumerCall客户端调用过程

一、Dubbo客户端调用过程概述

dubbo调用过程如下(集群模式的zookeeper,):

더보기

[KANMARS原创]-DUBBO源码解析(4)-referenceBean

DUBBO源码解析(4)-referenceBean

一、referenceBean概述

referenceBean也是较为常见的一个bean,当我们做如下配置的时候:

더보기

[KANMARS原创]-DUBBO源码解析(3)-serviceBean

DUBBO源码解析(3)-serviceBean

一、serviceBean概述

我们终于讲到了serviceBean这个看似和Dubbo有点直接关系的地方,因为对于dubbo来说,我们最常做的事情,就是配置一个

더보기

[KANMARS原创]-DUBBO源码解析(2)-proxyFactory

DUBBO源码解析(2)-proxyFactory

在ServiceConfig.java和ReferenceConfig.java中有一个神奇的类变量叫做proxyFactory

一、用途

proxyFactory的接口如下:

더보기

[KANMARS原创]-DUBBO源码解析(1)SPI机制

PS:恰逢暴雨、回不了家,写点文章……

DUBBO源码解析(1)-SPI机制

一、SPI机制

SPI机制的全称是Service Provider Interface,单从字面可以理解为Service提供者接口,正如从SPI的名字去理解SPI就是Service提供者接口;

더보기

[KANMARS原创]-人工神经网络brain.js算法解析



在机器学习这一个未来的、伟大的领域,有一个小小的分支叫做人工智能。




举个例子:照片识别、信用卡图片识别、声音语言解析、数据聚类与离散。




机器学习按照有无监督者(或者有无导师),区分为监督学习和无监督学习http://www.cnblogs.com/ysjxw/articles/1149004.html




监督学习是最常见的学习分类,在训练集中指出正确的结果,让网络自己去适应这种结果。




而无监督学习,则大致有两种实现思路。



第一种思路是在指导Agent时不为其指定明确的分类,而是在成功时采用某种形式的激励制度。



第二种思路是称之为聚合(原文为clustering,译者注)。这类学习类型的目标不是让效用函数最大化,而是找到训练数据中的近似点。聚合常常能发现那些与假设匹配的相当好的直观分类。例如,基于人口统计的聚合个体可能会在一个群体中形成一个富有的聚合,以及其他的贫穷的聚合。








今天我们来从算法上分析一下人工智能的一种实现brain.js。它有输入集、隐层、输出集的基本结构,采用了BP神经网络结构,有自己的激活函数simoid,有自己的误差backpropagation算法。








之前已经有一篇文章介绍了brain.js的用法和内部的代码结构,因此不再赘述,今天重点描述一下brain.js的BP算法和数据结构。了解这些是很有必要的,当你可以熟练掌握这些基础知识,你可以很直接的创造一套自己的人工智能工具,例如:一个简单的验证码识别工具,一个图片分类器,一个看似有点智能的自动对话程序……








第一章、构造函数








var NeuralNetwork = function(options) {

  options = options || {}; //初始化全局变量options

  this.learningRate = options.learningRate || 0.3; //设置学习比率 是options.learningRate或者0.3

  this.momentum = options.momentum || 0.1; //惯性比率,用于设置“新的变动的影响程度”

  this.hiddenSizes = options.hiddenLayers; //隐层的节点的数量是一个二维数组:层数/该层节点数



  this.binaryThresh = options.binaryThresh || 0.5; //阈值

}

在构造函数中初始化一些全局的值,如备注所式,用于设置一些具有全局性影响的值。









参数含义如下:



learningRate用于影响节点对新知识的学习速度,如果该比例较大,则每个node在变动时会采用较剧烈的幅度,速度会加快稳定性会下降



momentum用于影响节点对旧知识的保留,如果该比例较大,则节点发生的变动会有较剧烈的幅度,速度会加快稳定性会下降




hiddenLayers是隐层的节点的数量,是一个二维数组:层数/该层节点数,涉及在训练时(神经网络从训练开始,因此此句话等价于“在开始时”)初始化隐层节点的数量。




binaryThresh是一个阈值,在test模式下才能使用









第二章、训练








train: function(data, options) {

    data = this.formatData(data);



    ……

}



我们逐行解析一下,挑重要的看:

var iterations = options.iterations || 20000; //设置每个节点的迭代次数为20000,用逼近论逐步逼近目标



var errorThresh = options.errorThresh || 0.005; //误差的目标值为0.005



……



var inputSize = data[0].input.length; //设置输入数量为data[0]的输入的数组的长度



var outputSize = data[0].output.length; //设置输出数量为data[0]



var hiddenSizes = this.hiddenSizes; //隐层数量



if (!hiddenSizes) { //如果隐层数量未初始化,则设置隐层数量为一个数组,数组的0号元素为3到inputSize/2的MAX

      hiddenSizes = [Math.max(3, Math.floor(inputSize / 2))];

}



this.initialize(sizes); //初始化,详见第二章第一节、初始化








for (var i = 0; i < iterations && error > errorThresh; i++) { //遍历iterations次,并且在当前偏差大于误差的目标值为0.005的情况下进行训练

      var sum = 0; //误差初始值为0

      for (var j = 0; j < data.length; j++) { //对data数量进行逐个训练

        var err = this.trainPattern(data[j].input, data[j].output, learningRate);//详见第二章第二节训练匹配



        //训练trainPattern,传入的值为第j个输入,第j个输出,学习比率,输出结果为当前这一行数据,在各个网络层各个节点的误差的平方的平均数

        sum += err; //计算误差的和

      }

      error = sum / data.length; //计算误差的平均值



      if (log && (i % logPeriod == 0)) { //如果在logPeriod的倍数,则打印日志

        log(“iterations:”, i, “training error:”, error);

      }

      if (callback && (i % callbackPeriod == 0)) { //如果在callbackPeriod的倍数,则调用回调函数

        callback({ error: error, iterations: i });

      }

    }








第二章第一节、初始化



this.initialize(sizes);



在该方法内部,会对神经网络的数据结构进行初始化,代码比较简单,我们关键描述几个数据结构



this.outputLayer = this.sizes.length - 1; //输出层的最后一个下标



this.biases = []; //阈值的二维数组:层/值



this.weights = []; //权重的三维数组:层/节点/值 [layer][node][k]



this.outputs = []; //输出结果的二维数组:层/值



this.deltas = []; //偏差的二维数组:层/值



this.changes = []; // for momentum //改变值的三维数组:层/节点/值 [layer][node][k]



this.errors = []; //误差的二维数组:层/值



第二章第二节、训练匹配




this.trainPattern(data[j].input, data[j].output, learningRate);




该方法在两层循环中被调用,第一层循环为训练次数iterations,第二层循环为data[j]输入的数据的数量



完成了加快训练的效果(目前很多神经网络的重点就在提高训练速度和减少误差的方向上)



this.runInput(input);//信号的前馈传播,详见第二章第二节第一部分、信号的前馈传播



this.calculateDeltas(target);//计算误差deltas,详见第二章第二节第二部分、误差的反向传播一计算误差deltas



this.adjustWeights(learningRate);//重新计算权重,详见第二章第二节第三部分,误差的反向传播二权重的重新调整









第二章第二节第一部分、信号的前馈传播



this.runInput(input);//信号的前馈传播



对input为单行的输入数据向量进行操作,该方法代表了一个神经网络对某行数据的操作




this.outputs[0] = input;//设置this.outputs的第0个元素为输入的数据



    //针对每个输出层/隐层去执行,从第一层开始,(0层为输入)

    for (var layer = 1; layer <= this.outputLayer; layer++) {



      //针对当前隐层的节点进行操作 

      for (var node = 0; node < this.sizes[layer]; node++) { 



        //获取当前节点的权重数组

        var weights = this.weights[layer][node]; 

        //获取当前节点的阈值

        var sum = this.biases[layer][node]; 



        //对每个输入项进行遍历

        for (var k = 0; k < weights.length; k++) { 



          //总和等于当前节点和上一个节点的输入乘以权重

          sum += weights[k] input[k]; 

        }

        



        //激活函数-输出值等于1除以(1+-sum的以e为底的指数函数,即e的-sum次方,位于0-1之间)



        this.outputs[layer][node] = 1 / (1 + Math.exp(-sum));

      }

      var output = input = this.outputs[layer]; //递归将当前的输出作为下一层的输入

    }

    return output; //在所有层的神经网络通过之后,输出结果






上述代码中的激活函数,并不是去激活什么,而是指如何把“激活的神经元的特征”通过函数把特征保留并映射出来(保留特征,去除一些数据中的冗余),这是神经网络能解决非线性问题关键

激活函数是用来加入非线性因素的,因为线性模型的表达力不够

该算法为simoid激活函数,但是如果出现极值,该算法可能会出现大批节点死亡,即值为0无法激活的情况

其他可选算法推荐ReLU即f(x)=max(x,0);比较简单

关于激活函数的研究,可以参考http://www.mamicode.com/info-detail-873243.html

①单侧抑制 //通过激活函数把特征保留并映射出来

②相对宽阔的兴奋边界 //??

③稀疏激活性 //节省能量,生物神经具有稀疏激活性,因此在数学逻辑上也是需要有稀疏激活特性的









以上红色内容,是小白理解神经网络的关键~~~



但是对于专业数学家,这个是需要用数学证明的:证明特征是可再分的,再分的特征根据贝叶斯公式,多个特征将会休整人的信念,再根据逼近论用较简单的函数来代替复杂的函数。








最终可以通过任意个节点来拟合任何一个复杂函数。



这就是神经网络最神奇之处,它可以实现任何一个符合函数的拟合,也就是说:输入一个图片,它能告诉你这张图片上的物种叫做猫。












第二章第二节第二部分、误差的反向传播一计算误差deltas



this.calculateDeltas(target);//计算误差deltas

在BP神经网络中、误差的反向传播,进而对权重进行调整是核心内容








代码如下:



calculateDeltas: function(target) { //误差计算

    for (var layer = this.outputLayer; layer >= 0; layer–) { //对每个输出层进行遍历,逆向计算

      for (var node = 0; node < this.sizes[layer]; node++) { //对当前层的节点数进行遍历

        var output = this.outputs[layer][node]; //获取当前节点的输出



        var error = 0; //偏差为0

        if (layer == this.outputLayer) { //如果是最后一层

          error = target[node] - output; //误差等于目标值减去输出-》即初始误差

        }

        else { //非最后一层

          var deltas = this.deltas[layer + 1]; //获取到下一层的误差的数组



          //deltas在最后一层时赋予了初始值,此后随着层数的减少,低次获取上一次的误差//deletas是二维数组,层/值

          for (var k = 0; k < deltas.length; k++) {

            error += deltas[k] this.weights[layer + 1][k][node];



            //当前节点的error为下一层的偏差按照权重的总和值



            //此处对误差的评估有多种算法,算数加权  为其中一种,此外还有开方差等方法

          }

        }

        this.errors[layer][node] = error; //当前层,当前node的error

        this.deltas[layer][node] = error output (1 - output); 

      }

    }

  },



上述代码有两个要点



1、error的计算,是通过下一层的误差根据权重汇总起来的



2、deltas的计算,是通过公式deltas=error output (1-output)计算出的













在最后一行代码中,有一个神奇的deltas = erroroutput(1-output)




//当前层,当前node的为errors误差 当层的输出 (1-当层的输出)

//该算法为BP前馈申请网络的误差传播算法,详见论文http://www.doc88.com/p-601587528491.html

//大概原理如下:BP

//BP算法实质是求取误差函数的最小值问题。采用非线性规划中的最速下降方法,按误差函数的负

//梯度方向修改权系数。从其数学表达可知:多层网络的训练方法是把一个样本加到输入层,并根据

//向前传播的规则X(k,j) = f(U(k,j)),一层一层向输出层传递,最终在输出层得到输出X(m,i),把X(m,i)

//和期望输出Yi进行比较,若两者不等,则产生误差信号e,接着按下式反向传播修改权系

//此处公式推导可见http://blog.csdn.net/zhouchengyunew/article/details/6267193的“二、BP算法的数学表达”章节

//或者关于BP网络的数学表达可以参见http://www.cnblogs.com/wengzilin/archive/2013/04/24/3041019.html

//最终结论为this.deltas[layer][node] = error output (1 - output);



//但比较粗糙直观的理解是:



//计算出的error是子层误差的总和,而output和(1-output)是一种修正,因本例中激活函数为simoid,因此output在(0,1)之间

//因此output和(1-output)呈x在[0-1]区间的倒U型,因此output如果位于0.5时,error就会有较大的变动,



//位于两边时,就会有较小的变动,因此delta = error output (1 - output); 这个函数就会有一种“趋势”来



//倾向与0,结合adjustWeights误差反向传播调整公式,就会使误差在最终趋向于0->实现“误差降低的特性”













第二章第二节第三部分,误差的反向传播二权重的重新调整








this.adjustWeights(learningRate); //重新计算权重—-此处为核心操作








adjustWeights: function(learningRate) {

    for (var layer = 1; layer <= this.outputLayer; layer++) { //对所有的层进行判断,除了输入层

      var incoming = this.outputs[layer - 1]; //incoming为上一层的输出,这一层的输入



      for (var node = 0; node < this.sizes[layer]; node++) { //对当层的节点进行遍历

        var delta = this.deltas[layer][node]; //获取到当前节点的delta 是一个数字,为下一层的error下一层的output(1-下一层的output)



        for (var k = 0; k < incoming.length; k++) { //根据上一层的输入节点

          var change = this.changes[layer][node][k]; //获取到变动前的change



          change = (learningRate delta incoming[k]) //对当前节点的change进行修正  change = (学习比率 差值 上一层的该点的输出值) + (当前的保持的势能 之前的change)

                   + (this.momentum
change); //其核心原理在于error越小->delta越小->change越小—->具有逼近作用



          this.changes[layer][node][k] = change; //对当前的change进行保存

          this.weights[layer][node][k] += change; //将change增加到weight上

        }

        this.biases[layer][node] += learningRate delta; //重新设置biases阈值

      }

    }

  },











关键在于change 的计算和 weights的计算



change = (learningRate delta incoming[k]) + (this.momentum change) 




其核心原理在于error越小->delta越小->change越小—->具有逼近作用








由上文可知delta是趋向于0的,因此change是最终趋向于0









而weights的计算公式为



this.weights[layer][node][k] += change;



因为change最终趋于0,因此weights最终趋于固定值,即:神经网络的联结的权重最终趋于固定值。













第三章、运行








run: function(input) {

    if (this.inputLookup) {

      input = lookup.toArray(this.inputLookup, input);

    }



    var output = this.runInput(input);



    if (this.outputLookup) {

      output = lookup.toHash(this.outputLookup, output);

    }

    return output;

  },











调用了runInput方法,最终的输出结果为output



方法不再赘述。



—————————————————————————————————








由以上分析看到,神经网络最大的作用就是“拟合”一个复杂函数,该函数输入一堆复杂的值,通过训练,使得一些小函数的和最终模拟出了一个确定的值(逼近论)。








这种特性在“图像识别”“声音识别”等机器视觉和机器听觉领域十分有用。








但是:上述例子的输出结果只能是0~1的数值,代表了是或者否的判断,例如输入一堆图片,判断这些图片上是否有一只猫,它会告诉你是或者否








倘若:你需要输入一堆图片,然后将图片分成五类,或者输出一个图片的色情度是0%还是10000%,它需要进行一些深度的改造。



————————————————————————————————–



由于人类大脑本身的运行机制还不清晰,因此很难说这种程度的人工智能能否真正匹配真实的人类智能。








但前段时间的阿尔法狗人机大战,说明了人类的智能或许在本质上就是一种对过往的拟合,兴许抽象点说:能看懂的都是科学,看不懂但是有效果的叫做智能,看不懂也没效果但是还有人信的叫做宗教








人工做了一个程序,实现了看不懂但是有效果的功能,这就是人工智能。







此外人工智能领域从去年开始CNN比较火热,就是卷积神经网络,用隐层的深度换单个隐层的宽度,这又是另一个课题了,我比较有兴趣的是:卷积层究竟是由人类硬编码实现的?还是由人工智能训练出的?还是由人工智能在漫长的训练中自发形成的?









더보기

[KANMARS原创]-我究竟是怎么样的一个程序员


我究竟是怎么样一个程序员。总结那么几个标签:勤奋、浪、嘴炮型诗人。



——————————————————————————————–



一、勤奋



也许我在别的方面很懒,但是在编程方面我还是自认勤奋的,举个栗子:



1、结婚前一晚,花一夜看完了《UNIX进程间通讯》;



2、半夜睡不着就翻译一篇神经网络文章brain.js



3、星期六星期天不喜欢出门,喜欢在家看代码玩



4、。。。。。。



就像我之前看到的一句话:过了这条线,coding就不是为了钱是为了多巴胺了。这条线之上,程序不再是工具,而是玩具,会让你感到兴奋。你能感觉到语言,代码,算法,模式,思想都碎成粉碎,一块块等着你来拼。你能体会到程序里齿轮般环环相扣的精巧。你能闻到代码的味道,看到代码的形状。







我依稀觉得我差不多在这线的附近了,因为我看到程序就感觉看到了玩具一样,不论是任何语言,不论是任何模式。







而我的学习方法,大致是:



1、遇到一个不知道的东西,先让它能用,再让它好用;

2、把它拆成小碎块,然后拼成我需要的样子;

3、再系统学习一遍这个东西的文档,看看别人是怎么拼的,或者有没有什么神奇的小碎块;



4、随着小碎块越攒越多,我就能拼出越来越复杂的东西,我就越来越强大;











————–世界上有很多大神————-



————–你也可以成为其中一尊————–











二、浪



浪,是我对自己的评价。但当我想举例子的时候,还真举不出来…

难道是一颗闷骚的心脏,却配了一副老实的身体…







——但是真的很浪,竟然没出什么大事……



——从概率论上说,我这么干终有一天会倒霉的……







不过我至今没倒过霉…求虐…没倒过霉的人生是不完整的







浪的行为:主要体现在代码和技术上



1、我喜欢看别人的代码,沿着他的思路走,我就能看到当时候他脑袋里进了什么水,或者中了什么毒



2、技术上我喜欢走一些比较浪的路线,因为正常做没什么意思,如果时间充足的情况下,我还是喜欢走一些比较浪的路线



3、我喜欢伊布拉希莫维奇远超喜欢C罗和梅西——》这足以说明一切















三、嘴炮型诗人



朕是嘴炮型诗人………



这个毫无疑问,举例说明,我刚换的QQ签名:



                  朕就用灵长类动物看着爬虫一样的眼神看着你们,充满了鄙夷、不齿、怜悯。计数器20



此事是因为一群人在微信群里推脱着责任,各种使着无用的绊,各种可笑的推脱着责任…………..



在我看来,就是可笑……









더보기

[KANMARS原创]-基于隐层的前馈式神经网络之brain.js

睡不着,翻译点资料……

好久之前帮同学写过一篇论文,大致是基于ELM(Extreme Learning Machine极限学习机)的前馈式神经网络.

因此对神经网络的一些概念还算了解。

凑巧最近对人工智能感兴趣,所以百度了一下成型的产品,比如brain.js,然后翻译以及阅读一下。

더보기

[KANMARS原创]-非夜深不可听歌

好久没有听歌,忽然瞬间开始怀念暑假时的雨夜,在小阁楼上打开窗户,听着歌听着雨。

刚才在听歌,因为忽然想起了田馥甄和苏运莹唱的

더보기