第299篇 RNN与LSTM——序列建模的基础
前面聊了视觉里程计中的DeepVO,里面用到了LSTM来处理图像序列。你可能对RNN和LSTM有个大概印象,但细节记不太清了。这篇我们来好好捋一捋,这两个在机器人领域曾经非常重要的序列建模工具。
虽然现在Transformer在很多任务上已经取代了RNN/LSTM,但在一些实时性要求高的场景(比如机器人控制、传感器信号处理),LSTM仍然是主流选择。面试中问到序列建模,RNN/LSTM是绕不开的基础。
为什么需要循环神经网络
传统的CNN和全连接网络有个假设:输入之间是独立的。图像分类就是这个情况,一张猫的图片跟另一张狗的图片没有时序关系。
但机器人处理的数据很多是序列性质的。激光雷达的连续扫描、IMU的时间序列、语音信号、关节角度的历史轨迹——当前时刻的值跟过去密切相关。你要预测机器人下一秒的位置,不能只看当前的速度,还得知道加速度和历史轨迹。
RNN的核心思想是引入"记忆"。每个时间步,网络不仅接收当前输入,还接收上一步的隐藏状态。这个隐藏状态就像网络的记忆,编码了之前所有输入的信息。
# 最简单的RNN单元
class SimpleRNN:
def __init__(self, input_size, hidden_size):
self.Wx = np.random.randn(hidden_size, input_size) * 0.01
self.Wh = np.random.randn(hidden_size, hidden_size) * 0.01
self.b = np.zeros(hidden_size)
def step(self, x, h_prev):
# 当前输入 + 上一步隐藏状态 -> 新的隐藏状态
h = np.tanh(self.Wx @ x + self.Wh @ h_prev + self.b)
return h
看起来很简单对吧?每个时间步就做一次矩阵乘法加一个tanh。但问题就出在这个简单的结构上。
梯度消失:RNN的致命伤
RNN训练用的是BPTT(Backpropagation Through Time),把循环展开成多层网络,然后反向传播。问题在于,当你需要回溯很多时间步时,梯度会指数级衰减或爆炸。
想象一下:隐藏状态的更新是h_t = tanh(W·h_{t-1} + ...)。反向传播时,梯度要连乘W的转置很多遍。如果W的特征值小于1,梯度会指数衰减到接近零;如果大于1,梯度会爆炸。这就是梯度消失和梯度爆炸问题。
梯度消失意味着网络学不到长距离的依赖关系。比如一个序列中第1步的信息对第100步很重要,但梯度传了100步后已经消失了,网络无法建立这个联系。
举个具体的例子。假设你在训练一个LSTM来预测机器人的未来轨迹,输入是过去50步的关节角度。如果第5步的一个关键动作(比如碰到障碍物)对第50步的输出很重要,简单RNN很难学到这个关系,因为梯度从第50步传回第5步时已经衰减到几乎为零了。网络只能学到最近几步的局部模式。
梯度爆炸相对好处理,用梯度裁剪(gradient clipping)就行——梯度超过某个阈值就按比例缩小。PyTorch里一行代码就能搞定:torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)。但梯度消失没有这么简单的解法。
LSTM:用门控机制解决长期依赖
LSTM(Long Short-Term Memory)是Hochreiter在1997年提出的,专门解决RNN的梯度消失问题。它的核心设计是引入了三个"门"和一个独立的"细胞状态"(cell state)。
细胞状态C是一条信息高速公路,信息可以沿着它几乎无衰减地传递。三个门控制信息的流入流出:遗忘门决定丢弃哪些旧信息,输入门决定写入哪些新信息,输出门决定读出哪些信息。
# LSTM的核心计算
# f_t = sigmoid(Wf @ [h_prev, x_t] + bf) # 遗忘门
# i_t = sigmoid(Wi @ [h_prev, x_t] + bi) # 输入门
# o_t = sigmoid(Wo @ [h_prev, x_t] + bo) # 输出门
# C_t = f_t * C_prev + i_t * tanh(Wc @ [h_prev, x_t] + bc)
# h_t = o_t * tanh(C_t)
关键在遗忘门。当遗忘门接近1时,细胞状态几乎原封不动地传到下一步,梯度也能畅通无阻地回传。这就解决了长距离依赖的问题。网络可以学会在合适的时机"记住"和"遗忘"信息。
LSTM的参数量比简单RNN大不少。四个权重矩阵(三个门加一个候选值),每个都是(hidden_size, input_size + hidden_size)的维度。这意味着更容易过拟合,训练时需要更多的数据和正则化。
GRU:LSTM的简化版
GRU(Gated Recurrent Unit)是2014年Cho提出的LSTM简化版。它把遗忘门和输入门合并成一个"更新门",还去掉了独立的细胞状态,只用隐藏状态来传递信息。
GRU的参数比LSTM少约三分之一,训练速度更快,在很多任务上效果和LSTM相当。如果你的数据量不大或者序列不太长,GRU通常是个不错的选择。
但在一些需要精确控制长期记忆的任务上(比如长文本生成、复杂的时序推理),LSTM的表现通常更好。机器人领域两种都有人用,没有绝对的优劣。
还有个变种叫双向LSTM(BiLSTM)。它同时从前向后和从后向前处理序列,把两个方向的隐藏状态拼接起来。这在离线分析任务中很有用,比如给一段录制的传感器数据做标注,前后文信息都能利用。但在实时控制中不能用,因为你没有"未来"的信息。面试时如果被问到BiLSTM,要能区分在线和离线场景的适用性。
在机器人中的应用场景
RNN/LSTM在机器人中有哪些实际应用场景?
传感器融合是一个典型场景。机器人有多种传感器(IMU、编码器、激光雷达),采样率各不相同。用LSTM可以融合这些异构的时间序列数据,输出统一的狀態估计。相比卡尔曼滤波,LSTM能处理非线性的观测模型,但需要大量训练数据。
运动预测也很常见。预测周围行人和车辆的未来轨迹,对机器人的路径规划至关重要。用LSTM编码历史轨迹序列,预测未来若干步的位置。Social LSTM(2016)是这个方向的经典工作,它引入了"社会池化"机制来建模行人之间的交互。
控制策略中也会用到LSTM。对于需要记忆的任务(比如迷宫导航、物体搜索),纯前馈网络不够用,需要某种记忆机制。LSTM可以作为策略网络的记忆模块,让agent记住之前探索过的区域。
面试要点
面试中聊RNN/LSTM,有几个点要能讲清楚。
梯度消失的原因和LSTM的解决方案。这是最基础的,面试官一定会问。你要能解释为什么连乘导致梯度消失,LSTM的细胞状态为什么能缓解这个问题。
LSTM和Transformer的对比。Transformer用自注意力机制直接建模任意两个位置之间的关系,不受距离限制。而LSTM的信息传递是逐步的,距离越远衰减越多。但Transformer的计算复杂度是O(n²),对长序列不友好;LSTM是O(n)的,推理速度快。在机器人实时控制场景中,这个差异很关键。
实际使用中的调参经验。LSTM的层数一般1-3层就够了,太深容易过拟合。hidden_size根据任务复杂度选,通常128到512之间。dropout一般加在层与层之间,而不是时间维度上。序列长度也是个关键参数,太长训练慢且梯度仍然可能消失,太短丢失上下文信息。实践中一般截断到100-200步,用truncated BPTT来训练。这些经验在面试中能体现你的实战能力。
PyTorch还是TensorFlow?两个框架都支持LSTM,但PyTorch的接口更直观。PyTorch中nn.LSTM的输入格式是(seq_len, batch, input_size),很多人会被这个维度顺序搞晕。建议一开始就写个小脚本验证输入输出的shape,避免在调试时浪费时间在维度问题上。
给你的建议
虽然现在NLP领域基本被Transformer统治了,但在机器人的时序处理中LSTM仍然有用武之地。建议你动手实现一个简单的LSTM,不用框架,纯numpy写前向和反向传播。这个过程会帮你真正理解门控机制的工作原理。
然后找一个机器人相关的时序任务练手,比如用IMU数据做手势识别,或者用关节角度预测机器人末端轨迹。跑通之后你就算有实战经验了。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)