seed = 42
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
tf.random.set_seed(seed)
数据处理
seed
通过设置相同的种子值,你可以确保在不同运行中生成的随机数序列是相同的。这对于确保实验的可重复性非常重要,尤其是在机器学习和深度学习中,因为模型的初始化权重和训练过程中的随机性可能会影响最终的结果。
# 读取CSV数据
data_path = 'D:\\newroadnlp\\领角鸮.csv' # 替换为你的CSV文件路径
data = pd.read_csv(data_path, encoding='gbk') # 尝试使用 gbk 编码
data = data[data['高度'] > 0] # 过滤掉高度小于等于0的数据
data = data.sort_values(by=['终端', '时间']).reset_index(drop=True) # 确保数据按个体和时间排序,并重置索引
encoding='gbk':
这个参数指定了文件的编码格式。GBK 是一种常见的编码格式,主要用于简体中文字符。如果你的 CSV 文件是用 GBK 编码保存的,那么使用这个参数可以确保正确地读取文件中的中文字符。 encoding='gbk': 这个参数指定了文件的编码格式。GBK 是一种常见的编码格式,主要用于简体中文字符。如果你的 CSV 文件是用 GBK 编码保存的,那么使用这个参数可以确保正确地读取文件中的中文字符。
过滤掉高度小于等于0的数据可以提高数据质量、改善模型性能、符合业务逻辑,并生成更清晰的数据可视化结果。在数据处理过程中,确保数据的有效性和准确性是非常重要的。
features = data[['航向', '高度', '温度', '电压', '运动量']].values
targets = data[['经度', '纬度']].values
# 数据归一化
scaler_features = MinMaxScaler()
scaler_targets = MinMaxScaler()
MinMaxScaler
创建两个 MinMaxScaler
对象,用于对特征和目标变量进行归一化处理。
features_normalized = scaler_features.fit_transform(features)
targets_normalized = scaler_targets.fit_transform(targets)
fit_transform 方法
fit_transform 是 scikit-learn 中 MinMaxScaler 类的一个方法,它结合了 fit 和 transform 两个步骤: fit: 计算数据的最小值和最大值,用于后续的缩放。 transform: 使用计算得到的最小值和最大值对数据进行缩放。
sequences = []
sequences = []
seq_length = 12 # 可调参数:LSTM的输入序列长度
for individual in data['终端'].unique():
individual_data = data[data['终端'] == individual]
individual_features = features_normalized[individual_data.index]
individual_targets = targets_normalized[individual_data.index]
for i in range(len(individual_features) - seq_length):
seq_x = individual_features[i:i+seq_length]
seq_y = individual_targets[i+seq_length] # 预测下一个时间点的经度和纬度
sequences.append((seq_x, seq_y))
unique() 方法
unique() 方法返回一个包含唯一值的数组。它通常用于数据框的某一列,以获取该列中的所有唯一值。 返回值: 一个包含唯一值的 NumPy 数组。 排序: 返回的唯一值数组是排序后的。
# 分割训练集和测试集
train_sequences, test_sequences = train_test_split(sequences, test_size=0.2, random_state=42)
自定义一个dataset的数据类
# 自定义Dataset类
class BirdDataset(Dataset):
def __init__(self, sequences):
self.sequences = sequences
def __len__(self):
return len(self.sequences)
def __getitem__(self, idx)://__getitem__: 根据索引返回数据集中的一个样本。
seq_x, seq_y = self.sequences[idx]
return torch.tensor(seq_x, dtype=torch.float32), torch.tensor(seq_y, dtype=torch.float32)
数据加载器
train_loader = DataLoader(BirdDataset(train_sequences), batch_size=32, shuffle=True)
test_loader = DataLoader(BirdDataset(test_sequences), batch_size=32, shuffle=False)
注意力机制
定义attention机制
# 定义Attention机制
class Attention(nn.Module):
def __init__(self, hidden_size):
super(Attention, self).__init__()
self.attention_weights = nn.Linear(hidden_size, 1, bias=False)
def forward(self, lstm_output):
attention_scores = self.attention_weights(lstm_output).squeeze(-1) # (batch_size, seq_length)
attention_weights = torch.softmax(attention_scores, dim=1).unsqueeze(-1) # (batch_size, seq_length, 1)
weighted_output = lstm_output * attention_weights # 加权输出
return torch.sum(weighted_output, dim=1) # 时间维度上加权平均
hidden_size
: 这个参数指定了LSTM输出的特征维度,也就是每个时间步长上LSTM隐藏状态的维度。self.attention_weights = nn.Linear(hidden_size, 1, bias=False)
: 这里定义了一个线性层(也称为全连接层或密集层),它将LSTM的输出从hidden_size
维映射到1维。这是为了计算每个时间步长的注意力得分。bias=False
表示不使用偏置项。lstm_output
: LSTM的输出,形状通常为(batch_size, seq_length, hidden_size)
,其中batch_size
是批大小,seq_length
是序列长度,hidden_size
是隐藏状态的特征维度
attention_scores = self.attention_weights(lstm_output).squeeze(-1)
: 通过之前定义的线性层计算注意力得分,然后使用.squeeze(-1)
去除最后一个维度(因为输出形状为(batch_size, seq_length, 1)
,我们只需要(batch_size, seq_length)
)。attention_weights = torch.softmax(attention_scores, dim=1).unsqueeze(-1)
: 使用softmax函数将注意力得分转换为注意力权重,确保所有权重加起来为1。然后,使用.unsqueeze(-1)
增加一个维度,以便后续与LSTM输出进行元素乘法。weighted_output = lstm_output * attention_weights
: 对LSTM的输出进行加权,即每个时间步长的输出 乘以 相应的注意力权重。return torch.sum(weighted_output, dim=1)
: 在时间维度(dim=1
)上对加权后的输出进行求和,得到加权平均后的输出。这个输出将用于后续的处理或预测,形状为(batch_size, hidden_size)
。-
LSTM模型
class LSTMWithAttention(nn.Module):
def __init__(self, input_size=5, hidden_size=64, output_size=2, num_layers=2):
super(LSTMWithAttention, self).__init__()
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
self.attention = Attention(hidden_size)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
lstm_out, _ = self.lstm(x)
out = self.attention(lstm_out)
return self.fc(out)
input_size
: 输入数据的特征维度。hidden_size
: LSTM隐藏状态的维度。output_size
: 最终输出层的特征维度,通常对应于分类任务的类别数。num_layers
: LSTM的层数。多层LSTM可以捕获更复杂的序列信息。self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
: 定义了一个LSTM层。batch_first=True
表示输入张量的形状应为(batch_size, seq_length, input_size)
。self.attention = Attention(hidden_size)
: 定义了一个注意力机制模块,它接受LSTM隐藏状态的维度作为输入。这里假设Attention
类已经在其他地方被定义,并且它的接口与您的LSTMWithAttention
类中的使用方式相匹配。self.fc = nn.Linear(hidden_size, output_size)
: 定义了一个全连接层,用于将注意力加权后的LSTM输出映射到最终的输出维度。
初始化模型
model = LSTMWithAttention()
criterion = nn.MSELoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=0.001)
train_losses = []
test_losses = []
- LSTMWithAttention():
- LSTM(长短期记忆网络)是一种特殊的循环神经网络(RNN),它能够捕捉序列数据中的长期依赖关系。
- 注意力机制(Attention Mechanism)是一种让模型在处理输入序列时能够动态地聚焦于序列的不同部分的技术,从而提高了模型处理序列数据的能力。
LSTMWithAttention()
这个函数或类可能是自定义的,它结合了LSTM和注意力机制,用于处理具有时间依赖性的数据,如时间序列分析、自然语言处理等任务。
- nn.MSELoss():
- 这是PyTorch中定义的一个损失函数,用于计算模型预测值和真实值之间的均方误差。
- 公式为:MSE = n1∑i=1n(yi−y^i)2,其中 yi 是真实值,y^i 是预测值,n 是样本数量。
- MSE损失函数通常用于回归任务中,因为它衡量的是预测值与实际值之间差异的平方的平均值。
- torch.optim.AdamW(model.parameters(), lr=0.001):
- AdamW是Adam优化器的一个变体,它在Adam的基础上加入了权重衰减(L2正则化),有助于防止模型过拟合。
model.parameters()
指的是模型中的所有可训练参数。lr=0.001
指定了学习率,即参数更新的步长大小。学习率是一个超参数,需要调优以获得最佳性能。- 优化器的任务是更新模型的参数,以最小化损失函数
训练模型
# 设置训练的总轮次(epochs),这是一个可调整的超参数
epochs = 200 # 可调整
# 开始训练循环,遍历每一个epoch
for epoch in range(epochs):
# 将模型设置为训练模式,这对于某些层(如Dropout和BatchNorm)是必要的
model.train()
# 初始化当前epoch的训练损失为0
train_loss = 0
# 开始数据加载循环,遍历训练数据加载器中的每一个批次
for seq_x, seq_y in train_loader:
# 在每次迭代开始前清零优化器中的梯度,因为PyTorch会默认累加梯度
optimizer.zero_grad()
# 执行前向传播,将输入序列seq_x传递给模型,得到预测输出output
output = model(seq_x)
# 使用损失函数计算预测输出output与真实标签seq_y之间的损失
loss = criterion(output, seq_y)
# 执行反向传播,计算损失函数关于模型参数的梯度
loss.backward()
# 根据计算得到的梯度,优化器更新模型的参数
optimizer.step()
# 将当前批次的损失累加到train_loss中
train_loss += loss.item()
# 遍历完所有批次后,计算平均训练损失
train_loss /= len(train_loader)
# 将当前epoch的平均训练损失添加到train_losses列表中,以便后续分析
# 注意:train_losses列表需要在代码的其他部分被初始化(例如:train_losses = [])
train_losses.append(train_loss)
测试
# 将模型设置为评估(或推理)模式
model.eval()
# 初始化测试损失为0
test_loss = 0
# 使用torch.no_grad()上下文管理器来禁用梯度计算,以节省内存和计算量
# 因为在测试阶段我们不需要进行反向传播
with torch.no_grad():
# 开始遍历测试数据加载器中的每一个批次
for seq_x, seq_y in test_loader:
# 执行前向传播,得到预测输出
output = model(seq_x)
# 计算预测输出与真实标签之间的损失
# 注意:在测试阶段,我们通常也会计算损失,但主要是为了评估模型性能,而不是用于训练
loss = criterion(output, seq_y)
# 将当前批次的损失累加到test_loss中
test_loss += loss.item()
# 遍历完所有批次后,计算平均测试损失
test_loss /= len(test_loader)
# 将当前epoch的平均测试损失添加到test_losses列表中,以便后续分析
# 注意:test_losses列表需要在代码的其他部分被初始化(例如:test_losses = [])
test_losses.append(test_loss)
# 打印当前epoch的训练损失和测试损失
# 注意:由于这段代码可能是在训练循环的外部,因此我们需要使用epoch+1来显示正确的epoch数
# 同时,我们也假设train_loss是在最近的训练epoch中计算得到的
print(f'Epoch {epoch+1}/{epochs}, Train Loss: {train_loss:.4f}, Test Loss: {test_loss:.4f}')
可视化
# 可视化训练和测试损失
plt.figure(figsize=(10, 5))
plt.plot(range(1, epochs + 1), train_losses, label='Train Loss')
plt.plot(range(1, epochs + 1), test_losses, label='Test Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Training and Test Loss over Epochs')
torch.save(model.state_dict(), 'bird_lstm_attention_model.pth')
# 反归一化测试集预测
# 反归一化测试集预测
model.eval()
test_predictions = []
test_targets = []
with torch.no_grad():
for seq_x, seq_y in test_loader:
output = model(seq_x)
test_predictions.extend(output.numpy())
test_targets.extend(seq_y.numpy())
test_predictions = scaler_targets.inverse_transform(test_predictions)
test_targets = scaler_targets.inverse_transform(test_targets)
# 输出一些测试结果对比
for i in range(10):
print(f"实际经纬度: {test_targets[i]}, 预测经纬度: {test_predictions[i]}")
# 将模型设置为评估模式
model.eval()
# 初始化两个空列表,用于存储预测结果和实际目标值
test_predictions = []
test_targets = []
# 使用torch.no_grad()上下文管理器禁用梯度计算
with torch.no_grad():
# 遍历测试数据加载器中的每个批次
for seq_x, seq_y in test_loader:
# 执行前向传播,得到预测输出
output = model(seq_x)
# 将预测输出和实际目标值从PyTorch张量转换为NumPy数组,并扩展到列表中
# 注意:这里假设output和seq_y已经是批处理后的结果,且可以直接转换为NumPy数组
# 如果它们包含多个维度(例如,批次大小、序列长度、特征数),则可能需要进一步处理
test_predictions.extend(output.numpy())
test_targets.extend(seq_y.numpy())
# 使用目标值缩放器(scaler_targets)将预测结果和实际目标值从归一化状态反变换回原始尺度
# 假设scaler_targets是一个已经拟合了训练数据的缩放器对象
test_predictions = scaler_targets.inverse_transform(test_predictions)
test_targets = scaler_targets.inverse_transform(test_targets)
# 可视化预测和实际值对比
plt.figure(figsize=(10, 5))
plt.plot(test_targets[:50, 0], test_targets[:50, 1], 'bo-', label='True', alpha=0.7)
plt.plot(test_predictions[:50, 0], test_predictions[:50, 1], 'ro-', label='Pred', alpha=0.7)
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.title('True vs Pred')
plt.legend()
plt.show()
# 评估函数
def evaluate_metrics(y_true, y_pred):
r2 = r2_score(y_true, y_pred)
if (y_true > 0).all() and (y_pred > 0).all():
msle = mean_squared_log_error(y_true, y_pred)
else:
msle = None
print("MSLE cannot be computed due to non-positive values in y_true or y_pred.")
mse = mean_squared_error(y_true, y_pred)
rmse = mse ** 0.5
epsilon = 1e-10
mape = np.mean(np.abs((y_true - y_pred) / (y_true + epsilon))) * 100
mae = mean_absolute_error(y_true, y_pred)
medae = median_absolute_error(y_true, y_pred)
print(f'R² Score: {r2}')
print(f'Mean Squared Logarithmic Error (MSLE): {msle if msle is not None else "N/A"}')
print(f'Mean Squared Error (MSE): {mse}')
print(f'Root Mean Squared Error (RMSE): {rmse}')
print(f'Mean Absolute Percentage Error (MAPE): {mape}%')
print(f'Mean Absolute Error (MAE): {mae}')
print(f'Median Absolute Error (MedAE): {medae}')
# 评估测试集的预测值
evaluate_metrics(test_targets, test_predictions)
# 定义评估函数,接收真实值y_true和预测值y_pred作为输入
def evaluate_metrics(y_true, y_pred):
# 计算R²分数,评估模型对数据的拟合程度
r2 = r2_score(y_true, y_pred)
# 检查y_true和y_pred是否全部为正数,因为MSLE要求输入为正数
if (y_true > 0).all() and (y_pred > 0).all():
# 如果都是正数,则计算均方对数误差(MSLE)
msle = mean_squared_log_error(y_true, y_pred)
else:
# 如果不是全部为正数,则MSLE无法计算,设置为None并打印消息
msle = None
print("MSLE cannot be computed due to non-positive values in y_true or y_pred.")
# 计算均方误差(MSE),评估预测值与真实值之间差异的平方的平均值
mse = mean_squared_error(y_true, y_pred)
# 计算均方根误差(RMSE),是MSE的平方根,与原始数据的单位相同
rmse = mse ** 0.5
# 设置一个非常小的数epsilon,以避免在计算MAPE时出现除以零的情况
epsilon = 1e-10
# 计算平均绝对百分比误差(MAPE),评估预测值相对于真实值的平均误差百分比
mape = np.mean(np.abs((y_true - y_pred) / (y_true + epsilon))) * 100
# 计算平均绝对误差(MAE),评估预测值与真实值之间差异的平均绝对值
mae = mean_absolute_error(y_true, y_pred)
# 计算中位数绝对误差(MedAE),评估预测值与真实值之间差异的中位数绝对值
medae = median_absolute_error(y_true, y_pred)
# 打印所有评估指标的结果
print(f'R² Score: {r2}') # R²分数越接近1,模型拟合越好
print(f'Mean Squared Logarithmic Error (MSLE): {msle if msle is not None else "N/A"}') # MSLE适用于正数数据,衡量对数尺度上的误差
print(f'Mean Squared Error (MSE): {mse}') # MSE衡量预测值与真实值之间差异的平方的平均值
print(f'Root Mean Squared Error (RMSE): {rmse}') # RMSE是MSE的平方根,与数据单位相同,更易于解释
print(f'Mean Absolute Percentage Error (MAPE): {mape}%') # MAPE衡量预测值相对于真实值的平均误差百分比
print(f'Mean Absolute Error (MAE): {mae}') # MAE衡量预测值与真实值之间差异的平均绝对值
print(f'Median Absolute Error (MedAE): {medae}') # MedAE衡量预测值与真实值之间差异的中位数绝对值,对异常值更鲁棒
# 使用评估函数来评估测试集的预测值
# 假设test_targets和test_predictions是已经准备好的真实值和预测值数组
evaluate_metrics(test_targets, test_predictions)
开始调参
调参前
Epoch 200/200, Train Loss: 0.0165, Test Loss: 0.0150
R² Score: 0.7550792895044637
Mean Squared Logarithmic Error (MSLE): 3.485665415130247e-08
Mean Squared Error (MSE): 1.540669729746743e-05
Root Mean Squared Error (RMSE): 0.003925136596026619
Mean Absolute Percentage Error (MAPE): 0.010120023186038158%
Mean Absolute Error (MAE): 0.0024339692627736165
Median Absolute Error (MedAE): 0.0016457255771005208
开始第一次调参
epochs = 300 # 可调整 原200
seq_length = 12 # 可调参数:LSTM的输入序列长度 18
optimizer = torch.optim.AdamW(model.parameters(), lr=0.01)
效果-----
R² Score: 0.7470792800906856
Mean Squared Logarithmic Error (MSLE): 1.964423115565028e-08
Mean Squared Error (MSE): 1.0913569636720606e-05
Root Mean Squared Error (RMSE): 0.003303569226869721
Mean Absolute Percentage Error (MAPE): 0.005745777081096551%
Mean Absolute Error (MAE): 0.001926750160718343
Median Absolute Error (MedAE): 0.0011407200342414825
Epoch 300/300, Train Loss: 0.0086, Test Loss: 0.0159
实际经纬度: [109.99594 18.50601], 预测经纬度: [109.99434808 18.50533784]