numpy手写NLP模型(四)———— RNN

1. 模型介绍

首先介绍一下RNN,RNN全程为循环神经网络,主要用来解决一些序列化具有顺序的输入的问题。普通的前馈神经网络的输入单一决定输出,输出只由输入决定,比如一个单调函数的拟合,一个x决定一个y,前馈神经网络可以直接拟合出一条曲线并得到不错的拟合效果。再举一个例子,给出sin(x)的值,假设x属于[0, 2π),让你输出x的值是多少,前馈神经网络还能直接做到吗?显然是不可以的,因为一个sin(x)在一个周期内对应着多个值,单一的信息是不能直接确定x的值的,因此无法简单地通过前馈神经网络做到很好的拟合。那么这个时候要推测x的值,就不仅需要sin(x)的值,还需要sin(x)的值在前几个时刻的值来共同确定,放在连续函数中来说就是不仅需要函数在这个点的,还需要函数在这个点的导数才行。而RNN正是一个可以解决这样问题的神经网络,它不仅考虑当前的输入,还同时考虑之前多个时刻的输入,并由多个时刻的状态共同决定输出。

2. 模型

2.1 模型的输入

首先看看这个模型的结构图:

在这里插入图片描述
假设模型的原始输入是一段文本,这段文本显然是由一个一个单词挨个连接而成的,是有序的,比如一句话"I love you"和另一句话"You love me",意思就是不同的,尽管组成这两句话的单词是完全一样的。而RNN网络的输入就是这样一个有序的序列输入。同时每一个状态的输出不仅由当前节点的输入决定,同时还受上一时刻的状态所影响,这个理念和理解一句话中的某个单词很相似,当前单词的意思不仅由这个单词本身决定,还要受到之前的词,也就是当前单词所处的语境的影响。回到之前的例子,

2.2 模型的前向传播

首先,前文一直提到当前状态的输出不仅和当前的输入有关,还和上一个状态有关,体现在公式上就是:
在这里插入图片描述
在这里插入图片描述

2.3 模型的反向传播

模型的反向传播部分的话我就不多加阐述了,我参考的仍然是刘建平老师的博客,原博客讲得很好。其实自己也完完全全推导了一遍RNN的反向传播求导的公式,求出了最后每个元素求导的公式,奈何数学功底不够,没能强行变换成一个整体的公式,因为这个diag(1-h(t)^2)之前完全没想到可以这么整,可能你现在已经听不懂我在说啥了,但是如果你先自己推导一遍,然后卡住了之后再去看那个博客,你就会懂的(斜眼笑)

3. 模型的代码实现

目前代码还存在一些问题(也可能是很多问题),后续会修改

首先我们来看初始化部分:
window_size:表示输入单词的个数,也可以理解成 t
hidden_size:就是每个节点隐藏层的维数
embed_size:就是每个单词embedding的维数
n_class:就是分类的类数,比如单词预测中问题中,n_class就是词典的大小

# model.py
class RNN:
    def __init__(self, window_size, hidden_size, embed_size, n_class):
        self.hidden_size = hidden_size
        self.window_size = window_size
        self.n_classs = n_class
        self.embed_size = embed_size

        self.w = np.random.random((hidden_size, hidden_size))
        self.u = np.random.random((hidden_size, embed_size))
        self.v = np.random.random((n_class, hidden_size))
        self.b = np.random.random((hidden_size, 1))
        self.c = np.random.random((n_class, 1))
        self.x = []

        self.hidden_node_list = []
        # window_size means how many words in each input batch
        # for example, if i input "I love apple" into the network, then the window_size is 3
        for i in range(window_size):
            # initialize all the nodes in the hidden layer
            # then the net will calculate some related value in the forward part
            new_node = np.random.random((hidden_size, 1))
            self.hidden_node_list.append(new_node)

然后看一下前向传播:

# model.py
    def forward(self, x):
        self.x = x

        # default h0 = 0
        h0 = np.zeros((self.hidden_size, 1))

        # before calculate, we need to clear the node list of the net
        self.hidden_node_list.clear()

        # calculate some intermediate variables
        for i in range(self.window_size):
            new_node = Node()
            if i == 0:
                new_node.h = np.tanh(np.dot(self.u, x[0]) + np.dot(self.w, h0) + self.b)
            else:
                new_node.h = np.tanh(np.dot(self.u, x[i]) + np.dot(self.w, self.hidden_node_list[i - 1].h))
            new_node.d_tanh = 1 - new_node.h ** 2
            new_node.o = np.dot(self.v, new_node.h) + self.c
            self.hidden_node_list.append(new_node)

明白了RNN前向传播的公式的话,forward这部分应该很好懂。

接着看RNN的反向传播,建议数学推导搞明白后再看代码,那样会很明了:

# model.py
    
    def backward(self, target_output, lr):
        h0 = np.zeros((self.hidden_size, 1))

        # then calculate the gradient in each hidden layer
        predict = softmax(self.hidden_node_list[-1].o)
        for i in range(self.window_size):
            j = self.window_size - i - 1
            if i == 0:
                self.hidden_node_list[j].e = np.dot(self.v.T, predict - target_output)
            else:
                temp = np.dot(self.w.T, diag(self.hidden_node_list[j + 1].d_tanh))
                self.hidden_node_list[j].e = np.dot(temp, self.hidden_node_list[j + 1].e)
        dloss_w = np.zeros((self.hidden_size, self.hidden_size))
        dloss_u = np.zeros((self.hidden_size, self.embed_size))
        dloss_b = np.zeros((self.hidden_size, 1))

        dloss_c = predict - target_output
        dloss_v = np.dot(predict-target_output, self.hidden_node_list[-1].h.T)

        for i in range(self.window_size):
            temp = np.dot(diag(self.hidden_node_list[i].d_tanh), self.hidden_node_list[i].e)
            if i == 0:
                # seem unnecessary~
                dloss_w += 0
            else:
                dloss_w += np.dot(temp, self.hidden_node_list[i - 1].h.T)
            dloss_b += temp
            dloss_u += np.dot(temp, self.x[i].T)

        self.w -= lr * dloss_w
        self.u -= lr * dloss_u
        self.v -= lr * dloss_v
        self.b -= lr * dloss_b
        self.c -= lr * dloss_c

最后附上本博客的github代码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是用numpy实现W-RNN模型的代码: ```python import numpy as np # 定义激活函数 def sigmoid(x): return 1 / (1 + np.exp(-x)) # 定义tanh激活函数 def tanh(x): return np.tanh(x) # 定义W-RNN模型 class W_RNN: def __init__(self, input_dim, hidden_dim, output_dim): # 初始化参数 self.Wx = np.random.randn(input_dim, hidden_dim) * 0.01 self.Wh = np.random.randn(hidden_dim, hidden_dim) * 0.01 self.Wy = np.random.randn(hidden_dim, output_dim) * 0.01 self.bh = np.zeros((1, hidden_dim)) self.by = np.zeros((1, output_dim)) def forward(self, X): # 初始化隐藏状态 h = np.zeros((1, self.Wh.shape[0])) # 初始化加权系数矩阵 A = np.zeros((X.shape[0], self.Wh.shape[0])) for t in range(X.shape[0]): # 计算加权系数 a = np.dot(X[t], self.Wx) + np.dot(h, self.Wh) + self.bh A[t] = sigmoid(a) # 计算隐藏状态 h = tanh(np.dot(A[t]*h, self.Wh) + self.bh) # 计算输出结果 y = np.dot(h, self.Wy) + self.by return A, h, y def backward(self, X, Y, A, h, learning_rate=0.1): # 初始化梯度 dWx = np.zeros_like(self.Wx) dWh = np.zeros_like(self.Wh) dWy = np.zeros_like(self.Wy) dbh = np.zeros_like(self.bh) dby = np.zeros_like(self.by) dh_next = np.zeros_like(h) dA_next = np.zeros_like(A[0]) for t in reversed(range(X.shape[0])): # 计算输出误差 dy = Y - h.dot(self.Wy) # 计算隐藏状态误差 dh = dh_next + dy.dot(self.Wy.T) * (1 - np.power(tanh(h), 2)) # 计算加权系数误差 da = (dh.dot(self.Wh.T) + dA_next) * h * (1 - h) * A[t] * (1 - A[t]) # 更新梯度 dWx += X[t].T.dot(da) dWh += h.T.dot(da) dWy += h.T.dot(dy) dbh += np.sum(da, axis=0) dby += np.sum(dy, axis=0) # 计算前一时间步的误差 dh_next = da.dot(self.Wh.T) dA_next = da.dot(h.T) # 更新参数 self.Wx += learning_rate * dWx self.Wh += learning_rate * dWh self.Wy += learning_rate * dWy self.bh += learning_rate * dbh self.by += learning_rate * dby ``` 以上代码实现了一个基本的W-RNN模型,它包括了前向传播和反向传播两个部分,可以用于时间序列预测、自然语言处理等任务。需要注意的是,以上代码只是一个简单的实现,实际应用中可能需要进行更多的优化和改进。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值