❤️ 用“我喜欢你”讲解你的 RNN 代码全过程(超大白话)
📌 你需要知道的前提(非常简单)
在 RNN 里,“我喜欢你”不是直接输入文字,而是变成三个“向量”:
- “我” → 一个 5 维向量(input_size=5)
- “喜欢” → 一个 5 维向量
- “你” → 一个 5 维向量
为什么是 5 维?
因为你的 RNN 是 nn.RNN(5, 6, 1),规定每个词的向量必须是长度 5。
然后 RNN 会按顺序,一个一个词地读。
✨ 故事开始:RNN 是一个“小记事本机器人”
这个机器人叫 RNN 小明,它的特点:
- 每次只能看一个词(input_size=5 的向量)
- 它有一个“脑袋记忆”(hidden_size=6)
- 每看一个词,就会更新一次记忆
就像你让小明读一段话,他会边看边记:
🧩 对应到你的 RNN 代码
第一步:创建 RNN
翻译成大白话:
“小明每次读一个 5 维向量,他的脑容量是 6 维,他只有一层大脑。”
第二步:准备输入(“我喜欢你”)
你的代码里是这样:
1
| input = torch.randn(1, 3, 5)
|
虽然是随机数,但其实真实意义就是:
1 2 3
| sequence_length = 1 batch_size = 3 input_size = 5
|
如果换成“我喜欢你”:
1 2 3
| sequence_length = 3 (三个词) batch_size = 1 (一次只读一句话) input_size = 5 (每个词是 5 维)
|
真实输入应该像这样:
| 词 |
5 维向量(示例) |
| 我 |
[0.2, 0.8, -0.3, 0.1, 0.5] |
| 喜欢 |
[-0.4, 0.9, 0.1, -0.2, 0.7] |
| 你 |
[0.5, -0.6, 0.8, 0.3, 0.1] |
💡 RNN 是怎么“读句子”的?
RNN 小明开始读句子
⭐ 第 1 步:读到“我”
输入:
1 2
| x1 = “我”的向量 h0 = 初始记忆(你的代码里的 h0)
|
小明做的事情:
- 看“我”的向量
- 把向量和自己的初始记忆混合
- 得出新的记忆 h1
就像:
小明心想:“哦,对方说的是‘我’。记下来。”
输出(output[0])也是 h1。
⭐ 第 2 步:读到“喜欢”
输入:
1 2
| x2 = “喜欢”的向量 h1 = 上一步的记忆
|
小明做的事情:
- 看当前词“喜欢”
- 打开第 1 步的记忆(他还记着“我”)
- 综合两者,更新记忆变成 h2
就像:
“我……喜欢……嗯,可能在表达感情。”
输出(output[1])就是 h2。
⭐ 第 3 步:读到“你”
输入:
小明:
- 看“你”
- 想起之前读过“我喜欢”
- 把它们综合成最终记忆 h3
就像:
“我……喜欢……你!哦,我懂了!”
最终输出(output[2])就是 h3。
🏁 最后两个结果:output 和 hn
当读完全部句子后:
✔ output(每一步的结果):
1 2 3 4 5
| [ h1, # 读完“我”时的理解 h2, # 读完“喜欢”时的理解 h3 # 读完“你”时的理解 ]
|
✔ hn(最终的记忆)
也就是整句话读完后,小明的最终理解。
📌 对照你的代码结果
你输入的是 1 个时间步(seq_len=1),所以 output == hn
如果你输入“我喜欢你”(seq_len=3),就会变成:
1 2
| output = [h1, h2, h3] hn = h3
|
🎉 总结一句超级大白话
RNN 就像让一个小机器人读句子,他每读一个词,就把这个词和之前的记忆混合,更新自己的理解,一直到把整句话读完。
整个代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
| import torch import torch.nn as nn
def dm1_rnn_base(): input_size = 5 hidden_size = 6 num_layers = 1 model = nn.RNN(input_size, hidden_size, num_layers)
sequence_len = 1 batch_size = 3 x0 = torch.randn(sequence_len, batch_size, input_size)
h0 = torch.randn(num_layers, batch_size, hidden_size)
output, hn = model(x0, h0)
print(f'output--》{output}') print('*'*80) print(f'hn--》{hn}')
def dm2_rnn_len(): input_size = 5 hidden_size = 6 num_layers = 1 model = nn.RNN(input_size, hidden_size, num_layers)
sequence_len = 4 batch_size = 3 x0 = torch.randn(sequence_len, batch_size, input_size) print(f'x0 -->{x0.shape}')
h0 = torch.randn(num_layers, batch_size, hidden_size) print(f'h0--》{h0.shape}')
output, hn = model(x0, h0) print("一次性送入模型") print(f'output--》{output}') print('*'*80) print(f'hn--》{hn}') print('&'*80)
def dm3_rnn_batch(): input_size = 5 hidden_size = 6 num_layers = 1 model = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
sequence_len = 4 batch_size = 3 x0 = torch.randn(batch_size, sequence_len, input_size) print(f'x0 -->{x0.shape}')
h0 = torch.randn(num_layers, batch_size, hidden_size) print(f'h0--》{h0.shape}')
output, hn = model(x0, h0) print("一次性送入模型") print(f'output--》{output}') print('*'*80) print(f'hn--》{hn}') print('&'*80)
def dm4_rnn_numlayers(): input_size = 5 hidden_size = 6 num_layers = 2 model = nn.RNN(input_size, hidden_size, num_layers=2)
sequence_len = 4 batch_size = 3 x0 = torch.randn(sequence_len, batch_size, input_size) print(f'x0 -->{x0.shape}')
h0 = torch.randn(num_layers, batch_size, hidden_size) print(f'h0--》{h0.shape}')
output, hn = model(x0, h0) print("一次性送入模型") print(f'output--》{output}') print('*'*80) print(f'hn--》{hn}') print('&'*80)
def dm5_rnn_1(): rnn = nn.RNN(5, 6) x = torch.randn(3, 4, 5) output, hn = rnn(x) print(f'output--》{output}') print(f'hn--》{hn}') if __name__ == '__main__': dm5_rnn_1()
|
已知:
现在我创建了一个 rnn,输入的参数是 input 和 h0。input 的形状是(a,b,c)。h0 的形状是(d,e,f)请问
$$output,hn=rnn(input,h0)$$
运行之后得到的两个参数的形状是什么
input 的形状 (a, b, c)
seq_len = a
batch = b
input_size = c
h0 的形状 (d, e, f)
num_layers = d
batch = e(必须等于 b)
hidden_size = f
输出形状:
output(每个时间步的输出):
$$\text{output.shape} = (seq_len, batch, hidden_size) = (a, b, f)$$
hn(最后时间步的隐藏状态):
$$ \text{hn.shape} = (num_layers, batch, hidden_size) = (d, b, f)$$
注意:e 必须等于 b,否则会报错。
所以 单向 RNN 的输出形状总结为:
1 2
| output.shape = (a, b, f) hn.shape = (d, b, f)
|
对于下面这段代码来说:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
input_size = 5 hidden_size = 6 num_layers = 1 model = nn.RNN(input_size, hidden_size, num_layers)
sequence_len = 1 batch_size = 3 x0 = torch.randn(sequence_len, batch_size, input_size)
h0 = torch.randn(num_layers, batch_size, hidden_size)
output, hn = model(x0, h0)
print(f'output--》{output}') print('*'*80) print(f'hn--》{hn}')
|
已知参数:
input_size = 5
hidden_size = 6
num_layers = 1
sequence_len = 1
batch_size = 3
构建的 RNN:
1
| model = nn.RNN(input_size, hidden_size, num_layers)
|
输入:
1 2
| x0.shape = (sequence_len, batch_size, input_size) = (1, 3, 5) h0.shape = (num_layers, batch_size, hidden_size) = (1, 3, 6)
|
输出规则(单向 RNN,batch_first=False):
output 的形状:
1
| output.shape = (seq_len, batch, hidden_size) = (1, 3, 6)
|
hn 的形状:
1
| hn.shape = (num_layers, batch, hidden_size) = (1, 3, 6)
|
✅ 用你的变量表示:
1 2
| output.shape = (sequence_len, batch_size, hidden_size) hn.shape = (num_layers, batch_size, hidden_size)
|
所以在这段代码里,output 和 hn 的维度都是 (1, 3, 6),只是含义不同:
output:每个时间步的输出
hn:最后一个时间步的隐藏状态
总结一下就是: output 作为下一个输入,除了input_size的维度变成hidden_size(可以想象成为 CNN 的神经元从 input_size->hidden_size)以外其他的都不变,而 hn 直接就是都不变的。