机器学习004:循环神经网络实现与文本分类问题

本文详细介绍了如何实现循环神经网络,包括正向传播、文本向量化和反向传播过程,并应用于文本分类问题。通过Python和Numpy实现,同时探讨了在TensorFlow中的验证和预训练网络的使用。
摘要由CSDN通过智能技术生成

本周主要目标在于实现一个可用的循环神经网络。主要内容包括:循环神经网络以及求导(BPTT)、多层循环神经网络、文本向量化以及一个小的文本分类实践。实现过程中使用了 Numpy 用于矩阵运算。文章目的在于脱离编程语言束缚去学习算法,因此可以使用其他编程语言,同时语言中应该包含以下组件:矩阵乘法、矩阵分片。

正文没有使用机器学习库,但由于 Python 语言的速度原因,为了更快获得结果,使用了 Tensorflow 进行了预训练,这个过程不是必须的。文末会附这部分代码。

本周推荐阅读时间:15min。

1. 阅读建议

  • 推荐阅读时间:15min
  • 推荐阅读文章:Hochreiter S, Schmidhuber J. Long short-term memory[J]. Neural Computation, 1997, 9(8):1735-1780.

2. 软件环境

  • Python3
  • Numpy

3. 前置基础

  • 链式求导

4. 数据描述

  • 数据来源:谭松波整理语料
  • 数据地址:ChnSentiCorp
  • 数据简介:语料之中包含:酒店、电脑(笔记本)与书籍等领域。是包含正负样本的平衡数据集。

5. 理论部分

5.1 循环神经网络正向传播过程

循环神经网络的输入是时序相关的,因此其输入与输入可以描述为 $h_1,\cdots,h_T$、$y_1,\cdots,y_T$,为了保持时序信息,最简单的 RNN 函数形式为:

$$\begin{matrix}h_t=f(x_t,h_{t-1})\\\rightarrow h_t=tanh(concat[x_t, h_{t-1}]\cdot W+b)\\\rightarrow tanh(x_t\cdot W1+h_{t-1}\cdot W2+b)\end{matrix}$$(1.1)

其中 $x_t$ 的形式为 [BATCHSIZE, Features1],$h_t$ 的形式为 [BatchSize, Features2]。多层 RNN 网络可以在输出的基础上继续加入 RNN 函数:

$$\begin{matrix}h^l_t=f(x_t,h^l_{t-1})\\h^{l+1}_t=f(h^l_t, h^{l+1}_{t-1})\end{matrix}$$(1.2)

对于简单的 RNN 来说,状态向量与输出相同。

5.2 文本向量化正向传播

循环神经网络(RNN)产生之初就是为了处理文本问题。但是神经网络本身只能处理浮点型数据,因此需要将文本转化为适合于神经网络处理的向量。对于 1.1 式之中的 $x_t$ 为向量化后的文本,其是固定长度的(本文设定为 128)。下面对文本向量化过程进行叙述:

对文本每个字符进行单独编号 -> 整形数字 -> OneHot 向量 -> 乘以降维矩阵 $W_{N, 128}$(N 为字符个数)

在处理文本过程之中我们需要对所有文本之中不重复的字符(或词)进行单独编号(整形数字),编号是从 0 开始连续的。处理文本过程之中将文本转化为相应的整形数字表示。处理完成后将整形数字使用 OneHot 向量的方式进行表示。此时向量的维度很高(中文几千个字符,OneHot 向量长度也是如此)。为了将输入转化为适合处理的向量长度,需要乘以一个降维矩阵 $W_{N, 128}$,从而形成 $x_t$。这是对于向量一种线性降维的方式。

5.2 反向传播过程

RNN 函数反向传播过程与全链接网络类似:

$$\begin{matrix}e^l_{t-1}=\frac{\partial loss}{\partial h^l_{t-1}}=\frac{\partial loss}{\partial h^l_{t}}\circ f'(x_t,h^l_{t-1})\frac{\partial(x_t\cdot W1+h_{t-1}\cdot W2+b)}{\partial h_{t-1}}=e^l_t f' W2&(a)\\\Delta W1=\sum_t (x_t)^T\cdot (e_t^lf')&(b)\\\Delta W2=\sum_t (h_{t-1})^T\cdot (e_t^lf')&(c)\\e^{l}_{t}=\frac{\partial loss}{\partial h^{l}_{t}}=\frac{\partial loss}{\partial h^{l+1}_{t}}\circ f'(h_t^l,h^{l+1}_{t-1})\frac{\partial(h_t^l\cdot W1+h_{t-1}\cdot W2+b)}{\partial h_{t}^l}=e^{l+1}_t f' W1&(d)\end{matrix}$$(1.3)

1.3-a 称之为时间反向传播算法 BPTT,1.3-c 为层间传播。可训练参数为 1.3-bc。实际上传统的 RNN 网络与全链接网络并无不同。只是添加了时间反向传播项。

6. 代码部分

6.1 循环神经网络层
import numpy as npclass NN():    def __init__(self):        """        定义可训练参数        """        self.value = []        self.d_value = []        self.outputs = []        self.layer = []        self.layer_name = []    def tanh(self, x, n_layer=None, layer_par=None):        epx = np.exp(x)        enx = np.exp(-x)        return (epx-enx)/(epx+enx)    def d_tanh(self, x, n_layer=None, layer_par=None):        e2x = np.exp(2 * x)        return 4 * e2x / (1 + e2x) ** 2    def _rnncell(self, X, n_layer, layer_par):        """        RNN正向传播层        """        W = self.value[n_layer][0]        bias = self.value[n_layer][1]        b, h, c = np.shape(X)        _, h2 = np.shape(W)        outs = []        stats = []        s = np.zeros([b, h2])        for itr in range(h):            x = X[:, itr, :]            stats.append(s)            inx = np.concatenate([x, s], axis=1)            out = np.dot(inx, W) + bias            out = self.tanh(out)            s = out            outs.append(out)        outs = np.transpose(outs, (1, 0, 2))        stats= np.transpose(stats, (1, 0, 2))        return [outs, stats]    def _d_rnncell(self, error, n_layer, layer_par):        """        BPTT层,此层使用上一层产生的Error产生向前一层传播的error        """        inputs = self.outputs[n_layer][0]        states = self.outputs[n_layer + 1][1]        b, h, insize = np.shape(inputs)        back_error = [np.zeros([b, insize]) for itr in range(h)]        W = self.value[n_layer][0]        bias = self.value[n_layer][1]        dw = np.zeros_like(W)        db = np.zeros_like(bias)        w1 = W[:insize, :]        w2 = W[insize:, :]        for itrs in range(h - 1, -1, -1):            # 每一个时间步都要进行误差传播            if len(error[itrs]) == 0:                continue            else:                err = error[itrs]            for itr in range(itrs, -1, -1):                h = states[:, itr, :]                x = inputs[:, itr, :]                inx = np.concatenate([x, h], axis=1)                h1 = np.dot(inx, W) + bias                d_fe = self.d_tanh(h1)                err = d_fe * err                # 计算可训练参数导数                dw[:insize, :] += np.dot(x.T, err)                dw[insize:, :] += np.dot(h.T, err)                db += np.sum(err, axis=0)                # 计算传递误差                back_error[itr] += np.dot(err, w1.T)                err = np.dot(err, w2.T)        self.d_value[n_layer][0] = dw        self.d_value[n_layer][1] = db        return back_error    def basic_rnn(self, w, b):        self.value.append([w, b])        self.d_value.append([np.zeros_like(w), np.zeros_like(b)])        self.layer.append((self._rnncell, None, self._d_rnncell, None))        self.layer_name.append("rnn")
6.2 文本向量化层
    def _embedding(self, inputs, n_layer, layer_par):        W = self.value[n_layer][0]        F, E = np.shape(W)        B, L = np.shape(inputs)        # 转换成one-hot向量        inx = np.zeros([B * L, F])        inx[np.arange(B * L), inputs.reshape(-1)] = 1        inx = inx.reshape([B, L, F])        # 乘以降维矩阵        embed = np.dot(inx, W)        return [embed]    def _d_embedding(self, in_error, n_layer, layer_par):        inputs = self.outputs[n_layer][0]        W = self.value[n_layer][0]        F, E = np.shape(W)        B, L = np.shape(inputs)        inx = np.zeros([B * L, F])        inx[np.arange(B * L), inputs.reshape(-1)] = 1        error = np.transpose(in_error, (1, 0, 2))        _, _, C = np.shape(error)        error = error.reshape([-1, C])        # 计算降维矩阵的导数        self.d_value[n_layer][0] = np.dot(inx.T, error)        return []    def embedding(self, w):        self.value.append([w])        self.d_value.append([np.zeros_like(w)])        self.layer.append((self._embedding, None, self._d_embedding, None))        self.layer_name.append("embedding")      
6.3 使用 RNN 网络最后一层输出用于后续处理
    def _last_out(self, inputs, n_layer, layer_par):        return [inputs[:, -1, :]]    def _d_last_out(self, in_error, n_layer, layer_par):        X = self.outputs[n_layer][0]        b, h, c = np.shape(X)        error = [[] for itr in range(h)]        error[-1] = in_error        return error    def last_out(self):        self.value.append([])        self.d_value.append([])        self.layer.append((self._last_out, None, self._d_last_out, None))        self.layer_name.append("text_error")
6.4 网络其他部分

其他部分与前面所讲全链接网络、卷积神经网络类似:

    def _matmul(self, inputs, n_layer, layer_par):        W = self.value[n_layer][0]        return [np.dot(inputs, W)]    def _d_matmul(self, in_error, n_layer, layer_par):        W = self.value[n_layer][0]        inputs = self.outputs[n_layer][0]        self.d_value[n_layer][0] = np.dot(inputs.T, in_error)        error = np.dot(in_error, W.T)        return error    def matmul(self, filters):        self.value.append([filters])        self.d_value.append([np.zeros_like(filters)])        self.layer.append((self._matmul, None, self._d_matmul, None))        self.layer_name.append("matmul")    def _sigmoid(self, X, n_layer=None, layer_par=None):        return [1/(1+np.exp(-X))]    def _d_sigmoid(self, in_error, n_layer=None, layer_par=None):        X = self.outputs[n_layer][0]        return in_error * np.exp(-X)/(1 + np.exp(-X)) ** 2    def sigmoid(self):        self.value.append([])        self.d_value.append([])        self.layer.append((self._sigmoid, None, self._d_sigmoid, None))        self.layer_name.append("sigmoid")         def _relu(self, X, *args, **kw):        return [(X + np.abs(X))/2.]    def _d_relu(self, in_error, n_layer, layer_par):        X = self.outputs[n_layer][0]        drelu = np.zeros_like(X)        drelu[X>0] = 1        return in_error * drelu    def relu(self):        self.value.append([])        self.d_value.append([])        self.layer.append((self._relu, None, self._d_relu, None))        self.layer_name.append("relu")    def forward(self, X):        self.outputs.append([X])        net = [X]        for idx, lay in enumerate(self.layer):            method, layer_par, _, _ = lay            net = method(net[0], idx, layer_par)            self.outputs.append(net)        return self.outputs[-2][0]    def backward(self, Y):        error = self.layer[-1][2](Y, None, None)        self.n_layer = len(self.value)        for itr in range(self.n_layer-2, -1, -1):            _, _, method, layer_par = self.layer[itr]            #print("++++-", np.shape(error), np.shape(Y))            error = method(error, itr, layer_par)        return error    def apply_gradient(self, eta):        for idx, itr in enumerate(self.d_value):            if len(itr) == 0: continue            for idy, val in enumerate(itr):                self.value[idx][idy] -= val * eta    def fit(self, X, Y, eta=0.1):        self.forward(X)        self.backward(Y)        self.apply_gradient(eta)    def predict(self, X):        self.forward(X)        return self.outputs[-2]

7. 程序运行

7.1 文本分类网络模型

搭建文本分类网络时,网络模型为两层 RNN 网络,网络输出的最后一个时间步携带了整个文本的信息,因此使用最后一个输出搭建多层全链接网络用以后续处理,最终使用向量距离作为 loss 函数:

...#搭建网络模型method = NN()method.embedding(ew)method.basic_rnn(w1, b1)method.basic_rnn(w2, b2)method.last_out()method.matmul(w3)method.bias_add(b3)method.relu()method.matmul(w4)method.bias_add(b4)method.sigmoid()method.loss_square()for itr in range(1000):    # 获取数据    inx, iny = ...    pred = method.forward(inx)    method.backward(iny)    method.apply_gradient(0.001)    if itr% 20 == 0:        # 获取测试数据        inx, iny = ...        pred = method.forward(inx)        prd1 = np.argmax(pred, axis=1)        prd2 = np.argmax(iny, axis=1)        print(np.sum(prd1==prd2)/len(idx))

8. 附录

8.1 使用 TensorFlow 验证程序正确性

使用 TensorFlow 作为验证程序,验证方法为输出计算导数:

# 搭建多层神经网络batch_size = 1max_time = 10indata = tf.placeholder(dtype=tf.float64, shape=[batch_size, 10, 3])# 两层RNN网络cell = rnn.MultiRNNCell([rnn.BasicRNNCell(3) for itr in range(2)], state_is_tuple=True)state = cell.zero_state(batch_size, tf.float64)outputs = []states = []# 获取每一步输出,与状态for time_step in range(max_time):    (cell_output, state) = cell(indata[:, time_step, :], state)    outputs.append(cell_output)    states.append(state)y = tf.placeholder(tf.float64, shape=[batch_size, 3])# 定义loss函数loss = tf.square(outputs[-1]-y)opt = tf.train.GradientDescentOptimizer(1)# 获取可训练参数weights = tf.trainable_variables()# 计算梯度grad = opt.compute_gradients(loss, weights)sess = tf.Session()sess.run(tf.global_variables_initializer())# 获取变量值与梯度w1, b1, w2, b2 = sess.run(weights)dw1, db1, dw2, db2 = sess.run(grad, feed_dict={indata:np.ones([batch_size, 10, 3]), y:np.ones([batch_size, 3])})dw1 = dw1[0]db1 = db1[0]dw2 = dw2[0]db2 = db2[0]method = NN()method.basic_rnn(w1, b1)method.basic_rnn(w2, b2)method.last_out()method.loss_square()method.forward(np.ones([batch_size, 10, 3]))method.backward(np.ones([batch_size, 3]))print("TF Gradients", np.mean(dw1), np.mean(db1), np.mean(dw2), np.mean(db2))rnn.loss(np.ones([batch_size, 3]))for itr in method.d_value:    if len(itr) == 0:continue    print("NP Gradinets", np.mean(dw1, np.mean(db1), np.mean(dw2), np.mean(db2))

验证分程序仅用于演示思路。

8.2 TensorFlow 预训练网络
import tensorflow as tfinput_x = tf.placeholder(tf.int32, [None, 30], name='input_x')input_y = tf.placeholder(tf.float32, [None, 2], name='input_y')embedding = tf.get_variable('embedding', [vocab_size, 128])embedding_inputs = tf.nn.embedding_lookup(embedding, input_x)# 多层rnn网络cells = [tf.contrib.rnn.BasicRNNCell(128) for _ in range(2)]rnn_cell = tf.contrib.rnn.MultiRNNCell(cells, state_is_tuple=True)_outputs, _ = tf.nn.dynamic_rnn(cell=rnn_cell, inputs=embedding_inputs, dtype=tf.float32)last = _outputs[:, -1, :]  # 取最后一个时序输出作为结果net = tf.layers.dense(last, self.config.hidden_dim, name='fc1')net = tf.nn.relu(net)# 分类器logits = tf.layers.dense(net, 2, name='fc2')loss = tf.square(logits - input_y)....训练过程....# 获取变量并保存name = []array = []for itra, itrb in zip(tf.global_variables(), session.run(tf.global_variables())):    name.append(itra.name)    array.append(itrb)np.savez("par.npz", name=name, data=array)

本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。

阅读全文: http://gitbook.cn/gitchat/activity/5b0eb962d0b199202f42912b

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值