DAY 46 Inception网络及其思考
知识点回顾:
- 传统计算机视觉发展史:LeNet-->AlexNet-->VGGNet-->nceptionNet-->ResNet
> 之所以说传统,是因为现在主要是针对backbone-neck-head这样的范式做文章
-
inception模块和网络
-
特征融合方法阶段性总结:逐元素相加、逐元素相乘、concat通道数增加等
-
感受野与卷积核变体:深入理解不同模块和类的设计初衷
作业:
(一次稍微有点学术感觉的作业:)
-
对inception网络在cifar10上观察精度
-
消融实验:引入残差机制和cbam模块分别进行消融
1.对inception网络在cifar10上观察精度:
python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
# 设备配置:优先使用GPU,没有则用CPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")
# ---------------------- 1. 修正后的Inception模块和InceptionNet ----------------------
class Inception(nn.Module):
def __init__(self, in_channels):
"""
Inception模块初始化,实现多尺度特征并行提取与融合
参数:
in_channels: 输入特征图的通道数
"""
super(Inception, self).__init__()
# 1x1卷积分支:降维并提取通道间特征关系
self.branch1x1 = nn.Sequential(
nn.Conv2d(in_channels, 64, kernel_size=1),
nn.ReLU()
)
# 3x3卷积分支:先1x1降维,再3x3提取中等尺度特征
self.branch3x3 = nn.Sequential(
nn.Conv2d(in_channels, 96, kernel_size=1),
nn.ReLU(),
nn.Conv2d(96, 128, kernel_size=3, padding=1), # padding=1保持尺寸不变
nn.ReLU()
)
# 5x5卷积分支:先1x1降维,再5x5提取大尺度特征
self.branch5x5 = nn.Sequential(
nn.Conv2d(in_channels, 16, kernel_size=1),
nn.ReLU(),
nn.Conv2d(16, 32, kernel_size=5, padding=2), # padding=2保持尺寸不变
nn.ReLU()
)
# 池化分支:最大池化+1x1卷积降维
self.branch_pool = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1), # 保持尺寸不变
nn.Conv2d(in_channels, 32, kernel_size=1),
nn.ReLU()
)
def forward(self, x):
"""前向传播:四个分支并行计算,通道维度拼接"""
branch1x1 = self.branch1x1(x)
branch3x3 = self.branch3x3(x)
branch5x5 = self.branch5x5(x)
branch_pool = self.branch_pool(x)
# 通道维度拼接(dim=1),总通道数64+128+32+32=256
return torch.cat([branch1x1, branch3x3, branch5x5, branch_pool], dim=1)
class InceptionNet(nn.Module):
def __init__(self, num_classes=10):
super(InceptionNet, self).__init__()
# 修正conv1:适配CIFAR10的32×32输入(原7x7 stride=2会导致尺寸非整数)
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1), # 3x3卷积,保持32×32
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1) # 池化后变为16×16
)
self.inception1 = Inception(64) # 输入64通道,输出256通道
self.inception2 = Inception(256) # 输入256通道,输出256通道
self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) # 自适应池化到1×1,适配任意尺寸
self.fc = nn.Linear(256, num_classes) # 全连接层分类
def forward(self, x):
x = self.conv1(x) # [batch,3,32,32] → [batch,64,16,16]
x = self.inception1(x) # → [batch,256,16,16]
x = self.inception2(x) # → [batch,256,16,16]
x = self.avgpool(x) # → [batch,256,1,1]
x = torch.flatten(x, 1) # → [batch,256]
x = self.fc(x) # → [batch,10]
return x
# ---------------------- 2. 数据加载(CIFAR10) ----------------------
# 数据预处理:归一化(CIFAR10的均值和标准差)
transform = transforms.Compose([
transforms.RandomHorizontalFlip(), # 随机水平翻转,数据增强
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
# 加载训练集和测试集
train_dataset = datasets.CIFAR10(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.CIFAR10(
root='./data', train=False, download=True, transform=transform
)
# 数据加载器
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# ---------------------- 3. 训练和测试函数 ----------------------
def train(model, train_loader, criterion, optimizer, epoch):
"""训练一个epoch,返回训练损失"""
model.train() # 切换到训练模式
total_loss = 0.0
for i, (images, labels) in enumerate(train_loader):
# 数据移到指定设备
images = images.to(device)
labels = labels.to(device)
# 前向传播
outputs = model(images)
loss = criterion(outputs, labels)
# 反向传播+优化
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播
optimizer.step() # 更新参数
total_loss += loss.item()
# 打印训练进度
if (i+1) % 100 == 0:
print(f'Epoch [{epoch+1}], Step [{i+1}/{len(train_loader)}], Loss: {loss.item():.4f}')
avg_loss = total_loss / len(train_loader)
return avg_loss
def test(model, test_loader):
"""测试模型,返回测试精度"""
model.eval() # 切换到评估模式(禁用Dropout/BatchNorm的训练行为)
correct = 0
total = 0
with torch.no_grad(): # 禁用梯度计算,节省内存
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
# 获取预测结果(最大概率的类别)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f'Test Accuracy of the model on the 10000 test images: {accuracy:.2f} %')
return accuracy
# ---------------------- 4. 主训练流程 ----------------------
# 初始化模型、损失函数、优化器
model = InceptionNet(num_classes=10).to(device)
criterion = nn.CrossEntropyLoss() # 分类任务用交叉熵损失
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam优化器
# 训练参数
num_epochs = 30
train_losses = []
test_accs = []
# 开始训练
print("开始训练InceptionNet on CIFAR10...")
for epoch in range(num_epochs):
# 训练
train_loss = train(model, train_loader, criterion, optimizer, epoch)
train_losses.append(train_loss)
# 测试
test_acc = test(model, test_loader)
test_accs.append(test_acc)
# ---------------------- 5. 结果可视化 ----------------------
plt.figure(figsize=(12, 4))
# 训练损失曲线
plt.subplot(1, 2, 1)
plt.plot(range(1, num_epochs+1), train_losses, 'b-', label='Train Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss')
plt.legend()
# 测试精度曲线
plt.subplot(1, 2, 2)
plt.plot(range(1, num_epochs+1), test_accs, 'r-', label='Test Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.title('Test Accuracy')
plt.legend()
plt.tight_layout()
plt.savefig('inception_cifar10_result.png')
plt.show()
# 保存模型
torch.save(model.state_dict(), 'inception_cifar10.pth')
print("训练完成,模型已保存为 inception_cifar10.pth")
- 消融实验:引入残差机制和cbam模块分别进行消融
python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy as np
# 设备配置
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"使用设备: {device}")
# ---------------------- 1. 定义核心模块(CBAM + 残差Inception) ----------------------
# CBAM注意力模块(通道注意力+空间注意力)
class CBAM(nn.Module):
def __init__(self, channels, reduction=16):
super(CBAM, self).__init__()
# 通道注意力:压缩空间维度,关注重要通道
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channels, channels // reduction),
nn.ReLU(),
nn.Linear(channels // reduction, channels)
)
# 空间注意力:压缩通道维度,关注重要空间位置
self.spatial = nn.Sequential(
nn.Conv2d(2, 1, kernel_size=3, padding=1, bias=False),
nn.Sigmoid()
)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
# 通道注意力计算
b, c, h, w = x.size()
avg_out = self.fc(self.avg_pool(x).view(b, c)).view(b, c, 1, 1)
max_out = self.fc(self.max_pool(x).view(b, c)).view(b, c, 1, 1)
channel_att = self.sigmoid(avg_out + max_out)
x = x * channel_att # 通道维度加权
# 空间注意力计算
avg_out = torch.mean(x, dim=1, keepdim=True)
max_out, _ = torch.max(x, dim=1, keepdim=True)
spatial_att = self.spatial(torch.cat([avg_out, max_out], dim=1))
x = x * spatial_att # 空间维度加权
return x
# 基础Inception模块(基线)
class Inception(nn.Module):
def __init__(self, in_channels):
super(Inception, self).__init__()
self.branch1x1 = nn.Sequential(
nn.Conv2d(in_channels, 64, kernel_size=1),
nn.ReLU()
)
self.branch3x3 = nn.Sequential(
nn.Conv2d(in_channels, 96, kernel_size=1),
nn.ReLU(),
nn.Conv2d(96, 128, kernel_size=3, padding=1),
nn.ReLU()
)
self.branch5x5 = nn.Sequential(
nn.Conv2d(in_channels, 16, kernel_size=1),
nn.ReLU(),
nn.Conv2d(16, 32, kernel_size=5, padding=2),
nn.ReLU()
)
self.branch_pool = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
nn.Conv2d(in_channels, 32, kernel_size=1),
nn.ReLU()
)
def forward(self, x):
branch1x1 = self.branch1x1(x)
branch3x3 = self.branch3x3(x)
branch5x5 = self.branch5x5(x)
branch_pool = self.branch_pool(x)
return torch.cat([branch1x1, branch3x3, branch5x5, branch_pool], dim=1)
# 带残差的Inception模块(消融组1)
class ResInception(nn.Module):
def __init__(self, in_channels):
super(ResInception, self).__init__()
# 基础Inception分支
self.inception = Inception(in_channels)
# 残差捷径:如果输入通道≠输出通道(256),用1x1卷积匹配维度
self.shortcut = nn.Sequential()
if in_channels != 256:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, 256, kernel_size=1),
nn.ReLU()
)
def forward(self, x):
# 残差连接:Inception输出 + 捷径输出(逐元素相加)
inception_out = self.inception(x)
shortcut_out = self.shortcut(x)
return nn.ReLU()(inception_out + shortcut_out)
# ---------------------- 2. 构建3个消融实验模型 ----------------------
# 模型1:基线模型(基础InceptionNet)
class BaselineInceptionNet(nn.Module):
def __init__(self, num_classes=10):
super(BaselineInceptionNet, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
self.inception1 = Inception(64)
self.inception2 = Inception(256)
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(256, num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.inception1(x)
x = self.inception2(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
# 模型2:InceptionNet + 残差(消融组1)
class ResInceptionNet(nn.Module):
def __init__(self, num_classes=10):
super(ResInceptionNet, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
self.inception1 = ResInception(64) # 替换为带残差的Inception
self.inception2 = ResInception(256)
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(256, num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.inception1(x)
x = self.inception2(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
# 模型3:InceptionNet + CBAM(消融组2)
class CBAMInceptionNet(nn.Module):
def __init__(self, num_classes=10):
super(CBAMInceptionNet, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
self.inception1 = Inception(64)
self.cbam1 = CBAM(256) # Inception1后加CBAM
self.inception2 = Inception(256)
self.cbam2 = CBAM(256) # Inception2后加CBAM
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(256, num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.inception1(x)
x = self.cbam1(x) # CBAM增强特征
x = self.inception2(x)
x = self.cbam2(x) # CBAM增强特征
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
# ---------------------- 3. 数据加载(与基线一致,保证变量唯一) ----------------------
transform = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# ---------------------- 4. 训练/测试函数(通用,适配所有模型) ----------------------
def train_model(model, train_loader, criterion, optimizer, num_epochs=10):
"""训练模型,返回训练损失和测试精度列表"""
model.to(device)
train_losses = []
test_accs = []
for epoch in range(num_epochs):
# 训练阶段
model.train()
total_loss = 0.0
for i, (images, labels) in enumerate(train_loader):
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
if (i+1) % 100 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], Loss: {loss.item():.4f}')
# 记录训练损失
avg_loss = total_loss / len(train_loader)
train_losses.append(avg_loss)
# 测试阶段
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
acc = 100 * correct / total
test_accs.append(acc)
print(f'Epoch [{epoch+1}], Test Accuracy: {acc:.2f} %')
return train_losses, test_accs
# ---------------------- 5. 执行消融实验 ----------------------
# 超参数(所有模型保持一致)
num_epochs = 10
lr = 0.001
criterion = nn.CrossEntropyLoss()
# 实验1:训练基线模型
print("\n========== 训练基线模型(BaselineInceptionNet) ==========")
baseline_model = BaselineInceptionNet()
baseline_optimizer = optim.Adam(baseline_model.parameters(), lr=lr)
baseline_losses, baseline_accs = train_model(
baseline_model, train_loader, criterion, baseline_optimizer, num_epochs
)
# 实验2:训练残差版模型
print("\n========== 训练残差版模型(ResInceptionNet) ==========")
res_model = ResInceptionNet()
res_optimizer = optim.Adam(res_model.parameters(), lr=lr)
res_losses, res_accs = train_model(
res_model, train_loader, criterion, res_optimizer, num_epochs
)
# 实验3:训练CBAM版模型
print("\n========== 训练CBAM版模型(CBAMInceptionNet) ==========")
cbam_model = CBAMInceptionNet()
cbam_optimizer = optim.Adam(cbam_model.parameters(), lr=lr)
cbam_losses, cbam_accs = train_model(
cbam_model, train_loader, criterion, cbam_optimizer, num_epochs
)
# ---------------------- 6. 消融实验结果可视化与对比 ----------------------
plt.figure(figsize=(15, 6))
# 子图1:训练损失对比
plt.subplot(1, 2, 1)
plt.plot(range(1, num_epochs+1), baseline_losses, 'b-', label='Baseline')
plt.plot(range(1, num_epochs+1), res_losses, 'r-', label='Inception + Residual')
plt.plot(range(1, num_epochs+1), cbam_losses, 'g-', label='Inception + CBAM')
plt.xlabel('Epoch')
plt.ylabel('Training Loss')
plt.title('Ablation Experiment: Training Loss')
plt.legend()
plt.grid(alpha=0.3)
# 子图2:测试精度对比
plt.subplot(1, 2, 2)
plt.plot(range(1, num_epochs+1), baseline_accs, 'b-', label='Baseline')
plt.plot(range(1, num_epochs+1), res_accs, 'r-', label='Inception + Residual')
plt.plot(range(1, num_epochs+1), cbam_accs, 'g-', label='Inception + CBAM')
plt.xlabel('Epoch')
plt.ylabel('Test Accuracy (%)')
plt.title('Ablation Experiment: Test Accuracy')
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('ablation_experiment_result.png')
plt.show()
# 打印最终精度对比
print("\n========== 消融实验最终结果 ==========")
print(f"基线模型最终精度: {baseline_accs[-1]:.2f} %")
print(f"残差版模型最终精度: {res_accs[-1]:.2f} % (提升: {res_accs[-1]-baseline_accs[-1]:.2f} %)")
print(f"CBAM版模型最终精度: {cbam_accs[-1]:.2f} % (提升: {cbam_accs[-1]-baseline_accs[-1]:.2f} %)")
# 保存所有模型
torch.save(baseline_model.state_dict(), 'baseline_inception.pth')
torch.save(res_model.state_dict(), 'res_inception.pth')
torch.save(cbam_model.state_dict(), 'cbam_inception.pth')
print("\n所有模型已保存,消融实验完成!")
DAY 46 序列预测任务介绍
知识点回顾
- 序列预测介绍
a. 单步预测
b. 多步预测的2种方式
-
序列数据的处理:滑动窗口
-
多输入多输出任务的思路
-
经典机器学习在序列任务上的劣势;以随机森林为例
作业:手动构造类似的数据集(如cosx数据),观察不同的机器学习模型的差异
python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LinearRegression
from xgboost import XGBRegressor
from sklearn.metrics import mean_squared_error
# =============================================================
# ===== 步骤1:构造cosx为核心的合成时间序列数据集 =====
# =============================================================
# 生成cosx基础的时间序列(加入趋势项和噪声,模拟真实时序数据)
x = np.linspace(0, 100, 1000) # 1000个时间步,范围0-100
# 核心信号:cos(x) + 线性趋势 + 随机噪声
y = np.cos(x) + 0.05 * x + np.random.normal(0, 0.3, 1000)
# 定义关键参数(与原代码一致,保证可比性)
train_size = int(len(y) * 0.8) # 80%训练集,20%测试集
seq_length = 30 # 滑动窗口长度:用前30个时间步预测下1个
# =============================================================
# ===== 步骤2:数据预处理(标准化 + 滑动窗口构造序列) =====
# =============================================================
# 标准化(仅基于训练集拟合,避免数据泄露)
train_data_raw = y[:train_size]
scaler = MinMaxScaler(feature_range=(0, 1))
scaler.fit(train_data_raw.reshape(-1, 1)) # 必须reshape为二维(样本数, 特征数)
scaled_y = scaler.transform(y.reshape(-1, 1)).flatten()
# 滑动窗口构造时序数据集(前seq_length个值预测下1个值)
def create_sequences(data, seq_length):
X, y = [], []
for i in range(len(data) - seq_length):
X.append(data[i:i+seq_length]) # 输入:前30个时间步
y.append(data[i+seq_length]) # 输出:第31个时间步
return np.array(X), np.array(y)
# 对标准化后的数据构造序列
all_X, all_y = create_sequences(scaled_y, seq_length)
# 划分训练/测试集(注意滑动窗口后的索引对齐)
split_idx = train_size - seq_length
X_train = all_X[:split_idx]
y_train = all_y[:split_idx]
X_test = all_X[split_idx:]
y_test = all_y[split_idx:]
# 重塑输入形状:ML模型需要二维输入(样本数, 特征数),而非RNN的三维
X_train_ml = X_train.reshape(X_train.shape[0], -1) # (770, 30)
X_test_ml = X_test.reshape(X_test.shape[0], -1) # (200, 30)
# =============================================================
# ===== 步骤3:训练多个机器学习模型并评估 =====
# =============================================================
# 定义要对比的模型(随机森林、线性回归、XGBoost)
models = {
"线性回归": LinearRegression(n_jobs=-1),
"随机森林": RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1),
"XGBoost": XGBRegressor(n_estimators=100, random_state=42, n_jobs=-1)
}
# 存储各模型的预测结果和评估指标
predictions = {}
rmse_results = {}
print("===== 各模型训练与评估结果 =====")
for model_name, model in models.items():
# 训练模型
model.fit(X_train_ml, y_train)
# 预测
train_pred = model.predict(X_train_ml)
test_pred = model.predict(X_test_ml)
# 反标准化(还原到原始数据尺度)
train_pred = scaler.inverse_transform(train_pred.reshape(-1, 1))
test_pred = scaler.inverse_transform(test_pred.reshape(-1, 1))
y_train_orig = scaler.inverse_transform(y_train.reshape(-1, 1))
y_test_orig = scaler.inverse_transform(y_test.reshape(-1, 1))
# 计算RMSE(均方根误差,越小表示预测越准)
train_rmse = np.sqrt(mean_squared_error(y_train_orig, train_pred))
test_rmse = np.sqrt(mean_squared_error(y_test_orig, test_pred))
# 存储结果
predictions[model_name] = (train_pred, test_pred)
rmse_results[model_name] = (train_rmse, test_rmse)
# 打印评估结果
print(f"\n{model_name}:")
print(f" 训练集RMSE: {train_rmse:.4f}")
print(f" 测试集RMSE: {test_rmse:.4f}")
# =============================================================
# ===== 步骤4:可视化对比各模型预测效果 =====
# =============================================================
plt.figure(figsize=(18, 10))
# 绘制原始数据
plt.subplot(2, 1, 1)
plt.plot(y, label='原始cosx时序数据', color='gray', alpha=0.5)
# 绘制训练集/测试集分割线
plt.axvline(x=train_size, color='black', linestyle='--', label='训练/测试分割线')
# 绘制各模型的预测结果
colors = {"线性回归": "green", "随机森林": "blue", "XGBoost": "red"}
for model_name, (train_pred, test_pred) in predictions.items():
# 训练集预测值
train_pred_plot = np.empty_like(y)
train_pred_plot[:] = np.nan
train_pred_plot[seq_length : len(train_pred) + seq_length] = train_pred.flatten()
plt.plot(train_pred_plot, label=f'训练集预测 ({model_name})', color=colors[model_name], alpha=0.7)
# 测试集预测值
test_pred_plot = np.empty_like(y)
test_pred_plot[:] = np.nan
test_pred_plot[len(train_pred) + seq_length : len(y)] = test_pred.flatten()
plt.plot(test_pred_plot, label=f'测试集预测 ({model_name})', color=colors[model_name], linestyle='--')
plt.title('cosx时间序列 - 不同机器学习模型预测结果对比')
plt.xlabel('时间步')
plt.ylabel('值')
plt.legend()
plt.grid(True)
# 绘制RMSE对比柱状图
plt.subplot(2, 1, 2)
model_names = list(rmse_results.keys())
train_rmses = [rmse_results[name][0] for name in model_names]
test_rmses = [rmse_results[name][1] for name in model_names]
x_pos = np.arange(len(model_names))
width = 0.35
plt.bar(x_pos - width/2, train_rmses, width, label='训练集RMSE', alpha=0.8)
plt.bar(x_pos + width/2, test_rmses, width, label='测试集RMSE', alpha=0.8)
plt.xlabel('模型')
plt.ylabel('RMSE')
plt.title('各模型RMSE对比(越小越好)')
plt.xticks(x_pos, model_names)
plt.legend()
plt.grid(True, axis='y')
plt.tight_layout()
plt.show()