Skip to content

Commit 5b124d7

Browse files
WinstonLiytXianBW
andauthored
feat: refine the code in model description and fix some bugs in feedback.py (#288)
* fix some bugs in feedback.py * feat: kaggle templates related (#287) * add kaggle test * kaggle templates changes * rename two files * fix a grammar bug * fix a ci error * fix a bug --------- Co-authored-by: XianBW <36835909+XianBW@users.noreply.github.com>
1 parent 785fdc1 commit 5b124d7

11 files changed

Lines changed: 119 additions & 51 deletions

File tree

rdagent/scenarios/kaggle/developer/feedback.py

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,32 +46,6 @@ def process_results(current_result, sota_result):
4646

4747

4848
class KGHypothesisExperiment2Feedback(HypothesisExperiment2Feedback):
49-
def get_available_features(self, exp: Experiment):
50-
features = []
51-
52-
for feature_info in exp.experiment_workspace.data_description:
53-
task_info, feature_shape = feature_info
54-
features.append(
55-
{"name": task_info.factor_name, "description": task_info.factor_description, "shape": feature_shape}
56-
)
57-
58-
return features
59-
60-
def get_model_code(self, exp: Experiment):
61-
model_type = exp.sub_tasks[0].model_type if exp.sub_tasks else None
62-
if model_type == "XGBoost":
63-
return exp.sub_workspace_list[0].code_dict.get(
64-
"model_xgb.py"
65-
) # TODO Check if we need to replace this by using RepoAnalyzer
66-
elif model_type == "RandomForest":
67-
return exp.sub_workspace_list[0].code_dict.get("model_rf.py")
68-
elif model_type == "LightGBM":
69-
return exp.sub_workspace_list[0].code_dict.get("model_lgb.py")
70-
elif model_type == "NN":
71-
return exp.sub_workspace_list[0].code_dict.get("model_nn.py")
72-
else:
73-
return None
74-
7549
def generate_feedback(self, exp: Experiment, hypothesis: Hypothesis, trace: Trace) -> HypothesisFeedback:
7650
"""
7751
The `ti` should be executed and the results should be included, as well as the comparison between previous results (done by LLM).
@@ -109,9 +83,10 @@ def generate_feedback(self, exp: Experiment, hypothesis: Hypothesis, trace: Trac
10983
combined_result = process_results(current_result, current_result) # Compare with itself
11084
print("Warning: No previous experiments to compare against. Using current result as baseline.")
11185

112-
available_features = self.get_available_features(exp)
113-
# Get the appropriate model code
114-
model_code = self.get_model_code(exp)
86+
available_features = {
87+
task_info: feature_shape for task_info, feature_shape in exp.experiment_workspace.data_description
88+
}
89+
model_code = exp.experiment_workspace.model_description
11590

11691
# Generate the user prompt based on the action type
11792
if hypothesis.action == "Model tuning":

rdagent/scenarios/kaggle/developer/runner.py

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
1+
import json
12
import pickle
23
import shutil
34
from pathlib import Path
45

6+
from jinja2 import Environment, StrictUndefined
7+
58
from rdagent.app.kaggle.conf import KAGGLE_IMPLEMENT_SETTING
69
from rdagent.components.coder.factor_coder.config import FACTOR_IMPLEMENT_SETTINGS
710
from rdagent.components.coder.factor_coder.factor import FactorTask
11+
from rdagent.components.coder.model_coder.model import ModelTask
812
from rdagent.components.runner import CachedRunner
913
from rdagent.components.runner.conf import RUNNER_SETTINGS
10-
from rdagent.core.exception import FactorEmptyError, ModelEmptyError
14+
from rdagent.core.exception import CoderError, FactorEmptyError, ModelEmptyError
1115
from rdagent.core.experiment import ASpecificExp
12-
from rdagent.oai.llm_utils import md5_hash
16+
from rdagent.core.prompts import Prompts
17+
from rdagent.oai.llm_utils import APIBackend, md5_hash
1318
from rdagent.scenarios.kaggle.experiment.kaggle_experiment import (
1419
KGFactorExperiment,
1520
KGModelExperiment,
1621
)
1722

23+
prompt_dict = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
24+
1825

1926
class KGCachedRunner(CachedRunner[ASpecificExp]):
2027
def build_from_SOTA(self, exp: ASpecificExp) -> None:
@@ -23,7 +30,7 @@ def build_from_SOTA(self, exp: ASpecificExp) -> None:
2330
exp.experiment_workspace.data_description = exp.based_experiments[-1].experiment_workspace.data_description
2431
exp.experiment_workspace.model_description = exp.based_experiments[
2532
-1
26-
].experiment_workspace.model_description
33+
].experiment_workspace.model_description.copy()
2734

2835
def get_cache_key(self, exp: ASpecificExp) -> str:
2936
codes = []
@@ -38,22 +45,19 @@ def get_cache_key(self, exp: ASpecificExp) -> str:
3845
class KGModelRunner(KGCachedRunner[KGModelExperiment]):
3946
def develop(self, exp: KGModelExperiment) -> KGModelExperiment:
4047
self.build_from_SOTA(exp)
41-
if exp.sub_workspace_list[0].target_task.model_type == "XGBoost":
42-
if exp.sub_workspace_list[0].code_dict == {}:
43-
raise ModelEmptyError("No model is implemented")
44-
exp.experiment_workspace.inject_code(**{"model_xgb.py": exp.sub_workspace_list[0].code_dict["model.py"]})
45-
elif exp.sub_workspace_list[0].target_task.model_type == "RandomForest":
46-
if exp.sub_workspace_list[0].code_dict == {}:
47-
raise ModelEmptyError("No model is implemented")
48-
exp.experiment_workspace.inject_code(**{"model_rf.py": exp.sub_workspace_list[0].code_dict["model.py"]})
49-
elif exp.sub_workspace_list[0].target_task.model_type == "LightGBM":
50-
if exp.sub_workspace_list[0].code_dict == {}:
51-
raise ModelEmptyError("No model is implemented")
52-
exp.experiment_workspace.inject_code(**{"model_lgb.py": exp.sub_workspace_list[0].code_dict["model.py"]})
53-
elif exp.sub_workspace_list[0].target_task.model_type == "NN":
54-
if exp.sub_workspace_list[0].code_dict == {}:
55-
raise ModelEmptyError("No model is implemented")
56-
exp.experiment_workspace.inject_code(**{"model_nn.py": exp.sub_workspace_list[0].code_dict["model.py"]})
48+
49+
sub_ws = exp.sub_workspace_list[0]
50+
model_type = sub_ws.target_task.model_type
51+
52+
if sub_ws.code_dict == {}:
53+
raise ModelEmptyError("No model is implemented.")
54+
else:
55+
model_file_name = f"model_{model_type.lower()}.py"
56+
exp.experiment_workspace.inject_code(**{model_file_name: sub_ws.code_dict["model.py"]})
57+
58+
model_description = sub_ws.target_task.get_task_information()
59+
exp.experiment_workspace.model_description[model_type] = model_description
60+
5761
if RUNNER_SETTINGS.cache_result:
5862
cache_hit, result = self.get_cache_result(exp)
5963
if cache_hit:
@@ -72,6 +76,48 @@ def develop(self, exp: KGModelExperiment) -> KGModelExperiment:
7276

7377

7478
class KGFactorRunner(KGCachedRunner[KGFactorExperiment]):
79+
def extract_model_task_from_code(self, code: str) -> str:
80+
sys_prompt = (
81+
Environment(undefined=StrictUndefined)
82+
.from_string(prompt_dict["extract_model_task_from_code"]["system"])
83+
.render()
84+
)
85+
86+
user_prompt = (
87+
Environment(undefined=StrictUndefined)
88+
.from_string(prompt_dict["extract_model_task_from_code"]["user"])
89+
.render(file_content=code)
90+
)
91+
92+
model_task_description = APIBackend().build_messages_and_create_chat_completion(
93+
user_prompt=user_prompt,
94+
system_prompt=sys_prompt,
95+
json_mode=True,
96+
)
97+
98+
try:
99+
response_json_analysis = json.loads(model_task_description)
100+
task_desc = f"""name: {response_json_analysis['name']}
101+
description: {response_json_analysis['description']}
102+
"""
103+
task_desc += (
104+
f"formulation: {response_json_analysis['formulation']}\n"
105+
if response_json_analysis.get("formulation")
106+
else ""
107+
)
108+
task_desc += f"architecture: {response_json_analysis['architecture']}\n"
109+
task_desc += (
110+
f"variables: {json.dumps(response_json_analysis['variables'], indent=4)}\n"
111+
if response_json_analysis.get("variables")
112+
else ""
113+
)
114+
task_desc += f"hyperparameters: {json.dumps(response_json_analysis['hyperparameters'], indent=4)}\n"
115+
task_desc += f"model_type: {response_json_analysis['model_type']}\n"
116+
except json.JSONDecodeError:
117+
task_desc = "Failed to parse LLM's response as JSON"
118+
119+
return task_desc
120+
75121
def init_develop(self, exp: KGFactorExperiment) -> KGFactorExperiment:
76122
"""
77123
For the initial development, the experiment serves as a benchmark for feature engineering.
@@ -100,6 +146,22 @@ def init_develop(self, exp: KGFactorExperiment) -> KGFactorExperiment:
100146
feature_shape = org_data.shape[-1]
101147
exp.experiment_workspace.data_description.append((sub_task.get_task_information(), feature_shape))
102148

149+
sub_model_1_description = (
150+
self.extract_model_task_from_code(
151+
(exp.experiment_workspace.workspace_path / "model" / "model_randomforest.py").read_text()
152+
)
153+
+ f"""code: { (exp.experiment_workspace.workspace_path / "model" / "model_randomforest.py").read_text()}"""
154+
)
155+
sub_model_2_description = (
156+
self.extract_model_task_from_code(
157+
(exp.experiment_workspace.workspace_path / "model" / "model_xgboost.py").read_text()
158+
)
159+
+ f"""code: { (exp.experiment_workspace.workspace_path / "model" / "model_xgboost.py").read_text()}"""
160+
)
161+
162+
exp.experiment_workspace.model_description["XGBoost"] = sub_model_1_description
163+
exp.experiment_workspace.model_description["RandomForest"] = sub_model_2_description
164+
103165
if RUNNER_SETTINGS.cache_result:
104166
self.dump_cache_result(exp, result)
105167

@@ -133,7 +195,11 @@ def develop(self, exp: KGFactorExperiment) -> KGFactorExperiment:
133195

134196
result = exp.experiment_workspace.execute(run_env=env_to_use)
135197

198+
if result is None:
199+
raise CoderError("No result is returned from the experiment workspace")
200+
136201
exp.result = result
202+
137203
if RUNNER_SETTINGS.cache_result:
138204
self.dump_cache_result(exp, result)
139205

rdagent/scenarios/kaggle/experiment/meta_tpl/model/model_rf.py renamed to rdagent/scenarios/kaggle/experiment/meta_tpl/model/model_randomforest.py

File renamed without changes.

rdagent/scenarios/kaggle/experiment/meta_tpl/model/model_xgb.py renamed to rdagent/scenarios/kaggle/experiment/meta_tpl/model/model_xgboost.py

File renamed without changes.

rdagent/scenarios/kaggle/experiment/playground-series-s4e8_template/model/model_rf.py renamed to rdagent/scenarios/kaggle/experiment/playground-series-s4e8_template/model/model_randomforest.py

File renamed without changes.

rdagent/scenarios/kaggle/experiment/playground-series-s4e8_template/model/model_xgb.py renamed to rdagent/scenarios/kaggle/experiment/playground-series-s4e8_template/model/model_xgboost.py

File renamed without changes.

rdagent/scenarios/kaggle/experiment/prompts.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ kg_description_template:
88
"Competition Type": "The type of competition, e.g., 'Classification', 'Regression', 'Clustering', 'Prediction", "Time-Series Forecasting",
99
"Competition Description": "A brief description of the competition",
1010
"Target Description": "A description of the target variable to be predicted",
11-
"Competition Features": "A dict of relevant features used in the competition and their descriptions (if available)", # if you are not sure about the meaning of the feature, please add a (guess) before the description. Importantly, your feature name should be exactly the same as the feature name in the dataset!
1211
}
1312
Since these might be very similar column names in data like one_hot_encoded columns, you can use some regex to group them together.
1413

rdagent/scenarios/kaggle/experiment/scenario.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ def _analysis_competition_description(self):
6969
self.competition_type = response_json_analysis.get("Competition Type", "No type provided")
7070
self.competition_description = response_json_analysis.get("Competition Description", "No description provided")
7171
self.target_description = response_json_analysis.get("Target Description", "No target provided")
72-
self.competition_features = response_json_analysis.get("Competition Features", "No features provided")
7372
self.competition_features = self.source_data
7473

7574
@property

rdagent/scenarios/kaggle/experiment/workspace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def __init__(self, template_folder_path: Path, *args, **kwargs) -> None:
2929
super().__init__(*args, **kwargs)
3030
self.inject_code_from_folder(template_folder_path)
3131
self.data_description: list[str] = []
32-
self.model_description: str = ""
32+
self.model_description: dict[str, str] = {}
3333

3434
def generate_preprocess_data(
3535
self,

rdagent/scenarios/kaggle/prompts.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,27 @@ feature_selection_feedback_generation:
263263
4. Are there any domain-specific considerations that should inform our feature selection?
264264
265265
Remember to focus on the select() method in the model code, as this is where feature selection is implemented.
266+
267+
extract_model_task_from_code:
268+
system: |-
269+
You are an expert in analyzing code for machine learning models.
270+
user: |-
271+
Given the following code, summarize the machine learning model including:
272+
- Model architecture
273+
- Hyperparameters
274+
- Formulation and variables
275+
- Model type (one of XGBoost, RandomForest, LightGBM, NN)
276+
277+
Code:
278+
{{ file_content }}
279+
280+
Return the information in JSON format with the following structure:
281+
{
282+
"name": "",
283+
"description": "",
284+
"architecture": "",
285+
"hyperparameters": {},
286+
"formulation": "",
287+
"variables": {},
288+
"model_type": ""
289+
}

0 commit comments

Comments
 (0)