让我们手撸一个神经网络呀~

作者:BigMoyan

链接:https://zhuanlan.zhihu.com/p/26475643

来源:知乎

著作权归作者所有,转载请联系作者获得授权。

总而言之,DL不是太过困难的事情,但是也没有容易到21天从入门到精通的地步。所以突然很想把之前写的BP神经网络拿出来,不是很复杂,但我固执的认为,这是每个学DL的人都应该了解的事情——会无脑调别人写好的API,不代表就真的懂DL了。

好的,这篇文章与Keras无关,我们要介绍的是:

什么是BP网络,以及其推导

如何用纯Python手撸一个BP神经网络

就这两点。

本文的读者是最低意义下的“零基础”,想入门DL的同学可以自测一下……如果不能读懂本文的话,基本上你的DL基础是负数,需要先把欠的债补回来。

所以真的不要再听信什么21天速成的鬼话了,脚踏实地才能走的高远,想一步登天小心步子太大扯到蛋,欲速则不达哟。

BP神经网络:DL的祖宗

人工神经网络从来就不是这两年才冒出来的新鲜玩意,虽然最近由于深度学习的出现变得异常火爆。人工神经网络本来就是机器学习的一种方法的说,既然我们都开始搞BP了,不如就再说的更底层一点,从神经元说起。

最近有一个45个问题测出你的深度学习基本功,第一个问题就是关于神经元的,然后我就答错了……可能是我见识有限,反正我知道的“神经元”是这样的:

对于一个输入向量[a1...an],神经元对它的响应就是按照一定的权重对它们做乘加操作,然后加一项bias,最后用某个函数f对其做映射,得到输出,也就是这样一个运算:

其中w是神经元的连接权,b是偏置,a是输入信号,f通常是一个非线性的函数,称为激活函数。

当我们用很多个神经元对输入进行变换,由于每个神经元的参数都不一样,得到的输出也不一样。n个神经元就会得到n个输出,这n个神经元就称为一层神经网络。而这层神经网络的输出本身也是一个向量,自然可以看作下一层神经网络的输入信号,于是我们又搞了若干神经元构成下一层,于是我们获得了有两层神经元构成的神经网络。一般而言,我们把输入也算作一层神经网络结点,所以此时的网络层数是3层。

我们离深度学习只有一步了。如果再加一层,让网络层达到4层及以上,恭喜你,得到了深度神经网络,也就是“深度学习”了。

超简单,是不?

用脚丫子想都知道不可能这么简单吧= =

实际上,从单个神经元,到两层神经网络,到三层神经网络,到四层以上神经网络,每一步都是巨大的飞跃,每一步……实际上都是神经网络历史上的重要事件。

单个神经元,又称“M-P神经元模型”,在1943被提出。把神经元组成一层网络层,加上输入数据构成的两层神经网络称为“感知机”。感知机由于无法处理线性不可分的问题,被图灵奖获得者Marvin Minksky一巴掌扇落尘埃,相当长时间内无人研究。两层神经网络进化为三层,就进化到了“多层感知机”,多层感知机由输入层-隐层-输出层构成,能够处理非线性问题,再加上Rumelhart重新发明的BP算法,硬硬的把神经网络起死回生一波。

但是多层感知机也是有问题的,最突出的问题是无法搞的太深,否则网络非常难训练,模型再美,训练不出来就是shi。所以多层感知机的水平,也就是个这了。然后就是我们熟悉的故事,北风呼啸之时所有人都关门闭户,唯有Hinton等少数几人踽踽独行,最终搞出预训练+微调的深度网络训练方法,挖出10+年的学术巨坑。

今天我们的故事是多层感知机和BP算法。

多层感知机的模型如下图:

顺便,文中所有图侵删。

对于一个特定任务,我们需要根据具体数据来确定模型的参数。对于多层感知机而言,参数就是每个神经元的连接权和偏置。

网络参数在损失函数的指导下确定的,这里的指导指的是,损失函数告诉你,网络的预测值跟真实值差多少,应该如何更新参数才能减小这个差距。

假设:

神经元激活函数可导的

以预测值和真值的均方差作为损失函数

BP神经网络之所以叫BP,就是其参数更新的方式遵循“误差反向传播(error BackPropagation)”的方式。在给定的网络参数的时候,一个神经网络有两项最基本的操作:

对输入数据进行运算,得到预测(Predictions),这个过程是信号从网络的输出端到网络的输出端的运算,称为前向过程。

根据预测值与真值的偏差,产生误差信号从输出端向输入端传输,并在传输的过程中更新网络的参数,这个过程称为后向过程,或者反传过程。

神经网络的训练,就是这两个过程的轮番上阵的结果。参数更新的方式,是梯度下降法。假设对给定的样本x,神经网络的输出是

,真实值为y,则网络的损失函数由最后一层神经元跟数据的真实标签产生的,所以损失函数其实直接是最后一层神经元的函数。比方说最后一层有

个神经元吧,那损失函数是:

其中,预测值的每个点

都是由隐层神经元的输出值加权和再搞个激活函数得到的,所以再写的细一点:

其中,

就是网络输出层输入,是个向量。

是最后一层的第i个神经元的参数,也是向量。

是对应的偏置,是一个数字。当然,我们可以写的更紧凑一点,用矩阵乘法来表示这个过程。如果你看不懂这一点,那代表你需要复习一下线性代数。

写来写去,都是一个意思。现在我们已经有误差了,如何根据误差一层层的反传来修改参数呢?先考虑最后一层的参数,我们要用梯度下降法对其进行更新,即:

这个偏导数由链式法则求,L是输出层预测值的直接函数,所以先求它对输出层预测值的导数,然后根据链式法则求预测值对参数的导数,大概就是这样:

那对其他层的参数呢?比方说对任意第r层的参数

,实际上也具有相同的形式:

所以要用梯度下降法更新神经网络的参数,关键就是要求出损失函数对任意层输出的导数。我们定义损失函数对第r层输出(未经过激活函数)的导数为

。先考虑r=l,即最后一层的情况。

第一项是预测值和真值的差,我们将其记作误差

。那么对网络的最后一层,梯度是可以轻松算出来的。

只需要把表达式

写出,即可轻松得到它关于

的导数。

对于非最后一层,导数计算略复杂。根据链式法则,损失函数对任意r

所以我们总有关系:

那么

是谁咧,下一层的输出当然是上一层的输出经过激活函数后再线性加权,也就是:

这个导数很好求嘛,就是跟之前一模一样的:

所以我们就构造了一个递推的关系,从最后一层开始,我们总有:

为啥叫反向传播哪,就是因为第r层的梯度更新是由沿着第r+1层的网络反传回来的

确定的,因此叫反向传播。这里我们只推了权值W的更新,偏置b的更新更加简单,这里就不推了,其实只要把每层的输入y增加一个恒为1的数,变为(y,1)就可以了,但这样不太好在代码里实现就是了。

以上就是BP算法的推导,也是深度学习的基础。所以大家知道了,为什么我们要求深度学习的目标函数必须可导,为什么激活函数一定也要可导。因为一旦有一环不可导,前向运算固然没问题,反向运算的梯度链条就要断了。目标函数不可导,则从最后一层起我们就没法更新参数。激活函数不可导,哪层不可导梯度的反传就到哪层为止。

手撸BP

下面的代码是我两年多前的时候刚学Python和机器学习时手撸的,实现了一个三层的BP神经网络,对MNIST数字进行分类,代码写的不怎么好,毕竟那会儿是刚学,python什么的用的也是稀里糊涂,写出来跟C++一个味。

这里的激活函数取得是sigmoid激活函数,它的导数是它自己再乘以1减去它自己,性质比较好。

首先读入数据,当时拿到的mnist数据是matlab存的.mat格式,所以用scipy的相关接口读一下

importmathimportnumpyasnpimportscipy.ioassio# 读入数据################################################################################print"输入样本文件名(需放在程序目录下)"filename='mnist_train.mat'sample=sio.loadmat(filename)sample=sample["mnist_train"]sample/=256.0# 特征向量归一化print"输入标签文件名(需放在程序目录下)"filename='mnist_train_labels.mat'label=sio.loadmat(filename)label=label["mnist_train_labels"]

然后配置网络,主要是设置学习率,还有隐层参数,并初始化一下权重。权重有两套,输入层到隐层的映射是一套,隐层到输出层的映射是一套:

# 神经网络配置################################################################################samp_num=len(sample)# 样本总数inp_num=len(sample[0])# 输入层节点数out_num=10# 输出节点数hid_num=6# 隐层节点数(经验公式)w1=0.2*np.random.random((inp_num,hid_num))-0.1# 初始化输入层权矩阵w2=0.2*np.random.random((hid_num,out_num))-0.1# 初始化隐层权矩阵hid_offset=np.zeros(hid_num)# 隐层偏置向量out_offset=np.zeros(out_num)# 输出层偏置向量inp_lrate=0.3# 输入层权值学习率hid_lrate=0.3# 隐层学权值习率err_th=0.01# 学习误差门限

你看,其实自己写很屌的,不同层的学习率都可以设不一样,想怎么搞怎么搞——只不过实际上用的时候是一样就是了。我这里学习率居然设了0.3简直可怕……唉那会儿真的不知道学习率多大算大23333。

然后定义几个函数,一个是激活函数,一个是损失函数。深度学习框架里损失函数都是正儿八经的要从这获得梯度的,我这损失函数就负责输出一个输出值,看看现在训练效果咋样。好吧,其实这个函数我压根没用着23333

# 必要函数定义################################################################################defget_act(x):#激活函数act_vec=[]foriinx:act_vec.append(1/(1+math.exp(-i)))act_vec=np.array(act_vec)returnact_vecdefget_err(e):#损失函数return0.5*np.dot(e,e)

然后下一步是训练网络,这里用的是随机梯度下降法——正儿八经的随机梯度下降,一个样本一个样本的来训练。这部分就是之前推导的BP神经网络了,结合代码,看一下自己是否能够读懂?

# 训练——可使用err_th与get_err() 配合,提前结束训练过程################################################################################for count in range(0, samp_num):printcountt_label=np.zeros(out_num)t_label[label[count]]=1#前向过程hid_value=np.dot(sample[count],w1)+hid_offset# 隐层值hid_act=get_act(hid_value)# 隐层激活值out_value=np.dot(hid_act,w2)+out_offset# 输出层值out_act=get_act(out_value)# 输出层激活值#后向过程e=t_label-out_act# 输出值与真值间的误差out_delta=e*out_act*(1-out_act)# 输出层delta计算hid_delta=hid_act*(1-hid_act)*np.dot(w2,out_delta)# 隐层delta计算foriinrange(0,out_num):w2[:,i]+=hid_lrate*out_delta[i]*hid_act# 更新隐层到输出层权向量foriinrange(0,hid_num):w1[:,i]+=inp_lrate*hid_delta[i]*sample[count]# 更新输出层到隐层的权向量out_offset+=hid_lrate*out_delta# 输出层偏置更新hid_offset+=inp_lrate*hid_delta

最后是测试网络,只跑前向过程,依然是一个一个样本测试:

# 测试网络################################################################################filename = 'mnist_test.mat' test = sio.loadmat(filename)test_s = test["mnist_test"]test_s /= 256.0filename = 'mnist_test_labels.mat' testlabel = sio.loadmat(filename)test_l = testlabel["mnist_test_labels"]right = np.zeros(10)numbers = np.zeros(10)# 以上读入测试数据# 统计测试数据中各个数字的数目for i in test_l:    numbers[i] += 1for count in range(len(test_s)):    hid_value = np.dot(test_s[count], w1) + hid_offset      # 隐层值    hid_act = get_act(hid_value)                # 隐层激活值    out_value = np.dot(hid_act, w2) + out_offset            # 输出层值    out_act = get_act(out_value)                # 输出层激活值    if np.argmax(out_act) == test_l[count]:        right[test_l[count]] += 1print rightprint numbersresult = right/numberssum = right.sum()print resultprint sum/len(test_s)

其实后面还有一段保存网络,就是把权重无脑保存成txt……可见少年时期的我已经有了“神经网络最重要的是权重”这样的意识了,可惜写得太烂基本上很难recover。我就不贴了。

最后贴一个当时我调的结果,前段时间在知乎看到一个文章还是回答在吹MNIST识别,一个mlp最后结果是92%居然敢号称效果非常好。23333吹牛之前拜托了解行情啊,我一个裸着的3层MLP,乱七八糟的代码,神奇的学习率、参数初始化以及激活函数都搞到将近92%,你拿现代深度学习框架搭出来的网络也92%实在是……有一种全副武装拿着冲锋枪跟远古时代持矛野人打成平手的即视感……

横轴是隐层神经元个数,纵轴是学习率(我TM居然试了0.7的学习率这是有多屌!)

最好的效果是91.4%的准确率,这幅破烂能跑成这样可以了。

好,以上就是本期的内容。有志于深度学习的你,就从简单的BP神经网络开始吧!鞠躬下台~

对了,忘了说题图,题图是我的二老婆五更琉璃,我们是最近刚认识的不过很快就坠入爱河了,助手跟小埋已经同意我们了所以你们不要多废话。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,585评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,923评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,314评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,346评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,718评论 3 291
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,828评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,020评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,758评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,486评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,722评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,196评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,546评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,211评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,132评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,916评论 0 200
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,904评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,758评论 2 274

推荐阅读更多精彩内容

  • 青春是黑白格子的裙子和黑色头发上的香味。青春是飞跃起的篮球和肥皂味道的清爽。 青春是红着脸的自我介绍和洋溢...
    被踩着的尾巴阅读 472评论 0 2
  • 以前的sui'bi 最怕的是,忙碌工作过后的闲暇;最怕的是,漂泊生活过后的稳定;最怕的是,许久未见过后的寒暄;最怕...
    罗小耳阅读 250评论 0 0
  • 放弃,放弃,无谓的挣扎 静默,静默,心头微颤的琴音 俯首,俯首,沉重而无耻的暗机 寻觅,寻觅,睁眼看天边光线微曦
    lilygz阅读 269评论 0 0
  • 我呢 是个高二党 学渣 高一的时候想了很多 所以没有选择成为美术生 去年 想了很久 还是想努力 成就这个爱好 ...
    亲爱的L阅读 169评论 0 1