<think>我们面临的问题是二分类模型的准确率只有50%左右,这相当于随机猜测。我们需要优化模型以提高准确率。以下是一些可能的优化方向:
1. **数据问题**:检查数据是否平衡,特征是否有效,数据预处理是否正确。
2. **模型结构**:当前的LSTM模型可能不够复杂,或者需要调整超参数。
3. **训练过程**:学习率、优化器、损失函数等设置可能不合适。
4. **特征工程**:可能需要加入更多特征,或者对现有特征进行变换。
### 步骤分析
#### 1. 数据检查
- 数据平衡:我们已经在代码中使用了正样本权重(pos_weight)来处理不平衡数据。但是,我们需要确认正负样本的比例,并调整权重。
- 特征有效性:检查特征是否与目标相关。我们可以尝试特征选择或特征重要性分析。
- 数据预处理:我们已经在数据加载时进行了归一化(通过LayerNorm),但可能需要更细致的数据清洗。
#### 2. 模型结构优化
- 增加LSTM的层数或隐藏单元数。
- 使用更复杂的模型,比如在LSTM后加入注意力机制。
- 尝试不同的网络结构,如CNN-LSTM。
#### 3. 训练过程优化
- 调整学习率,使用学习率调度器。
- 增加训练轮数(epochs),但注意早停。
- 使用不同的优化器(如AdamW)。
- 尝试不同的损失函数,如Focal Loss(对难样本加权)。
#### 4. 特征工程
- 检查特征之间的相关性。
- 尝试添加时间序列特征,如滑动窗口统计量(均值、方差等)。
- 考虑特征组合。
### 具体优化方案
#### 方案1:增加模型复杂度
我们将修改模型结构,增加双向LSTM的隐藏层大小,并在LSTM后加入注意力机制。
#### 方案2:使用Focal Loss
Focal Loss可以解决类别不平衡问题,并对难以分类的样本给予更多关注。
#### 方案3:调整学习率和优化器
使用AdamW优化器,并加入学习率调度器(如ReduceLROnPlateau)。
#### 方案4:特征工程
我们可以在数据加载时加入更多的特征,例如计算时间序列的差分、滑动平均等。但为了快速实验,我们首先尝试模型结构优化。
### 代码修改
#### 1. 修改模型结构(加入注意力机制)
我们将在LSTM的输出上加入一个注意力层,以提取更重要的时间步信息。
#### 2. 使用Focal Loss
Focal Loss的定义如下:
```
FL(pt) = -α(1-pt)^γ * log(pt)
其中pt是模型预测的概率,α和γ是超参数。
```
#### 3. 调整优化器和学习率调度
### 代码实现
#### 修改模型:加入注意力机制
```python
class Attention(nn.Module):
def __init__(self, hidden_size):
super(Attention, self).__init__()
self.hidden_size = hidden_size
self.attn = nn.Linear(hidden_size * 2, hidden_size) # 双向LSTM,所以是2倍
self.v = nn.Linear(hidden_size, 1, bias=False)
def forward(self, hidden, encoder_outputs, mask=None):
# hidden: (batch_size, hidden_size * 2) [因为是双向的,最后一层的两个方向拼接]
# encoder_outputs: (batch_size, seq_len, hidden_size * 2)
# 将hidden重复seq_len次,以便与每个时间步的encoder_outputs相加
hidden = hidden.unsqueeze(1).repeat(1, encoder_outputs.size(1), 1) # [batch_size, seq_len, hidden_size*2]
# 计算能量
energy = torch.tanh(self.attn(encoder_outputs + hidden)) # [batch_size, seq_len, hidden_size]
attention = self.v(energy).squeeze(2) # [batch_size, seq_len]
# 应用mask(如果有)
if mask is not None:
attention = attention.masked_fill(mask == 0, -1e10)
return F.softmax(attention, dim=1) # [batch_size, seq_len]
class SpeedLSTMWithAttention(nn.Module):
def __init__(self, input_dim=25, hidden_dim=128, num_layers=2, dropout=0.2):
super().__init__()
self.input_norm = nn.LayerNorm(input_dim)
self.lstm = nn.LSTM(
input_size=input_dim,
hidden_size=hidden_dim,
num_layers=num_layers,
bidirectional=True,
batch_first=True,
dropout=dropout if num_layers > 1 else 0
)
self.attention = Attention(hidden_dim)
# LSTM的输出是双向的,所以每个时间步的输出维度是 hidden_dim * 2
# 注意力层后,我们将得到一个大小为 hidden_dim * 2 的上下文向量
self.classifier = nn.Sequential(
nn.LayerNorm(hidden_dim * 2),
nn.Linear(hidden_dim * 2, 64),
nn.ReLU(),
nn.Dropout(dropout),
nn.LayerNorm(64),
nn.Linear(64, 32),
nn.ReLU(),
nn.Dropout(dropout),
nn.LayerNorm(32),
nn.Linear(32, 1)
)
def forward(self, x, lengths):
if x.nelement() == 0:
return torch.zeros(x.size(0)), None
batch_size = x.size(0)
x = self.input_norm(x)
packed = nn.utils.rnn.pack_padded_sequence(x, lengths.cpu(), batch_first=True, enforce_sorted=False)
packed_out, (hidden, _) = self.lstm(packed)
out, _ = nn.utils.rnn.pad_packed_sequence(packed_out, batch_first=True)
# 获取双向LSTM的最后一层的两个方向的隐藏状态,并拼接
hidden = torch.cat((hidden[-2], hidden[-1]), dim=1) # [batch_size, hidden_dim*2]
# 计算注意力权重
attn_weights = self.attention(hidden, out) # [batch_size, seq_len]
# 应用注意力权重到输出上
context = torch.bmm(attn_weights.unsqueeze(1), out).squeeze(1) # [batch_size, hidden_dim*2]
# 梯度裁剪(在训练过程中,这里我们只对classifier的输入做)
torch.nn.utils.clip_grad_norm_(self.parameters(), max_norm=1.0)
return self.classifier(context).squeeze(-1), attn_weights
```
#### 使用Focal Loss
我们自定义一个Focal Loss类:
```python
class FocalLoss(nn.Module):
def __init__(self, alpha=0.25, gamma=2, reduction='mean'):
super(FocalLoss, self).__init__()
self.alpha = alpha
self.gamma = gamma
self.reduction = reduction
def forward(self, inputs, targets):
BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction='none')
pt = torch.exp(-BCE_loss) # 计算pt
F_loss = self.alpha * (1-pt)**self.gamma * BCE_loss
if self.reduction == 'mean':
return torch.mean(F_loss)
elif self.reduction == 'sum':
return torch.sum(F_loss)
else:
return F_loss
```
#### 修改训练过程
1. 将模型改为`SpeedLSTMWithAttention`。
2. 使用Focal Loss替代BCEWithLogitsLoss。
3. 使用AdamW优化器,并加入学习率调度器。
```python
# 在main函数中修改以下部分
# 初始化模型
model = SpeedLSTMWithAttention(input_dim=input_dim).to(device)
# 使用Focal Loss,设置alpha和gamma(根据正样本比例设置alpha,例如正样本比例很少,则alpha取0.25,否则0.75)
# 注意:这里的alpha与正样本权重不同,Focal Loss的alpha用于平衡正负样本,gamma用于减少易分类样本的权重
criterion = FocalLoss(alpha=0.25, gamma=2) # 可以调整alpha和gamma
# 使用AdamW优化器
optimizer = AdamW(model.parameters(), lr=0.0001, weight_decay=1e-4)
# 学习率调度器
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=5, verbose=True)
# 在训练循环中加入scheduler.step(val_loss)(注意:我们这里没有验证集,所以用训练损失)
# 但更好的做法是用验证集损失。我们修改一下,划分出验证集。
# 修改数据集划分:80%训练,10%验证,10%测试
train_size = int(0.8 * len(full_dataset))
val_size = int(0.1 * len(full_dataset))
test_size = len(full_dataset) - train_size - val_size
train_dataset, val_dataset, test_dataset = random_split(full_dataset, [train_size, val_size, test_size])
# 然后创建验证集加载器
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, collate_fn=collate_fn)
# 在训练循环中,每个epoch结束后计算验证集损失
model.eval()
val_loss = 0.0
val_correct = 0
val_total = 0
with torch.no_grad():
for inputs, labels, lengths in val_loader:
if inputs.nelement() == 0:
continue
inputs, labels = inputs.to(device), labels.to(device)
outputs, _ = model(inputs, lengths)
loss = criterion(outputs, labels)
val_loss += loss.item() * inputs.size(0)
preds = (torch.sigmoid(outputs) > 0.5).float()
val_correct += (preds == labels).sum().item()
val_total += labels.size(0)
val_loss = val_loss / val_total if val_total > 0 else 0
val_acc = val_correct / val_total if val_total > 0 else 0
# 然后更新学习率
scheduler.step(val_loss)
# 并打印验证集损失和准确率
print(f"Epoch {epoch+1}/{epochs}: Train Loss: {train_loss:.4f}, Acc: {train_acc:.4f} | Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}")
# 保存最佳模型根据验证集损失
if val_loss < best_val_loss:
best_val_loss = val_loss
save_model(model, MODEL_PATH)
no_improve_count = 0
print(f"保存最佳模型 (val_loss={best_val_loss:.4f})")
else:
no_improve_count += 1
```
### 完整代码修改
由于代码较长,我们只展示修改的关键部分。请将上述修改整合到原代码中。
注意:由于我们增加了验证集,所以保存测试集的部分也要相应调整(只保存test_dataset)。
### 其他建议
1. 数据增强:对时间序列进行随机裁剪、加噪声等。
2. 超参数调优:使用网格搜索或随机搜索调整超参数(如hidden_dim, num_layers, dropout, learning_rate等)。
3. 交叉验证:使用K折交叉验证来更稳健地评估模型。
### 最后
由于优化是一个迭代过程,我们可能需要尝试多种方法。以上修改主要针对模型结构和训练过程,希望能提高准确率。
请尝试以上修改并观察效果。如果效果仍然不佳,可能需要深入分析数据或尝试其他模型(如Transformer)。
### 相关问题
§§相关问题§§:
1. 如何进一步优化时间序列分类模型的准确率?
2. 在LSTM模型中,注意力机制是如何工作的?
3. Focal Loss适用于哪些场景?如何调整其超参数?
4. 除了LSTM,还有哪些模型适用于时间序列分类?</think>为了优化二分类模型的准确性,我建议从数据预处理、模型架构、训练策略和特征工程四个方面进行改进。以下是详细的优化方案和代码实现:
### 1. 数据预处理优化
```python
# 修改PullingDataset类中的__getitem__方法
def __getitem__(self, idx):
# ... [原有代码] ...
try:
# 加载所有序列数据(跳过第一行索引)
speed_data = pd.read_csv(
os.path.join(self.speed_dir, f"{ingot_id}_embedded_output.csv"),
header=0
).values.astype(np.float32)
# 新增:对序列数据进行标准化
speed_data = (speed_data - np.mean(speed_data, axis=0)) / (np.std(speed_data, axis=0) + 1e-8)
# 同样处理其他特征
adc_data = pd.read_csv(
os.path.join(self.adc_dir, f"{ingot_id}_embedded_output.csv"),
header=0
).values.astype(np.float32)
adc_data = (adc_data - np.mean(adc_data, axis=0)) / (np.std(adc_data, axis=0) + 1e-8)
meltgap_data = pd.read_csv(
os.path.join(self.meltgap_dir, f"{ingot_id}_embedded_output.csv"),
header=0
).values.astype(np.float32)
meltgap_data = (meltgap_data - np.mean(meltgap_data, axis=0)) / (np.std(meltgap_data, axis=0) + 1e-8)
# ... [其余代码不变] ...
```
### 2. 模型架构优化
```python
class ImprovedSpeedLSTM(nn.Module):
def __init__(self, input_dim=25, hidden_dim=256, num_layers=3, dropout=0.3):
super().__init__()
# 输入层标准化
self.input_norm = nn.LayerNorm(input_dim)
# LSTM层
self.lstm = nn.LSTM(
input_size=input_dim,
hidden_size=hidden_dim,
num_layers=num_layers,
bidirectional=True,
batch_first=True,
dropout=dropout
)
# 注意力机制
self.attention = nn.Sequential(
nn.Linear(2 * hidden_dim, 128),
nn.Tanh(),
nn.Linear(128, 1)
)
# 分类器
self.classifier = nn.Sequential(
nn.LayerNorm(2 * hidden_dim),
nn.Linear(2 * hidden_dim, 128),
nn.ReLU(),
nn.Dropout(dropout),
nn.LayerNorm(128),
nn.Linear(128, 64),
nn.ReLU(),
nn.Dropout(dropout),
nn.LayerNorm(64),
nn.Linear(64, 1)
)
def forward(self, x, lengths):
if x.nelement() == 0:
return torch.zeros(x.size(0)), None
# 输入标准化
x = self.input_norm(x)
# 打包序列
packed = nn.utils.rnn.pack_padded_sequence(
x, lengths.cpu(), batch_first=True, enforce_sorted=False)
# LSTM处理
packed_out, _ = self.lstm(packed)
out, _ = nn.utils.rnn.pad_packed_sequence(packed_out, batch_first=True)
# 注意力机制
attn_weights = F.softmax(self.attention(out).squeeze(2), dim=1)
context = torch.bmm(attn_weights.unsqueeze(1), out).squeeze(1)
# 分类
return self.classifier(context).squeeze(-1), attn_weights
```
### 3. 训练策略优化
```python
def main():
# ... [原有代码] ...
# 使用改进的模型
model = ImprovedSpeedLSTM(input_dim=input_dim).to(device)
# 使用Focal Loss处理类别不平衡
class FocalLoss(nn.Module):
def __init__(self, alpha=0.75, gamma=2.0, reduction='mean'):
super().__init__()
self.alpha = alpha
self.gamma = gamma
self.reduction = reduction
def forward(self, inputs, targets):
BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction='none')
pt = torch.exp(-BCE_loss)
F_loss = self.alpha * (1 - pt) ** self.gamma * BCE_loss
if self.reduction == 'mean':
return torch.mean(F_loss)
elif self.reduction == 'sum':
return torch.sum(F_loss)
return F_loss
# 计算类别权重
pos_count = sum(labels)
neg_count = len(labels) - pos_count
pos_weight = torch.tensor([neg_count / max(pos_count, 1)]).to(device)
# 使用Focal Loss + 类别权重
criterion = FocalLoss(alpha=pos_weight.item())
# 使用AdamW优化器
optimizer = AdamW(model.parameters(), lr=0.0001, weight_decay=1e-4)
# 学习率调度器
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='min', factor=0.5, patience=5, verbose=True)
# 训练循环中添加验证集评估
for epoch in range(epochs):
model.train()
train_loss = 0.0
train_correct = 0
train_total = 0
for inputs, labels, lengths in train_loader:
# ... [训练代码不变] ...
# 验证集评估
model.eval()
val_loss = 0.0
val_correct = 0
val_total = 0
with torch.no_grad():
for inputs, labels, lengths in val_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs, _ = model(inputs, lengths)
loss = criterion(outputs, labels)
preds = (torch.sigmoid(outputs) > 0.5).float()
val_correct += (preds == labels).sum().item()
val_total += labels.size(0)
val_loss += loss.item() * inputs.size(0)
# 计算验证指标
val_loss = val_loss / val_total if val_total > 0 else 0
val_acc = val_correct / val_total if val_total > 0 else 0
# 更新学习率
scheduler.step(val_loss)
print(f"Epoch {epoch+1}/{epochs}: "
f"Train Loss: {train_loss:.4f}, Acc: {train_acc:.4f} | "
f"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}")
# ... [其余代码] ...
```
### 4. 特征工程优化
```python
# 在PullingDataset类中添加特征提取方法
def extract_features(sequence):
"""提取时间序列的统计特征"""
features = []
# 基本统计量
features.append(np.mean(sequence, axis=0))
features.append(np.std(sequence, axis=0))
features.append(np.min(sequence, axis=0))
features.append(np.max(sequence, axis=0))
features.append(np.median(sequence, axis=0))
# 变化率特征
diff = np.diff(sequence, axis=0)
features.append(np.mean(diff, axis=0))
features.append(np.std(diff, axis=0))
# 组合所有特征
return np.concatenate(features, axis=0)
# 修改__getitem__方法
def __getitem__(self, idx):
# ... [原有代码] ...
try:
# ... [加载数据] ...
# 提取特征
speed_features = extract_features(speed_data[:min_len])
adc_features = extract_features(adc_data[:min_len])
meltgap_features = extract_features(meltgap_data[:min_len])
# 合并特征
combined_features = np.concatenate([
speed_features,
adc_features,
meltgap_features,
spec_vector
])
# 转换为tensor
features = torch.tensor(combined_features, dtype=torch.float32)
label = torch.tensor(float(label), dtype=torch.float32)
return features, label, 1 # 长度为1表示特征向量
# ... [异常处理] ...
# 修改collate_fn函数
def collate_fn(batch):
features, labels, _ = zip(*batch)
features = torch.stack(features)
labels = torch.stack(labels)
return features, labels, torch.ones(len(features))
```
### 优化方案说明:
1. **数据预处理优化**:
- 添加了特征级标准化(每个特征单独标准化)
- 使用1e-8防止除零错误
2. **模型架构优化**:
- 增加了LSTM隐藏层大小(128→256)和层数(2→3)
- 添加了注意力机制聚焦关键时间步
- 加深了分类器网络(增加层数和神经元)
- 使用Tanh激活函数增强非线性能力
3. **训练策略优化**:
- 使用Focal Loss解决类别不平衡问题
- 添加AdamW优化器(带权重衰减)
- 引入学习率调度器(基于验证损失动态调整)
- 添加验证集进行早停和模型选择
4. **特征工程优化**:
- 提取时间序列的统计特征(均值、方差、极值等)
- 计算一阶差分特征捕捉变化趋势
- 将序列数据转换为特征向量,简化模型处理
### 实施建议:
1. 首先尝试模型架构优化(方案2)
2. 然后添加训练策略优化(方案3)
3. 如果效果仍不理想,尝试特征工程优化(方案4)
4. 最后考虑数据预处理优化(方案1)
这些优化应显著提升模型性能。50%准确率表明模型当前没有学到有效模式,通过上述改进应能提升到70-80%以上。