feat: Added loop code for Kaggle scene. (#211)

* fuse all code into one commit

* remove container auto

* change remove method

* add kaggle env start

* change kaggle api

* change structure

* add crawler

* add requirements

* refeact the code

* delete mistaken codes

* merge docker settings and crawler

* add chrome install README for crawler usage

* Connect scen with Kaggle to download data

* Reformat some files to pass CI.

* fix some ci errors

* fix a ci error

* fix a ci error

---------

Co-authored-by: Bowen Xian <xianbowen@outlook.com>
This commit is contained in:
WinstonLiyt
2024-08-19 10:58:28 +08:00
committed by GitHub
parent 7a4044dc1a
commit 96424bd1ff
16 changed files with 848 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
from pathlib import Path
from pydantic_settings import BaseSettings
from rdagent.components.workflow.conf import BasePropSetting
class PropSetting(BasePropSetting):
class Config:
env_prefix = "KG_"
"""Use `KG_` as prefix for environment variables"""
protected_namespaces = ()
"""Add 'model_' to the protected namespaces"""
# 1) overriding the default
scen: str = "rdagent.scenarios.kaggle.experiment.model_experiment.KGModelScenario"
"""Scenario class for data mining model"""
hypothesis_gen: str = "rdagent.scenarios.kaggle.proposal.model_proposal.KGModelHypothesisGen"
"""Hypothesis generation class"""
hypothesis2experiment: str = "rdagent.scenarios.kaggle.proposal.model_proposal.KGModelHypothesis2Experiment"
"""Hypothesis to experiment class"""
coder: str = "rdagent.scenarios.kaggle.developer.model_coder.KGModelCoSTEER"
"""Coder class"""
runner: str = "rdagent.scenarios.kaggle.developer.model_runner.KGModelRunner"
"""Runner class"""
summarizer: str = "rdagent.scenarios.kaggle.developer.feedback.KGModelHypothesisExperiment2Feedback"
"""Summarizer class"""
evolving_n: int = 10
"""Number of evolutions"""
evolving_n: int = 10
competition: str = ""
PROP_SETTING = PropSetting()
+65
View File
@@ -0,0 +1,65 @@
from collections import defaultdict
import fire
from rdagent.app.kaggle.conf import PROP_SETTING
from rdagent.components.workflow.conf import BasePropSetting
from rdagent.components.workflow.rd_loop import RDLoop
from rdagent.core.exception import ModelEmptyError
from rdagent.core.proposal import (
Hypothesis2Experiment,
HypothesisExperiment2Feedback,
HypothesisGen,
Trace,
)
from rdagent.core.utils import import_class
from rdagent.log import rdagent_logger as logger
class ModelRDLoop(RDLoop):
def __init__(self, PROP_SETTING: BasePropSetting):
with logger.tag("init"):
scen: Scenario = import_class(PROP_SETTING.scen)(PROP_SETTING.competition)
logger.log_object(scen, tag="scenario")
self.hypothesis_gen: HypothesisGen = import_class(PROP_SETTING.hypothesis_gen)(scen)
logger.log_object(self.hypothesis_gen, tag="hypothesis generator")
self.hypothesis2experiment: Hypothesis2Experiment = import_class(PROP_SETTING.hypothesis2experiment)()
logger.log_object(self.hypothesis2experiment, tag="hypothesis2experiment")
self.coder: Developer = import_class(PROP_SETTING.coder)(scen)
logger.log_object(self.coder, tag="coder")
self.runner: Developer = import_class(PROP_SETTING.runner)(scen)
logger.log_object(self.runner, tag="runner")
self.summarizer: HypothesisExperiment2Feedback = import_class(PROP_SETTING.summarizer)(scen)
logger.log_object(self.summarizer, tag="summarizer")
self.trace = Trace(scen=scen)
super(RDLoop, self).__init__()
skip_loop_error = (ModelEmptyError,)
def main(path=None, step_n=None, competition=None):
"""
Auto R&D Evolving loop for models in a kaggle{} scenario.
You can continue running session by
.. code-block:: python
dotenv run -- python rdagent/app/kaggle/model.py [--competition titanic] $LOG_PATH/__session__/1/0_propose --step_n 1 # `step_n` is a optional paramter
"""
if competition:
PROP_SETTING.competition = competition
if path is None:
model_loop = ModelRDLoop(PROP_SETTING)
else:
model_loop = ModelRDLoop.load(path)
model_loop.run(step_n=step_n)
if __name__ == "__main__":
fire.Fire(main)
+20
View File
@@ -0,0 +1,20 @@
# Kaggle Crawler
## Install chrome & chromedriver for Linux
In one folder
```shell
# install chrome
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ./google-chrome-stable_current_amd64.deb
google-chrome --version
# install chromedriver
wget https://storage.googleapis.com/chrome-for-testing-public/<chrome-version>/linux64/chromedriver-linux64.zip
unzip chromedriver-linux64.zip
cd chromedriver-linux64
sudo mv chromedriver /usr/local/bin
sudo chmod +x /usr/local/bin/chromedriver
chromedriver --version
```
@@ -0,0 +1,68 @@
import json
from pathlib import Path
from jinja2 import Environment, StrictUndefined
from rdagent.core.experiment import Experiment
from rdagent.core.prompts import Prompts
from rdagent.core.proposal import (
Hypothesis,
HypothesisExperiment2Feedback,
HypothesisFeedback,
Trace,
)
from rdagent.log import rdagent_logger as logger
from rdagent.oai.llm_utils import APIBackend
from rdagent.utils import convert2bool
feedback_prompts = Prompts(file_path=Path(__file__).parent.parent.parent / "qlib" / "prompts.yaml")
DIRNAME = Path(__file__).absolute().resolve().parent
class KGModelHypothesisExperiment2Feedback(HypothesisExperiment2Feedback):
"""Generated feedbacks on the hypothesis from **Executed** Implementations of different tasks & their comparisons with previous performances"""
def generate_feedback(self, exp: Experiment, hypothesis: Hypothesis, trace: Trace) -> HypothesisFeedback:
"""
The `ti` should be executed and the results should be included, as well as the comparison between previous results (done by LLM).
For example: `mlflow` of Qlib will be included.
"""
logger.info("Generating feedback...")
# Define the system prompt for hypothesis feedback
system_prompt = feedback_prompts["model_feedback_generation"]["system"]
# Define the user prompt for hypothesis feedback
context = trace.scen
SOTA_hypothesis, SOTA_experiment = trace.get_sota_hypothesis_and_experiment()
user_prompt = (
Environment(undefined=StrictUndefined)
.from_string(feedback_prompts["model_feedback_generation"]["user"])
.render(
context=context,
last_hypothesis=SOTA_hypothesis,
last_task=SOTA_experiment.sub_tasks[0].get_task_information() if SOTA_hypothesis else None,
last_code=SOTA_experiment.sub_workspace_list[0].code_dict.get("model.py") if SOTA_hypothesis else None,
last_result=SOTA_experiment.result if SOTA_hypothesis else None,
hypothesis=hypothesis,
exp=exp,
)
)
# Call the APIBackend to generate the response for hypothesis feedback
response_hypothesis = APIBackend().build_messages_and_create_chat_completion(
user_prompt=user_prompt,
system_prompt=system_prompt,
json_mode=True,
)
# Parse the JSON response to extract the feedback
response_json_hypothesis = json.loads(response_hypothesis)
return HypothesisFeedback(
observations=response_json_hypothesis.get("Observations", "No observations provided"),
hypothesis_evaluation=response_json_hypothesis.get("Feedback for Hypothesis", "No feedback provided"),
new_hypothesis=response_json_hypothesis.get("New Hypothesis", "No new hypothesis provided"),
reason=response_json_hypothesis.get("Reasoning", "No reasoning provided"),
decision=convert2bool(response_json_hypothesis.get("Decision", "false")),
)
@@ -0,0 +1,3 @@
from rdagent.components.coder.model_coder.CoSTEER import ModelCoSTEER
KGModelCoSTEER = ModelCoSTEER
@@ -0,0 +1,38 @@
import shutil
import uuid
from pathlib import Path
import pandas as pd
from rdagent.components.coder.model_coder.model import ModelExperiment, ModelFBWorkspace
from rdagent.components.runner import CachedRunner
from rdagent.components.runner.conf import RUNNER_SETTINGS
from rdagent.core.developer import Developer
from rdagent.core.exception import ModelEmptyError
from rdagent.log import rdagent_logger as logger
from rdagent.scenarios.kaggle.experiment.model_experiment import KGModelExperiment
from rdagent.utils.env import KGDockerEnv
class KGModelRunner(CachedRunner[KGModelExperiment]):
def develop(self, exp: KGModelExperiment) -> KGModelExperiment:
if RUNNER_SETTINGS.cache_result:
cache_hit, result = self.get_cache_result(exp)
if cache_hit:
exp.result = result
return exp
if exp.sub_workspace_list[0].code_dict.get("model.py") is None:
raise ModelEmptyError("model.py is empty")
# to replace & inject code
exp.experiment_workspace.inject_code(**{"model.py": exp.sub_workspace_list[0].code_dict["model.py"]})
env_to_use = {"PYTHONPATH": "./"}
result = exp.experiment_workspace.execute(run_env=env_to_use)
exp.result = result
if RUNNER_SETTINGS.cache_result:
self.dump_cache_result(exp, result)
return exp
@@ -0,0 +1,25 @@
FROM pytorch/pytorch:latest
# For GPU support, please choose the proper tag from https://hub.docker.com/r/pytorch/pytorch/tags
RUN apt-get clean && apt-get update && apt-get install -y \
curl \
vim \
git \
build-essential \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
RUN python -m pip install numpy
RUN python -m pip install --upgrade cython
# RUN python -m pip install -e .
RUN python -m pip install pandas
# RUN pip install pyg_lib torch_scatter torch_sparse torch_cluster -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121.html
RUN pip install torch_geometric
RUN pip install ogb
RUN pip install networkx
RUN pip install scikit-learn
RUN pip install catboost
RUN pip install xgboost
RUN pip install sparse
@@ -0,0 +1,113 @@
import json
from pathlib import Path
from jinja2 import Environment, StrictUndefined
from rdagent.components.coder.model_coder.model import (
ModelExperiment,
ModelFBWorkspace,
ModelTask,
)
from rdagent.core.prompts import Prompts
from rdagent.core.scenario import Scenario
from rdagent.oai.llm_utils import APIBackend
from rdagent.scenarios.kaggle.experiment.workspace import KGFBWorkspace
from rdagent.scenarios.kaggle.kaggle_crawler import crawl_descriptions
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
class KGModelExperiment(ModelExperiment[ModelTask, KGFBWorkspace, ModelFBWorkspace]):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.experiment_workspace = KGFBWorkspace(template_folder_path=Path(__file__).parent / "model_template")
class KGModelScenario(Scenario):
def __init__(self, competition: str) -> None:
super().__init__()
self.competition = competition
self.competition_descriptions = crawl_descriptions(competition)
self.competition_type = None
self.competition_description = None
self.target_description = None
self.competition_features = None
self._analysis_competition_description()
def _analysis_competition_description(self):
# TODO: use gpt to analyze the competition description
sys_prompt = (
Environment(undefined=StrictUndefined)
.from_string(prompt_dict["kg_description_template"]["system"])
.render()
)
user_prompt = (
Environment(undefined=StrictUndefined)
.from_string(prompt_dict["kg_description_template"]["user"])
.render(
competition_descriptions=self.competition_descriptions,
)
)
response_analysis = APIBackend().build_messages_and_create_chat_completion(
user_prompt=user_prompt,
system_prompt=sys_prompt,
json_mode=True,
)
response_json_analysis = json.loads(response_analysis)
self.competition_type = response_json_analysis.get("Competition Type", "No type provided")
self.competition_description = response_json_analysis.get("Competition Description", "No description provided")
self.target_description = response_json_analysis.get("Target Description", "No target provided")
self.competition_features = response_json_analysis.get("Competition Features", "No features provided")
@property
def background(self) -> str:
background_template = prompt_dict["kg_model_background"]
background_prompt = (
Environment(undefined=StrictUndefined)
.from_string(background_template)
.render(
competition_type=self.competition_type,
competition_description=self.competition_description,
target_description=self.target_description,
competition_features=self.competition_features,
)
)
return background_prompt
@property
def source_data(self) -> str:
raise NotImplementedError("source_data is not implemented")
@property
def output_format(self) -> str:
return prompt_dict["kg_model_output_format"]
@property
def interface(self) -> str:
return prompt_dict["kg_model_interface"]
@property
def simulator(self) -> str:
return prompt_dict["kg_model_simulator"]
@property
def rich_style_description(self) -> str:
return """
kaggle scen """
def get_scenario_all_desc(self) -> str:
return f"""Background of the scenario:
{self.background}
The interface you should follow to write the runnable code:
{self.interface}
The output of your code should be in the format:
{self.output_format}
The simulator user can use to test your model:
{self.simulator}
"""
@@ -0,0 +1,3 @@
## This folder is a template to be copied from for each model implementation & running process.
Components: Dummy model.py, versatile conf.yaml, and a result reader.
@@ -0,0 +1,137 @@
import random
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from model import model_cls
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
# Set random seed for reproducibility
SEED = 42
random.seed(SEED)
torch.manual_seed(SEED)
np.random.seed(SEED)
def compute_metrics_for_classification(y_true, y_pred):
"""Compute accuracy metric for classification."""
accuracy = accuracy_score(y_true, y_pred)
return accuracy
def train_model(X_train, y_train, X_valid, y_valid):
"""Define and train the model."""
X_train_dense = X_train.toarray() if hasattr(X_train, "toarray") else X_train
X_valid_dense = X_valid.toarray() if hasattr(X_valid, "toarray") else X_valid
X_train_tensor = torch.tensor(X_train_dense, dtype=torch.float32)
y_train_tensor = torch.tensor(y_train, dtype=torch.float32).unsqueeze(1)
X_valid_tensor = torch.tensor(X_valid_dense, dtype=torch.float32)
y_valid_tensor = torch.tensor(y_valid, dtype=torch.float32).unsqueeze(1)
# Define the model
model = model_cls(num_features=X_train.shape[1])
# Define loss function and optimizer
criterion = nn.BCELoss() # Binary cross entropy loss
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# Train the model
num_epochs = 150 # Number of epochs
for epoch in range(num_epochs):
model.train()
optimizer.zero_grad()
y_train_pred = model(X_train_tensor)
loss = criterion(y_train_pred, y_train_tensor)
loss.backward()
optimizer.step()
# Evaluate model on validation set after each epoch
model.eval()
with torch.no_grad():
y_valid_pred = model(X_valid_tensor)
valid_loss = criterion(y_valid_pred, y_valid_tensor)
print(f"Epoch {epoch+1}/{num_epochs}, Loss: {loss.item()}, Validation Loss: {valid_loss.item()}")
return model
def predict(model, X):
"""Make predictions using the trained model."""
X_dense = X.toarray() if hasattr(X, "toarray") else X
X_tensor = torch.tensor(X_dense, dtype=torch.float32)
model.eval()
with torch.no_grad():
y_pred = model(X_tensor)
y_pred = y_pred.numpy().flatten()
return y_pred > 0.5 # Apply threshold to get boolean predictions
if __name__ == "__main__":
# Load and preprocess the data
data_df = pd.read_csv("/root/.data/train.csv")
data_df = data_df.drop(["PassengerId", "Name"], axis=1)
X = data_df.drop(["Transported"], axis=1)
y = data_df.Transported.to_numpy()
# Identify numerical and categorical features
numerical_cols = [cname for cname in X.columns if X[cname].dtype in ["int64", "float64"]]
categorical_cols = [cname for cname in X.columns if X[cname].dtype == "object"]
# Define preprocessors for numerical and categorical features
categorical_transformer = Pipeline(
steps=[("imputer", SimpleImputer(strategy="most_frequent")), ("onehot", OneHotEncoder(handle_unknown="ignore"))]
)
numerical_transformer = Pipeline(steps=[("imputer", SimpleImputer(strategy="mean"))])
# Combine preprocessing steps
preprocessor = ColumnTransformer(
transformers=[
("cat", categorical_transformer, categorical_cols),
("num", numerical_transformer, numerical_cols),
]
)
# Split the data into training and validation sets
X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.10, random_state=SEED)
# Fit the preprocessor on the training data and transform both training and validation data
preprocessor.fit(X_train)
X_train = preprocessor.transform(X_train)
X_valid = preprocessor.transform(X_valid)
# Train the model
model = train_model(X_train, y_train, X_valid, y_valid)
# Evaluate the model on the validation set
y_valid_pred = predict(model, X_valid)
accuracy = compute_metrics_for_classification(y_valid, y_valid_pred)
print("Final Accuracy on validation set: ", accuracy)
# Save the validation accuracy
pd.Series(data=[accuracy], index=["ACC"]).to_csv("./submission.csv")
# Load and preprocess the test set
submission_df = pd.read_csv("/root/.data/test.csv")
submission_df = submission_df.drop(["PassengerId", "Name"], axis=1)
X_test = preprocessor.transform(submission_df)
# Make predictions on the test set and save them
y_test_pred = predict(model, X_test)
pd.Series(y_test_pred).to_csv("./submission_update.csv", index=False)
# submit predictions for the test set
submission_df = pd.read_csv("/root/.data/test.csv")
submission_df = submission_df.drop(["PassengerId", "Name"], axis=1)
X_test = preprocessor.transform(submission_df)
y_test_pred = predict(model, X_test)
y_test_pred.to_csv("./submission_update.csv")
@@ -0,0 +1,74 @@
kg_description_template:
system: |-
You are an assistant that extracts structured information from unstructured text.
user: |-
Based on the following competition description, please extract the following details:
1. Competition Type
2. Competition Description
3. Target Description
4. Competition Features
Competition Description: {{ competition_descriptions }}
kg_model_background: |-
You are solving this data science tasks of {{ competition_type }}:
{{competition_description}}
We provide an overall pipeline in train.py. Now fill in the provided train.py script to train a {{ competition_type }} model to get a good performance on this task.
The model is a machine learning or deep learning structure designed to predict {{ target_description }}.
The data is extracted from the competition dataset, focusing on passenger attributes like {{ competition_features }}.
The model is defined in the following parts:
- Name: The name of the model.
- Description: A description of the model.
- Architecture: The detailed architecture of the model, such as neural network layers or tree structures.
The model should provide clear and detailed documentation of its architecture and hyperparameters.
kg_model_interface: |-
Your python code should follow the interface to better interact with the user's system.
You code should contain several parts:
1. The import part: import the necessary libraries.
2. A class that is a subclass of pytorch.nn.Module. This class should have an __init__ function and a forward function, which inputs a tensor and outputs a tensor.
3. Set a variable called "model_cls" to the class you defined.
The user will save your code into a python file called "model.py". Then the user imports model_cls in file "model.py" after setting the cwd into the directory:
```python
from model import model_cls
```
So your python code should follow the pattern:
```python
class XXXModel(torch.nn.Module):
...
model_cls = XXXModel
```
The model has one types, "Tabular" for tabular model. The input shape to a tabular model is (batch_size, num_features).
The output shape of the model should be (batch_size, 1).
The "batch_size" is a dynamic value which is determined by the input of forward function.
The "num_features" is static which will be provided to the model through init function.
User will initialize the tabular model with the following code:
```python
model = model_cls(num_features=num_features)
```
User will initialize the tabular model with the following code:
```python
model = model_cls(num_features=num_features)
```
No other parameters will be passed to the model so give other parameters a default value or just make them static.
Don't write any try-except block in your python code. The user will catch the exception message and provide the feedback to you. Also, don't write main function in your python code. The user will call the forward method in the model_cls to get the output tensor.
Please notice that your model should only use current features as input. The user will provide the input tensor to the model's forward function.
kg_model_output_format: |-
Your output should be a tensor with shape (batch_size, 1).
The output tensor should be saved in a file named "output.pth" in the same directory as your python file.
The user will evaluate the shape of the output tensor so the tensor read from "output.pth" should be 8 numbers.
kg_model_simulator: |-
The models will be trained on the Spaceship Titanic dataset and evaluated on their ability to predict whether passengers were transported using metrics like accuracy and AUC-ROC.
Model performance will be iteratively improved based on feedback from evaluation results.
@@ -0,0 +1,33 @@
import subprocess
import zipfile
from pathlib import Path
import pandas as pd
from rdagent.app.kaggle.conf import PROP_SETTING
from rdagent.core.experiment import FBWorkspace
from rdagent.log import rdagent_logger as logger
from rdagent.utils.env import DockerEnv, KGDockerEnv
class KGFBWorkspace(FBWorkspace):
def __init__(self, template_folder_path: Path, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.inject_code_from_folder(template_folder_path)
def execute(self, run_env: dict = {}, *args, **kwargs) -> str:
qtde = KGDockerEnv(PROP_SETTING.competition)
qtde.prepare()
execute_log = qtde.run(
local_path=str(self.workspace_path),
entry=f"python train.py",
env=run_env,
)
csv_path = self.workspace_path / "submission.csv"
if not csv_path.exists():
logger.error(f"File {csv_path} does not exist.")
return None
return pd.read_csv(csv_path, index_col=0).iloc[:, 0]
@@ -0,0 +1,77 @@
import json
import time
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
LOCAL_PATH = "/data/userdata/share/kaggle_competition_descriptions"
options = webdriver.ChromeOptions()
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--headless")
service = Service("/usr/local/bin/chromedriver")
def crawl_descriptions(competition: str, wait: float = 3.0, force: bool = False) -> dict[str, str]:
if (fp := Path(f"{LOCAL_PATH}/{competition}.json")).exists() and not force:
print(f"Found {competition}.json, loading from local file.")
with fp.open("r") as f:
return json.load(f)
driver = webdriver.Chrome(options=options, service=service)
overview_url = f"https://www.kaggle.com/competitions/{competition}/overview"
driver.get(overview_url)
time.sleep(wait)
site_body = driver.find_element(By.ID, "site-content")
descriptions = {}
# Get the subtitles
elements = site_body.find_elements(By.CSS_SELECTOR, f"a[href^='/competitions/{competition}/overview/']")
subtitles = []
for e in elements:
inner_text = ""
for child in e.find_elements(By.XPATH, ".//*"):
inner_text += child.get_attribute("innerHTML").strip()
subtitles.append(inner_text)
# Get main contents
contents = []
elements = site_body.find_elements(By.CSS_SELECTOR, ".sc-iWlrxG.cMAZdc")
for e in elements:
content = e.get_attribute("innerHTML")
contents.append(content)
assert len(subtitles) == len(contents) + 1 and subtitles[-1] == "Citation"
for i in range(len(subtitles) - 1):
descriptions[subtitles[i]] = contents[i]
# Get the citation
element = site_body.find_element(By.CSS_SELECTOR, ".sc-ifyrTC.sc-fyziuY")
citation = element.get_attribute("innerHTML")
descriptions[subtitles[-1]] = citation
data_url = f"https://www.kaggle.com/competitions/{competition}/data"
driver.get(data_url)
time.sleep(wait)
data_element = driver.find_element(By.CSS_SELECTOR, ".sc-iWlrxG.cMAZdc")
descriptions["Data Description"] = data_element.get_attribute("innerHTML")
driver.quit()
with open(f"{LOCAL_PATH}/{competition}.json", "w") as f:
json.dump(descriptions, f)
return descriptions
if __name__ == "__main__":
from kaggle.api.kaggle_api_extended import KaggleApi
api = KaggleApi()
api.authenticate()
cs = api.competitions_list()
for c in cs:
name = c.ref.split("/")[-1]
crawl_descriptions(name)
@@ -0,0 +1,105 @@
import json
from pathlib import Path
from typing import List, Tuple
from jinja2 import Environment, StrictUndefined
from rdagent.components.coder.model_coder.model import ModelExperiment, ModelTask
from rdagent.components.proposal.model_proposal import (
ModelHypothesis,
ModelHypothesis2Experiment,
ModelHypothesisGen,
)
from rdagent.core.prompts import Prompts
from rdagent.core.proposal import Hypothesis, Scenario, Trace
from rdagent.scenarios.kaggle.experiment.model_experiment import KGModelExperiment
prompt_dict = Prompts(file_path=Path(__file__).parent.parent.parent / "qlib" / "prompts.yaml")
KGModelHypothesis = ModelHypothesis
class KGModelHypothesisGen(ModelHypothesisGen):
"""
# NOTE: we can share this class across different data mining scenarios
# It may better to move the class into components folder like `rdagent/components/proposal/model_proposal.py`
# Here is the use case:
.. code-block:: python
class XXXDMModelHypothesisGen(DMModelHypothesisGen):
prompts: Prompts = a_specifc_prompt_dict
"""
def __init__(self, scen: Scenario) -> Tuple[dict, bool]:
super().__init__(scen)
def prepare_context(self, trace: Trace) -> Tuple[dict, bool]:
hypothesis_feedback = (
Environment(undefined=StrictUndefined)
.from_string(prompt_dict["hypothesis_and_feedback"])
.render(trace=trace)
)
context_dict = {
"hypothesis_and_feedback": hypothesis_feedback,
"RAG": "",
"hypothesis_output_format": prompt_dict["hypothesis_output_format"],
"hypothesis_specification": "...",
}
return context_dict, True
def convert_response(self, response: str) -> ModelHypothesis:
response_dict = json.loads(response)
hypothesis = KGModelHypothesis(
hypothesis=response_dict["hypothesis"],
reason=response_dict["reason"],
concise_reason=response_dict["concise_reason"],
concise_observation=response_dict["concise_observation"],
concise_justification=response_dict["concise_justification"],
concise_knowledge=response_dict["concise_knowledge"],
)
return hypothesis
class KGModelHypothesis2Experiment(ModelHypothesis2Experiment):
def prepare_context(self, hypothesis: Hypothesis, trace: Trace) -> Tuple[dict, bool]:
scenario = trace.scen.get_scenario_all_desc()
experiment_output_format = prompt_dict["model_experiment_output_format"]
hypothesis_and_feedback = (
Environment(undefined=StrictUndefined)
.from_string(prompt_dict["hypothesis_and_feedback"])
.render(trace=trace)
)
experiment_list: List[ModelExperiment] = [t[1] for t in trace.hist]
model_list = []
for experiment in experiment_list:
model_list.extend(experiment.sub_tasks)
return {
"target_hypothesis": str(hypothesis),
"scenario": scenario,
"hypothesis_and_feedback": hypothesis_and_feedback,
"experiment_output_format": experiment_output_format,
"target_list": model_list,
"RAG": ...,
}, True
def convert_response(self, response: str, trace: Trace) -> ModelExperiment:
response_dict = json.loads(response)
tasks = []
for model_name in response_dict:
description = response_dict[model_name]["description"]
formulation = response_dict[model_name]["formulation"]
architecture = response_dict[model_name]["architecture"]
variables = response_dict[model_name]["variables"]
hyperparameters = response_dict[model_name]["hyperparameters"]
model_type = response_dict[model_name]["model_type"]
tasks.append(
ModelTask(model_name, description, formulation, architecture, variables, hyperparameters, model_type)
)
exp = KGModelExperiment(tasks)
exp.based_experiments = [t[1] for t in trace.hist if t[2]]
return exp
+41
View File
@@ -9,6 +9,7 @@ Tries to create uniform environment for the agent to run;
import os
import subprocess
import sys
import zipfile
from abc import abstractmethod
from pathlib import Path
from typing import Dict, Generic, Optional, TypeVar
@@ -159,6 +160,24 @@ class DMDockerConf(DockerConf):
shm_size: str | None = "16g"
class KGDockerConf(DockerConf):
class Config:
env_prefix = "KG_DOCKER_"
build_from_dockerfile: bool = True
dockerfile_folder_path: Path = Path(__file__).parent.parent / "scenarios" / "kaggle" / "docker"
image: str = "local_kg:latest"
# image: str = "gcr.io/kaggle-gpu-images/python:latest"
mount_path: str = "/workspace/kg_workspace/"
default_entry: str = "python train.py"
extra_volumes: dict = {
# TODO connect to the place where the data is stored
Path("git_ignore_folder/data").resolve(): "/root/.data/"
}
share_data_path: str = "/data/userdata/share/kaggle"
# physionet.org/files/mimic-eicu-fiddle-feature/1.0.0/FIDDLE_mimic3
class DockerEnv(Env[DockerConf]):
# TODO: Save the output into a specific file
@@ -284,3 +303,25 @@ class DMDockerEnv(DockerEnv):
os.system(cmd)
else:
logger.info("Data already exists. Download skipped.")
class KGDockerEnv(DockerEnv):
"""Qlib Torch Docker"""
def __init__(self, competition: str, conf: DockerConf = KGDockerConf()):
super().__init__(conf)
self.competition = competition
def prepare(self):
"""
Download image & data if it doesn't exist
"""
super().prepare()
# download data
data_path = f"{self.conf.share_data_path}/{self.competition}"
subprocess.run(["kaggle", "competitions", "download", "-c", self.competition, "-p", data_path])
# unzip data
with zipfile.ZipFile(f"{data_path}/{self.competition}.zip", "r") as zip_ref:
zip_ref.extractall(data_path)
+4
View File
@@ -65,3 +65,7 @@ docker
streamlit
plotly
st-theme
# kaggle crawler
selenium
kaggle