-
隐藏状态(Hidden State):
- 想象一下你在和朋友聊天,你们聊了很多话题,你的大脑会记住你们聊过的内容,这样你就能在对话中引用之前的内容。在RNN这类神经网络中,隐藏状态就像是你的大脑记忆,它记录了到目前为止处理的所有信息,这样网络就能在下一步的计算中使用这些信息。
-
细胞状态(Cell State):
- 还是用聊天的例子,细胞状态就像是你们聊天的笔记,它详细记录了每个话题的细节。在LSTM这种特殊类型的神经网络中,细胞状态是一种特殊的记忆,它不仅记得你们聊了什么,还记得每个话题的具体内容。这样,即使你们聊了很长时间,LSTM也能回忆起很久以前的细节。
在LSTM中,隐藏状态和细胞状态一起工作,隐藏状态帮助网络决定下一步应该输出什么,而细胞状态则确保网络不会忘记任何重要的信息。这就像是你在聊天时,既能回应朋友的话(隐藏状态),又能记住你们之前聊过的所有细节(细胞状态)。这种机制使得LSTM非常适合处理需要记忆的信息,比如语音识别或者翻译句子,因为它能够理解和记住整个对话的历史。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import numpy as np
import random# 读取数据
with open('eng.txt', 'r', encoding='utf-8') as f:text = f.read() # 从文件中读取整个文本内容# 创建字符到索引的映射
chars = sorted(list(set(text))) # 获取文本中所有独特的字符,并排序
char_to_idx = {ch: i for i, ch in enumerate(chars)} # 创建字符到索引的字典
idx_to_char = {i: ch for i, ch in enumerate(chars)} # 创建索引到字符的字典# 转换为索引序列
data = [char_to_idx[ch] for ch in text] # 将文本中的每个字符转换为其对应的索引class CharDataset(Dataset):def __init__(self, data, seq_length=64):self.data = data # 存储数据self.seq_length = seq_length # 序列长度# 检查数据长度是否足够if len(self.data) <= self.seq_length:raise ValueError(f"Data length ({len(self.data)}) is not sufficient for the sequence length ({self.seq_length}).")def __len__(self):# 返回不小于0的长度return max(0, len(self.data) - self.seq_length)def __getitem__(self, idx):if idx >= len(self): # 检查索引是否超出范围raise IndexError(f"Index {idx} out of range for dataset of length {len(self)}.")input_seq = self.data[idx:idx + self.seq_length] # 获取输入序列target_seq = self.data[idx + 1:idx + self.seq_length + 1] # 获取目标序列return torch.tensor(input_seq, dtype=torch.long), torch.tensor(target_seq, dtype=torch.long) # 返回输入和目标序列seq_length = 64 # 定义序列长度
batch_size = 32 # 定义批次大小# 检查数据长度是否符合要求
if len(data) <= seq_length:raise ValueError(f"Data length ({len(data)}) is too short for the given sequence length ({seq_length}).")dataset = CharDataset(data, seq_length) # 创建数据集实例
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True) # 创建数据加载器,打乱数据class CharLSTM(nn.Module):def __init__(self, vocab_size, embed_dim, hidden_dim, num_layers=1):super(CharLSTM, self).__init__() # 初始化父类self.embed = nn.Embedding(vocab_size, embed_dim) # 嵌入层self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers, batch_first=True) # LSTM 层self.fc = nn.Linear(hidden_dim, vocab_size) # 全连接层def forward(self, x, h0=None, c0=None):if h0 is None or c0 is None:h0 = torch.zeros(self.lstm.num_layers, x.size(0), self.lstm.hidden_size).to(x.device)c0 = torch.zeros(self.lstm.num_layers, x.size(0), self.lstm.hidden_size).to(x.device)x = self.embed(x) # 将输入通过嵌入层out, (h, c) = self.lstm(x, (h0, c0)) # 通过 LSTM 层out = self.fc(out) # 通过全连接层return out, (h, c) # 返回输出和隐藏状态vocab_size = len(chars) # 字符词汇表大小
embed_dim = 128 # 嵌入维度
hidden_dim = 256 # 隐藏层维度
num_layers = 2 # LSTM 层数model = CharLSTM(vocab_size, embed_dim, hidden_dim, num_layers) # 创建模型实例
criterion = nn.CrossEntropyLoss() # 定义损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001) # 定义优化器device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 选择计算设备
model.to(device) # 将模型移动到选定的设备num_epochs = 10 # 训练轮数
for epoch in range(num_epochs):model.train() # 设置模型为训练模式total_loss = 0 # 初始化总损失for inputs, targets in dataloader: # 迭代数据加载器inputs, targets = inputs.to(device), targets.to(device) # 将数据移动到设备# 初始化隐藏状态和细胞状态h0 = torch.zeros(num_layers, inputs.size(0), hidden_dim).to(device)c0 = torch.zeros(num_layers, inputs.size(0), hidden_dim).to(device)optimizer.zero_grad() # 清零梯度outputs, _ = model(inputs, h0, c0) # 前向传播,传入隐藏状态loss = criterion(outputs.view(-1, vocab_size), targets.view(-1)) # 计算损失loss.backward() # 反向传播optimizer.step() # 更新参数total_loss += loss.item() # 累加损失avg_loss = total_loss / len(dataloader) # 计算平均损失print(f'Epoch {epoch + 1}/{num_epochs}, Loss: {avg_loss:.4f}') # 打印当前 epoch 的损失def generate_text(model, start_string, length=1000, temperature=1.0):model.eval() # 设置模型为评估模式with torch.no_grad(): # 关闭梯度计算# 将起始字符串转换为索引input_seq = [char_to_idx[ch] for ch in start_string]input_seq = torch.tensor(input_seq, dtype=torch.long).unsqueeze(0).to(device) # 转换为张量并移动到设备# 初始化隐藏状态和细胞状态h, c = torch.zeros(num_layers, input_seq.size(0), hidden_dim).to(device), torch.zeros(num_layers,input_seq.size(0),hidden_dim).to(device)# 生成的文本generated_text = start_stringfor i in range(length): # 生成指定长度的文本# 模型前向传播output, (h, c) = model(input_seq, h, c)# 获取最后一个时间步的输出output = output[:, -1, :]# 应用温度调整output = output / temperatureprobs = torch.softmax(output, dim=-1)# 采样下一个字符next_idx = torch.multinomial(probs, 1).item()next_char = idx_to_char[next_idx]# 添加生成的字符到生成的文本中generated_text += next_char# 更新输入张量为新的字符input_seq = torch.tensor([[next_idx]], dtype=torch.long).to(device)return generated_text # 返回生成的文本# 生成文本
start_string = "The quick brown fox" # 起始字符串
generated_text = generate_text(model, start_string, length=1000) # 生成 500 个字符的文本print(generated_text) # 打印生成的文本
想象你和你的朋友正在计划一个大型聚会。你们每天都在讨论不同的细节,比如时间、地点、邀请哪些人等等。每次讨论后,你们都会总结到目前为止的决定,并记下还需要解决的问题。
在这个例子中:
-
嵌入后的向量序列(x):就像是你们每天讨论的笔记,里面包含了所有的细节和决策。
-
LSTM 层:就像是你们用来记录和更新聚会计划的超级笔记本。它不仅能记住你们已经决定的事情,还能记住你们需要考虑的事情。
-
输出(out):在每次讨论后,你们会有一个更新后的计划,这就是 LSTM 层的输出。它代表了基于到目前为止所有讨论的信息,你们对聚会计划的最新理解。
-
隐藏状态(h):这就像是你们总结的当前决定。比如,你们可能已经决定了聚会的日期和时间,但还没有决定地点。这个隐藏状态就是你们在当前时间点上认为最重要的信息。
-
细胞状态(c):这就像是你们的“待办事项”列表,记录了所有你们还需要讨论和决定的事情。这个细胞状态帮助你们在下次讨论时能够快速回忆起还有哪些事情没有解决。
所以,out, (h, c) = self.lstm(x, (h0, c0))
这行代码的意思是:你们把每天的讨论笔记(嵌入后的向量序列)输入到超级笔记本(LSTM 层)中,得到了最新的聚会计划(输出),并且更新了你们的当前决定(隐藏状态)和待办事项(细胞状态)。这样,无论讨论进行到哪一天,你们都能保持信息的最新状态,并准备好进行下一次讨论。