深度學習課程--assign3--RNN簡單理解

馮寶興發表於2020-11-27

Recurrent Neural Network - RNN

為了更好地理解RNN的框架,我們將會用numpy手寫RNN的各種layers。之後再用tensorflow 的keras來直接用函式定義layers。所以首先要理解RNN,RNN的簡單結構我們是知道的,如下圖所示,可以注意到對於多次隱層的迴圈,用的權值是一樣的。
在這裡插入圖片描述
下面將會參考這個網站提供的例子來理解 RNN的網路結構:
http://iamtrask.github.io/2015/11/15/anyone-can-code-lstm/

我們將會用python簡單的建立 兩個input,16個隱藏層,一個output。比如是 加法的運算。a+b = c
比如 a = 12 ,變成二進位制 2^3 + 2 ^ 2 ,才用8個數。即[0,0,0,0,1,1,0,0]
b = 4 即 [0,0,0,0,0,1,0,0]
所以c應該為 16 即為[0,0,0,1,0,0,0,0] 作為ground truth。我們將會用RNN來預測 任意的兩個二進位制數字相加的結果,並跟ground truth做對比。
所以首先是來create 二進位制的資料集

我們將會用np.unpackbits來轉換十進位制整數為二進位制。下面便是np.unpackbits的用法

a = np.array([[2], [7], [23]], dtype=np.uint8)
a.shape   #3行1列 
b = np.unpackbits(a, axis=1)
#變成二進位制 
b  # 2^1 = 2 ; 2^2+2^1+2^0 = 7 ; 2^4+2^2+2^1+2^0 =16+4+2+1=23

在這裡插入圖片描述
所以,我們將會建立2^8 = 256個zheng

Data Generation

import copy
import numpy as np
np.random.seed(123)
#建立 二進位制的字典 -- 方便隨機提取
int2binary = {}
#定義8個數字  
binary_dim = 8 

largest_num = pow(2,binary_dim)  #2**8 = 256

#把256個數 全部轉化為 二進位制 即有256個 二進位制 
binary = np.unpackbits(np.array([range(largest_num)],dtype=np.uint8).T,axis=1)

#然後把binary放入字典int2binary,方便隨機提取 
for i in range(largest_num):
    int2binary[i] = binary[i]

在這裡插入圖片描述

設定RNN網路引數

#設定網路維度
alpha = 0.1 
input_dim = 2
hidden_dim = 16
output_dim = 1 

#設定網路weights 權重引數
w0 = 2*np.random.random((input_dim,hidden_dim))-1   #隨機設定(2,16)的引數
print(w0.shape)
w1 = 2*np.random.random((hidden_dim,output_dim)) - 1
print(w1.shape)
wh = 2*np.random.random((hidden_dim,hidden_dim)) - 1
print(wh.shape)

(2, 16)
(16, 1)
(16, 16)
同時也要準備好 backprop 的資料 ,即delta gradient的值

dw0 = np.zeros_like(w0)

dw1 = np.zeros_like(w1)

dwh = np.zeros_like(wh)

RNN的作用是 根據 兩個二進位制 a , b 來 predict a + b 的值。

下面是訓練的過程 ,重複10000次 ,然後求導更新pred值,到最後的預測已經很準了。

#定義sigmoid函式
def sigmoid(x):
    output = 1/(1+np.exp(-x))
    return output

#定義sigmoid的求導 derivate
def sigmoid_grads(s):
    d = s*(1-s)
    return d 
#定義一個函式 把二進位制轉換為 十進位制 
def bin2dec(b):
    out = 0
    for i, x in enumerate(b[::-1]):
        out += x * pow(2, i)  
    return out
#這部分是為了畫圖~ 畫出 accs和errs via epoch
errs = list()
accs = list()

error = 0
accuracy = 0

對於每次feedward之後預測完,便從後面到前面開始求導~來更新權重values

for n in range(10000):
	#隨機產生數字 a 和 b 
	############################################################
	#256/2 在1-128內中隨機找一個引數 
	a_int = np.random.randint(largest_num/2) 
	#然後用隨機產生的num在字典那裡提取相應的二進位制數字 
	a = int2binary[a_int]  
	
	#同理,用隨機產生的num 來提取二進位制的數值
	b_int = np.random.randint(largest_num/2)
	b = int2binary[b_int]
	
	#然後同理產生 ground true c 
	c_int = a_int + b_int
	c = int2binary[c_int]
	############################################################
	#現在開始RNN網路的建立  來pred a和b相加
	#開始預測 c ,於是建立 d的形狀 
	d = np.zeros_like(c)
	#建立error
	total_error = 0 
	#建立forward的時候 layer1的values 值,然後初始化為0 ,有hidden_dim=16個0,作為隱藏層 
	H = list()
	H.append(np.zeros(hidden_dim))
	#建立layer2 往後的求導 
	layer2_grads = list()
	
	############################################################
	#現在往前計算 feedward
	for i in range(binary_dim):
		#往前的公式是 
		#layer1 = sigmoid(x * w0+ h0 * wh) 這裡暫時忽略bias 
		#layer2 = sigmoid(layer1 * h1),這裡也暫時忽略 bias
		 
		#首先提取a,b和c的8個數 的每一個 數字 
		X = np.array([[a[binary_dim - i - 1],b[binary_dim - i - 1]]])
		y = np.array([[c[binary_dim - i - 1]]]).T
		
		#開始計算feedward,layer1_values[-1]代表最後一層layer1
		layer1 = sigmoid(np.dot(X,w0)+np.dot(H[-1],wh)) 
		#layer1的shape是 (1,16)
		#放入隱藏層H
		H.append(copy.deepcopy(layer1))	
		layer2 = sigmoid(np.dot(layer1,w1))
		#layer2的shape是 (1)
		d[binary_dim - i - 1] = np.round(layer2[0][0])
		#feedward之後,需要計算error值,跟c相對比 這裡即y和layer2對比。
		layer2_error = y - layer2   #目標函式
		total_error += np.abs(layer2_err[0])
		
		#然後對目標函式 往後面求導 即 layer2_error * derivate 
		#求導 求到 w1 那裡 的整體 後面需要乘 layer1
		grad = layer2_error * sigmoid_derivative(layer2) 
		layer2_grads.append(grad)
	future_layer1_grad = np.zeros(hidden_dim)
	############################################################

    ############################################################
	#現在往後計算 backward
	for j in range(binary_dim):
		#從二進位制的前頭開始求導~
		X = np.array([[a[j],b[j]]])
		layer1 = =H[-j - 1]  #提取最後的隱藏層 
		prev_layer1 =H[-j-2] ##再往前提取一層 算數
		#從layer2的導數list提取最後一個 來計算 這是為了計算dw1  = layer1* grads(layers)
		layer2_grad = layer2_grads[-(j+1)]
        dw1 += np.atleast_2d(layer1).T.dot(layer2_grad)

		#開始backprop計算 dw0,dwh  這裡的backprop也是有關係 前者的back會影響後面的backward
		layer1_grad = (future_layer1_grad.dot(wh.T) + layer2_grad.dot(w1.T)) * sigmoid_grads(layer1)
        dwh += np.atleast_2d(prev_layer1).T.dot(layer1_grad)
        dw0 += X.T.dot(layer1_grad)
        
        future_layer1_grad = layer1_grad
	w0 += dw0 * alpha
	w1 += dw1 * alpha
	wh += dwh * alpha
	dw0 *= 0
	dw1 *= 0
	dwh *= 0
	#更新結束 
    ############################################################
    
	############################################################
	#這部分是為了畫圖 
	error += total_error
	if (bin2dec(d) == c_int):
		accuracy += 1
    if (n % 20 == 0):
        errs.append(error / 20)
        accs.append(accuracy / 20)
        error = 0
        accuracy = 0
	############################################################
    if(n % 1000 == 0):
    	print('Iter', n)
        print('Error is: ', str(total_err))
        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("------------")
        #end 
	############################################################
	

在這裡插入圖片描述

視覺化部分

import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(errs, label='error')
plt.plot(accs, label='accuracy')
plt.legend()

在這裡插入圖片描述

相關文章