脑机接口时代曾经到来?作为万能分类器,各类神经网络在近年有了长足的倒退

比方卷积神经网络,也在现在的图片辨认畛域获得最好的问题

明天的指标是JS手撕通用神经网络????,无任何库依赖

神经网络概述

  • 人工神经网络( Artificial Neural Networks, 简写为ANNs)也简称为神经网络(NNs)或称作连贯模型(Connectionist Model) ,是对人脑或天然神经网络(Natural Neural Network) 「若干」根本个性的形象和模仿。

  • 俺们的神经元看上去很厉害
  • 目前在神经网络钻研办法上已造成多个流派,最富有成果的钻研工作包含:多层网络BP算法,Hopfield网络模型,自适应共振实践,自组织特色映射实践等。人工神经网络是在古代神经科学的根底上提出来的。它尽管反映了人脑性能的基本特征,但远不是天然神经网络的真切刻画,而只是它的某种简化形象和模仿。
  • 明天大抵的学习流程,日常问题=>感知机=>向前流传=>向后流传=>多层BP神经网络->案例=>小结
  • 为啥要学习?机器始终在学习,作为人类请放弃学习????

日常数据分类问题

简略感受一下

  • 先看个动图

  • 上图的过程,就是通过机器学习,最终确定一组参数,最初将2组数据尽可能离开????
  • 通过一条直线进行分类,就是线性分类器

再来看一个经典分类例子

  • 逻辑电路中咱们都学过与门,或门,与非门,或非门,异或门
  • 「题外话」:门电路造成组合逻辑,触发器组合造成时序逻辑,2者独特实现数字逻辑,最终造成俺们的CPU????。。。
  • 输出=>输入 的真值表,如下图

  • 除了XOR,其余逻辑操作真值表 都能够被一条直线划分,如下图

  • 1条直线根本闯天下,但XOR呢,无奈用一条直线搞定,那应该如何?????一朵小乌云?

  • 看一下 无奈线性分类的动图,机器一直学习,但陷入震荡????

相似XOR分类的解决方案

  • 咱们能够用各种机器学习实现,类XOR数据集分类,如下图

  • 明天要用的是上图中, 左下模式-神经网络,实现非线性分类器????

人工神经网络

感知机

  • 感知机(perceptron)是由美国学者 Frank Rosenblatt 在 1957 年提出来的,感知机是神经网络(深度学习)的起源算法
  • 能够简略的认为一个感知机就是一个神经元,承受输出,解决后输入,也能够输出到下一个感知机????
  • 一个感知机,由多个输出,其中,参数w叫做权值向量(weight),b称为偏置(bias),activation function是激活函数,最终得出Oj
  • 留神b 在上图没有明确标注出,能够认为是一个截距,每个感知机对应1个b
  • 整个感知机,了解起来是很简略的,他模仿了生物神经元的特定性能????
  • 下面的f(x) ,你能够简略认为就是一条直线,所以对于除了XOR的逻辑门,咱们能够通过1个感知机进行分类
  • 上面咱们会用JS建设一个感知单元类。
  • 神经网络次要有三个基本要素: 「权重、偏置和激活函数」,咱们的指标就是调整这3个参数

激活函数

  • 起非线性映射的作用,其可将神经元的输入幅度限度在肯定范畴内,个别限度在(-1~1)或(0~1)之间。最罕用的激活函数是Sigmoid函数。
  • Sigmoid函数在之前文章中有提及

  • 上面代码中咱们会实现,3种激活函数,及其导数

多层感知机-神经网络

  • 先看动图

  • 上图有1层输出层,2层暗藏层,1层输入层
  • 输出层有2个维度输出,输入层有2个维度输入
  • 大部分的分类工作,用2层暗藏层网络足矣

正向流传

  • 多层神经网络的正向流传大概是这样的:

  • 输出的手写数字,通过2层,暗藏层后,取得了 0-9,10个分类
  • 为啥会失去0-9,那是由外面的w 参数,b参数, 激活函数,确定的 。你要做的只是乘起来求和
  • 784是什么鬼?28x28,每张图片的像素个数 ????,这里应用了 one-hot独热编码
  • 如何确定这些参数值呢?应用反向流传,也就是常说的学习(训练)

反向流传

  • 如果正向流传是小学生,那反向流传一下就跳到了大学预备班????,可能这就是所谓后方高能吧
  • 咱们尽可能简略的叙述这个过程
复合函数的偏导 链式法则
  • 为啥要这么简单?
  • 因为在w ,b 不确定的时候,咱们先通过正向流传得出一个值,这个值和实在的值的差,造成一个损失函数Loss()
  • 咱们要做的就是 调整 这些 w 和 b 值,求Loss函数的最小值
  • 咱们通过几百上千次的微调 这些w 和 b 来让残差(理论和预测的差)变小
  • 而这些微调,咱们用数学语言就是求L函数的w偏导 b偏导
  • 整个过程如下:

  • “正向流传”求损失,“反向流传”传误差。同时,神经网络每层的每个神经元都能够依据误差信号修改每层的w 和 b的值
  • 上图的 就是梯度,别搞错是一个向量哦????
  • 了解这个过程,写出代码并不难????
  • 而这个一直微调的过程咱们叫梯度降落法:

  • 上图对J函数求,0 和 1的偏导,每次对多个参数调一点点,摸索着,找到复合函数的最小值 ????
  • 最初,依据这些梯度值,更新w,b参数调整后的新值,进入下一轮正向流传
  • 多层神经网络好比丹炉,而咱们失去的参数好比金丹。于是AI工程师,又被成为炼丹师

干货,上代码

  • 首先是辅助函数
 function activeFunc (x, type = 'relu') {  let lamb = 1.0507009873554804934193349852946  let alpha = 1.6732632423543772848170429916717  let temp = 0  switch (type) {    case 'sign':      return x >= 0 ? 1 : -1    case 'sigmoid':      return 1 / (1 + Math.exp(-x))    case 'tanh':      return (Math.exp(x) - Math.exp(-x)) / (Math.exp(x) + Math.exp(-x))    case 'relu':      return x > 0 ? lamb * x : lamb * alpha * (Math.exp(x) - 1)    case 'diffsigmoid':      x = 1 / (1 + Math.exp(-x))      return x * (1 - x)    case 'difftanh':      x = Math.tanh(x)      return 1 - x ** 2    case 'diffrelu':      return x > 0 ? 1 : alpha * Math.exp(x)  }} function mse (realArr = [], predArr = []) {  return (    realArr.reduce((a, b, idx) => a + (b - predArr[idx] || 0) ** 2, 0) /    realArr.length  )}//穿插熵损失函数备选 function crossEntropy (a, y) {  return -y * Math.log(a) - (1 - y) * Math.log(1 - a)} 
  • 3个罕用激活函数及其导数函数,做了一个6合1的函数,目标为了处处可微,并是输入非线性化。你也能够离开写
  • 这点很重要否则,线性组合还是线性的,模型无奈进行简单拟合
  • mse平方差求和,目标为了计算残差,更好的察看模型逐渐收敛的过程
  • 感知机类
 class Perceptron {  constructor (inputObj = {}) {    this.config = inputObj  }  set config (o) {    this.type = o.type || 'hidden' // input hidden output 神经元的类型 cov pool    this.inputDimNum = o.inputDimNum || 2 //输出维度    this.outputDimNum = o.outputDimNum || 2 //输入维度    this.inputArr = []    this.beforeOutput = 0    this.output = 0    this.activeFunc = o.activeFunc || 'sigmoid'    this.wb =      o.wb ||      $.math.genRange(1, this.inputDimNum, 0).map( x =>        randMinMax(          -Math.sqrt(1 / this.inputDimNum), //传统的初始化种子          Math.sqrt(1 / this.inputDimNum)        )      ) //w 向量 0.5    if (!o.wb) this.wb.push(1) //b 设置成 1    this.wbT = $.math.mat.transpose([this.wb]) // w+b转置矩阵    this.derivative = 0 //导数值  }  setWb (a) {    this.wb = a    this.wbT = $.math.mat.transpose([this.wb]) // w+b转置矩阵  }  predict (inputArr) {    if (inputArr.length < this.inputDimNum) throw new Error('输出维度太少')    this.inputArr = inputArr    this.beforeOutput = $.math.mat.mul([[...inputArr, 1]], this.wbT)[0][0]    this.output = activeFunc(this.beforeOutput, this.activeFunc)    return this.output  }} 
  • 感知机类,次要实现 向前流传,就是属性多,其余很简略????
  • 这里应用 predict 来实现 向前流传
  • 向后流传,须要乘以误差矩阵,与偏导的乘积,作为上一层的误差矩阵,从尾部想神经网的头部传输
 function backPropagation (realArr) {    if (typeof realArr === 'number') {      realArr = [realArr]    }    //第L行    let L = networkArr.length - 1    let deltaArr = []    // 计算偏导    for (let i = L; i > 0; i--) {      deltaArr[i] = []      if (i === L) {        for (let d = 0; d < networkArr[i]; d++) {          deltaArr[i].push(            (networkObj[i][d].output - realArr[d]) *              activeFunc(networkObj[i][d].beforeOutput, 'diffsigmoid') //最初一层的误差          )        }      } else {        let weightArr = []        for (let k = 0; k < networkArr[i + 1]; k++) {          weightArr.push(networkObj[i + 1][k].wb)        }        let weightArrT = $.math.mat.transpose(weightArr)        for (let d = 0; d < weightArrT.length - 1; d++) {          deltaArr[i][d] = 0          weightArrT[d].map((x, idx) => {            deltaArr[i][d] +=              deltaArr[i + 1][idx] *              x *              activeFunc(networkObj[i][d].beforeOutput, 'diffsigmoid') // 矩阵点乘          })        }      }    }    //更新每一层的每个wb值    for (let i = L; i > 0; i--) {      for (let k = 0; k < networkArr[i]; k++) {        let wbArr = networkObj[i][k].inputArr.map((x, idx) => {          return x * deltaArr[i][k]        })        wbArr.push(deltaArr[i][k])        networkObj[i][k].wb.map((x, idx) => {          wbArr[idx] = x - learnRate * wbArr[idx]        })        networkObj[i][k].setWb(wbArr)      }    }  } 
  • 学习Train的过程,非常简单暴力
 let epoch = 1e5 //世代 你也能够认为是迭代次数 for (i = 0; i < epoch; i++) {    let mseArr = []    for (let k = 0; k < data.length; k++) { //数据行数      forwardPropagation(data[k]) // 每个数据的维度      backPropagation(dataTag[k]) // 每个数据的分类标记    }  } 
  • 其次你须要一个createNetwork函数循环产生多个,感知机实例。代码太简略就列举了
  • 对,这就完了,你的丹炉曾经搞定,能够开始炼丹了。????
  • 以上矩阵基本操作,封装在meeko的代数外围库中。

实例执行

XOR分类
  • xor无奈通过一般回归进行宰割,咱们看看本人的丹炉成果
  • 2,2,2 1个隐层网络

  • 在14485次迭代后,误差忽然减小,这也是大部分神经网络学习的,特色,要急躁炼丹
  • 最初输入的 就是金丹了,间接,拿这些w 和 b 的参数,就能够间接断言XOR操作了
  • 有了丹药,输入是霎时的。这也是大部分同学在网上下载他人训练好的模型的用法????
鸢尾花分类
  • 咱们应用之前的150个 鸢尾花数据集,对花的种类进行分类
  • 咱们把3类花,进行one-hot编码 [1,0,0] [0,1,0] [0,0,1] 代表3类,输出4维
  • 在之前的《特色工程文章中》,咱们应用PCA将鸢尾花的维度从4维升高到了2维
  • 本次,咱们先不降维,间接暴力应用神经网络分类????
  • 咱们创立一个 10个神经元的暗藏层
let networkArr = [4, 10, 3]networkObj = createNetwork(networkArr)// 循环产生多层多个感知机
  • 139秒后,咱们在87170次迭代后,失去了满足阀值eps的丹药

  • 咱们调整参数创立新的神经网络,2个暗藏层, 1个有10个神经元的,1个有3个神经元
let networkArr = [4, 10, 3, 3] // 这个构造是看到matlab 鸢尾花例子用了这个参数networkObj = createNetwork(networkArr) 
  • 因为随机参数造成的差别,广泛来说 4,10,3,3构造会比 4,10,3 好一些,训练工夫随网络变大而迅速减少。
  • 但同样的epoch,多层能够用更多的 参数可调,当然对于不同的数据集 状况有所不同。

  • 几次运行下来,会发现常常有1个点 [ 6, 2.7, 5.1, 1.6 ]  没有被正确辨认

  • 咱们作图看一下

  • 这个点是在2个花种类之间,人工筛选也会呈现谬误。太高的网络层数 一样会造成过拟合,这个丹药(参数),最终是十分称心的。
  • 用784,100,10 这样的网络模型,能够训练手写 MINST数据集 到97%+ 的精准率,这是十分好玩的事件。
  • 至此自建的多层神经网络,还是有肯定的精准性的????

浏览器中运行也是妥妥的????

进一步学习

  • 支流神经网络退出了更多不同性能的节点,但万变不离其宗
  • 比方罕用的卷积神经网,就退出了,卷积层和池化层????
  • 参数的优化,次要有动静学习率,参数初始化办法,归一,动量,扭转降落形式等
  • 对于神经网络咱们曾经现龙在田了
  • 其余类型的神经网络

总结

  • 理解通用人工神经网络的正、反向流传
  • 理解多层神经网络,神经网络就是丹炉,取得的参数就是金丹
  • 通过JS实现之,将算法用到理论业务场景,自动化所有,缩小人工
  • 逐渐把握多层神经分类的技巧
  • 理解其余支流神经网络