RNN二進位制加法例項

hearthougan發表於2018-09-25

本文是根據前兩篇詳細展示RNN的網路結構以及詳細闡述基於時間的反向傳播演算法(Back-Propagation Through Time,BPTT)來找的一個RNN例項,本例子可以幫助對RNN的前向傳播以及後向傳播,以及RNN結構的理解。整個過程符合下圖RNN結構描述:

# -*- coding: utf-8 -*-

"""
Created on Mon Sep 24 17:02:41 2018

@author: Abner_hg
"""

import numpy as np
import copy

def sigmoid(x):     #sigmoid
    return 1/(1+np.exp(-x))
 
# convert output of sigmoid function to its derivative   #計算sigmoid函式的倒數
def sigmoid_output_to_derivative(output):    #Sigmoid函式求導
    return output*(1-output)
 

binary_dim = 8 #二進位制的長度
int2binary = {}#儲存數字及其對應的二進位制表示

largest_num = pow(2, binary_dim)    # 生成binary_dim所能表示的最大的數字
num_arr = np.array([range(largest_num)], dtype = np.uint8).T  #生成0到largest之間的所有數字
binary = np.unpackbits(num_arr, axis = 1)  #將對應的數字轉為二進位制

for i in range(largest_num):  #儲存數字及其對應的二進位制
    int2binary[i] = binary[i]

alpha = 0.1  #學習率
input_dim = 2  #輸入的維度
hidden_dim = 16  #隱藏層神經元的個數
output_dim = 1  #輸出的維度


W = 2 * np.random.random((input_dim, hidden_dim)) - 1   #輸入至神經元的w0,維度為2X16,取值約束在[-1,1]間
U = 2 * np.random.random((hidden_dim, hidden_dim)) - 1  #神經元至輸出層的權重w1,維度為16X1,取值約束在[-1,1]間
V = 2 * np.random.random((hidden_dim, output_dim)) - 1  #神經元前一時刻狀態至當前狀態權重wh,維度為16X16,取值約束在[-1,1]間                                   

W_update = np.zeros_like(W)  #構造與W相同維度的矩陣,並初始化為全0,用於存放所有時刻產生W的梯度;
U_update = np.zeros_like(U)  #構造與U相同維度的矩陣,並初始化為全0,用於存放所有時刻產生U的梯度;
V_update = np.zeros_like(V)  #構造與V相同維度的矩陣,並初始化為全0,用於存放所有時刻產生V的梯度;


for epoch in range(10000):  #總共迭代10000次
    a_int = np.random.randint(largest_num >> 2)  #在小於largest_num / 2之中隨機挑選一個數字
    a = int2binary[a_int]   #a_int對應的二進位制
    
    b_int = np.random.randint(largest_num - a_int)  #在小於largest_num -a_int之中隨機挑選一個數字,之所以這麼選取,是為了防止a_int+b_int超出了largest_num,還要重新轉化為二進位制,沒有查字典快,當然,這裡也可以修改
    b = int2binary[b_int]   #b_int對應的二進位制      
    
    c_int = a_int + b_int
    c = int2binary[c_int]   #c_int對應的二進位制
    
    predict = np.zeros_like(c)   #predict就是預測結果,因此和真正值c的二進位制維度是一樣的
    
    total_error = 0   #總的損失,等價於L
    all_ht_values = list()   #隱藏層的輸出
    all_ht_values.append(np.zeros(hidden_dim)) #8*16,用於存放所有的ht的值
    delta_zt_values = list()   #存放所有豎直方向的梯度,即每個時刻損失,對輸出層的輸入求導
    
    for pos in range(binary_dim):   #正向傳播,從低位開始計算,也就是從右至左
        #生成輸入和輸出
        x = np.array([[a[binary_dim - pos - 1], b[binary_dim - pos -1]]])   #每次從a和b的低位各取出1位
        y = np.array([[c[binary_dim-pos-1]]]).T  #對應的真實值
        pre_ht = all_ht_values[-1]   #前一時刻隱藏層的輸出,即ht-1
        ht = sigmoid(np.dot(x, W) + np.dot(pre_ht, U))   #當前時刻隱藏層的輸出,即ht
        
        y_hat = sigmoid(np.dot(ht, V))   #當前時刻輸出層的輸出
        predict[binary_dim - pos - 1] = np.round(y_hat[0][0])   #predict預測值

        Lt = y - y_hat   #每一時刻產生的損失
        
        delta_zt_values.append((Lt)*sigmoid_output_to_derivative(y_hat))   #每一時刻都要計算,豎直方向的損失,對輸出層的輸入的導數
        total_error += np.abs(Lt[0])   #加入總的損失
        
        all_ht_values.append(copy.deepcopy(ht))  #存入當前時刻隱藏層的輸出,以便後續需要
    
    #到目前為止binary_dim位的二進位制已經計算完畢,接下來就是反向傳播進行引數更新,BPTT
    #我們記pre_delta_st = delta_t = delta Lt / delta st,則:
    pre_delta_st = np.zeros(hidden_dim)
    
    for pos in range(binary_dim):
        X = np.array([[a[pos], b[pos]]])  #當前時刻,所對應的二進位制輸入
        ht = all_ht_values[-pos-1]
        pre_ht = all_ht_values[-pos-2]
        
        delta_zt = delta_zt_values[-pos-1]   #當前時刻,豎直方向貢獻的損失值,這個正向傳播的時候已經求出
        #delta L / delta st = (delta L /delta ht)  * (delta ht / delta st) 
        #  = [(delta L /delta zt) * (delta zt /delta ht) + (delta L /delta st+1) * (delta st+1 /delta ht)] * (delta ht / delta st)
        #  = [(V.T * delta_zt) + (U.T * delta_st+1)] * sigmoid_output_to_derivative(ht)
        delta_st = (delta_zt.dot(V.T) + pre_delta_st.dot(U.T)) * sigmoid_output_to_derivative(ht)
        
        V_update += np.atleast_2d(ht).T.dot(delta_zt)#計入損失
        W_update += X.T.dot(delta_st)
        U_update += np.atleast_2d(pre_ht).T.dot(delta_st)
        
        pre_delta_st = delta_st
    
    W += W_update * alpha
    V += V_update * alpha
    U += U_update * alpha    
 
    W_update *= 0
    V_update *= 0
    U_update *= 0

    # print out progress
    if(epoch % 500 == 0):  #每500次列印結果
        
        print ("  a_binary:      " + str(a))
        print ("+ b_binary:      " + str(b))
        print ("--------------------------------")
        print ("   Predict:      " + str(predict))
        
        out = 0
        for index,x in enumerate(reversed(predict)):
            out += x*pow(2,index)
        print ("     a + b:      "+str(a_int) + " + " + str(b_int) + " = " + str(out))
        print ('\n')
        print (" True_value:   " + str(c))
        print ("      Error:   " + str(total_error))
        

        
        print ('\n')
        print ("##################################")
        print ("##################################")
        print ('\n')    
   

參考:

Anyone Can Learn To Code an LSTM-RNN in Python (Part 1: RNN)

相關文章