这个阶段会给cute-dl添加循环层,使之可以支持RNN--循环神经网络. 具体目标包括:python
RNN模型用来捕捉序列数据的特征. 给定一个长度为T的输入系列\(X=(x_1, x_2, .., X_T)\), RNN层输出一个长度为T的序列\(H=(h_1, h_2, ..., H_T)\), 对于任意时间步t, 能够表示为:网络
函数δ是sigmoid函数:app
\(H_t\)包含了前面第1到t-1步的全部信息。 和CNN层相似, CNN层在空间上共享参数, RNN层在时间步上共享参数\(W_x, W_h, b\).框架
RNN层中隐藏层的数量为T-2, 若是T较大(超过10), 反向传播是很容易出现梯度爆炸. GRU和LSTM就是为了解决这个问题而诞生, 这两种模型,可让RNN可以支持长度超过1000的输入序列。dom
GRU使用了不一样功能的门控单元, 分别捕捉序列上不一样时间跨度的的依赖关系。每一个门控单元都会都有独立的参数, 这些参数在时间步上共享。ide
GRU的门控单元有:
\(R_t = δ(X_tW^r_x + H_{t-1}W^r_h + b^r)\), 重置门用于捕捉短时间依赖关系.
\(U_t = δ(X_tW^u_x + H_{t-1}W^u_h + b^u)\), 更新门用于捕捉长期依赖关系
\(\bar{H}_t = tanh(X_t\bar{W}_x + (R_t * H_{t-1})\bar{W}_h + \bar{b})\)
除此以外, 还有一个输出单元:
\(H_t = U_t * H_{t-1} + (1-U_t)*\bar{H}_t\)
函数
LSTM的设计思路和GRU相似, 一样使用了多个门控单元:
\(I_t = δ(X_tW^i_x + H_{t-1}W^i_h + b^i)\), 输入门,过滤记忆门的输出.
\(F_t = δ(X_tW^f_x + H_{t-1}W^f_h + b^f)\), 遗忘门, 过滤前面时间步的记忆.
\(O_t = δ(X_tW^o_x + H_{t-1}W^o_h + b^o)\), 输出门, 过滤当前时间步的记忆.
\(M_t = tanh(X_tW^m_x + H_{t-1}W^m_h + b^m)\), 记忆门.
它还有本身独有的记忆单元和输出单元:
\(\bar{M}_t = F_t * \bar{M}_{t-1} + I_t * M_t\)
\(H_t = O_t * tanh(\bar{M}_t)\)
设计要求:学习
文件: cutedl/rnn_layers.py, 类名: RNN
这个类是RNN层基类, 它主要功能是控制向前传播和向后传播的主流程.
初始化参数:测试
''' out_units 输出单元数 in_units 输入单元数 stateful 保留当前批次的最后一个时间步的状态做为下一个批次的输入状态, 默认False不保留 RNN 的输入形状是(m, t, in_units) m: batch_size t: 输入系列的长度 in_units: 输入单元数页是输入向量的维数 输出形状是(m, t, out_units) ''' def __init__(self, out_units, in_units=None, stateful=False, activation='linear'):
向前传播
def forward(self, in_batch, training): m, T, n = in_batch.shape out_units = self.__out_units #全部时间步的输出 hstatus = np.zeros((m, T, out_units)) #上一步的输出 pre_hs = self.__pre_hs if pre_hs is None: pre_hs = np.zeros((m, out_units)) #隐藏层循环过程, 沿时间步执行 for t in range(T): hstatus[:, t, :] = self.hiden_forward(in_batch[:,t,:], pre_hs, training) pre_hs = hstatus[:, t, :] self.__pre_hs = pre_hs #pdb.set_trace() if not self.stateful: self.__pre_hs = None return hstatus
反向传播
def backward(self, gradient): m, T, n = gradient.shape in_units = self.__in_units grad_x = np.zeros((m, T, in_units)) #pdb.set_trace() #从最后一个梯度开始反向执行. for t in range(T-1, -1, -1): grad_x[:,t,:], grad_hs = self.hiden_backward(gradient[:,t,:]) #pdb.set_trace() if t - 1 >= 0: gradient[:,t-1,:] = gradient[:,t-1,:] + grad_hs #pdb.set_trace() return grad_x
文件: cutedl/rnn_layers.py, 类名: GateUint
门控单元是RNN层基础的参数单元. 和Dense层相似,它是Layer的子类,负责学习和使用参数。但在学习和使用参数的方式上有很大的不一样:
下面咱们会主要看一下GateUnit特别之处的代码.
在__ init__方法中定义参数和栈:
#3个参数 self.__W = None #当前时间步in_batch权重参数 self.__Wh = None #上一步输出的权重参数 self.__b = None #偏置量参数 #输入栈 self.__hs = [] #上一步输出 self.__in_batchs = [] #当前时间步的in_batch
正向传播:
def forward(self, in_batch, hs, training): W = self.__W.value b = self.__b.value Wh = self.__Wh.value out = in_batch @ W + hs @ Wh + b if training: #向前传播训练时把上一个时间步的输出和当前时间步的in_batch压栈 self.__hs.append(hs) self.__in_batchs.append(in_batch) #确保反向传播开始时参数的梯度为空 self.__W.gradient = None self.__Wh.gradient = None self.__b.gradient = None return self.activation(out)
反向传播:
def backward(self, gradient): grad = self.activation.grad(gradient) W = self.__W.value Wh = self.__Wh.value pre_hs = self.__hs.pop() in_batch = self.__in_batchs.pop() grad_in_batch = grad @ W.T grad_W = in_batch.T @ grad grad_hs = grad @ Wh.T grad_Wh = pre_hs.T @ grad grad_b = grad.sum(axis=0) #反向传播计算 if self.__W.gradient is None: #当前批次第一次 self.__W.gradient = grad_W else: #累积当前批次的全部梯度 self.__W.gradient = self.__W.gradient + grad_W if self.__Wh.gradient is None: self.__Wh.gradient = grad_Wh else: self.__Wh.gradient = self.__Wh.gradient + grad_Wh if self.__b.gradient is None: self.__b.gradient = grad_b else: self.__b.gradient = self.__b.gradient + grad_b return grad_in_batch, grad_hs
文件: cutedl/rnn_layers.py, 类名: GRU
隐藏单初始化:
def set_parent(self, parent): super().set_parent(parent) out_units = self.out_units in_units = self.in_units #pdb.set_trace() #重置门 self.__g_reset = GateUnit(out_units, in_units) #更新门 self.__g_update = GateUnit(out_units, in_units) #候选输出门 self.__g_cddout = GateUnit(out_units, in_units, activation='tanh') self.__g_reset.set_parent(self) self.__g_update.set_parent(self) self.__g_cddout.set_parent(self) #重置门乘法单元 self.__u_gr = MultiplyUnit() #输出单元 self.__u_out = GRUOutUnit()
向前传播:
def hiden_forward(self, in_batch, pre_hs, training): gr = self.__g_reset.forward(in_batch, pre_hs, training) gu = self.__g_update.forward(in_batch, pre_hs, training) ugr = self.__u_gr.forward(gr, pre_hs, training) cddo = self.__g_cddout.forward(in_batch, ugr, training) hs = self.__u_out.forward(gu, pre_hs, cddo, training) return hs
反向传播:
def hiden_backward(self, gradient): grad_gu, grad_pre_hs, grad_cddo = self.__u_out.backward(gradient) #pdb.set_trace() grad_in_batch, grad_ugr = self.__g_cddout.backward(grad_cddo) #计算梯度的过程当中须要累积上一层输出的梯度 grad_gr, g_pre_hs = self.__u_gr.backward(grad_ugr) grad_pre_hs = grad_pre_hs + g_pre_hs g_in_batch, g_pre_hs = self.__g_update.backward(grad_gu) grad_in_batch = grad_in_batch + g_in_batch grad_pre_hs = grad_pre_hs + g_pre_hs g_in_batch, g_pre_hs = self.__g_reset.backward(grad_gr) grad_in_batch = grad_in_batch + g_in_batch grad_pre_hs = grad_pre_hs + g_pre_hs #pdb.set_trace() return grad_in_batch, grad_pre_hs
文件: cutedl/rnn_layers.py, 类名: LSTM
隐藏单元初始化:
def set_parent(self, layer): super().set_parent(layer) in_units = self.in_units out_units = self.out_units #输入门 self.__g_in = GateUnit(out_units, in_units) #遗忘门 self.__g_forget = GateUnit(out_units, in_units) #输出门 self.__g_out = GateUnit(out_units, in_units) #记忆门 self.__g_memory = GateUnit(out_units, in_units, activation='tanh') self.__g_in.set_parent(self) self.__g_forget.set_parent(self) self.__g_out.set_parent(self) self.__g_memory.set_parent(self) #记忆单元 self.__memory_unit =LSTMMemoryUnit() #输出单元 self.__out_unit = LSTMOutUnit()
向前传播:
def hiden_forward(self, in_batch, hs, training): g_in = self.__g_in.forward(in_batch, hs, training) #pdb.set_trace() g_forget = self.__g_forget.forward(in_batch, hs, training) g_out = self.__g_out.forward(in_batch, hs, training) g_memory = self.__g_memory.forward(in_batch, hs, training) memory = self.__memory_unit.forward(g_forget, g_in, g_memory, training) cur_hs = self.__out_unit.forward(g_out, memory, training) return cur_hs
反向传播:
def hiden_backward(self, gradient): #pdb.set_trace() grad_out, grad_memory = self.__out_unit.backward(gradient) grad_forget, grad_in, grad_gm = self.__memory_unit.backward(grad_memory) grad_in_batch, grad_hs = self.__g_memory.backward(grad_gm) tmp1, tmp2 = self.__g_out.backward(grad_out) grad_in_batch += tmp1 grad_hs += tmp2 tmp1, tmp2 = self.__g_forget.backward(grad_forget) grad_in_batch += tmp1 grad_hs += tmp2 tmp1, tmp2 = self.__g_in.backward(grad_in) grad_in_batch += tmp1 grad_hs += tmp2 return grad_in_batch, grad_hs
接下来, 验证示例将会构建一个简单的RNN模型, 使用该模型拟合一个正余弦叠加函数:
#采样函数 def sample_function(x): y = 3*np.sin(2 * x * np.pi) + np.cos(x * np.pi) + np.random.uniform(-0.05,0.05,len(x)) return y
训练数据集和测试数据集在这个函数的不一样定义域区间内样. 训练数据集的采样区间为[1, 200.01), 测试数据集的采样区间为[200.02, 240.002). 模型任务是预测这个函数值的序列.
示例代码在examples/rnn/fit_function.py文件中.
def fit_gru(): model = Model([ rnn.GRU(32, 1), nn.Filter(), nn.Dense(32), nn.Dense(1, activation='linear') ]) model.assemble() fit('gru', model)
训练报告:
def fit_lstm(): model = Model([ rnn.LSTM(32, 1), nn.Filter(), nn.Dense(2), nn.Dense(1, activation='linear') ]) model.assemble() fit('lstm', model)
训练报告:
这个阶段,框架新增了RNN的两个最多见的实现:GRU和LSTM, 相应地增长了它须要的激活函数. cute-dl已经具有了构建最基础RNN模型的能力。经过验证发现, GRU模型和LSTM模型在简单任务上都表现出了很好的性能。会添加嵌入层,使框架可以构建文本分类任务的模型,而后在imdb-review(电影评价)数据集上进行验证.