第十九節,使用RNN實現一個退位減法器


退位減法具有RNN的特性,即輸入的兩個數相減時,一旦發生退位運算,需要將中間狀態保存起來,當高位的數傳入時將退位標志一並傳入參與計算。

我們在做減法運算時候,把減數和被減數轉換為二進制然后進行運算。我們定義一個RNN網絡,輸入節點數為2個,依次傳入減數和被減數的二進制序列值,隱藏層節點數為16個,由於二進制減法輸出值為0或者1,所以輸出節點數為可以設置為1個或者2個,如果設置為2個,表示看成一個二分類問題;如果設置為1個,就表示輸出的值,這里我們設置輸出節點數為1個,輸出層使用的是S型函數。

一 定義基本函數

由於使用二進制運算,每一位的值只可能為0或者1,因此激活函數全部選擇S型函數。

#規定隨機數生成器的種子,可以每次得到一樣的值
np.random.seed(0)
'''
一 定義基本函數
'''

def sigmoid(x):
    '''
    定義S型函數
    
    args:
        x:輸入數或list、ndarray
    '''
    return 1/(1+np.exp(-x))
    
def sigmoid_output_to_derivative(output):
    '''
    定義sigmod的導數
    
    args:
        output:sigmoid函數的輸出  假設要計算x=5時,sigmoid函數的導數,此處就傳入sigmoid(5)
    '''
    return output*(1-output)

二 建立二進制映射關系

我們定義減法的最大限制在256以內,即8位數的減法,定義int與二進制之間的映射字典int2binary。

'''
二 建立二進制映射

定義的減法最大值限制在256以內,即8位二進制的減法,定義int與二進制之間的映射字典int2binary
'''
#整數到其二進制表示的映射字典
int2binary = {}
#二進制的位數
binary_dim = 8
#計算0-255的二進制表示
largest_number = pow(2,binary_dim)
'''
注意 np.array([range(largest_number)],dtype=np.uint8) 返回的是[[0,1,2,3...255]] 形狀1x256 如果使用這個后面需要.T進行轉置
np.array(range(largest_number),dtype=np.uint8) 返回的是[0,1,2,...255]形狀為(256,) 盡量不使用這種形狀不明確的
然后按行轉為二進制 得到256x8
'''
binary = np.unpackbits(
        np.array(range(largest_number),dtype=np.uint8).reshape(256,1),axis=1)
#建立int-二進制映射
for i in range(largest_number):
    #向字典中追加數據
    int2binary[i] = binary[i]
    

三 定義參數

定義權重和偏置,這里我們為了簡化運算,忽略偏置。

'''
三 定義參數

隱藏層的權重synapse_0(2x16),輸出層的權重synapse_1(16x1),循環節點的權重synapse_h(16x16)
這里只設置權重 忽略偏置
'''

#參數設置
learning_rate = 0.9                 #學習速率
input_dim = 2                       #輸入節點的個數為2,減數和被減數
hidden_dim = 16                     #隱藏層節點個數
output_dim = 1                      #輸出節點個數

n_samples = 10000                   #樣本個數 

#初始化網絡  np.random.random生成一個[0,1)之間隨機浮點數或size大小浮點數組
synapse_0 = (2*np.random.random((input_dim,hidden_dim))-1)*0.05   #-0.05~0.05之間
synapse_1 = (2*np.random.random((hidden_dim,output_dim))-1)*0.05  #-0.05~0.05之間
synapse_h = (2*np.random.random((hidden_dim,hidden_dim))-1)*0.05   #-0.05~0.05之間

#用於存放反向傳播的權重梯度值
synapse_0_update = np.zeros_like(synapse_0)
synapse_1_update = np.zeros_like(synapse_1)
synapse_h_update = np.zeros_like(synapse_h)

四 准備樣本數據

隨機生成減數和被減數數據,並計算結果。這里要求被減數要大於減數。最后並把這些數轉換為二進制。

'''
四 准備樣本數據
'''


#建立循環生成樣本數據,先生成兩個數a,b,如果a小於b,就交換位置,保證被減數大
for i in range(n_samples):
    #生成一個數字a 被減數  范圍[0,256)之間的整數
    a_int = np.random.randint(largest_number)   
    #生成一個數字b 減數,b的最大值取得是largest_number/2 
    b_int = np.random.randint(largest_number/2)   
    #如果生成的b>a交換
    if a_int < b_int:
        tmp = b_int
        b_int = a_int
        a_int = tmp
     
    #二進制編碼
    a = int2binary[a_int]    #被減數
    b = int2binary[b_int]    #減數
    c = int2binary[a_int - b_int]  #差值

五 模型初始化

初始化神經網絡的預測值為0,初始化總誤差為0,定義layer_2_deltas存儲反向傳播過程中輸出層的誤差,layer_1_values存放隱藏層的輸出值,由於第一個數據傳入時,沒有前面的隱藏層輸出值來作為本次的輸入,所以需要為其定義一個初始值,這里定義為0.1.

    '''
    五 模型初始化
    '''
    d = np.zeros_like(c)     #存儲神經網絡的預測值 初始化為0
    over_all_error = 0       #初始化總誤差為0
    
    layer_2_deltas = list()  #存儲每個時間點輸出層的誤差
    layer_1_values = list()  #存儲每個時間點隱藏層的值
    
    layer_1_values.append(np.ones(hidden_dim)*0.1)  #一開始沒有隱藏層(t=1),所以初始化原始值為0.1

六 正向傳播

循環遍歷每個二進制,從個位開始依次相減,並將中間隱藏層的輸出傳入下一位的計算(退位減法),把每一個時間點的誤差導數都記錄下來,同時統計總誤差,為輸出准備。

    '''
    六 正向傳播
    '''
    #循環遍歷每一個二進制位
    for position in range(binary_dim):
        #生成輸入和輸出 從右向左,每次取兩個輸入數字的一個bit位
        X = np.array([[a[binary_dim - position- 1],b[binary_dim - position - 1]]])
        #正確答案
        y = np.array([[c[binary_dim - position -1]]]).T
        
        #計算隱藏層輸出 新的隱藏層 = 輸入層 + 之前的隱藏層
        layer_1 = sigmoid(np.dot(X,synapse_0) + np.dot(layer_1_values[-1],synapse_h))        
        #將隱藏層保存下來,下個事件序列可以使用
        layer_1_values.append(copy.deepcopy(layer_1))
        
        #計算輸出層
        layer_2 = sigmoid(np.dot(layer_1,synapse_1))
        
        #預測誤差
        layer_2_error = layer_2 - y
        #把每個時間點的誤差導數都記錄下來
        layer_2_deltas.append((layer_2_error)*sigmoid_output_to_derivative(layer_2))
        
        #總誤差
        over_all_error += np.abs(layer_2_error[0])
        
        #記錄每個預測的bit位
        d[binary_dim - position - 1] = np.round(layer_2[0][0])
    
    fuature_layer_1_delta = np.zeros(hidden_dim)

最后一行代碼是為了反向傳播准備的初始化。同正向傳播一樣,反向傳播是從最后一次往前反向計算誤差,對於每一個當前的計算都需要有它的下一次結果參與。反向計算從最后一次開始的,它沒有后一次的輸入,這里初始化為0.

七 反向訓練

初始化之后,開始從高位往回訓練,一次對每一位的所有層計算誤差,並根據每層誤差對權重求梯度,得到其調整值,最終將每一位算出的各層權重的調整值加載一塊乘以學習率,來更新各層的權重,完成一次優化訓練。

    '''
    七 反向訓練
    '''
    for position in range(binary_dim):
        X = np.array([[a[position],b[position]]])     #最后一次的兩個輸入
        layer_1 = layer_1_values[-position-1]         #當前時間點的隱藏層
        prev_layer_1 = layer_1_values[-position-2]    #前一個時間點的隱藏層
        
        layer_2_delta = layer_2_deltas[-position-1]   #當前時間點輸出導數
        #通過后一個時間點的一隱藏層誤差和當前時間點的輸出層誤差,計算當前時間點的隱藏層誤差
        layer_1_delta = (fuature_layer_1_delta.dot(synapse_h.T) + layer_2_delta.dot(synapse_1.T))*sigmoid_output_to_derivative(layer_1)

        
        
        #等完成了所有反向傳播誤差計算,才會更新權重矩陣,先暫時把梯度矩陣存起來
        synapse_1_update += np.atleast_2d(layer_1).T.dot(layer_2_delta)
        synapse_h_update += np.atleast_2d(prev_layer_1).T.dot(layer_1_delta)
        synapse_0_update += X.T.dot(layer_1_delta)
        
        fuature_layer_1_delta = layer_1_delta
        
    #完成所有反向傳播之后,更新權重矩陣,並把矩陣梯度變量清0
    synapse_0 -= synapse_0_update*learning_rate
    synapse_1 -= synapse_1_update*learning_rate
    synapse_h -= synapse_h_update*learning_rate
    synapse_0_update = 0
    synapse_1_update = 0
    synapse_h_update = 0

八 輸出

    
    '''
    八 打印輸出結果
    '''
    if i%800 == 0:
        print('總誤差:',str(over_all_error))
        print('Pred:',str(d))
        print('True:',str(c))
        out = 0
        for index,x in enumerate(reversed(d)):
            out += x*pow(2,index)
        print(str(a_int) + '-' + str(b_int) + '=' + str(out))
        print('-------------------------------------------------')

可以看到隨着迭代次數的增加,計算越來越准確。

完整代碼:

# -*- coding: utf-8 -*-
"""
Created on Sun May  6 17:33:24 2018

@author: lenovo
"""

'''
裸寫一個退位減法器
使用python編寫簡單循環神經網絡擬合一個退位減法的操作,觀察其反向傳播過程
'''

import  numpy as np
import copy

#規定隨機數生成器的種子,可以每次得到一樣的值
np.random.seed(0)
'''
一 定義基本函數
'''

def sigmoid(x):
    '''
    定義S型函數
    
    args:
        x:輸入數或list、ndarray
    '''
    return 1/(1+np.exp(-x))
    
def sigmoid_output_to_derivative(output):
    '''
    定義sigmod函數輸出的導數
    
    args:
        output: output:sigmoid函數的輸出  假設要計算x=5時,sigmoid函數的導數,此處就傳入sigmoid(5)
    '''
    return output*(1-output)


'''
二 建立二進制映射

定義的減法最大值限制在256以內,即8位二進制的減法,定義int與二進制之間的映射字典int2binary
'''
#整數到其二進制表示的映射字典
int2binary = {}
#二進制的位數
binary_dim = 8
#計算0-255的二進制表示
largest_number = pow(2,binary_dim)
'''
注意 np.array([range(largest_number)],dtype=np.uint8) 返回的是[[0,1,2,3...255]] 形狀1x256 如果使用這個后面需要.T進行轉置
np.array(range(largest_number),dtype=np.uint8) 返回的是[0,1,2,...255]形狀為(256,) 盡量不使用這種形狀不明確的
然后按行轉為二進制 得到256x8
'''
binary = np.unpackbits(
        np.array(range(largest_number),dtype=np.uint8).reshape(256,1),axis=1)
#建立int-二進制映射
for i in range(largest_number):
    #向字典中追加數據
    int2binary[i] = binary[i]
    
    
'''
三 定義參數

隱藏層的權重synapse_0(2x16),輸出層的權重synapse_1(16x1),循環節點的權重synapse_h(16x16)
這里只設置權重 忽略偏置
'''

#參數設置
learning_rate = 0.9                 #學習速率
input_dim = 2                       #輸入節點的個數為2,減數和被減數
hidden_dim = 16                     #隱藏層節點個數
output_dim = 1                      #輸出節點個數

n_samples = 10000                   #樣本個數 

#初始化網絡  np.random.random生成一個[0,1)之間隨機浮點數或size大小浮點數組
synapse_0 = (2*np.random.random((input_dim,hidden_dim))-1)*0.05   #-0.05~0.05之間
synapse_1 = (2*np.random.random((hidden_dim,output_dim))-1)*0.05  #-0.05~0.05之間
synapse_h = (2*np.random.random((hidden_dim,hidden_dim))-1)*0.05   #-0.05~0.05之間

#用於存放反向傳播的權重梯度值
synapse_0_update = np.zeros_like(synapse_0)
synapse_1_update = np.zeros_like(synapse_1)
synapse_h_update = np.zeros_like(synapse_h)


'''
四 准備樣本數據
'''


#建立循環生成樣本數據,先生成兩個數a,b,如果a小於b,就交換位置,保證被減數大
for i in range(n_samples):
    #生成一個數字a 被減數  范圍[0,256)之間的整數
    a_int = np.random.randint(largest_number)   
    #生成一個數字b 減數,b的最大值取得是largest_number/2 
    b_int = np.random.randint(largest_number/2)   
    #如果生成的b>a交換
    if a_int < b_int:
        tmp = b_int
        b_int = a_int
        a_int = tmp
     
    #二進制編碼
    a = int2binary[a_int]    #被減數
    b = int2binary[b_int]    #減數
    c = int2binary[a_int - b_int]  #差值
    
    '''
    五 模型初始化
    '''
    d = np.zeros_like(c)     #存儲神經網絡的預測值 初始化為0
    over_all_error = 0       #初始化總誤差為0
    
    layer_2_deltas = list()  #存儲每個時間點輸出層的誤差
    layer_1_values = list()  #存儲每個時間點隱藏層的值
    
    layer_1_values.append(np.ones(hidden_dim)*0.1)  #一開始沒有隱藏層(t=1),所以初始化原始值為0.1
    
    '''
    六 正向傳播
    '''
    #循環遍歷每一個二進制位
    for position in range(binary_dim):
        #生成輸入和輸出 從右向左,每次取兩個輸入數字的一個bit位
        X = np.array([[a[binary_dim - position- 1],b[binary_dim - position - 1]]])
        #正確答案
        y = np.array([[c[binary_dim - position -1]]]).T
        
        #計算隱藏層輸出 新的隱藏層 = 輸入層 + 之前的隱藏層
        layer_1 = sigmoid(np.dot(X,synapse_0) + np.dot(layer_1_values[-1],synapse_h))        
        #將隱藏層保存下來,下個事件序列可以使用
        layer_1_values.append(copy.deepcopy(layer_1))
        
        #計算輸出層
        layer_2 = sigmoid(np.dot(layer_1,synapse_1))
        
        #預測誤差
        layer_2_error = layer_2 - y
        #把每個時間點的誤差導數都記錄下來
        layer_2_deltas.append((layer_2_error)*sigmoid_output_to_derivative(layer_2))
        
        #總誤差
        over_all_error += np.abs(layer_2_error[0])
        
        #記錄每個預測的bit位
        d[binary_dim - position - 1] = np.round(layer_2[0][0])
    
    fuature_layer_1_delta = np.zeros(hidden_dim)
    
    
    '''
    七 反向訓練
    '''
    for position in range(binary_dim):
        X = np.array([[a[position],b[position]]])     #最后一次的兩個輸入
        layer_1 = layer_1_values[-position-1]         #當前時間點的隱藏層
        prev_layer_1 = layer_1_values[-position-2]    #前一個時間點的隱藏層
        
        layer_2_delta = layer_2_deltas[-position-1]   #當前時間點輸出導數
        #通過后一個時間點的一隱藏層誤差和當前時間點的輸出層誤差,計算當前時間點的隱藏層誤差
        layer_1_delta = (fuature_layer_1_delta.dot(synapse_h.T) + layer_2_delta.dot(synapse_1.T))*sigmoid_output_to_derivative(layer_1)

        
        
        #等完成了所有反向傳播誤差計算,才會更新權重矩陣,先暫時把梯度矩陣存起來
        synapse_1_update += np.atleast_2d(layer_1).T.dot(layer_2_delta)
        synapse_h_update += np.atleast_2d(prev_layer_1).T.dot(layer_1_delta)
        synapse_0_update += X.T.dot(layer_1_delta)
        
        fuature_layer_1_delta = layer_1_delta
        
    #完成所有反向傳播之后,更新權重矩陣,並把矩陣梯度變量清0
    synapse_0 -= synapse_0_update*learning_rate
    synapse_1 -= synapse_1_update*learning_rate
    synapse_h -= synapse_h_update*learning_rate
    synapse_0_update = 0
    synapse_1_update = 0
    synapse_h_update = 0
    
    
    '''
    八 打印輸出結果
    '''
    if i%800 == 0:
        print('總誤差:',str(over_all_error))
        print('Pred:',str(d))
        print('True:',str(c))
        out = 0
        for index,x in enumerate(reversed(d)):
            out += x*pow(2,index)
        print(str(a_int) + '-' + str(b_int) + '=' + str(out))
        print('-------------------------------------------------')
        
        
    










    
View Code

 參考文獻

[1]循環神經網絡(RNN)模型與前向反向傳播算法

[2]深度學習——循環神經網絡RNN(一)_反向傳播算法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM