fuse all change into one commit (#298)

This commit is contained in:
WinstonLiyt
2024-09-23 21:43:35 +08:00
committed by GitHub
parent 2523234d83
commit 087b7fa600
11 changed files with 192 additions and 58 deletions
@@ -7,7 +7,6 @@ import pandas as pd
from fea_share_preprocess import clean_and_impute_data, preprocess_script
from scipy import stats
from sklearn.metrics import accuracy_score, matthews_corrcoef
from sklearn.preprocessing import LabelEncoder
# Set random seed for reproducibility
SEED = 42
@@ -37,7 +36,6 @@ def import_module_from_path(module_name, module_path):
# 1) Preprocess the data
# TODO 如果已经做过数据预处理了,不需要再做了
X_train, X_valid, y_train, y_valid, X_test, ids = preprocess_script()
# 2) Auto feature engineering
@@ -9,53 +9,52 @@ from tqdm import tqdm
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Modified model for multi-class classification
class HybridFeatureInteractionModel(nn.Module):
def __init__(self, num_features, num_classes):
super(HybridFeatureInteractionModel, self).__init__()
# Restored three-layer model structure
class FeatureInteractionModel(nn.Module):
def __init__(self, num_features):
super(FeatureInteractionModel, self).__init__()
self.fc1 = nn.Linear(num_features, 128)
self.bn1 = nn.BatchNorm1d(128)
self.fc2 = nn.Linear(128, 64)
self.bn2 = nn.BatchNorm1d(64)
self.fc3 = nn.Linear(64, num_classes) # Output nodes equal to num_classes
self.fc3 = nn.Linear(64, 1)
self.dropout = nn.Dropout(0.3)
def forward(self, x):
x = F.relu(self.bn1(self.fc1(x)))
x = F.relu(self.bn2(self.fc2(x)))
x = self.dropout(x)
x = self.fc3(x) # No activation here, use CrossEntropyLoss
x = torch.sigmoid(self.fc3(x))
return x
# Training function
def fit(X_train, y_train, X_valid, y_valid):
num_features = X_train.shape[1]
num_classes = len(np.unique(y_train)) # Determine number of classes
model = HybridFeatureInteractionModel(num_features, num_classes).to(device)
criterion = nn.CrossEntropyLoss() # Use CrossEntropyLoss for multi-class
model = FeatureInteractionModel(num_features).to(device)
criterion = nn.BCELoss() # Binary classification problem
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# Convert to TensorDataset and create DataLoader
train_dataset = TensorDataset(
torch.tensor(X_train.to_numpy(), dtype=torch.float32), torch.tensor(y_train.to_numpy(), dtype=torch.long)
torch.tensor(X_train.to_numpy(), dtype=torch.float32), torch.tensor(y_train.reshape(-1), dtype=torch.float32)
)
valid_dataset = TensorDataset(
torch.tensor(X_valid.to_numpy(), dtype=torch.float32), torch.tensor(y_valid.to_numpy(), dtype=torch.long)
torch.tensor(X_valid.to_numpy(), dtype=torch.float32), torch.tensor(y_valid.reshape(-1), dtype=torch.float32)
)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=32, shuffle=False)
# Train the model
model.train()
for epoch in range(5): # just for quick run
for epoch in range(5):
print(f"Epoch {epoch + 1}/5")
epoch_loss = 0
for X_batch, y_batch in tqdm(train_loader, desc="Training", leave=False):
X_batch, y_batch = X_batch.to(device), y_batch.to(device)
X_batch, y_batch = X_batch.to(device), y_batch.to(device) # Move data to the device
optimizer.zero_grad()
outputs = model(X_batch)
loss = criterion(outputs, y_batch)
outputs = model(X_batch).squeeze(1) # Reshape outputs to [32]
loss = criterion(outputs, y_batch) # Adjust target shape
loss.backward()
optimizer.step()
epoch_loss += loss.item()
@@ -69,10 +68,9 @@ def predict(model, X):
model.eval()
predictions = []
with torch.no_grad():
X_tensor = torch.tensor(X.values, dtype=torch.float32).to(device)
X_tensor = torch.tensor(X.values, dtype=torch.float32).to(device) # Move data to the device
for i in tqdm(range(0, len(X_tensor), 32), desc="Predicting", leave=False):
batch = X_tensor[i : i + 32]
pred = model(batch)
pred = torch.argmax(pred, dim=1).cpu().numpy() # Use argmax to get class
batch = X_tensor[i : i + 32] # Predict in batches
pred = model(batch).squeeze().cpu().numpy() # Move results back to CPU
predictions.extend(pred)
return np.array(predictions)
return np.array(predictions) # Return boolean predictions
@@ -10,9 +10,9 @@ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Restored three-layer model structure
class HybridFeatureInteractionModel(nn.Module):
class FeatureInteractionModel(nn.Module):
def __init__(self, num_features):
super(HybridFeatureInteractionModel, self).__init__()
super(FeatureInteractionModel, self).__init__()
self.fc1 = nn.Linear(num_features, 128)
self.bn1 = nn.BatchNorm1d(128)
self.fc2 = nn.Linear(128, 64)
@@ -31,7 +31,7 @@ class HybridFeatureInteractionModel(nn.Module):
# Training function
def fit(X_train, y_train, X_valid, y_valid):
num_features = X_train.shape[1]
model = HybridFeatureInteractionModel(num_features).to(device)
model = FeatureInteractionModel(num_features).to(device)
criterion = nn.BCELoss() # Binary classification problem
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
@@ -10,9 +10,9 @@ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Modified model for regression
class HybridFeatureInteractionModel(nn.Module):
class FeatureInteractionModel(nn.Module):
def __init__(self, num_features):
super(HybridFeatureInteractionModel, self).__init__()
super(FeatureInteractionModel, self).__init__()
self.fc1 = nn.Linear(num_features, 128)
self.bn1 = nn.BatchNorm1d(128)
self.fc2 = nn.Linear(128, 64)
@@ -31,7 +31,7 @@ class HybridFeatureInteractionModel(nn.Module):
# Training function
def fit(X_train, y_train, X_valid, y_valid):
num_features = X_train.shape[1]
model = HybridFeatureInteractionModel(num_features).to(device)
model = FeatureInteractionModel(num_features).to(device)
criterion = nn.MSELoss() # Use MSELoss for regression
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
@@ -35,6 +35,7 @@ class KGScenario(Scenario):
self.target_description = None
self.competition_features = None
self._analysis_competition_description()
self.if_action_choosing_based_on_UCB = False
self._background = self.background
@@ -0,0 +1,76 @@
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, TensorDataset
from tqdm import tqdm
# Check if a GPU is available
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Restored three-layer model structure
class FeatureInteractionModel(nn.Module):
def __init__(self, num_features):
super(FeatureInteractionModel, self).__init__()
self.fc1 = nn.Linear(num_features, 128)
self.bn1 = nn.BatchNorm1d(128)
self.fc2 = nn.Linear(128, 64)
self.bn2 = nn.BatchNorm1d(64)
self.fc3 = nn.Linear(64, 1)
self.dropout = nn.Dropout(0.3)
def forward(self, x):
x = F.relu(self.bn1(self.fc1(x)))
x = F.relu(self.bn2(self.fc2(x)))
x = self.dropout(x)
x = torch.sigmoid(self.fc3(x))
return x
# Training function
def fit(X_train, y_train, X_valid, y_valid):
num_features = X_train.shape[1]
model = FeatureInteractionModel(num_features).to(device)
criterion = nn.BCELoss() # Binary classification problem
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# Convert to TensorDataset and create DataLoader
train_dataset = TensorDataset(
torch.tensor(X_train.to_numpy(), dtype=torch.float32), torch.tensor(y_train.reshape(-1), dtype=torch.float32)
)
valid_dataset = TensorDataset(
torch.tensor(X_valid.to_numpy(), dtype=torch.float32), torch.tensor(y_valid.reshape(-1), dtype=torch.float32)
)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
valid_loader = DataLoader(valid_dataset, batch_size=32, shuffle=False)
# Train the model
model.train()
for epoch in range(5):
print(f"Epoch {epoch + 1}/5")
epoch_loss = 0
for X_batch, y_batch in tqdm(train_loader, desc="Training", leave=False):
X_batch, y_batch = X_batch.to(device), y_batch.to(device) # Move data to the device
optimizer.zero_grad()
outputs = model(X_batch).squeeze(1) # Reshape outputs to [32]
loss = criterion(outputs, y_batch) # Adjust target shape
loss.backward()
optimizer.step()
epoch_loss += loss.item()
print(f"End of epoch {epoch + 1}, Avg Loss: {epoch_loss / len(train_loader):.4f}")
return model
# Prediction function
def predict(model, X):
model.eval()
predictions = []
with torch.no_grad():
X_tensor = torch.tensor(X.values, dtype=torch.float32).to(device) # Move data to the device
for i in tqdm(range(0, len(X_tensor), 32), desc="Predicting", leave=False):
batch = X_tensor[i : i + 32] # Predict in batches
pred = model(batch).squeeze().cpu().numpy() # Move results back to CPU
predictions.extend(pred)
return np.array(predictions) # Return boolean predictions
@@ -5,7 +5,7 @@ from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.preprocessing import LabelEncoder
def prepreprocess():
@@ -17,7 +17,7 @@ def prepreprocess():
data_df = data_df.drop(["PassengerId"], axis=1)
X = data_df.drop(["Transported"], axis=1)
y = data_df[["Transported"]]
y = data_df["Transported"]
label_encoder = LabelEncoder()
y = label_encoder.fit_transform(y) # Convert class labels to numeric
@@ -37,45 +37,39 @@ def preprocess_fit(X_train: pd.DataFrame):
categorical_cols = [cname for cname in X_train.columns if X_train[cname].dtype == "object"]
# Define preprocessors for numerical and categorical features
categorical_transformer = Pipeline(
steps=[
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore")),
]
)
label_encoders = {col: LabelEncoder().fit(X_train[col]) for col in categorical_cols}
numerical_transformer = Pipeline(steps=[("imputer", SimpleImputer(strategy="mean"))])
# Combine preprocessing steps
preprocessor = ColumnTransformer(
transformers=[
("cat", categorical_transformer, categorical_cols),
("num", numerical_transformer, numerical_cols),
]
],
remainder="passthrough",
)
# Fit the preprocessor on the training data
preprocessor.fit(X_train)
return preprocessor
return preprocessor, label_encoders
def preprocess_transform(X: pd.DataFrame, preprocessor):
def preprocess_transform(X: pd.DataFrame, preprocessor, label_encoders):
"""
Transforms the given DataFrame using the fitted preprocessor.
Ensures the processed data has consistent features across train, validation, and test sets.
"""
# Transform the data using the fitted preprocessor
X_array = preprocessor.transform(X).toarray()
# Encode categorical features
for col, le in label_encoders.items():
# Handle unseen labels by setting them to a default value (e.g., -1)
X[col] = X[col].apply(lambda x: le.transform([x])[0] if x in le.classes_ else -1)
# Get feature names for the columns in the transformed data
categorical_cols = [cname for cname in X.columns if X[cname].dtype == "object"]
feature_names = preprocessor.named_transformers_["cat"]["onehot"].get_feature_names_out(
categorical_cols
).tolist() + [cname for cname in X.columns if X[cname].dtype in ["int64", "float64"]]
# Transform the data using the fitted preprocessor
X_array = preprocessor.transform(X)
# Convert arrays back to DataFrames
X_transformed = pd.DataFrame(X_array, columns=feature_names, index=X.index)
X_transformed = pd.DataFrame(X_array, columns=X.columns, index=X.index)
return X_transformed
@@ -96,16 +90,16 @@ def preprocess_script():
X_train, X_valid, y_train, y_valid = prepreprocess()
# Fit the preprocessor on the training data
preprocessor = preprocess_fit(X_train)
preprocessor, label_encoders = preprocess_fit(X_train)
# Preprocess the train, validation, and test data
X_train = preprocess_transform(X_train, preprocessor)
X_valid = preprocess_transform(X_valid, preprocessor)
X_train = preprocess_transform(X_train, preprocessor, label_encoders)
X_valid = preprocess_transform(X_valid, preprocessor, label_encoders)
# Load and preprocess the test data
submission_df = pd.read_csv("/kaggle/input/test.csv")
passenger_ids = submission_df["PassengerId"]
submission_df = submission_df.drop(["PassengerId"], axis=1)
X_test = preprocess_transform(submission_df, preprocessor)
X_test = preprocess_transform(submission_df, preprocessor, label_encoders)
return X_train, X_valid, y_train, y_valid, X_test, passenger_ids
@@ -10,9 +10,9 @@ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Restored three-layer model structure
class HybridFeatureInteractionModel(nn.Module):
class FeatureInteractionModel(nn.Module):
def __init__(self, num_features):
super(HybridFeatureInteractionModel, self).__init__()
super(FeatureInteractionModel, self).__init__()
self.fc1 = nn.Linear(num_features, 128)
self.bn1 = nn.BatchNorm1d(128)
self.fc2 = nn.Linear(128, 64)
@@ -31,7 +31,7 @@ class HybridFeatureInteractionModel(nn.Module):
# Training function
def fit(X_train, y_train, X_valid, y_valid):
num_features = X_train.shape[1]
model = HybridFeatureInteractionModel(num_features).to(device)
model = FeatureInteractionModel(num_features).to(device)
criterion = nn.BCELoss() # Binary classification problem
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
@@ -22,7 +22,7 @@ def fit(X_train: pd.DataFrame, y_train: pd.DataFrame, X_valid: pd.DataFrame, y_v
params = {
"nthred": -1,
}
num_round = 180
num_round = 100
evallist = [(dtrain, "train"), (dvalid, "eval")]
bst = xgb.train(params, dtrain, num_round, evallist)
+1 -1
View File
@@ -39,7 +39,7 @@ hypothesis_and_feedback: |-
hypothesis_output_format: |-
The output should follow JSON format. The schema is as follows:
{
"action": "The action that the user wants to take based on the information provided. should be one of ["Feature engineering", "Feature processing", "Model feature selection", "Model tuning"]"
"action": "If "hypothesis_specification" provides the action you need to take, please follow "hypothesis_specification" to choose the action. Otherwise, based on previous experimental results, suggest the action you believe is most appropriate at the moment. It should be one of ["Feature engineering", "Feature processing", "Model feature selection", "Model tuning"]"
"hypothesis": "The new hypothesis generated based on the information provided.",
"reason": "The reason why you generate this hypothesis. It should be comprehensive and logical. It should cover the other keys below and extend them.",
"concise_reason": "Two-line summary. First line focuses on a concise justification for the change. Second line generalizes a knowledge statement.",
+68 -1
View File
@@ -1,4 +1,5 @@
import json
import math
from pathlib import Path
from typing import List, Tuple
@@ -81,6 +82,20 @@ class KGHypothesisGen(ModelHypothesisGen):
def __init__(self, scen: Scenario) -> Tuple[dict, bool]:
super().__init__(scen)
self.action_counts = {
"Feature engineering": 0,
"Feature processing": 0,
"Model feature selection": 0,
"Model tuning": 0,
}
self.reward_estimates = {
"Feature engineering": 0.0,
"Feature processing": 0.0,
"Model feature selection": 0.0,
"Model tuning": 0.0,
}
self.confidence_parameter = 1.0
self.initial_performance = 0.0
def generate_RAG_content(self, trace: Trace) -> str:
if trace.knowledge_base is None:
@@ -162,6 +177,52 @@ class KGHypothesisGen(ModelHypothesisGen):
)
return RAG_content
def update_reward_estimates(self, trace: Trace) -> None:
if len(trace.hist) > 0:
last_entry = trace.hist[-1]
last_action = last_entry[0].action
last_result = last_entry[1].result
# Extract performance_t
performance_t = last_result.get("performance", 0.0)
# Get performance_{t-1}
if len(trace.hist) > 1:
prev_entry = trace.hist[-2]
prev_result = prev_entry[1].result
performance_t_minus_1 = prev_result.get("performance", 0.0)
else:
performance_t_minus_1 = self.initial_performance
reward = performance_t_minus_1 - performance_t
n_o = self.action_counts[last_action]
mu_o = self.reward_estimates[last_action]
self.reward_estimates[last_action] += (reward - mu_o) / n_o
else:
# First iteration, nothing to update
pass
def execute_next_action(self, trace: Trace) -> str:
actions = list(self.action_counts.keys())
t = sum(self.action_counts.values()) + 1
# If any action has not been tried yet, select it
for action in actions:
if self.action_counts[action] == 0:
selected_action = action
self.action_counts[selected_action] += 1
return selected_action
c = self.confidence_parameter
ucb_values = {}
for action in actions:
mu_o = self.reward_estimates[action]
n_o = self.action_counts[action]
ucb = mu_o + c * math.sqrt(math.log(t) / n_o)
ucb_values[action] = ucb
# Select action with highest UCB
selected_action = max(ucb_values, key=ucb_values.get)
self.action_counts[selected_action] += 1
return selected_action
def prepare_context(self, trace: Trace) -> Tuple[dict, bool]:
hypothesis_and_feedback = (
(
@@ -173,16 +234,22 @@ class KGHypothesisGen(ModelHypothesisGen):
else "No previous hypothesis and feedback available since it's the first round."
)
if self.scen.if_action_choosing_based_on_UCB:
action = self.execute_next_action(trace)
context_dict = {
"hypothesis_and_feedback": hypothesis_and_feedback,
"RAG": self.generate_RAG_content(trace),
"hypothesis_output_format": prompt_dict["hypothesis_output_format"],
"hypothesis_specification": None,
"hypothesis_specification": f"next experiment action is {action}"
if self.scen.if_action_choosing_based_on_UCB
else None,
}
return context_dict, True
def convert_response(self, response: str) -> ModelHypothesis:
response_dict = json.loads(response)
hypothesis = KGHypothesis(
hypothesis=response_dict["hypothesis"],
reason=response_dict["reason"],