4.5 RNN迴圈神經網路(recurrent neural network)

一字千金 發表於 2021-07-05
神經網路

 自己開發了一個股票智慧分析軟體,功能很強大,需要的點選下面的連結獲取:

https://www.cnblogs.com/bclshuai/p/11380657.html

1.1  RNN迴圈神經網路(recurrent neural network)

1.1.1          RNN簡介

RNN迴圈神經網路會迴圈的加入上一時刻的狀態作為輸入,得出下一時刻的輸出。解決的是具有時序關聯性的問題,例如股票趨勢預測,需要上一時刻的股票價格輸入作為下一時刻的輸出,又比如輸入預測,當你輸入I am studen時,神經網路會根據你前面的輸入推斷出下一時刻你會輸入t。而卷積神經網路處理的輸入之間沒有關聯關係,例如手寫數字識別例子中,各個手寫數字沒有任何時間上的關聯性。

1.1.2          迴圈神經網路結構

 4.5	RNN迴圈神經網路(recurrent neural network)

 

 

從一個簡單的圖弄清楚迴圈神經網路的結構和概念,如上圖所示。

x輸入層,s是隱藏層,o是輸出層,u是輸入層到隱藏層的引數,v是隱藏層到輸出層的引數,迴圈神經網路的隱藏層的值s不僅僅取決於當前這次的輸入x,還取決於上一次隱藏層的值s。權重矩陣w就是隱藏層上一次的值作為這一次的輸入的權重。上圖左邊的圓圈表示的就是迴圈的概念。如果將迴圈神經網路按照時間序列展開,就是上圖右側所示,下一刻輸出和前面的所有的輸入和狀態都建立起了關聯性,這就是迴圈神經網路能處理時序性問題的原因

用公式表示迴圈神經網路的結構如下:

 4.5	RNN迴圈神經網路(recurrent neural network)

式1是輸出層的計算公式,輸出層是一個全連線層,也就是它的每個節點都和隱藏層的每個節點相連。V是輸出層的權重矩陣,g是啟用函式。式2是隱藏層的計算公式,它是迴圈層。U是輸入x的權重矩陣,W是上一次的值作為這一次的輸入的權重矩陣,f是啟用函式。迴圈層和全連線層的區別就是迴圈層多了一個權重矩陣 W。

如果將公式(2)帶入公式(1)中,並且不斷的迴圈展開,會發現公式展開和圖片中按照時間序列展開有異曲同工之妙。這也是為什麼迴圈神經網路關聯前面所有輸入和狀態的原因。

 4.5	RNN迴圈神經網路(recurrent neural network)

 

 

概念理解了,公式也明白了。迴圈神經網路不是像上圖那樣輸入、隱藏、輸出都是一個節點那樣簡單,而是一個網路。S是由多個節點組成的隱藏層。

 4.5	RNN迴圈神經網路(recurrent neural network)

 

 

參考文獻:

https://zhuanlan.zhihu.com/p/30844905

https://zybuluo.com/hanbingtao/note/541458

1.1.3          tensorflow實現迴圈神經網路原始碼例項

將網上的一段英文先獲取英文中所有的字元,然後將字元進行編碼,然後再將將英文轉碼位數字,對數字進行抽樣,隨機抽取1000個連續的50個字元,送入迴圈神經網路RNN進行訓練,讓設計網路具有一定的語言記憶功能,然後再讓迴圈神經網路去預測輸出一段話。具體實現步驟如下:

(1)    從亞馬遜的網站讀取一個文字 ,裡面是一段英文,獲取英文的無重複字符集;將字符集進行排序、數字編碼(0,n);並且定義抽取連續長度的字串介面;

(2)    定義迴圈神經網路模型,,初始化init函式中定義迴圈層,全連線層。call函式中定義層的組合連線。predict函式定義預測輸出結果;

(3)    隨機抽取連續的字串輸入神經網路進行訓練,用梯度下降法優化模型引數;

(4)    將訓練好的模型用於字元預測,隨機抽取長度為40的字元,輸入訓練好的模型預測出下一個字元,然後再將抽取的字元後面39個,和預測的字元組合成新的40個字元輸入模型進行預測,依次迴圈,輸出預測的50個字元。

程式碼例項

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plot
class Daataloder:
    def __init__(self):
        #下載檔案
        path=tf.keras.utils.get_file('nietzsche.txt',origin="https://s3.amazonaws.com/text-datasets/nietzsche.txt")
       #開啟檔案
        with open(path,encoding='utf-8') as f:
            #讀取檔案,並且將字元全部轉換為小寫,這是一段英文
            self.raw_text=f.read().lower()
            #去除換行符
            self.raw_text=self.raw_text.replace('\n','')
            self.raw_text_len=len(self.raw_text)
            #先轉換為set去除重複的字元,然後在轉為list進行升序排序
            self.chars=sorted(list(set(self.raw_text)))
            self.chars_len=len(self.chars)
            #遍歷字元,獲取字元到索引的字典對映
            self.chars_index=dict((c,i) for i,c in enumerate(self.chars))
            self.chars_index_len=len(self.chars_index)
            # 遍歷字元,獲取索引到字元的字典對映
            self.index_char=dict((i,c) for i,c in enumerate(self.chars))
            #將文字按照chars_index資料字典對映為數字
            self.text=[self.chars_index[c] for c in self.raw_text]
            self.textlen=len(self.text)

    def get_batch(self,seq_length,batch_size):
        seq=[]
        next_char=[]
        for i in range(batch_size):
            #隨機產生一個索引
            index=np.random.randint(0,len(self.text)-seq_length)
            #在self.text中擷取起始索引為index的,長度為seq_length的陣列
            seq.append(self.text[index:index+seq_length])
            #將擷取的資料的下一個字元作為正確的字元標籤,要和訓練的預測資料結果做對比
            next_char.append(self.text[index+seq_length])
            #seq大小是[batch_size,seq_length].next_char大小是[batch_size]
        return np.array(seq),np.array(next_char)
class RNN(tf.keras.Model):
    def __init__(self,num_chars,batch_size,seq_length):
        super().__init__()
        self.num_chars=num_chars#字符集中字元數量
        self.batch_size=batch_size#訓練資料的數量
        self.seq_length=seq_length#一批訓練資料的大小
        #定義一個迴圈層,長短期記憶網路(LSTM,Long Short-Term Memory)是一種時間迴圈神經網路
        self.cell=tf.keras.layers.LSTMCell(units=256)
        #定義一個全連線層
        self.dence=tf.keras.layers.Dense(units=self.num_chars)

    def call(self,inputs,from_logits=False):
        # #one_hot是將一維陣列轉化為對應元素值對應的位置為1,其餘都為0二維陣列,例如[1,2,3,4],depth=5,轉化為4行5列的矩陣
        # [[0. 1. 0. 0. 0.]
        #  [0. 0. 1. 0. 0.]
        #  [0. 0. 0. 1. 0.]
        #  [0. 0. 0. 0. 1.]]
        #print(inputs)#shape=(50, 40)
        inputs=tf.one_hot(inputs,depth=self.num_chars)
        #print(inputs)#shape=(50, 40, 56)
        #獲取初始的狀態
        state=self.cell.get_initial_state(batch_size=self.batch_size,dtype=tf.float32)
        #將長度為40的字串(數字轉碼)一個個的傳入迴圈層,得出最後的結果
        for t in range(self.seq_length):
            #print(inputs)#shape=(50, 40, 56)
            #print(inputs[:,t,:])# shape=(50, 56)
            output,state=self.cell(inputs[:,t,:],state)
            #print(output)# shape=(50, 56)
            #print("state:")
            #print(state)# shape=(50, 56)
        #用全連線層去處理output,輸出預測字元
        logits=self.dence(output)## shape=(50, 56),50組資料,每組輸出的字元對應字符集中的
        if from_logits:
            return logits#非訓練時,返回陣列,用概率分佈去抽樣,增加文字豐富性
        else:
            return tf.nn.softmax(logits)#訓練的時候用概率最大的
    def predict(self,inputs,temprrature=1.):
        #用模型輸出概率陣列
        batch_size,_=tf.shape(inputs)
        #用模型輸出概率陣列
        logict=self(inputs,from_logits=True)
        #用temprrature控制概率分佈圖形狀, 越大概率分佈越平緩,各個值的概率越接近,生成的文字越豐富
        #softmax則是歸一化,陣列中所有值除以和,值範圍0-1,值總和為1
        prob=tf.nn.softmax(logict/temprrature).numpy()
        #按照概率分佈隨機抽取一個字元作為預測的下一個字元,概率越大的抽中越大,概率小的也有可能被抽中,增加輸出結果豐富性
        return np.array([np.random.choice(self.num_chars,p=prob[i,:]) for i in range(batch_size.numpy())])



#定義超引數
num_batch=1000#資料的訓練次數
seq_length=40#一組資料的長度
batch_size=50#資料的組數
learning_rate=0.001#學習率
data_loader=Daataloder()#建立資料載入物件
#建立模型物件
model=RNN(num_chars=len(data_loader.chars),batch_size=batch_size,seq_length=seq_length)
#建立引數優化器
opimister=tf.keras.optimizers.Adam(learning_rate=learning_rate)
#儲存每次訓練的誤差用於畫圖
arryindex=np.arange(num_batch)
arryloss=np.zeros(num_batch)
for index in range(num_batch):
    #隨機獲取資料
    x,y=data_loader.get_batch(seq_length,batch_size)
    with tf.GradientTape() as tape:
        #通過模型預測資料
        y_pred = model(x)
        #和標籤資料計算誤差
        loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
        #計算梯度
        grads=tape.gradient(loss,model.variables)
        loss=tf.reduce_mean(loss)
        arryindex[index]=index
        arryloss[index]=loss
        print("batch %d :loss%f" % (index,loss))
        #更新引數
        opimister.apply_gradients(grads_and_vars=zip(grads,model.variables))
#畫出訓練誤差隨訓練次數的圖片圖
plot.plot(arryindex,arryloss,c='r')
plot.show()
X_,_=data_loader.get_batch(seq_length,1)
for diversity in[0.2,0.5,1.0,1.2]:
    X=X_
    print(diversity)
    for s in range(seq_length):
        index=X_[0,s]
        print(data_loader.index_char[index], end='', flush=True)
    for t in range(50):
        #X為是從文章中隨機獲取的一個長度為40的一維陣列
        y_pred=model.predict(X,diversity)
        print(data_loader.index_char[y_pred[0]],end='',flush=True)
        #一維陣列,一個資料變成1*1的二維 張量
        y_pred=np.expand_dims(y_pred,axis=1)
        #取X中後面39個字元,加上預測出的字元,連線成新的40個字元,繼續輸入得出下一字元
        X=np.concatenate([X[:,1:],y_pred],axis=-1)#第一個冒號表示所有行,第二個1:表示從第一個起後面所有的字元
       # print(X)
    print("\n")

 


訓練的誤差曲線圖

4.5	RNN迴圈神經網路(recurrent neural network)

 

 

不同的引數temperature,相同的訓練模型,相同的輸入字串,不同的輸出的結果如下,前40個字元是隨機抽取文字中一段連續的文字,後面的50個字元是用迴圈網路依次預測出的字元,引數temperature越小,概率分佈曲線圖越陡峭,概率最大的字元輸出的概率也越大,輸出的文字豐富性低。預測的文字沒有啥含義,說明升級網路模型還是需要優化。

0.2

nish the anti-semitic bawlers out of the sore the the the and the the the the the the the

 

0.5

nish the anti-semitic bawlers out of the mon the ment ond the mond the the as and os ins o

 

1.0

nish the anti-semitic bawlers out of thenvoun serti; gficej)ingatresmors po sy the komnmos

 

1.2

nish the anti-semitic bawlers out of the hit asd phith"rorydyrtlingeree.e -om thol fof inm