numpy 實現(xiàn)反向傳播學習筆記

本博客內(nèi)容來源于網(wǎng)絡以及其他書籍蹭沛,結合自己學習的心得進行重編輯章鲤,因為看了很多文章不便一一標注引用,如圖片文字等侵權帚呼,請告知刪除皱蹦。

傳統(tǒng)2D計算機視覺學習筆記目錄------->傳送門
傳統(tǒng)3D計算機視覺學習筆記目錄------->傳送門
深度學習學習筆記目錄 ------------------->傳送門

本文簡介

本文的主要目的就是描述出怎么使用numpy實現(xiàn)一個簡單的神經(jīng)網(wǎng)絡,通過反向傳播完成訓練的過程沈自,正如題目一樣辜妓。當然我們不會像成熟的深度學習框架一樣內(nèi)部實現(xiàn)自動求導籍滴,那就太麻煩了。通過自己手寫這么一份代碼捶索,可以讓自己加深深度神經(jīng)網(wǎng)絡到底是怎么運作的灰瞻,以達到我們的目的辅甥,而不再是完完全全的黑箱了燎竖。

目前網(wǎng)上有很多相關的文章构回,我自己也通過那些文章得到很多的認識再最初學習的時候,但是總是感覺有一些不足脐供,比如為了追求代碼簡潔借跪,而失去了結構性,而我們使用的pytorch或者tensorflow有很好的面型對象的結構歇由。所以本文實現(xiàn)的代碼更注重結構性果港,和可拓展性,可以在此基礎上在實現(xiàn)其他的一些簡單的層谢谦。那么開始吧


圖文無關

分步實現(xiàn)思路

首先我們知道神經(jīng)網(wǎng)絡是有一些layer(層)組成的的回挽,我們目前主要關注隱藏層欠气,因為神經(jīng)網(wǎng)絡的主要計算是在隱藏層。這些層分別可以進行前向推導队塘,反向傳播宜鸯,參數(shù)更新,所以我們先寫這些層的基類鸿市,為方便調(diào)試,我們在初始化類時陌凳,要給層一個名字内舟。

class BaseLayer:
    def __init__(self,name):
        self.name = name
    def forward(self, input):            #前向推導
        pass
    def backward(self,grad):             #反向傳播
        pass
    def update(self):                    #參數(shù)更新
        pass

接著我們要實現(xiàn)全連接層验游,激活函數(shù),以及損失函數(shù)崔梗。激活函數(shù)我們實現(xiàn)簡單的sigmoid激活函數(shù)垒在,損失函數(shù)我們實現(xiàn)帶有softmax的CrossEntropyLoss。有關簡單的激活函數(shù)和損失函數(shù)我會在其他文章詳細描述权悟。

我們先實現(xiàn)sigmoid激活函數(shù)推盛,由于sigmoid 中我們不需要更新任何的參數(shù)耘成,所以不用重載參數(shù)更新函數(shù)驹闰。

class SigmoidLayer(BaseLayer):
    def __init__(self, name):
        super(SigmoidLayer,self).__init__(name)
    def forward(self,input):
        self.output = 1/(1+np.exp(-input))
        return self.output
    def backward(self,grad):
        grad = grad * self.output*(1-self.output)
        return grad

然后我們實現(xiàn)全連接層,在此我們將學習率簡化為1师妙,初始參數(shù)設置為正太分布隨機參數(shù)屹培,優(yōu)化器也是最簡單的批量梯度下降(BGD)

class LinearLayer(BaseLayer):
    def __init__(self,name,input_channels,output_channels):
        super(LinearLayer,self).__init__(name)
        self.weight = np.random.randn( input_channels,output_channels )
        self.bias = np.random.randn(1,output_channels)
    def forward(self,input):
        self.input = input
        self.output = np.dot(self.input,self.weight)+ self.bias          # y = wx +b
        return self.output
    def backward(self,grad):
        self.batch_size = grad.shape[0]
        self.grad_w = np.dot(self.input.T,grad )/self.batch_size     # δw = δg * x
        self.grad_b = np.sum( grad , axis=0,keepdims= True )/self.batch_size
        grad = np.dot(grad,self.weight.T)
        return grad
    def update(self):
        self.weight -= self.grad_w
        self.bias -= self.grad_b

然后我們來實現(xiàn)損失函數(shù)褪秀,以及softmax,我們可以將softmax的反向傳播與CrossEntropy反向傳播一起執(zhí)行仑氛,可以簡化整個過程。

class SoftMaxLayer(BaseLayer):
    def __init__(self, name):
        super(SoftMaxLayer,self).__init__(name)
    def forward(self,input):
        vec_max = np.max( input,axis=1 )[np.newaxis,:].T
        input -= vec_max
        exp = np.exp(input)
        output = exp / (np.sum(exp,axis=1)[np.newaxis,:].T)
        return output

class SMCrossEntropyLossLayer(BaseLayer):
    def __init__(self, name):
        super(SMCrossEntropyLossLayer,self).__init__(name)
    def forward(self,pred,real):
        self.softmax_p = SoftMaxLayer("softmax").forward(pred)
        self.real = real
        loss = 0
        for i in range(self.real.shape[0]):
            loss += -np.log( self.softmax_p[i,real[i]] )
        loss /= self.real.shape[0]
        return loss
    def backward(self):
        for i in range(self.real.shape[0]):
            self.softmax_p[i,self.real[i]] -= 1
        self.softmax_p = self.softmax_p / self.real.shape[0]
        return self.softmax_p

現(xiàn)在我們將神經(jīng)網(wǎng)絡的基本的幾個層實現(xiàn)完了,現(xiàn)在我們要將這些隱層組建成一個網(wǎng)絡出吹。我們實現(xiàn)一個基本的網(wǎng)絡框架趋箩,然后再通過新的子類繼承基類,只需要該變隱層結構就可以了跳芳。由于準備訓練一個mnist手寫數(shù)字數(shù)據(jù)竹勉,所以第一層的輸入的維度是784。

class NetBase:
    def __init__(self):
        self.layers = []
        
    def forward(self,input):
        for layer in self.layers:
            input = layer.forward(input)
        pred = SoftMaxLayer("softmax").forward(input)
        return input,pred
    def backward(self,grad):
        for layer in  reversed(self.layers):
            grad = layer.backward(grad)
            layer.update()

class SimpleNet(NetBase):
    def __init__(self):
        super(SimpleNet,self).__init__()
        self.layers = [
            LinearLayer(name="full1",input_channels= 784, output_channels= 512),
            SigmoidLayer(name="relu1"),
            LinearLayer(name="full2",input_channels=512,output_channels=128),
            SigmoidLayer(name="sigmoid2"),
            LinearLayer(name="full3",input_channels=128,output_channels=10)
        ]

整體代碼

現(xiàn)在我們將網(wǎng)絡結構的代碼以及訓練代碼放到一起吓歇。

#BaseNet.py
import numpy as np
class BaseLayer:
    def __init__(self,name):
        self.name = name
    def forward(self, input):
        pass
    def backward(self,grad):
        pass
    def update(self):
        pass

class SigmoidLayer(BaseLayer):
    def __init__(self, name):
        super(SigmoidLayer,self).__init__(name)
    def forward(self,input):
        self.output = 1/(1+np.exp(-input))
        return self.output
    def backward(self,grad):
        grad = grad * self.output*(1-self.output)
        return grad

class LinearLayer(BaseLayer):
    def __init__(self,name,input_channels,output_channels):
        super(LinearLayer,self).__init__(name)
        self.weight = np.random.randn( input_channels,output_channels )
        self.bias = np.random.randn(1,output_channels)
    def forward(self,input):
        self.input = input
        self.output = np.dot(self.input,self.weight)+ self.bias
        return self.output
    def backward(self,grad):
        self.batch_size = grad.shape[0]
        self.grad_w = np.dot(self.input.T,grad )/self.batch_size 
        self.grad_b = np.sum( grad , axis=0,keepdims= True )/self.batch_size
        grad = np.dot(grad,self.weight.T)
        return grad
    def update(self):
        self.weight -= self.grad_w
        self.bias -= self.grad_b

class SoftMaxLayer(BaseLayer):
    def __init__(self, name):
        super(SoftMaxLayer,self).__init__(name)
    def forward(self,input):
        vec_max = np.max( input,axis=1 )[np.newaxis,:].T
        input -= vec_max
        exp = np.exp(input)
        output = exp / (np.sum(exp,axis=1)[np.newaxis,:].T)
        return output

class SMCrossEntropyLossLayer(BaseLayer):
    def __init__(self, name):
        super(SMCrossEntropyLossLayer,self).__init__(name)
    def forward(self,pred,real):
        self.softmax_p = SoftMaxLayer("softmax").forward(pred)
        self.real = real
        loss = 0
        for i in range(self.real.shape[0]):
            loss += -np.log( self.softmax_p[i,real[i]] )
        loss /= self.real.shape[0]
        return loss
    def backward(self):
        for i in range(self.real.shape[0]):
            self.softmax_p[i,self.real[i]] -= 1
        self.softmax_p = self.softmax_p / self.real.shape[0]
        return self.softmax_p

class NetBase:
    def __init__(self):
        self.layers = []
        
    def forward(self,input):
        for layer in self.layers:
            input = layer.forward(input)
        pred = SoftMaxLayer("softmax").forward(input)
        return input,pred
    def backward(self,grad):
        for layer in  reversed(self.layers):
            grad = layer.backward(grad)
            layer.update()

class SimpleNet(NetBase):
    def __init__(self):
        super(SimpleNet,self).__init__()
        self.layers = [
            LinearLayer(name="full1",input_channels= 784, output_channels= 512),
            SigmoidLayer(name="relu1"),
            LinearLayer(name="full2",input_channels=512,output_channels=128),
            SigmoidLayer(name="sigmoid2"),
            LinearLayer(name="full3",input_channels=128,output_channels=10)
        ]

訓練部分代碼,由于numpy沒有使用gpu來進行訓練杏慰,訓練整體還是比較慢的,所以我們只訓練了 前100個數(shù)據(jù)缘滥,通過觀察loss 就可以驗證我們的網(wǎng)絡是否進行工作。

#train.py
import BaseNet
import numpy as np
import matplotlib.pyplot as plt
import os

training_set_inputs  = []
training_set_outputs   = []

def read_mnist(mnist_image_file, mnist_label_file):
    if 'train' in os.path.basename(mnist_image_file):
        num_file = 60000
    else:
        num_file = 10000
    with open(mnist_image_file, 'rb') as f1:
        image_file = f1.read()
    with open(mnist_label_file, 'rb') as f2:
        label_file = f2.read()
    image_file = image_file[16:]
    label_file = label_file[8:]
    for i in range(num_file):
        label = int(label_file[i])
        image_list = [int(item) for item in image_file[i*784:i*784+784]]
        image_np = np.array(image_list, dtype=np.uint8).reshape(28*28)
        training_set_outputs.append([label])
        training_set_inputs.append( image_np )

train_image_file = '/home/eric/data/mnist/train-images-idx3-ubyte'
train_label_file = '/home/eric/data/mnist/train-labels-idx1-ubyte'
read_mnist(train_image_file, train_label_file)
training_set_inputs = np.array( training_set_inputs )
training_set_outputs = np.array( training_set_outputs )

training_set_inputs = training_set_inputs[:100,:]
training_set_outputs = training_set_outputs[:100,:]

net  = BaseNet.SimpleNet()
loss = BaseNet.SMCrossEntropyLossLayer("loss")

x = []
y=[]
for i in range(10000):
    input = training_set_inputs
    output,pred = net.forward(input)
    loss_value = np.squeeze(loss.forward(output,training_set_outputs))
    print(i,loss_value,np.sum( (np.equal(pred.argmax(axis = 1),training_set_outputs.T)))/ training_set_outputs.shape[0] )
    x.append(i)
    y.append(loss_value)

    delta = loss.backward()
    net.backward(delta)

plt.plot(x,y,'r--')
plt.title('loss')
plt.show()

總結

寫完這篇文章甫恩,才發(fā)現(xiàn)代碼太多,沒有太多的文字敘述,感覺要是一點點解釋肖抱,怕是累死我异旧,估計沒有人像我這么笨吧。自己認為學習的過程還是需要自己用手就敲一遍荤崇,觀察一下每個狀態(tài)的輸出,才能更好的理解术荤。雖然代碼很多但是其實也可以壓縮成十幾行瓣戚,但是對初學者就太不友好了。


重要的事情說三遍:

如果我的文章對您有所幫助舱权,那就點贊加個關注唄 ( * ^ __ ^ * )

如果我的文章對您有所幫助仑嗅,那就點贊加個關注唄 ( * ^ __ ^ * )

如果我的文章對您有所幫助,那就點贊加個關注唄 ( * ^ __ ^ * )

傳統(tǒng)2D計算機視覺學習筆記目錄------->傳送門
傳統(tǒng)3D計算機視覺學習筆記目錄------->傳送門
深度學習學習筆記目錄 ------------------->傳送門

任何人或團體鸵贬、機構全部轉載或者部分轉載脖捻、摘錄,請保留本博客鏈接或標注來源颜价。博客地址:開飛機的喬巴

作者簡介:開飛機的喬巴(WeChat:zhangzheng-thu)诉濒,現(xiàn)主要從事機器人抓取視覺系統(tǒng)以及三維重建等3D視覺相關方面夕春,另外對slam以及深度學習技術也頗感興趣及志,歡迎加我微信或留言交流相關工作。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末率寡,一起剝皮案震驚了整個濱河市倚搬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捅僵,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件上荡,死亡現(xiàn)場離奇詭異酪捡,居然都是意外死亡纳账,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門金刁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來议薪,“玉大人斯议,你說我怎么就攤上這事『哂” “怎么了恋昼?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長挟炬。 經(jīng)常有香客問我嗦哆,道長,這世上最難降的妖魔是什么粥喜? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任额湘,我火速辦了婚禮,結果婚禮上缩挑,老公的妹妹穿的比我還像新娘供置。我一直安慰自己,他們只是感情好芥丧,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布续担。 她就那樣靜靜地躺著,像睡著了一般乖仇。 火紅的嫁衣襯著肌膚如雪询兴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天警儒,我揣著相機與錄音蜀铲,去河邊找鬼属百。 笑死,一個胖子當著我的面吹牛隆夯,可吹牛的內(nèi)容都是我干的别伏。 我是一名探鬼主播忧额,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼睦番,長吁一口氣:“原來是場噩夢啊……” “哼耍属!你這毒婦竟也來了巩检?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤领舰,失蹤者是張志新(化名)和其女友劉穎迟螺,沒想到半個月后矩父,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡民轴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年杉武,在試婚紗的時候發(fā)現(xiàn)自己被綠了辙售。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡祈搜,死狀恐怖士八,靈堂內(nèi)的尸體忽然破棺而出婚度,到底是詐尸還是另有隱情,我是刑警寧澤蝗茁,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布哮翘,位于F島的核電站,受9級特大地震影響阻课,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抹恳,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一署驻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秽荞,春花似錦扬跋、人聲如沸倍奢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間翩概,已是汗流浹背牍鞠。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工胁后, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像膳算,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容