退位減法具有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('-------------------------------------------------')
參考文獻
