From b1ddba388ade0bbfee923f73d5f3aadb8f3eafd0 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 11 Jul 2024 10:50:34 +0800 Subject: [PATCH] model proposal first version (#61) * init code * first version of model proposal --- rdagent/app/qlib_rd_loop/conf.py | 12 +-- rdagent/app/qlib_rd_loop/factor.py | 4 +- rdagent/app/qlib_rd_loop/model.py | 8 +- rdagent/components/coder/model_coder/model.py | 6 -- .../components/proposal/factor_proposal.py | 16 ++-- rdagent/components/proposal/model_proposal.py | 96 +++++++++++++++++++ rdagent/components/proposal/prompts.yaml | 34 +++---- .../scenarios/qlib/experiment/prompts.yaml | 2 + rdagent/scenarios/qlib/factor_proposal.py | 7 +- rdagent/scenarios/qlib/model_proposal.py | 81 ++++++++++++++++ .../model_task_implementation/__init__.py | 6 +- rdagent/scenarios/qlib/prompts.yaml | 18 +++- .../scenarios/qlib/task_generator/feedback.py | 3 + .../scenarios/qlib/task_generator/model.py | 5 +- 14 files changed, 248 insertions(+), 50 deletions(-) create mode 100644 rdagent/scenarios/qlib/model_proposal.py diff --git a/rdagent/app/qlib_rd_loop/conf.py b/rdagent/app/qlib_rd_loop/conf.py index 7ae8fc8d..a97f76ff 100644 --- a/rdagent/app/qlib_rd_loop/conf.py +++ b/rdagent/app/qlib_rd_loop/conf.py @@ -14,12 +14,12 @@ class PropSetting(BaseSettings): ) # TODO: model part is not finished yet - qlib_model_scen: str = "" - qlib_model_hypothesis_gen: str = "" - qlib_model_hypothesis2experiment: str = "" - qlib_model_coder: str = "" - qlib_model_runner: str = "" - qlib_model_summarizer: str = "" + qlib_model_scen: str = "rdagent.scenarios.qlib.experiment.model_experiment.QlibModelScenario" + qlib_model_hypothesis_gen: str = "rdagent.scenarios.qlib.model_proposal.QlibModelHypothesisGen" + qlib_model_hypothesis2experiment: str = "rdagent.scenarios.qlib.model_proposal.QlibModelHypothesis2Experiment" + qlib_model_coder: str = "rdagent.scenarios.qlib.model_task_implementation.QlibModelCoSTEER" + qlib_model_runner: str = "rdagent.scenarios.qlib.task_generator.model.QlibModelRunner" + qlib_model_summarizer: str = "rdagent.scenarios.qlib.task_generator.feedback.QlibModelHypothesisExperiment2Feedback" evolving_n: int = 10 diff --git a/rdagent/app/qlib_rd_loop/factor.py b/rdagent/app/qlib_rd_loop/factor.py index 2a790597..cfabe90a 100644 --- a/rdagent/app/qlib_rd_loop/factor.py +++ b/rdagent/app/qlib_rd_loop/factor.py @@ -4,6 +4,8 @@ TODO: Factor Structure RD-Loop from dotenv import load_dotenv +from rdagent.core.scenario import Scenario + load_dotenv(override=True) from rdagent.app.qlib_rd_loop.conf import PROP_SETTING @@ -16,7 +18,7 @@ from rdagent.core.proposal import ( from rdagent.core.task_generator import TaskGenerator from rdagent.core.utils import import_class -scen = import_class(PROP_SETTING.qlib_factor_scen)() +scen: Scenario = import_class(PROP_SETTING.qlib_factor_scen)() hypothesis_gen: HypothesisGen = import_class(PROP_SETTING.qlib_factor_hypothesis_gen)(scen) diff --git a/rdagent/app/qlib_rd_loop/model.py b/rdagent/app/qlib_rd_loop/model.py index c30aa08f..f8b07211 100644 --- a/rdagent/app/qlib_rd_loop/model.py +++ b/rdagent/app/qlib_rd_loop/model.py @@ -9,21 +9,23 @@ from rdagent.app.qlib_rd_loop.conf import PROP_SETTING from rdagent.core.proposal import ( Hypothesis2Experiment, HypothesisExperiment2Feedback, + HypothesisGen, Trace, ) +from rdagent.core.scenario import Scenario from rdagent.core.task_generator import TaskGenerator from rdagent.core.utils import import_class -scen = import_class(PROP_SETTING.qlib_model_scen)() +scen: Scenario = import_class(PROP_SETTING.qlib_model_scen)() -hypothesis_gen = import_class(PROP_SETTING.qlib_model_hypothesis_gen)(scen) +hypothesis_gen: HypothesisGen = import_class(PROP_SETTING.qlib_model_hypothesis_gen)(scen) hypothesis2experiment: Hypothesis2Experiment = import_class(PROP_SETTING.qlib_model_hypothesis2experiment)() qlib_model_coder: TaskGenerator = import_class(PROP_SETTING.qlib_model_coder)(scen) qlib_model_runner: TaskGenerator = import_class(PROP_SETTING.qlib_model_runner)(scen) -qlib_model_summarizer: HypothesisExperiment2Feedback = import_class(PROP_SETTING.qlib_model_hypothesis2experiment)(scen) +qlib_model_summarizer: HypothesisExperiment2Feedback = import_class(PROP_SETTING.qlib_model_hypothesis2experiment)() trace = Trace(scen=scen) for _ in range(PROP_SETTING.evolving_n): diff --git a/rdagent/components/coder/model_coder/model.py b/rdagent/components/coder/model_coder/model.py index bf8d76a8..93c9451d 100644 --- a/rdagent/components/coder/model_coder/model.py +++ b/rdagent/components/coder/model_coder/model.py @@ -15,12 +15,6 @@ from rdagent.utils import get_module_by_module_path class ModelTask(Task): - # TODO: it should change when the Task changes. - name: str - description: str - formulation: str - variables: Dict[str, str] # map the variable name to the variable description - def __init__( self, name: str, description: str, formulation: str, variables: Dict[str, str], model_type: Optional[str] = None ) -> None: diff --git a/rdagent/components/proposal/factor_proposal.py b/rdagent/components/proposal/factor_proposal.py index cba6728b..cc1dbc7c 100644 --- a/rdagent/components/proposal/factor_proposal.py +++ b/rdagent/components/proposal/factor_proposal.py @@ -37,16 +37,18 @@ class FactorHypothesisGen(HypothesisGen): system_prompt = ( Environment(undefined=StrictUndefined) - .from_string(prompt_dict["factor_hypothesis_gen"]["system_prompt"]) + .from_string(prompt_dict["hypothesis_gen"]["system_prompt"]) .render( + targets="factors", scenario=self.scen.get_scenario_all_desc(), hypothesis_output_format=context_dict["hypothesis_output_format"], ) ) user_prompt = ( Environment(undefined=StrictUndefined) - .from_string(prompt_dict["factor_hypothesis_gen"]["user_prompt"]) + .from_string(prompt_dict["hypothesis_gen"]["user_prompt"]) .render( + targets="factors", hypothesis_and_feedback=context_dict["hypothesis_and_feedback"], RAG=context_dict["RAG"], ) @@ -60,8 +62,6 @@ class FactorHypothesisGen(HypothesisGen): class FactorHypothesis2Experiment(Hypothesis2Experiment[FactorExperiment]): - def __init__(self) -> None: - super().__init__() @abstractmethod def prepare_context(self, hypothesis: Hypothesis, trace: Trace) -> Tuple[dict, bool]: ... @@ -73,19 +73,21 @@ class FactorHypothesis2Experiment(Hypothesis2Experiment[FactorExperiment]): context, json_flag = self.prepare_context(hypothesis, trace) system_prompt = ( Environment(undefined=StrictUndefined) - .from_string(prompt_dict["factor_hypothesis2experiment"]["system_prompt"]) + .from_string(prompt_dict["hypothesis2experiment"]["system_prompt"]) .render( + targets="factors", scenario=trace.scen.get_scenario_all_desc(), experiment_output_format=context["experiment_output_format"], ) ) user_prompt = ( Environment(undefined=StrictUndefined) - .from_string(prompt_dict["factor_hypothesis2experiment"]["user_prompt"]) + .from_string(prompt_dict["hypothesis2experiment"]["user_prompt"]) .render( + targets="factors", target_hypothesis=context["target_hypothesis"], hypothesis_and_feedback=context["hypothesis_and_feedback"], - factor_list=context["factor_list"], + target_list=context["target_list"], RAG=context["RAG"], ) ) diff --git a/rdagent/components/proposal/model_proposal.py b/rdagent/components/proposal/model_proposal.py index e69de29b..57696296 100644 --- a/rdagent/components/proposal/model_proposal.py +++ b/rdagent/components/proposal/model_proposal.py @@ -0,0 +1,96 @@ +from abc import abstractmethod +from pathlib import Path +from typing import Tuple + +from jinja2 import Environment, StrictUndefined + +from rdagent.components.coder.model_coder.model import ModelExperiment +from rdagent.core.prompts import Prompts +from rdagent.core.proposal import ( + Hypothesis, + Hypothesis2Experiment, + HypothesisGen, + Scenario, + Trace, +) +from rdagent.oai.llm_utils import APIBackend + +prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml") + +ModelHypothesis = Hypothesis + + +class ModelHypothesisGen(HypothesisGen): + + # The following methods are scenario related so they should be implemented in the subclass + @abstractmethod + def prepare_context(self, trace: Trace) -> Tuple[dict, bool]: ... + + @abstractmethod + def convert_response(self, response: str) -> ModelHypothesis: ... + + def gen(self, trace: Trace) -> ModelHypothesis: + context_dict, json_flag = self.prepare_context(trace) + + system_prompt = ( + Environment(undefined=StrictUndefined) + .from_string(prompt_dict["hypothesis_gen"]["system_prompt"]) + .render( + targets="factors", + scenario=self.scen.get_scenario_all_desc(), + hypothesis_output_format=context_dict["hypothesis_output_format"], + ) + ) + user_prompt = ( + Environment(undefined=StrictUndefined) + .from_string(prompt_dict["hypothesis_gen"]["user_prompt"]) + .render( + targets="factors", + hypothesis_and_feedback=context_dict["hypothesis_and_feedback"], + RAG=context_dict["RAG"], + ) + ) + + resp = APIBackend().build_messages_and_create_chat_completion(user_prompt, system_prompt, json_mode=json_flag) + + hypothesis = self.convert_response(resp) + + return hypothesis + + +class ModelHypothesis2Experiment(Hypothesis2Experiment[ModelExperiment]): + def __init__(self) -> None: + super().__init__() + + @abstractmethod + def prepare_context(self, hypothesis: Hypothesis, trace: Trace) -> Tuple[dict, bool]: ... + + @abstractmethod + def convert_response(self, response: str, trace: Trace) -> ModelExperiment: ... + + def convert(self, hypothesis: Hypothesis, trace: Trace) -> ModelExperiment: + context, json_flag = self.prepare_context(hypothesis, trace) + system_prompt = ( + Environment(undefined=StrictUndefined) + .from_string(prompt_dict["hypothesis2experiment"]["system_prompt"]) + .render( + targets="factors", + scenario=trace.scen.get_scenario_all_desc(), + experiment_output_format=context["experiment_output_format"], + ) + ) + user_prompt = ( + Environment(undefined=StrictUndefined) + .from_string(prompt_dict["hypothesis2experiment"]["user_prompt"]) + .render( + targets="factors", + target_hypothesis=context["target_hypothesis"], + hypothesis_and_feedback=context["hypothesis_and_feedback"], + target_list=context["target_list"], + RAG=context["RAG"], + ) + ) + + resp = APIBackend().build_messages_and_create_chat_completion(user_prompt, system_prompt, json_mode=json_flag) + + return self.convert_response(resp, trace) diff --git a/rdagent/components/proposal/prompts.yaml b/rdagent/components/proposal/prompts.yaml index e979c670..56a217b3 100644 --- a/rdagent/components/proposal/prompts.yaml +++ b/rdagent/components/proposal/prompts.yaml @@ -1,41 +1,41 @@ -factor_hypothesis_gen: +hypothesis_gen: system_prompt: |- - The user is trying to generate new hypothesis on the factors in data-driven research and development. - The factors are used in a certain scenario, the scenario is as follows: + The user is trying to generate new hypothesis on the {{targets}} in data-driven research and development. + The {{targets}} are used in a certain scenario, the scenario is as follows: {{ scenario }} The user has made several hypothesis on this scenario and did several evaluation on them. The user will provide this information to you. - To help you generate new hypothesis, the user has prepared some additional information for you. You should use this information to help generate new factors. + To help you generate new hypothesis, the user has prepared some additional information for you. You should use this information to help generate new {{targets}}. Please generate the output following the format below: {{ hypothesis_output_format }} user_prompt: |- The user has made several hypothesis on this scenario and did several evaluation on them. The former hypothesis and the corresponding feedbacks are as follows: {{ hypothesis_and_feedback }} - To help you generate new factors, we have prepared the following information for you: + To help you generate new {{targets}}, we have prepared the following information for you: {{ RAG }} Please generate the new hypothesis based on the information above. -factor_hypothesis2experiment: +hypothesis2experiment: system_prompt: |- - The user is trying to generate new factors based on the hypothesis generated in the previous step. - The factors are used in certain scenario, the scenario is as follows: + The user is trying to generate new {{targets}} based on the hypothesis generated in the previous step. + The {{targets}} are used in certain scenario, the scenario is as follows: {{ scenario }} - The user will use the factors generated to do some experiments. The user will provide this information to you: - 1. The target hypothesis you are targeting to generate factors for. + The user will use the {{targets}} generated to do some experiments. The user will provide this information to you: + 1. The target hypothesis you are targeting to generate {{targets}} for. 2. The hypothesis generated in the previous steps and their corresponding feedbacks. - 3. Former proposed factors on similar hypothesis. - 4. Some additional information to help you generate new factors. + 3. Former proposed {{targets}} on similar hypothesis. + 4. Some additional information to help you generate new {{targets}}. Please generate the output following the format below: {{ experiment_output_format }} user_prompt: |- The user has made several hypothesis on this scenario and did several evaluation on them. - The target hypothesis you are targeting to generate factors for is as follows: + The target hypothesis you are targeting to generate {{targets}} for is as follows: {{ target_hypothesis }} The former hypothesis and the corresponding feedbacks are as follows: {{ hypothesis_and_feedback }} - The former proposed factors on similar hypothesis are as follows: - {{ factor_list }} - To help you generate new factors, we have prepared the following information for you: + The former proposed {{targets}} on similar hypothesis are as follows: + {{ target_list }} + To help you generate new {{targets}}, we have prepared the following information for you: {{ RAG }} - Please generate the new factors based on the information above. \ No newline at end of file + Please generate the new {{targets}} based on the information above. \ No newline at end of file diff --git a/rdagent/scenarios/qlib/experiment/prompts.yaml b/rdagent/scenarios/qlib/experiment/prompts.yaml index 71bce0f5..73051d11 100644 --- a/rdagent/scenarios/qlib/experiment/prompts.yaml +++ b/rdagent/scenarios/qlib/experiment/prompts.yaml @@ -92,6 +92,8 @@ qlib_model_interface: |- 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. + qlib_model_output_format: |- Your output should be a tensor with shape (batch_size, 1). diff --git a/rdagent/scenarios/qlib/factor_proposal.py b/rdagent/scenarios/qlib/factor_proposal.py index 216d4dc8..7be92420 100644 --- a/rdagent/scenarios/qlib/factor_proposal.py +++ b/rdagent/scenarios/qlib/factor_proposal.py @@ -5,7 +5,6 @@ from typing import List, Tuple from jinja2 import Environment, StrictUndefined from rdagent.components.coder.factor_coder.factor import FactorExperiment, FactorTask -from rdagent.components.coder.factor_coder.utils import get_data_folder_intro from rdagent.components.proposal.factor_proposal import ( FactorHypothesis, FactorHypothesis2Experiment, @@ -23,7 +22,7 @@ class QlibFactorHypothesisGen(FactorHypothesisGen): def __init__(self, scen: Scenario) -> Tuple[dict, bool]: super().__init__(scen) - def prepare_context(self, trace: Trace) -> None: + def prepare_context(self, trace: Trace) -> Tuple[dict, bool]: hypothesis_feedback = ( Environment(undefined=StrictUndefined) .from_string(prompt_dict["hypothesis_and_feedback"]) @@ -45,7 +44,7 @@ class QlibFactorHypothesisGen(FactorHypothesisGen): class QlibFactorHypothesis2Experiment(FactorHypothesis2Experiment): def prepare_context(self, hypothesis: Hypothesis, trace: Trace) -> Tuple[dict | bool]: scenario = trace.scen.get_scenario_all_desc() - experiment_output_format = prompt_dict["experiment_output_format"] + experiment_output_format = prompt_dict["factor_experiment_output_format"] hypothesis_and_feedback = ( Environment(undefined=StrictUndefined) @@ -64,7 +63,7 @@ class QlibFactorHypothesis2Experiment(FactorHypothesis2Experiment): "scenario": scenario, "hypothesis_and_feedback": hypothesis_and_feedback, "experiment_output_format": experiment_output_format, - "factor_list": factor_list, + "target_list": factor_list, "RAG": ..., }, True diff --git a/rdagent/scenarios/qlib/model_proposal.py b/rdagent/scenarios/qlib/model_proposal.py new file mode 100644 index 00000000..d5165dee --- /dev/null +++ b/rdagent/scenarios/qlib/model_proposal.py @@ -0,0 +1,81 @@ +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 + +prompt_dict = Prompts(file_path=Path(__file__).parent / "prompts.yaml") + +QlibModelHypothesis = ModelHypothesis + + +class QlibModelHypothesisGen(ModelHypothesisGen): + 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"], + } + return context_dict, True + + def convert_response(self, response: str) -> ModelHypothesis: + response_dict = json.loads(response) + hypothesis = QlibModelHypothesis(hypothesis=response_dict["hypothesis"], reason=response_dict["reason"]) + return hypothesis + + +class QlibModelHypothesis2Experiment(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"] + variables = response_dict[model_name]["variables"] + model_type = response_dict[model_name]["model_type"] + tasks.append(ModelTask(model_name, description, formulation, variables, model_type)) + exp = ModelExperiment(tasks) + exp.based_experiments = [t[1] for t in trace.hist if t[2]] + return exp diff --git a/rdagent/scenarios/qlib/model_task_implementation/__init__.py b/rdagent/scenarios/qlib/model_task_implementation/__init__.py index fad4c284..bfe80a94 100644 --- a/rdagent/scenarios/qlib/model_task_implementation/__init__.py +++ b/rdagent/scenarios/qlib/model_task_implementation/__init__.py @@ -1,5 +1,3 @@ -from rdagent.components.coder.model_coder.one_shot import ModelCodeWriter +from rdagent.components.coder.model_coder.CoSTEER import ModelCoSTEER - -class QlibModelCodeWriter(ModelCodeWriter): - ... +QlibModelCoSTEER = ModelCoSTEER diff --git a/rdagent/scenarios/qlib/prompts.yaml b/rdagent/scenarios/qlib/prompts.yaml index 6f48af23..a10ef598 100644 --- a/rdagent/scenarios/qlib/prompts.yaml +++ b/rdagent/scenarios/qlib/prompts.yaml @@ -11,7 +11,7 @@ hypothesis_output_format: |- "reason": "The reason why you generate this hypothesis." } -experiment_output_format: |- +factor_experiment_output_format: |- The output should follow JSON format. The schema is as follows: { "factor name 1": { @@ -32,3 +32,19 @@ experiment_output_format: |- } # Don't add ellipsis (...) or any filler text that might cause JSON parsing errors here! } + +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 1": { + "description": "detailed description of model 1", + "formulation": "latex formulation of model 1", + "variables": { + "variable or function name 1": "description of variable or function 1", + "variable or function name 2": "description of variable or function 2" + } + "model_type": "type of model 1, Tabular or TimesSeries" + } + # Don't add ellipsis (...) or any filler text that might cause JSON parsing errors here! + } \ No newline at end of file diff --git a/rdagent/scenarios/qlib/task_generator/feedback.py b/rdagent/scenarios/qlib/task_generator/feedback.py index 9fed7e89..0c6a09dd 100644 --- a/rdagent/scenarios/qlib/task_generator/feedback.py +++ b/rdagent/scenarios/qlib/task_generator/feedback.py @@ -5,3 +5,6 @@ from rdagent.core.proposal import HypothesisExperiment2Feedback class QlibFactorHypothesisExperiment2Feedback(HypothesisExperiment2Feedback): ... + + +class QlibModelHypothesisExperiment2Feedback(HypothesisExperiment2Feedback): ... diff --git a/rdagent/scenarios/qlib/task_generator/model.py b/rdagent/scenarios/qlib/task_generator/model.py index 7fc0fabc..c7f336e4 100644 --- a/rdagent/scenarios/qlib/task_generator/model.py +++ b/rdagent/scenarios/qlib/task_generator/model.py @@ -2,7 +2,7 @@ from rdagent.components.coder.model_coder.model import ModelImplementation from rdagent.core.task_generator import TaskGenerator -class QlibModelImplementation(TaskGenerator[ModelImplementation]): +class QlibModelRunner(TaskGenerator[ModelImplementation]): """ Docker run Everything in a folder @@ -14,3 +14,6 @@ class QlibModelImplementation(TaskGenerator[ModelImplementation]): - pt_model_uri: hard-code `model.py:Net` in the config - let LLM modify model.py """ + + def generate(self, exp: ModelImplementation) -> ModelImplementation: + return exp # TODO IMPLEMENT THIS