Data mining (#103)

* scen

* scen2

* app

* fix

* Simplify workflow

* We can share more code in new scenarios

* rename model to rd loop

* Optimize data path

* Update rdagent/app/data_mining/model.py

* Add TODO

* Support GPU

* gpu

---------

Co-authored-by: SH-Src <suhan.c@outlook.com>
This commit is contained in:
you-n-g
2024-07-24 16:56:27 +08:00
committed by GitHub
parent 226a0470ac
commit c707a40073
21 changed files with 688 additions and 20 deletions
+10
View File
@@ -0,0 +1,10 @@
We encourage to set the TODOs in code. But some TODOs are more global.
So we place it here.
- [ ] Aligning the naming of files in components & scenarios.
- We would like to have the same logic for naming convention in components(reusable components for all scenarios) and scenarios (componets for specific scenario).
- But now we have following mismatch
- `coder` in `components` & `developer` in `components`
- [ ] The name of the folders mismatch with the content in them.
- Why are scenarios in experiments?
-5
View File
@@ -1,5 +0,0 @@
# TODO
If we have more efforts, include more scenario.
+29
View File
@@ -0,0 +1,29 @@
from pathlib import Path
from rdagent.components.workflow.conf import BasePropSetting
class PropSetting(BasePropSetting):
class Config:
env_prefix = "DM_" # Use MODEL_CODER_ as prefix for environment variables
protected_namespaces = () # Add 'model_' to the protected namespaces
# 1) overriding the default
scen: str = "rdagent.scenarios.data_mining.experiment.model_experiment.DMModelScenario"
hypothesis_gen: str = "rdagent.scenarios.data_mining.proposal.model_proposal.DMModelHypothesisGen"
hypothesis2experiment: str = "rdagent.scenarios.data_mining.proposal.model_proposal.DMModelHypothesis2Experiment"
coder: str = "rdagent.scenarios.data_mining.developer.model_coder.DMModelCoSTEER"
runner: str = "rdagent.scenarios.data_mining.developer.model_runner.DMModelRunner"
summarizer: str = "rdagent.scenarios.data_mining.developer.feedback.DMModelHypothesisExperiment2Feedback"
evolving_n: int = 10
# 2) Extra config for the scenario
# physionet account
# NOTE: You should apply the account in https://physionet.org/
username: str = ''
password: str = ''
PROP_SETTING = PropSetting()
+27
View File
@@ -0,0 +1,27 @@
import fire
from rdagent.app.data_mining.conf import PROP_SETTING
from rdagent.components.workflow.rd_loop import RDLoop
from rdagent.core.exception import ModelEmptyError
class ModelRDLoop(RDLoop):
skip_loop_error = (ModelEmptyError,)
def main(path=None, step_n=None):
"""
You can continue running session by
.. code-block:: python
dotenv run -- python rdagent/app/data_mining/model.py $LOG_PATH/__session__/1/0_propose --step_n 1 # `step_n` is a optional paramter
"""
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)
@@ -15,12 +15,14 @@ from rdagent.core.proposal import (
)
from rdagent.oai.llm_utils import APIBackend
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
ModelHypothesis = Hypothesis
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
class ModelHypothesisGen(HypothesisGen):
prompts: Prompts = prompt_dict
# The following methods are scenario related so they should be implemented in the subclass
@abstractmethod
def prepare_context(self, trace: Trace) -> Tuple[dict, bool]:
@@ -35,7 +37,7 @@ class ModelHypothesisGen(HypothesisGen):
system_prompt = (
Environment(undefined=StrictUndefined)
.from_string(prompt_dict["hypothesis_gen"]["system_prompt"])
.from_string(ModelHypothesisGen.prompts["hypothesis_gen"]["system_prompt"])
.render(
targets="model",
scenario=self.scen.get_scenario_all_desc(),
@@ -45,7 +47,7 @@ class ModelHypothesisGen(HypothesisGen):
)
user_prompt = (
Environment(undefined=StrictUndefined)
.from_string(prompt_dict["hypothesis_gen"]["user_prompt"])
.from_string(ModelHypothesisGen.prompts["hypothesis_gen"]["user_prompt"])
.render(
targets="model",
hypothesis_and_feedback=context_dict["hypothesis_and_feedback"],
@@ -61,6 +63,8 @@ class ModelHypothesisGen(HypothesisGen):
class ModelHypothesis2Experiment(Hypothesis2Experiment[ModelExperiment]):
prompts: Prompts = prompt_dict
def __init__(self) -> None:
super().__init__()
@@ -76,7 +80,7 @@ class ModelHypothesis2Experiment(Hypothesis2Experiment[ModelExperiment]):
context, json_flag = self.prepare_context(hypothesis, trace)
system_prompt = (
Environment(undefined=StrictUndefined)
.from_string(prompt_dict["hypothesis2experiment"]["system_prompt"])
.from_string(ModelHypothesis2Experiment.prompts["hypothesis2experiment"]["system_prompt"])
.render(
targets="model",
scenario=trace.scen.get_scenario_all_desc(),
@@ -85,7 +89,7 @@ class ModelHypothesis2Experiment(Hypothesis2Experiment[ModelExperiment]):
)
user_prompt = (
Environment(undefined=StrictUndefined)
.from_string(prompt_dict["hypothesis2experiment"]["user_prompt"])
.from_string(ModelHypothesis2Experiment.prompts["hypothesis2experiment"]["user_prompt"])
.render(
targets="model",
target_hypothesis=context["target_hypothesis"],
+21
View File
@@ -0,0 +1,21 @@
from pydantic_settings import BaseSettings
class BasePropSetting(BaseSettings):
"""
The common part of the config for RD Loop to propose and developement
You can add following config in the subclass to distinguish the environment variables.
.. code-block:: python
class Config:
env_prefix = "DM_MODEL_" # Use MODEL_CODER_ as prefix for environment variables
protected_namespaces = () # Add 'model_' to the protected namespaces
"""
scen: str = ""
hypothesis_gen: str = ""
hypothesis2experiment: str = ""
coder: str = ""
runner: str = ""
summarizer: str = ""
evolving_n: int = 10
+64
View File
@@ -0,0 +1,64 @@
"""
Model workflow with session control
It is from `rdagent/app/qlib_rd_loop/model.py` and try to replace `rdagent/app/qlib_rd_loop/RDAgent.py`
"""
from typing import Any
from rdagent.components.workflow.conf import BasePropSetting
from rdagent.core.developer import Developer
from rdagent.core.proposal import (
Hypothesis2Experiment,
HypothesisExperiment2Feedback,
HypothesisGen,
Trace,
)
from rdagent.core.scenario import Scenario
from rdagent.core.utils import import_class
from rdagent.log import rdagent_logger as logger
from rdagent.utils.workflow import LoopMeta, LoopBase
class RDLoop(LoopBase, metaclass=LoopMeta):
def __init__(self, PROP_SETTING: BasePropSetting):
scen: Scenario = import_class(PROP_SETTING.scen)()
self.hypothesis_gen: HypothesisGen = import_class(PROP_SETTING.hypothesis_gen)(scen)
self.hypothesis2experiment: Hypothesis2Experiment = import_class(PROP_SETTING.hypothesis2experiment)()
self.coder: Developer = import_class(PROP_SETTING.coder)(scen)
self.runner: Developer = import_class(PROP_SETTING.runner)(scen)
self.summarizer: HypothesisExperiment2Feedback = import_class(PROP_SETTING.summarizer)(scen)
self.trace = Trace(scen=scen)
super().__init__()
def propose(self, prev_out: dict[str, Any]):
with logger.tag("r"): # research
hypothesis = self.hypothesis_gen.gen(self.trace)
logger.log_object(hypothesis, tag="hypothesis generation")
return hypothesis
def exp_gen(self, prev_out: dict[str, Any]):
with logger.tag("r"): # research
exp = self.hypothesis2experiment.convert(prev_out["propose"], self.trace)
logger.log_object(exp.sub_tasks, tag="experiment generation")
return exp
def coding(self, prev_out: dict[str, Any]):
with logger.tag("d"): # develop
exp = self.coder.develop(prev_out["exp_gen"])
logger.log_object(exp.sub_workspace_list, tag="coder result")
return exp
def running(self, prev_out: dict[str, Any]):
with logger.tag("ef"): # evaluate and feedback
exp = self.runner.develop(prev_out["coding"])
logger.log_object(exp, tag="runner result")
return exp
def feedback(self, prev_out: dict[str, Any]):
feedback = self.summarizer.generate_feedback(prev_out["running"], prev_out["propose"], self.trace)
logger.log_object(feedback, tag="feedback")
self.trace.hist.append((prev_out["propose"],prev_out["running"] , feedback))
+7
View File
@@ -9,6 +9,7 @@ from typing import Generic, TypeVar
from rdagent.core.evaluation import Feedback
from rdagent.core.experiment import ASpecificExp, Experiment
from rdagent.core.prompts import Prompts
from rdagent.core.scenario import Scenario
# class data_ana: XXX
@@ -81,6 +82,12 @@ class Trace(Generic[ASpecificScen]):
class HypothesisGen(ABC):
# NOTE: the design is a little wierd
# - Sometimes we want accurate access the prompts in a specific level
# - It renders the prompt to a specific abstract level
# - Sometimes we want to access the most recent level prompts
prompts: Prompts # this is a class level prompt.
def __init__(self, scen: Scenario) -> None:
self.scen = scen
@@ -0,0 +1,70 @@
# TODO:
# Implement to feedback.
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 DMModelHypothesisExperiment2Feedback(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_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
DMModelCoSTEER = ModelCoSTEER
@@ -0,0 +1,39 @@
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.data_mining.experiment.model_experiment import DMModelExperiment
from rdagent.utils.env import DMDockerEnv
class DMModelRunner(CachedRunner[DMModelExperiment]):
def develop(self, exp: DMModelExperiment) -> DMModelExperiment:
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,51 @@
from pathlib import Path
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.scenarios.data_mining.experiment.workspace import DMFBWorkspace
prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml")
class DMModelExperiment(ModelExperiment[ModelTask, DMFBWorkspace, ModelFBWorkspace]):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.experiment_workspace = DMFBWorkspace(template_folder_path=Path(__file__).parent / "model_template")
class DMModelScenario(Scenario):
@property
def background(self) -> str:
return prompt_dict["dm_model_background"]
@property
def source_data(self) -> str:
raise NotImplementedError("source_data is not implemented")
@property
def output_format(self) -> str:
return prompt_dict["dm_model_output_format"]
@property
def interface(self) -> str:
return prompt_dict["dm_model_interface"]
@property
def simulator(self) -> str:
return prompt_dict["dm_model_simulator"]
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,92 @@
from pathlib import Path
import pandas as pd
import torch
import torch.nn.functional as F
from torchvision import transforms, datasets
from torch.utils.data import DataLoader, Dataset
import torch.nn as nn
import sparse
import random
import os
from model import model_cls
from sklearn.metrics import accuracy_score, roc_auc_score
import numpy as np
# Set device for training
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# device = torch.device("cpu")
class MyDataset(Dataset):
def __init__(self, x, label, device):
self.x1 = x
self.label = label
self.device = device
def __len__(self):
return len(self.label)
def __getitem__(self, idx):
if torch.is_tensor(idx):
idx = idx.tolist()
return torch.FloatTensor(self.x1[idx]).to(self.device), \
torch.tensor(self.label[idx], dtype=torch.float).to(self.device)
def collate_fn(batch):
x, label = [], []
for data in batch:
x.append(data[0])
label.append(data[1])
return torch.stack(x, 0), torch.stack(label, 0)
datapath = '/root/.data'
# datapath = '/home/v-suhancui/RD-Agent/physionet.org/files/mimic-eicu-fiddle-feature/1.0.0/FIDDLE_mimic3'
X = sparse.load_npz(datapath+'/features/ARF_12h/X.npz').todense()
df_pop = pd.read_csv(datapath+'/population/ARF_12h.csv')['ARF_LABEL']
X = X.transpose(0, 2, 1)
indices = [i for i in range(len(df_pop))]
random.shuffle(indices)
split_point = int(0.7 * len(df_pop))
X_train, y_train = X[indices[:split_point]], np.array(df_pop[indices[:split_point]])
X_test, y_test = X[indices[split_point:]], np.array(df_pop[indices[split_point:]])
train_dataloader = DataLoader(MyDataset(X_train, y_train, device), collate_fn=collate_fn, shuffle=True, drop_last=True, batch_size=64)
test_dataloader = DataLoader(MyDataset(X_test, y_test, device), collate_fn=collate_fn, shuffle=False, drop_last=False, batch_size=64)
num_features = 4816
num_timesteps = 12
# Define the optimizer and loss function
model = model_cls(num_features=num_features, num_timesteps=num_timesteps).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
criterion = nn.CrossEntropyLoss()
# Train the model
for i in range(10):
for data in train_dataloader:
x, y = data
out = model(x)
optimizer.zero_grad()
loss = criterion(out.squeeze(), y)
loss.backward()
optimizer.step()
y_pred = []
for data in test_dataloader:
x, y = data
out = model(x)
y_pred.append(out.cpu().detach().numpy())
acc = roc_auc_score(y_test, np.concatenate(y_pred))
print(acc)
# Save the predictions to submission.csv
with open('./submission.txt', 'w') as f:
f.write(str(acc))
@@ -0,0 +1,50 @@
dm_model_background: |-
The model is a machine learning or deep learning structure used in clinical settings to predict whether the patient will suffer from acute respiratory failure (ARF) based on their vital signs monitored in ICU.
The data is extracted from MIMIC-III using FIDDLE pipeline, and we focus on the ARF_12h prediction task, which means we use the first 12 hours data to predict the onset of ARF on discharge.
The model is defined in the following parts:
1. Name: The name of the model.
2. Description: The description of the model.
3. 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.
dm_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 which is a sub-class of pytorch.nn.Module. This class should should have a 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 type, "TimeSeries". The input shape to a time series model is (batch_size, num_features, num_timesteps). 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" and "num_timesteps" are static which will be provided to the model through init function.
User will initialize the time series model with the following code:
```python
model = model_cls(num_features=num_features, num_timesteps=num_timesteps)
```
No other parameters will be passed to the model so give other parameters a default value or just make them static.
Remember to permute the input tensor since the input tensor is in the shape of (batch_size, num_features, num_timesteps) and a normal time series model is expecting the input tensor in the shape of (batch_size, num_timesteps, num_features).
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.
dm_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.
dm_model_simulator: |-
The models will be put to train on MIMIC-III dataset and evaluate their performance in terms of roc score (Area Under Curve Receiver Operating Characteristics Curve). Hypothesis is improved upon checking the feedback on the results.
@@ -0,0 +1,31 @@
from pathlib import Path
import pandas as pd
from rdagent.core.experiment import FBWorkspace
from rdagent.log import rdagent_logger as logger
from rdagent.utils.env import DMDockerEnv
from rdagent.app.data_mining.conf import PROP_SETTING
class DMFBWorkspace(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 = DMDockerEnv()
qtde.prepare(PROP_SETTING.username, PROP_SETTING.password)
execute_log = qtde.run(
local_path=str(self.workspace_path),
entry=f"python train.py",
env=run_env,
)
csv_path = self.workspace_path / "submission.txt"
if not csv_path.exists():
logger.error(f"File {csv_path} does not exist.")
return None
with open(self.workspace_path / "submission.txt", 'r') as f:
return f.read()
@@ -0,0 +1,93 @@
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.data_mining.experiment.model_experiment import DMModelExperiment
prompt_dict = Prompts(file_path=Path(__file__).parent.parent.parent / "qlib" / "prompts.yaml")
DMModelHypothesis = ModelHypothesis
class DMModelHypothesisGen(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": prompt_dict["model_hypothesis_specification"]
}
return context_dict, True
def convert_response(self, response: str) -> ModelHypothesis:
response_dict = json.loads(response)
hypothesis = DMModelHypothesis(hypothesis=response_dict["hypothesis"], reason=response_dict["reason"])
return hypothesis
class DMModelHypothesis2Experiment(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"]
architecture = response_dict[model_name]["architecture"]
hyperparameters = response_dict[model_name]["hyperparameters"]
model_type = response_dict[model_name]["model_type"]
tasks.append(ModelTask(model_name, description, architecture, hyperparameters, model_type))
exp = DMModelExperiment(tasks)
exp.based_experiments = [t[1] for t in trace.hist if t[2]]
return exp
+4 -1
View File
@@ -138,7 +138,7 @@ model_experiment_output_format: |-
So far please only design one model to test the hypothesis!
The output should follow JSON format. The schema is as follows:
{
"model_name (The name of the model)": {
"model_name 1 (The name of the model)": {
"description": "A detailed description of the model",
"architecture": "A detailed description of the model's architecture, e.g., neural network layers or tree structures",
"hyperparameters": {
@@ -147,6 +147,9 @@ model_experiment_output_format: |-
"hyperparameter_name_3": "value of hyperparameter 3"
},
"model_type": "Tabular or TimeSeries" # Should be one of "Tabular" or "TimeSeries"
},
"model_name 2 (The name of the model)": {
...
}
}
Usually a larger model works better than a smaller one. Hence, the parameters should be larger.
+35 -1
View File
@@ -3,8 +3,8 @@ The motiviation of the utils is for environment management
Tries to create uniform environment for the agent to run;
- All the code and data is expected included in one folder
"""
# TODO: move the scenario specific docker env into other folders.
import os
import subprocess
@@ -125,6 +125,7 @@ class DockerConf(BaseSettings):
# So we just want to download it once.
network: str | None = "bridge" # the network mode for the docker
shm_size: str | None = None
enable_gpu: bool = True # because we will automatically disable GPU if not available. So we enable it by default.
class QlibDockerConf(DockerConf):
@@ -141,6 +142,19 @@ class QlibDockerConf(DockerConf):
enable_gpu: bool = True
class DMDockerConf(DockerConf):
class Config:
env_prefix = "DM_DOCKER_"
build_from_dockerfile: bool = True
dockerfile_folder_path: Path = Path(__file__).parent.parent / "scenarios" / "data_mining" / "docker"
image: str = "local_dm:latest"
mount_path: str = "/workspace/dm_workspace/"
default_entry: str = "python train.py"
extra_volumes: dict = {Path("~/.rdagent/.data/physionet.org/files/mimic-eicu-fiddle-feature/1.0.0/FIDDLE_mimic3/").expanduser().resolve(): "/root/.data/"}
shm_size: str | None = "16g"
# physionet.org/files/mimic-eicu-fiddle-feature/1.0.0/FIDDLE_mimic3
class DockerEnv(Env[DockerConf]):
# TODO: Save the output into a specific file
@@ -243,3 +257,23 @@ class QTDockerEnv(DockerEnv):
self.run(entry=cmd)
else:
logger.info("Data already exists. Download skipped.")
class DMDockerEnv(DockerEnv):
"""Qlib Torch Docker"""
def __init__(self, conf: DockerConf = DMDockerConf()):
super().__init__(conf)
def prepare(self, username: str, password: str):
"""
Download image & data if it doesn't exist
"""
super().prepare()
data_path = next(iter(self.conf.extra_volumes.keys()))
if not (Path(data_path)).exists():
logger.info("We are downloading!")
cmd = 'wget -r -N -c -np --user={} --password={} -P ~/.rdagent/.data/ https://physionet.org/files/mimic-eicu-fiddle-feature/1.0.0/'.format(username, password)
os.system(cmd)
else:
logger.info("Data already exists. Download skipped.")
+25 -8
View File
@@ -13,7 +13,7 @@ from tqdm.auto import tqdm
from collections import defaultdict
from dataclasses import dataclass
from dataclasses import dataclass, field
import datetime
from typing import Callable
from rdagent.log import rdagent_logger as logger
@@ -21,15 +21,23 @@ from rdagent.log import rdagent_logger as logger
class LoopMeta(type):
def __new__(cls, clsname, bases, attrs):
# move custommized steps into steps
@staticmethod
def _get_steps(bases):
"""
get all the `steps` of base classes and combine them to a single one.
"""
steps = []
for name in attrs.keys():
if not name.startswith("__"):
for base in bases:
steps.extend(LoopMeta._get_steps(base.__bases__) + getattr(base,"steps", []))
return steps
def __new__(cls, clsname, bases, attrs):
# move custommized steps into steps
steps = LoopMeta._get_steps(bases) # all the base classes of parents
for name, attr in attrs.items():
if not name.startswith("__") and isinstance(attr, Callable):
steps.append(name)
attrs["steps"] = steps
return super().__new__(cls, clsname, bases, attrs)
@@ -44,6 +52,8 @@ class LoopBase:
steps: list[Callable] # a list of steps to work on
loop_trace: dict[int, list[LoopTrace]]
skip_loop_error: tuple[Exception] = field(default_factory=tuple) # you can define a list of error that will skip current loop
def __init__(self):
self.loop_idx = 0 # current loop index
self.step_idx = 0 # the index of next step to be run
@@ -73,7 +83,14 @@ class LoopBase:
name = self.steps[si]
func = getattr(self, name)
self.loop_prev_out[name] = func(self.loop_prev_out)
try:
self.loop_prev_out[name] = func(self.loop_prev_out)
# TODO: Fix the error logger.exception(f"Skip loop {li} due to {e}")
except self.skip_loop_error as e:
logger.warning(f"Skip loop {li} due to {e}")
self.loop_idx += 1
self.step_index = 0
continue
end = datetime.datetime.now(datetime.timezone.utc)