Python實現RNN


一般的前饋神經網絡中, 輸出的結果只與當前輸入有關與歷史狀態無關, 而遞歸神經網絡(Recurrent Neural Network, RNN)神經元的歷史輸出參與下一次預測.

本文中我們將嘗試使用RNN處理二進制加法問題: 兩個加數作為兩個序列輸入, 從右向左處理加數序列.和的某一位不僅與加數的當前位有關, 還與上一位的進位有關.

詞語的含義與上下文有關, 未來的狀態不僅與當前相關還與歷史狀態相關. 因為這種性質, RNN非常適合自然語言處理和時間序列分析等任務.


RNN與前饋神經網絡最大的不同在於多了一條反饋回路, 將RNN展開即可得到前饋神經網絡.

RNN同樣采用BP算法進行訓練, 誤差反向傳播時需要逆向通過反饋回路.

定義輸出層誤差為:

\[E_j= sigmod'(O_j)*(T_j-O_j) =O_j(1-O_j)(T_j-O_j) \]

其中, \(O_j\)是預測輸出, \(T_j\)是參考輸出.

因為隱含層沒有參考輸出, 采用下一層的誤差加權和代替\(T_j - O_j\). 對於隱含層神經元而言這里的下一層可能是輸出層, 也可能是其自身.

更多關於BP算法的內容可以參考BP神經網絡與Python實現

定義RNN結構

完整的代碼可以在rnn.py找到.

因為篇幅原因, 相關工具函數請在完整源碼中查看, 文中不再贅述.

這里我們定義一個簡單的3層遞歸神經網絡, 隱含層神經元的輸出只與當前狀態以及上一個狀態有關.

定義RNN類:

class RNN:
    def __init__(self):
        self.input_n = 0
        self.hidden_n = 0
        self.output_n = 0
        self.input_weights = []  # (input, hidden)
        self.output_weights = []  # (hidden, output)
        self.hidden_weights = []  # (hidden, hidden)

    def setup(self, ni, nh, no):
        self.input_n = ni
        self.hidden_n = nh
        self.output_n = no
        self.input_weights = make_rand_mat(self.input_n, self.hidden_n)
        self.output_weights = make_rand_mat(self.hidden_n, self.output_n)
        self.hidden_weights = make_rand_mat(self.hidden_n, self.hidden_n)

這里定義了幾個比較重要的矩陣:

  • input_weights: 輸入層和隱含層之間的連接權值矩陣.

  • output_weights: 隱含層和輸出層之間的連接權值矩陣

  • hidden_weights: 隱含層反饋回路權值矩陣, 反饋回路從一個隱含層神經元出發到另一個隱含層神經元.

因為本文的RNN只有一階反饋, 因此只需要一個反饋回路權值矩陣.對於n階RNN來說需要n個反饋權值矩陣.

定義test()方法作為示例代碼的入口:

def test(self):
    self.setup(2, 16, 1)
    for i in range(20000):
        a_int = int(rand(0, 127))
        a = int_to_bin(a_int, dim=8)
        a = np.array([int(t) for t in a])

        b_int = int(rand(0, 127))
        b = int_to_bin(b_int, dim=8)
        b = np.array([int(t) for t in b])

        c_int = a_int + b_int
        c = int_to_bin(c_int, dim=8)
        c = np.array([int(t) for t in c])

        guess, error = self.do_train([a, b], c, dim=8)

        if i % 1000 == 0:
            print("Predict:" + str(guess))
            print("True:" + str(c))
			print("Error:" + str(error))
			
            out = 0
            for index, x in enumerate(reversed(guess)):
                out += x * pow(2, index)
            print str(a_int) + " + " + str(b_int) + " = " + str(out)

            result = str(self.predict([a, b], dim=8))
            print(result)

            print "==============="

do_train方法僅進行一次訓練, 這里我們生成了20000組訓練數據每組數據僅執行一次訓練.

predict方法

predict方法執行一次前饋過程, 以給出預測輸出序列.

def predict(self, case, dim=0):
    guess = np.zeros(dim)
    hidden_layer_history = [np.zeros(self.hidden_n)]

    for i in range(dim):
        x = np.array([[c[dim - i - 1] for c in case]])

        hidden_layer = sigmoid(np.dot(x, self.input_weights) + np.dot(hidden_layer_history[-1], self.hidden_weights))
        output_layer = sigmoid(np.dot(hidden_layer, self.output_weights))
        guess[dim - i - 1] = np.round(output_layer[0][0])  # if you don't like int, change it

        hidden_layer_history.append(copy.deepcopy(hidden_layer))

初始化guess向量作為預測輸出, hidden_layer_history列表保存隱含層的歷史值用於計算反饋的影響.

自右向左遍歷序列, 對每個元素進行一次前饋.

hidden_layer = sigmoid(np.dot(x, self.input_weights) + np.dot(hidden_layer_history[-1], self.hidden_weights))

上面這行代碼是前饋的核心, 隱含層的輸入由兩部分組成:

  • 來自輸入層的輸入np.dot(x, self.input_weights).

  • 來自上一個狀態的反饋np.dot(hidden_layer_history[-1], self.hidden_weights).

output_layer = sigmoid(np.dot(hidden_layer, self.output_weights))
guess[dim - position - 1] = np.round(output_layer[0][0])

上面這行代碼執行輸出層的計算, 因為二進制加法的原因這里對輸出結果進行了取整.

train方法

定義train方法來控制迭代過程:

def train(self, cases, labels, dim=0, learn=0.1, limit=1000):
    for i in range(limit):
        for j in range(len(cases)):
            case = cases[j]
            label = labels[j]
            self.do_train(case, label, dim=dim, learn=learn)

do_train方法實現了具體的訓練邏輯:

def do_train(self, case, label, dim=0, learn=0.1):
    input_updates = np.zeros_like(self.input_weights)
    output_updates = np.zeros_like(self.output_weights)
    hidden_updates = np.zeros_like(self.hidden_weights)

    guess = np.zeros_like(label)
    error = 0

    output_deltas = []
    hidden_layer_history = [np.zeros(self.hidden_n)]

    for i in range(dim):
        x = np.array([[c[dim - i - 1] for c in case]])
        y = np.array([[label[dim - i - 1]]]).T

        hidden_layer = sigmoid(np.dot(x, self.input_weights) + np.dot(hidden_layer_history[-1], self.hidden_weights))
        output_layer = sigmoid(np.dot(hidden_layer, self.output_weights))

        output_error = y - output_layer
        output_deltas.append(output_error * sigmoid_derivative(output_layer))
        error += np.abs(output_error[0])

        guess[dim - i - 1] = np.round(output_layer[0][0])

        hidden_layer_history.append(copy.deepcopy(hidden_layer))

    future_hidden_layer_delta = np.zeros(self.hidden_n)
    for i in range(dim):
        x = np.array([[c[i] for c in case]])
        hidden_layer = hidden_layer_history[-i - 1]
        prev_hidden_layer = hidden_layer_history[-i - 2]

        output_delta = output_deltas[-i - 1]
        hidden_delta = (future_hidden_layer_delta.dot(self.hidden_weights.T) +
                        output_delta.dot(self.output_weights.T)) * sigmoid_derivative(hidden_layer)

        output_updates += np.atleast_2d(hidden_layer).T.dot(output_delta)
        hidden_updates += np.atleast_2d(prev_hidden_layer).T.dot(hidden_delta)
        input_updates += x.T.dot(hidden_delta)

        future_hidden_layer_delta = hidden_delta

    self.input_weights += input_updates * learn
    self.output_weights += output_updates * learn
    self.hidden_weights += hidden_updates * learn

    return guess, error

訓練邏輯中兩次遍歷序列, 第一次遍歷執行前饋過程並計算輸出層誤差.

第二次遍歷計算隱含層誤差, 下列代碼是計算隱含層誤差的核心:

hidden_delta = (future_hidden_layer_delta.dot(self.hidden_weights.T) +
                        output_delta.dot(self.output_weights.T)) * sigmoid_derivative(hidden_layer)

因為隱含層在前饋過程中參與了兩次, 所以會有兩層神經元反向傳播誤差:

  • 輸出層傳遞的誤差加權和output_delta.dot(self.output_weights.T)
  • 反饋回路中下一層隱含神經元傳遞的誤差加權和future_hidden_layer_delta.dot(self.hidden_weights.T)

將兩部分誤差求和然后乘自身輸出的sigmoid導數sigmoid_derivative(hidden_layer)即為隱含層誤差, 這里與普通前饋網絡中的BP算法是一致的.

測試結果

執行test()方法可以看到測試結果:

Predict:[1 0 0 0 1 0 1 0]
True:[1 0 0 0 1 0 1 0]
123 + 15 = 138
===============
Error:[ 0.22207356]
Predict:[1 0 0 0 1 1 1 1]
True:[1 0 0 0 1 1 1 1]
72 + 71 = 143
===============
Error:[ 0.3532948]
Predict:[1 1 0 1 0 1 0 0]
True:[1 1 0 1 0 1 0 0]
118 + 94 = 212
===============
Error:[ 0.35634191]
Predict:[0 1 0 0 0 0 0 0]
True:[0 1 0 0 0 0 0 0]
41 + 23 = 64

預測精度還是很令人滿意的.


免責聲明!

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



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