神經網路篇——從程式碼出發理解BP神經網路

十里潮汐發表於2021-01-01

       一提到反向傳播演算法,我們就不自覺的想到隨機梯度下降、sigmoid啟用函式和最讓人頭大的反向傳播法則的推導,即便是把反向傳播神經網路的原理學了一遍,也還是一頭霧水,在這裡推薦一本小編認為把BP神經網路講的最通透的教材《Python神經網路程式設計》。下面小編將以程式碼的方式帶著大家一步一步實現BP神經網路,並且還會再用框架的方式實現BP網路,用來和原始碼作比較,認識一下TensorFlow2.0的強大。

        我們首先要理清建立BP神經網路的目的,其次,確定BP神經網路的結構,簡單地以一個輸入層,一個隱藏層,一個輸出層為例,我們來思考一下寫程式碼的思路,然後一步一步實現。

在這裡,我們建立BP神經網路的目的是為了做預測,我們這裡用700條MG時間序列資料作為訓練資料,輸入維度是10,輸出為1,為了方便,我們設定一個隱藏層,這個隱藏層包含10個節點,所以我們的BP神經網路的結構就是[10,10,1]。接下來,開始思考一下流程:

1、讀取資料,將資料分為訓練集和測試集,注意這裡的資料預設都是np.array()的格式。

2、初始化BP網路,主要是確定網路結構,輸入層、隱藏層、輸出層個數,以及隨機初始化相互連線的權值

3、呼叫訓練方法,傳入資料進行訓練

    1. 前向傳播    

    2. 反向傳播

    3. 儲存模型進行測試

      裡面還有很多細節,我們邊寫邊完善,整個程式的框架已經很明確了。從第一步開始,讀取資料,第一步中需要注意的是,讀取的資料最好進行歸一化處理,一方面,這樣做有助於提高預測精度,另一方面,如果使用sigmoid作為啟用函式的話,它的輸出是在[0,1]之間的,如果資料不進行歸一化,不在[0,1]區間的數是沒有辦法預測的。這裡我們直接呼叫sklearn中的最大最小歸一化的方法就可以,為了方便讀者理解程式碼,我們將import緊挨著程式碼:

  #防止資料用科學計數法輸出  np.set_printoptions(suppress=True)  #讀取資料  way1 = 'Traindata11.csv'  data = np.array(pd.DataFrame(pd.read_csv(way1)))  # 歸一化所有資料  from sklearn.preprocessing import MinMaxScaler  format_minmax = MinMaxScaler()  data = format_minmax.fit_transform(data)  x = data[:,:-1]     #需要訓練的資料  y = data[:,-1]      #label,標籤  #初始化BP網路  #訓練網路

第二步,我們要初始化網路,確定網路的結構:

    def __init__(self, ni, nh, no):        """        構造神經網路        :param ni:輸入單元數量        :param nh:隱藏單元數量        :param no:輸出單元數量        """        self.ni = ni + 1  # +1 是為了偏置節點        self.nh = nh        self.no = no        # 啟用值(輸出值)        self.ai = [1.0] * self.ni   #這裡會生成一個1*ni維,數值全為1的列表        self.ah = [1.0] * self.nh        self.ao = [1.0] * self.no        # 權重矩陣        self.wi = np.random.randn(self.ni, self.nh)     #輸入層到隱藏層        self.wo = np.random.randn(self.nh, self.no)  # 隱藏層到輸出層        # 記錄權重矩陣的上次梯度        self.ci = np.zeros([self.ni, self.nh])        self.co = np.zeros([self.nh, self.no])

第三步開始訓練並儲存模型:

    def train(self,train,lable,max_iterations=1000, N=0.5, M=0.1):        """        訓練        :param train:訓練集        :param lable:標籤        :param max_iterations:最大迭代次數        :param N:本次學習率        :param M:上次學習率        """        for i in range(max_iterations): #迭代最大訓練次數(epoch)            for j in range(len(train)):  #訓練集                inputs = train[j]   #輸入向量                targets = lable[j]  #目標值                self.forword_propagation(inputs)    #前向傳播訓練                error = self.back_propagation(targets, N, M)    #反向傳播訓練,傳入lable            if i % 50 == 0:     #每50次輸出一次誤差(loss),loss函式為mse                print('Combined error', error)        winame = 'wi.npy'        woname = 'wo.npy'        np.save(winame, self.wi)        np.save(woname, self.wo)

接下來我們就要實現前向傳播和反向傳播的方法了,我們按照順序從前向傳播開始,前向傳播用到了sigmoid作為啟用函式,所以我們把sigmoid函式也一併列出:

    def sigmoid(self,x):        """        sigmoid 函式,1/(1+e^-x)        :param x:        :return:        """        return 1.0 / (1.0 + math.exp(-x))    def forword_propagation(self, inputs):        """        前向傳播進行分類        :param inputs:輸入        :return:類別        """        if len(inputs) != self.ni - 1:  #檢查輸入資料的維度是否和宣告的一致            print('輸入維度有誤,請重新檢查輸入')        for i in range(self.ni - 1):    #按順序將n維的input對映到n個輸入節點上            self.ai[i] = inputs[i]        for j in range(self.nh):        #遍歷隱藏層節點,每個隱藏層節點的值為n個輸入節點*對應權值(輸入層和隱藏層)的和            sum = 0.0            for i in range(self.ni):                sum += (self.ai[i] * self.wi[i][j])            self.ah[j] = self.sigmoid(sum)  #將節點值經過sigmoid函式啟用後,變為[0,1]之間的值        for k in range(self.no):    #遍歷輸出層節點,每個輸出層節點的值為n個隱藏層節點*對應權值(隱藏層和輸出層)的和            sum = 0.0            for j in range(self.nh):                sum += (self.ah[j] * self.wo[j][k])            self.ao[k] = self.sigmoid(sum)  ##將節點值經過sigmoid函式啟用後,變為[0,1]之間的值        return self.ao  #返回輸出層的值

接下來是反向傳播,反向傳播中需要對sigmoid進行求導,為了方便我們直接寫出sigmoid的求導結果,另外這裡的誤差函式(loss)選擇mse:

     def dsigmoid(self,y):        """        sigmoid 函式的導數        :param y:        :return:        """        return y * (1 - y)     def back_propagation(self, targets, N, M):        """        反向傳播演算法        :param targets: 例項的類別        :param N: 本次學習率        :param M: 上次學習率        :return: 最終的誤差平方和的一半        """        # 計算輸出層 deltas,求導數        # dE/dw[j][k] = (t[k] - ao[k]) * s'( SUM( w[j][k]*ah[j] ) ) * ah[j]        output_deltas = [0.0] * self.no     #初始化輸出層列表為0        for k in range(self.no):            error = targets[k] - self.ao[k]     #計算輸出層誤差            output_deltas[k] = error * self.dsigmoid(self.ao[k])    #計算每一個節點對誤差的影響大小,佔比越大,影響越大        # 根據delta更新隱藏層和輸出層之間的權值        for j in range(self.nh):            for k in range(self.no):                # output_deltas[k] * self.ah[j] 才是 dError/dweight[j][k]                change = output_deltas[k] * self.ah[j]  #根據比例計算需要調整的值                self.wo[j][k] += N * change + M * self.co[j][k]     #調整權值,調整方法有很多,例如隨機梯度下降,這裡只是簡單寫了一下                self.co[j][k] = change  #儲存調整的梯度        # 計算隱藏層 deltas        hidden_deltas = [0.0] * self.nh        for j in range(self.nh):            error = 0.0            for k in range(self.no):                error += output_deltas[k] * self.wo[j][k]            hidden_deltas[j] = error * self.dsigmoid(self.ah[j])        # 更新輸入層權值        for i in range(self.ni):            for j in range(self.nh):                change = hidden_deltas[j] * self.ai[i]                # print 'activation',self.ai[i],'synapse',i,j,'change',change                self.wi[i][j] += N * change + M * self.ci[i][j]                self.ci[i][j] = change        # 計算MSE        error = 0.0        for k in range(len(targets)):            error += (targets[k] - self.ao[k]) ** 2        error = error/len(y)        return error       

到這裡就差不多實現了一個最簡單的BP神經網路,不過小編強烈反對用這個程式碼來進行實戰,這個程式碼只適用於理解BP神經網路。在下一篇中,我將用Tensorflow框架和Pytorch框架來實現一個通用的BP神經網路,並且解答一下為什麼要使用啟用函式。

不知不覺,2021年到來了,馬上天就要亮了,這一篇是小編的第一篇公眾號,僅以此銘記2021.1.1日凌晨。祝各位讀者,元旦快樂!!!

歡迎關注公眾號“NNResearch”

 

公眾號傳送“BP神經網路圖書”,即可獲取電子版圖書。

公眾號傳送“BP原始碼”,即可獲取完整版原始碼。

相關文章