19 KiB
19 KiB
In [1]:
import os
import pandas as pd
import torch
import torch.nn as nn
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from torch.optim.adam import Adam
from torch.utils.data import DataLoader, Dataset
from tqdm import tqdm
# 检查是否可以使用CUDA
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# 自定义数据集类
class InsuranceDataset(Dataset):
"""
保险费数据的自定义数据集类。
属性:
X (torch.FloatTensor): 特征张量
y (torch.FloatTensor): 目标张量(测试集可选)
"""
def __init__(self, X, y=None):
self.X = torch.FloatTensor(X)
self.y = torch.FloatTensor(y) if y is not None else None
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
if self.y is not None:
return self.X[idx], self.y[idx]
return self.X[idx]
# 神经网络模型
class InsuranceNet(nn.Module):
"""
保险费预测的神经网络架构。
架构:
- 输入层: input_size 个神经元
- 隐藏层1: 128个神经元,使用ReLU激活和0.2的dropout
- 隐藏层2: 64个神经元,使用ReLU激活和0.2的dropout
- 隐藏层3: 32个神经元,使用ReLU激活
- 输出层: 1个神经元(保费预测)
参数:
input_size (int): 输入特征数量
"""
def __init__(self, input_size):
super(InsuranceNet, self).__init__()
self.model = nn.Sequential(
nn.Linear(input_size, 128),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(128, 64),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(64, 32),
nn.ReLU(),
nn.Linear(32, 1),
)
def forward(self, x):
return self.model(x)
# 数据预处理函数
def preprocess_data(df):
"""
预处理用于模型训练/推理的输入数据框。
步骤:
1. 处理数值/分类列中的缺失值
2. 编码分类变量
3. 使用StandardScaler缩放数值特征
参数:
df (pd.DataFrame): 输入数据框
返回:
tuple: (预处理后的数据框, StandardScaler实例)
"""
# 创建副本以避免修改原始数据框
df = df.copy()
# 处理缺失值
numeric_cols = df.select_dtypes(include=["int64", "float64"]).columns
categorical_cols = df.select_dtypes(include=["object"]).columns
# 用均值填充数值型缺失值
df[numeric_cols] = df[numeric_cols].fillna(df[numeric_cols].mean())
# 用众数填充分类型缺失值
for col in categorical_cols:
df[col] = df[col].fillna(df[col].mode().iloc[0])
# 使用标签编码将分类列转换为数值
for col in categorical_cols:
df[col] = df[col].astype("category").cat.codes
# 缩放到0-1之间
if len(df[col].unique()) > 1:
df[col] = (df[col] - df[col].min()) / (df[col].max() - df[col].min())
# 缩放数值列
scaler = StandardScaler()
df[numeric_cols] = scaler.fit_transform(df[numeric_cols])
return df, scaler
# 训练函数
def train_model(
model, train_loader, val_loader, criterion, optimizer, epochs, patience=5
):
"""
训练神经网络模型,包含早停机制。
特点:
- 训练和验证的进度条显示
- 可配置耐心值的早停机制
- 模型检查点(保存最佳模型)
- 训练和验证损失追踪
参数:
model: 神经网络模型
train_loader: 训练数据加载器
val_loader: 验证数据加载器
criterion: 损失函数
optimizer: 优化算法
epochs (int): 最大训练轮数
patience (int): 早停耐心值
"""
best_val_loss = float("inf")
early_stopping_counter = 0
for epoch in range(epochs):
model.train()
train_loss = 0
# 训练循环,带进度条
train_pbar = tqdm(train_loader, desc=f"Epoch {epoch+1}/{epochs} [Train]")
for X, y in train_pbar:
X, y = X.to(device), y.to(device)
optimizer.zero_grad()
outputs = model(X)
loss = criterion(outputs, y.unsqueeze(1))
loss.backward()
optimizer.step()
train_loss += loss.item()
train_pbar.set_postfix({"loss": f"{loss.item():.4f}"})
# 验证循环
model.eval()
val_loss = 0
with torch.no_grad():
val_pbar = tqdm(val_loader, desc=f"Epoch {epoch+1}/{epochs} [Val]")
for X, y in val_pbar:
X, y = X.to(device), y.to(device)
outputs = model(X)
loss = criterion(outputs, y.unsqueeze(1))
val_loss += loss.item()
val_pbar.set_postfix({"loss": f"{loss.item():.4f}"})
train_loss /= len(train_loader)
val_loss /= len(val_loader)
print(f"Epoch {epoch+1}/{epochs}:")
print(f"Average Train Loss: {train_loss:.4f}")
print(f"Average Val Loss: {val_loss:.4f}")
# 保存最佳模型并检查是否需要早停
if val_loss < best_val_loss:
best_val_loss = val_loss
os.makedirs("model", exist_ok=True)
torch.save(model.state_dict(), "model/best_model.pth")
early_stopping_counter = 0
else:
early_stopping_counter += 1
if early_stopping_counter >= patience:
print(f"\nEarly stopping triggered after {epoch + 1} epochs")
break
# 加载数据
train_data = pd.read_csv("data/train.csv")
# 分离特征和目标
X = train_data.drop(["Premium Amount", "id"], axis=1)
y = train_data["Premium Amount"]
# 预处理数据
X, _ = preprocess_data(X)
# 分割数据
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)
# 创建数据集和数据加载器
train_dataset = InsuranceDataset(X_train.values, y_train.values)
val_dataset = InsuranceDataset(X_val.values, y_val.values)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32)
# 初始化模型、损失函数和优化器
model = InsuranceNet(input_size=X.shape[1]).to(device)
criterion = nn.MSELoss()
optimizer = Adam(model.parameters(), lr=0.001)
# 训练模型
train_model(model, train_loader, val_loader, criterion, optimizer, epochs=50)Using device: cuda
Epoch 1/50 [Train]: 100%|██████████| 30000/30000 [01:34<00:00, 318.12it/s, loss=1218314.2500] Epoch 1/50 [Val]: 100%|██████████| 7500/7500 [00:12<00:00, 578.67it/s, loss=730219.3750]
Epoch 1/50: Average Train Loss: 759551.6394 Average Val Loss: 742400.7666
Epoch 2/50 [Train]: 100%|██████████| 30000/30000 [01:22<00:00, 361.49it/s, loss=898675.0000] Epoch 2/50 [Val]: 100%|██████████| 7500/7500 [00:12<00:00, 621.52it/s, loss=702472.7500]
Epoch 2/50: Average Train Loss: 752933.1257 Average Val Loss: 745812.2019
Epoch 3/50 [Train]: 100%|██████████| 30000/30000 [01:23<00:00, 359.04it/s, loss=826401.8750] Epoch 3/50 [Val]: 100%|██████████| 7500/7500 [00:12<00:00, 588.86it/s, loss=715124.3750]
Epoch 3/50: Average Train Loss: 752149.8664 Average Val Loss: 745544.8684
Epoch 4/50 [Train]: 100%|██████████| 30000/30000 [01:37<00:00, 306.89it/s, loss=766985.6250] Epoch 4/50 [Val]: 100%|██████████| 7500/7500 [00:12<00:00, 600.04it/s, loss=712116.6250]
Epoch 4/50: Average Train Loss: 750905.8054 Average Val Loss: 742820.6678
Epoch 5/50 [Train]: 100%|██████████| 30000/30000 [01:24<00:00, 353.95it/s, loss=485303.9375] Epoch 5/50 [Val]: 100%|██████████| 7500/7500 [00:13<00:00, 564.96it/s, loss=686663.2500]
Epoch 5/50: Average Train Loss: 748635.4376 Average Val Loss: 765226.0651
Epoch 6/50 [Train]: 100%|██████████| 30000/30000 [01:31<00:00, 327.58it/s, loss=655594.3125] Epoch 6/50 [Val]: 100%|██████████| 7500/7500 [00:12<00:00, 578.19it/s, loss=707683.7500]
Epoch 6/50: Average Train Loss: 746957.6908 Average Val Loss: 763867.3913 Early stopping triggered after 6 epochs
In [2]:
# 加载测试数据和样本提交文件作为参考
test_data = pd.read_csv("data/test.csv")
sample_submission = pd.read_csv("data/sample_submission.csv")
test_features = test_data.drop("id", axis=1)
# 预处理测试数据
test_features = preprocess_data(test_features)[0]
# 创建测试数据集和数据加载器
test_dataset = InsuranceDataset(test_features.values)
test_loader = DataLoader(test_dataset, batch_size=32)
# 初始化并加载训练好的模型
model = InsuranceNet(input_size=test_features.shape[1]).to(device)
model.load_state_dict(torch.load("model/best_model.pth"))
model.eval()
# 进行预测
predictions = []
with torch.no_grad():
for X in test_loader:
X = X.to(device)
outputs = model(X)
predictions.extend(outputs.cpu().numpy())
# 创建提交文件,使用与样本提交相同的格式
submission = pd.DataFrame()
submission[sample_submission.columns[0]] = test_data["id"] # 使用样本中的精确列名
submission[sample_submission.columns[1]] = predictions # 使用样本中的精确列名
# 确保与样本提交使用相同的数据类型
for col in submission.columns:
submission[col] = submission[col].astype(sample_submission[col].dtype)
os.makedirs("results", exist_ok=True)
submission.to_csv("results/submission.csv", index=False)
print("Predictions saved to results/submission.csv")E:\TEMP\ipykernel_24612\1443286675.py:16: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.
model.load_state_dict(torch.load("model/best_model.pth"))
Predictions saved to results/submission.csv