Pytorch使用collate_fn拼接维度不同的数据LSTM

本文介绍了如何在Pytorch中使用DataLoader的collate_fn参数来处理LSTM模型训练时,数据维度不一致的问题。通过自定义collate_fn函数,实现了将不同序列长度的数据拼接成适合LSTM模型输入的形状,解决了LSTM模型要求固定序列长度输入的挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Pytorch使用collate_fn拼接维度不同的数据LSTM

DataLoader有一个参数collate_fn,这个参数接收自定义collate函数,该函数在数据加载(即通过Dataloader取一个batch数据)之前,定义对每个batch数据的处理行为。
看下面的示例:

import torch
from torch.utils.data import Dataset, DataLoader,\
TensorDataset

def collate(data_):
	"""
	data_是一个列表,长度和DataLoader中定义的batch_size相等,
	每一个列表元素为从Dataset采样一次得到的数据,
	比如batch_size为2,从Dataset一次采样的数据为x,y,
	那么data_表示为[(x1,y1),(x2,y2)]。而从DataLoader出来的
	数据是 X=[x1,x2]^T和Y=[y1,y2]^T,
	下面的代码就是将data_变成X和Y的形式。
	"""
	
	x, y = zip(*data_) # zip 可以将多个列表(或元组)的对应元素拼在一起,这样x1和x2就在一个列表里,y1和y2在一个列表里
	x = torch.stack(x) # 把列表变成张量形式,stack默认在维度0拼接,维度大小等于batch_size大小
	y = torch.stack(y)
	return x, y
	
data = torch.rand(100,128)  # 生成x数据
label = torch.randint(0,2, (100,)).float()  # 生成y标签数据
dataset = TensorDataset(dat
import torch import torch.nn as nn import torch.nn.functional as F import os import numpy as np import pandas as pd import pickle from torch.utils.data import Dataset, DataLoader, random_split from torch.optim import Adam # 配置文件路径 LABEL_FILE = r'E:\Python_Eswin\growing\dataset_202507_10spec\necking_ingot1_2_match_cleaned_data_above_50rows_cleaned.csv' SPEED_DIR = r'E:\Python_Eswin\growing\dataset_202507_10spec\ingot_tau_cal\Pull Speed Actual_embedded_5' ADC_DIR = r'E:\Python_Eswin\growing\dataset_202507_10spec\ingot_tau_cal\ADC Actual V_embedded_6' MELTGAP_DIR = r'E:\Python_Eswin\growing\dataset_202507_10spec\ingot_tau_cal\Melt_Gap_embedded_5' SPEC_FILE = r'E:\Python_Eswin\growing\dataset_202507_10spec\ingot_10_spec_length_cleaned_above_50rows.csv' MODEL_PATH = r"D:\anaconda\envs\pytorch\GrowingProject\lstm\for_10spec\saved_model\best_model.pth" TEST_SET_PATH = r"D:\anaconda\envs\pytorch\GrowingProject\lstm\for_10spec\saved_model\test_set.pkl" # 确保目录存在 os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True) class PullingDataset(Dataset): def __init__(self, label_df, speed_dir, adc_dir, meltgap_dir, spec_file): # 保存原始标签DataFrame self.labels = label_df self.speed_dir = speed_dir self.adc_dir = adc_dir self.meltgap_dir = meltgap_dir self.spec_data = self._load_spec_data(spec_file) # 加载全局参数数据 # 预计算所有序列长度并过滤有效样本 self.lengths = [] self.valid_indices = [] # 调试信息:打印全局参数文件中的ID数量 print(f"全局参数文件中找到的铸锭ID数量: {len(self.spec_data)}") print(f"前5个铸锭ID: {list(self.spec_data.keys())[:5]}") for idx, ingot_id in enumerate(self.labels.iloc[:, 0]): # 确保ID为字符串并去除可能的前后空格 ingot_id_str = str(ingot_id).strip() # 调试信息:打印当前处理的ID if idx < 5: # 只打印前5个用于调试 print(f"处理标签文件中的铸锭ID: '{ingot_id_str}' (索引: {idx})") # 检查所有必需文件是否存在 speed_path = os.path.join(speed_dir, f"{ingot_id_str}_embedded_output.csv") adc_path = os.path.join(adc_dir, f"{ingot_id_str}_embedded_output.csv") meltgap_path = os.path.join(meltgap_dir, f"{ingot_id_str}_embedded_output.csv") file_exists = ( os.path.exists(speed_path) and os.path.exists(adc_path) and os.path.exists(meltgap_path) and ingot_id_str in self.spec_data ) if file_exists: try: # 获取各序列长度(跳过第一行索引) speed_df = pd.read_csv(speed_path, header=0) adc_df = pd.read_csv(adc_path, header=0) meltgap_df = pd.read_csv(meltgap_path, header=0) speed_len = len(speed_df) adc_len = len(adc_df) meltgap_len = len(meltgap_df) # 取最小长度 min_len = min(speed_len, adc_len, meltgap_len) if min_len > 0: self.lengths.append(min_len) self.valid_indices.append(idx) else: print(f"警告: {ingot_id_str} 文件有空数据") except Exception as e: print(f"读取文件错误: {ingot_id_str} - {e}") else: missing_files = [] if not os.path.exists(speed_path): missing_files.append("speed") if not os.path.exists(adc_path): missing_files.append("ADC") if not os.path.exists(meltgap_path): missing_files.append("MeltGap") if ingot_id_str not in self.spec_data: missing_files.append("spec_params") # 调试信息:检查为什么找不到spec_params print(f"在全局参数文件中找不到ID: '{ingot_id_str}'") if len(self.spec_data) > 0: print(f"全局参数文件中的ID示例: {list(self.spec_data.keys())[:5]}") print(f"文件缺失: {ingot_id_str} ({', '.join(missing_files)})") # 过滤有效样本 if len(self.valid_indices) > 0: self.labels = self.labels.iloc[self.valid_indices] print(f"有效样本数量: {len(self.labels)}") # 如果没有有效样本,打印更多信息 if len(self.labels) == 0: print("错误: 没有找到有效样本!") print(f"标签文件中的ID示例: {self.labels.iloc[:5, 0].tolist()}") print(f"全局参数文件中的ID示例: {list(self.spec_data.keys())[:5]}") print("请检查ID是否匹配(包括类型和格式)") def _load_spec_data(self, file_path): """加载全局参数文件,返回{ingot_id: 特征向量}""" try: df = pd.read_csv(file_path, header=0) print(f"成功加载全局参数文件: {file_path}") print(f"文件包含 {len(df)} 行数据") except Exception as e: print(f"加载全局参数文件错误: {e}") return {} spec_dict = {} for _, row in df.iterrows(): # 处理ID格式:先转换为浮点数再转为整数,最后转为字符串(去除小数部分) try: ingot_id = str(int(float(row.iloc[0]))) # 关键修改:处理浮点型ID spec_dict[ingot_id] = row.iloc[1:10].values.astype(np.float32) except ValueError as e: print(f"转换ID错误: {row.iloc[0]} - {e}") continue print(f"全局参数文件中找到的铸锭ID数量: {len(spec_dict)}") print(f"处理后ID示例: {list(spec_dict.keys())[:5]}") return spec_dict def __len__(self): return len(self.labels) def __getitem__(self, idx): row = self.labels.iloc[idx] ingot_id = str(row.iloc[0]).strip() # 确保ingot_id是字符串并去除空格 label = row.iloc[11] # 第12列: 标签 (0或1) try: # 加载所有序列数据(跳过第一行索引) speed_data = pd.read_csv( os.path.join(self.speed_dir, f"{ingot_id}_embedded_output.csv"), header=0 ).values.astype(np.float32) adc_data = pd.read_csv( os.path.join(self.adc_dir, f"{ingot_id}_embedded_output.csv"), header=0 ).values.astype(np.float32) meltgap_data = pd.read_csv( os.path.join(self.meltgap_dir, f"{ingot_id}_embedded_output.csv"), header=0 ).values.astype(np.float32) # 获取全局参数向量 spec_vector = self.spec_data[ingot_id] # 取最小长度 min_len = min(len(speed_data), len(adc_data), len(meltgap_data)) # 截取相同长度的序列 speed_data = speed_data[:min_len, :] adc_data = adc_data[:min_len, :] meltgap_data = meltgap_data[:min_len, :] # 创建全局参数矩阵(复制min_len次) spec_matrix = np.tile(spec_vector, (min_len, 1)) # 合并所有特征 combined_data = np.concatenate([ speed_data, # 5维 adc_data, # 6维 meltgap_data, # 5维 spec_matrix # 9维 ], axis=1) # 转换为tensor seq = torch.tensor(combined_data, dtype=torch.float32) label = float(label) label = torch.tensor(label, dtype=torch.float32) return seq, label, min_len except Exception as e: print(f"错误: 无法处理铸锭 {ingot_id}: {e}") return torch.zeros(0), torch.tensor(0.0), 0 def collate_fn(batch): sequences, labels, lengths = zip(*batch) max_len = max(lengths) if lengths else 0 # 过滤掉空序列 valid_indices = [i for i, seq in enumerate(sequences) if len(seq) > 0] if not valid_indices: return torch.zeros(0), torch.zeros(0), torch.zeros(0) sequences = [sequences[i] for i in valid_indices] labels = [labels[i] for i in valid_indices] lengths = [lengths[i] for i in valid_indices] # 获取特征维度 feat_dim = sequences[0].shape[1] if sequences[0].nelement() > 0 else 0 # 填充序列 padded_seqs = torch.zeros(len(sequences), max_len, feat_dim) for i, seq in enumerate(sequences): padded_seqs[i, :lengths[i]] = seq lengths = torch.tensor(lengths, dtype=torch.long) labels = torch.stack(labels) return padded_seqs, labels, lengths def save_test_set(test_dataset, path): test_data = [] for i in range(len(test_dataset)): test_data.append(test_dataset[i]) with open(path, 'wb') as f: pickle.dump(test_data, f) class SpeedLSTM(nn.Module): def __init__(self, input_dim=25, hidden_dim=128, num_layers=2, dropout=0.2): super().__init__() # 添加LayerNorm稳定训练 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.classifier = nn.Sequential( nn.LayerNorm(2 * hidden_dim), nn.Linear(2 * hidden_dim, 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 x = self.input_norm(x) # 输入标准化 packed = nn.utils.rnn.pack_padded_sequence(x, lengths.cpu(), batch_first=True, enforce_sorted=False) packed_out, _ = self.lstm(packed) out, _ = nn.utils.rnn.pad_packed_sequence(packed_out, batch_first=True) # 取最后一个有效时间步 last_output = out[torch.arange(out.size(0)), lengths - 1] # 梯度裁剪 torch.nn.utils.clip_grad_norm_(self.parameters(), max_norm=1.0) return self.classifier(last_output).squeeze(-1), None def save_model(model, MODEL_PATH): os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True) try: torch.save(model.state_dict(), MODEL_PATH) print(f"模型成功保存至: {MODEL_PATH}") except Exception as e: print(f"模型保存失败: {e}") def load_model(path, input_dim=25): model = SpeedLSTM(input_dim) model.load_state_dict(torch.load(path, map_location=torch.device('cpu'))) model.eval() print(f"从 {path} 加载模型") return model def main(): print(f"使用设备: {'GPU' if torch.cuda.is_available() else 'CPU'}") # 加载标签数据 - 跳过第一行索引 try: # 使用header=0跳过第一行索引 labels_df = pd.read_csv(LABEL_FILE, header=0) print(f"加载标签数据: {labels_df.shape[0]} 行") except Exception as e: print(f"加载数据错误: {e}") return # 创建完整数据集 full_dataset = PullingDataset( labels_df, SPEED_DIR, ADC_DIR, MELTGAP_DIR, SPEC_FILE ) if len(full_dataset) == 0: print("错误: 没有有效样本!") return # 检查第一个样本获取输入维度 sample, label, length = full_dataset[0] if len(sample) > 0: input_dim = sample.shape[1] print(f"检测到输入维度: {input_dim} (5拉速 + 6ADC + 5MeltGap + 9全局参数)") else: print("警告: 无法获取样本维度, 使用默认值25") input_dim = 25 # 划分训练/验证集 (80%/20%) train_size = int(0.8 * len(full_dataset)) test_size = len(full_dataset) - train_size train_dataset, test_dataset = random_split(full_dataset, [train_size, test_size]) # 保存测试集 save_test_set(test_dataset, TEST_SET_PATH) print(f"保存测试集到 {TEST_SET_PATH}") # 创建数据加载器 train_loader = DataLoader( train_dataset, batch_size=32, shuffle=True, collate_fn=collate_fn) # 初始化模型 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = SpeedLSTM(input_dim=input_dim).to(device) # 计算类别权重 labels = [] for i in range(len(full_dataset)): try: _, label_val, _ = full_dataset[i] labels.append(float(label_val.item())) except Exception as e: print(f"错误: 获取标签值失败: {e}") pos_count = sum(labels) neg_count = len(labels) - pos_count pos_weight = torch.tensor([neg_count / max(pos_count, 1)]).to(device) print(f"类别分布: 负样本 {neg_count}, 正样本 {pos_count}") print(f"正样本权重: {pos_weight.item():.2f}") criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight) optimizer = Adam(model.parameters(), lr=0.0001, weight_decay=1e-4) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # 训练参数 best_val_loss = float('inf') epochs = 100 early_stop_patience = 20 no_improve_count = 0 print("\n开始训练...") for epoch in range(epochs): model.train() train_loss = 0.0 train_correct = 0 train_total = 0 for inputs, labels, lengths in train_loader: if inputs.nelement() == 0: continue inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs, _ = model(inputs, lengths) loss = criterion(outputs, labels) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() preds = (torch.sigmoid(outputs) > 0.5).float() train_correct += (preds == labels).sum().item() train_total += labels.size(0) train_loss += loss.item() * inputs.size(0) # 计算平均损失和准确率 train_loss = train_loss / train_total if train_total > 0 else 0 train_acc = train_correct / train_total if train_total > 0 else 0 print(f"Epoch {epoch + 1}/{epochs}: " f"Train Loss: {train_loss:.4f}, " f"Acc: {train_acc:.4f}") # 保存最佳模型 if train_loss < best_val_loss: best_val_loss = train_loss save_model(model, MODEL_PATH) no_improve_count = 0 print(f"保存最佳模型 (loss={best_val_loss:.4f})") else: no_improve_count += 1 if no_improve_count >= early_stop_patience: print(f"早停: {early_stop_patience}个epoch无改进") break print(f"\n训练完成! 最佳模型保存至: {MODEL_PATH}") if __name__ == "__main__": main() 我想要优化这个二分类预测模型的准确性,目前准确度在50%左右
最新发布
07-25
<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%以上。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值