使用神经网络拟合6项参数
- 1. 数据预处理
- 1.1 添加参数解析
- 1.2 数据预处理逻辑
- 1.3 数据归一化及划分
- 1.4 数据标签处理逻辑
- 1.5 数据转torch
- 2. 定义model
- 2.1 CNN_LSTM
- 2.2 Transformer
- 3. 定义train脚本
- 3.1 loss和optimizer
- 3.2 train
- 3.3 predict
1. 数据预处理
1.1 添加参数解析
为了方便管理模型和训练等参数,统一用参数解析。
def parse_args():
"""
解析命令行参数并返回参数对象。
返回:
- args (argparse.Namespace): 解析后的参数对象
"""
parser = argparse.ArgumentParser(description='命令行参数解析示例')
# 添加参数
parser.add_argument('--input', type=str, default='input.csv', help='输入文件的路径')
parser.add_argument('--output', type=str, default='output.csv', help='输出文件的路径')
parser.add_argument('--data_group', type=int, default=401, help='数据隔多少行划分一组')
parser.add_argument('--test_size', type=float, default=0.1, help='测试集划分比例')
# parser.add_argument('--batch_size', type=int, default=32, help='每批次的样本数量,默认值是 32')
parser.add_argument('--batch_size', type=int, default=64, help='每批次的样本数量,默认值是 32')
parser.add_argument('--epochs', type=int, default=50, help='训练的轮数,默认值是 10')
parser.add_argument('--learning_rate', type=float, default=0.001, help='学习率,默认值是 0.001')
parser.add_argument('--model_name', type=str, default='CNN_LSTM', help='选择模型,eg:LSTM、CNN_LSTM、Transformer')
parser.add_argument('--predict_para', type=str, default='AAdl1', help='m1, AAdl1, AAdl2, PPa0, nn, ccd1, ccd2')
# 解析参数
args = parser.parse_args()
return args
1.2 数据预处理逻辑
最近一个小项目需要搭建神经网络
根据输入数据
的特征去拟合
对应的6项参数
。
输入是input.csv
,一共是876987x2
的两列数据,分别代表特征1:Var1
,特征2:Var2
;使用pandas库读取,并提取出实部(如果存在的话),然后将它转换为浮点数。
6项参数对应的真实值如下,维度为:2187x7
,每一列分别对应一项参数的真实标签值。
def clean_complex_number(val):
val = str(val)
if 'i' in val:
val = val.split('+')[0] if '+' in val else val.split('-')[0]
return float(val)
input_df = pd.read_csv(args.input)
output_df = pd.read_csv(args.output)
#提取出实部(如果存在的话),然后将它转换为浮点数
input_df = input_df.applymap(clean_complex_number)
output_df = output_df.applymap(clean_complex_number)
接着,输入特征是876987x2
,也就是876987行, 每401行分组,并将每组展平成一维数组,一共876987÷401=2187组
,最后的输入特征处理维度为:2187x802(401x2)
。full_df就是拼接上标签,就是维度:2187x809
。
#876987 x 2
num_features = input_df.shape[1] # 特征数 2
#一共876987行, 每401行分组,并将每组展平成一维数组
#每组就是1x401x2 = 1x802
#一共876987÷401=2187组
grouped = input_df.groupby(input_df.index // args.data_group).apply(lambda x: x.values.ravel())
#2187x802
new_input_df = pd.DataFrame(grouped.tolist(), index=grouped.index)
new_input_df.columns = [f'feature_{i}' for i in range(args.data_group * num_features)]
#将 new_input_df 和 output_df 合并成一个完整的数据框 full_df。
#2187x809
full_df = pd.concat([new_input_df, output_df.reset_index(drop=True)], axis=1)
train_df, test_df = train_test_split(full_df, test_size=args.test_size, random_state=2024)
1.3 数据归一化及划分
接着,进行归一化处理:
scaler = StandardScaler()
train_features = train_df.iloc[:, :-output_df.shape[1]]
train_labels = train_df.iloc[:, -output_df.shape[1]:]
test_features = test_df.iloc[:, :-output_df.shape[1]]
test_labels = test_df.iloc[:, -output_df.shape[1]:]
X_train = scaler.fit_transform(train_features)
y_train = np.array(train_labels)
X_val = scaler.transform(test_features)
y_val = np.array(test_labels)
1.4 数据标签处理逻辑
由于第一参数的真实值只存在于400、420、430的这三种值,所以我们将其映射到0、1、2,这样更有利于模型的拟合。
def map_unique_values(data):
"""
将数据中的唯一值映射到从0开始的整数。
参数:
data (numpy.ndarray): 输入的一维数据数组
返回:
numpy.ndarray: 映射后的数据数组
"""
# 获取唯一值及其数量
unique_values = np.unique(data)
# 创建映射字典
mapping = {val: idx for idx, val in enumerate(unique_values)}
# 将数据映射到新的值
mapped_data = np.array([mapping[val] for val in data])
return mapped_data.reshape(-1, 1)
# 只对第一列数据进行映射
y_train[:, 0:1] = map_unique_values(y_train[:, 0])
y_val[:, 0:1] = map_unique_values(y_val[:, 0])
1.5 数据转torch
# 转换为 PyTorch 张量
X_train_tensor = torch.tensor(X_train, dtype=torch.float32)
y_train_tensor = torch.tensor(y_train, dtype=torch.float32)
X_val_tensor = torch.tensor(X_val, dtype=torch.float32)
y_val_tensor = torch.tensor(y_val, dtype=torch.float32)
# 创建数据加载器
if args.model_name != 'LSTM':
X_train_tensor = X_train_tensor.unsqueeze(1)
X_val_tensor = X_val_tensor.unsqueeze(1)
if args.model_name == 'Transformer':
X_train_tensor = X_train_tensor.permute(0, 2, 1) # 重新排列维度以适应 Transformer 输入
X_val_tensor = X_val_tensor.permute(0, 2, 1) # 重新排列维度以适应 Transformer 输入
train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
train_loader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True)
input_size = X_train.shape[1] # 802
# 选择推理设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
if args.model_name == 'Transformer':
model = TransformerModel().to(device)
elif args.model_name == 'CNN_LSTM':
hidden_size = 100
output_size = 7
model = CNN_LSTM_Model(input_size, hidden_size, output_size).to(device)
2. 定义model
2.1 CNN_LSTM
这里我定义了1个CNN和LSTM的hybird结构。
class CNN_LSTM_Model(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(CNN_LSTM_Model, self).__init__()
# CNN 部分
self.conv1 = nn.Conv1d(1, 16, kernel_size=5, stride=1, padding=2)
self.relu = nn.ReLU()
self.maxpool = nn.MaxPool1d(kernel_size=2)
self.conv2 = nn.Conv1d(16, 32, kernel_size=5, stride=1, padding=2)
# 计算 CNN 输出的特征维度
self.cnn_output_size = 32 * (input_size // 4) # 输入大小 / 2^2
# LSTM 部分
self.lstm = nn.LSTM(self.cnn_output_size, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# CNN 部分
x = self.relu(self.conv1(x))
x = self.maxpool(x)
x = self.relu(self.conv2(x))
x = self.maxpool(x)
# 展平 CNN 输出
x = x.permute(0, 2, 1) # (batch_size, seq_len, feature_size) 这里的 seq_len 是时间步长
# LSTM 部分
x = x.flatten(1)
h_lstm, _ = self.lstm(x)
out = self.fc(h_lstm) # 只取最后一个时间步的输出
return out
2.2 Transformer
class TransformerModel(nn.Module):
def __init__(self):
super(TransformerModel, self).__init__()
self.input_embed = nn.Linear(1, 32) # 嵌入到更高维度
self.pos_encoder = nn.Parameter(torch.randn(802, 32)) # 位置编码
encoder_layers = nn.TransformerEncoderLayer(d_model=32, nhead=4)
self.transformer_encoder = nn.TransformerEncoder(encoder_layers, num_layers=1)
self.fc = nn.Linear(32, 7) # 输出层
def forward(self, x):
x = self.input_embed(x) + self.pos_encoder[:x.size(1), :] # 加入位置编码
x = x.permute(1, 0, 2) # 调整维度以匹配 PyTorch Transformer 输入需求
x = self.transformer_encoder(x)
x = x.mean(dim=0) # 池化层
x = self.fc(x)
return x
3. 定义train脚本
3.1 loss和optimizer
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=args.learning_rate)
3.2 train
for epoch in range(args.epochs):
model.train()
for batch_X, batch_y in train_loader:
outputs = model(batch_X.to(device))
loss = criterion(outputs, batch_y.to(device))
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f'Epoch [{epoch+1}/{args.epochs}], Loss: {loss.item():.4f}')
model.eval()
with torch.no_grad():
y_pred_tensor = model(X_val_tensor.to(device))
y_pred = y_pred_tensor.cpu().numpy()
for i in range(y_pred.shape[1]):
y_pred[:, i:i+1] = map_to_discrete_values(y_pred[:, i], np.unique(y_val[:, i]))
3.3 predict
x_axis = range(len(y_pred))
figure_list = ['m1', 'AAdl1', 'AAdl2', 'PPa0', 'nn', 'ccd1', 'ccd2']
for i, param in enumerate(figure_list):
plt.figure(figsize=(10, 6))
plt.scatter(x_axis, y_pred[:, i], color='blue', alpha=0.5, label='Predicted')
plt.scatter(x_axis, y_val[:, i], color='red', alpha=0.5, label='True Label')
plt.xlabel('item')
plt.ylabel(figure_list[i])
plt.title(f'{param} - True vs Predicted')
# 计算均方误差(MSE)
mse = np.mean((y_val[:, i] - y_pred[:, i]) ** 2)
# 打印 MSE 值
print(f"{args.model_name}--MSE of {figure_list[i]}: {mse:.4f}")
plt.legend()
plt.show()
其中,可视化的第一个参数拟合结果如下: