mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-08-05 11:07:43 +00:00
69fc1fd911
* Fixes on kaggle output * feat: add kaggle s3e14 template (#394) * add s3e14 template * fix CI * Initialisation of a template of competition * add kaggle s3e16 template (#396) * get kaggle competition scores (#397) * Adding a new competition s4e6 * feat: s4e5 (#400) * init for s4e5 * edit s4e5 * ci issue * feat: S4e3 (#402) * Initialisation of a template of competition * Adding a new competition s4e6 * Competition Initialised * Fixed to make sure that now it runs * Fixing for CI * correct evaluation (#403) * find rank in leaderboard (#405) * fix: model templates for KG scenario (#408) * fix feature selection for some models * feat select template * Updating the prompts for a more powerful model tuning * refine the prompt * fix: template error in s4e6 * feat: show simple execution time in demo (#410) * show time in kaggle demo * change color * fix a small bug * edit loop.py and proposal * delete useless files * CI issues * ci issue --------- Co-authored-by: XianBW <36835909+XianBW@users.noreply.github.com> Co-authored-by: Haoran Pan <167847254+TPLin22@users.noreply.github.com> Co-authored-by: Way2Learn <118058822+Xisen-Wang@users.noreply.github.com> Co-authored-by: WinstonLiyt <1957922024@qq.com> Co-authored-by: TPLin22 <tplin2@163.com>
371 lines
15 KiB
Python
371 lines
15 KiB
Python
import json
|
|
import math
|
|
from pathlib import Path
|
|
from typing import List, Tuple
|
|
|
|
from jinja2 import Environment, StrictUndefined
|
|
|
|
from rdagent.app.kaggle.conf import KAGGLE_IMPLEMENT_SETTING
|
|
from rdagent.components.coder.factor_coder.factor import FactorTask
|
|
from rdagent.components.coder.model_coder.model import ModelExperiment, ModelTask
|
|
from rdagent.components.knowledge_management.vector_base import VectorBase
|
|
from rdagent.components.proposal.model_proposal import (
|
|
ModelHypothesis,
|
|
ModelHypothesis2Experiment,
|
|
ModelHypothesisGen,
|
|
)
|
|
from rdagent.core.exception import ModelEmptyError
|
|
from rdagent.core.prompts import Prompts
|
|
from rdagent.core.proposal import Hypothesis, Scenario, Trace
|
|
from rdagent.scenarios.kaggle.experiment.kaggle_experiment import (
|
|
KG_SELECT_MAPPING,
|
|
KGFactorExperiment,
|
|
KGModelExperiment,
|
|
)
|
|
from rdagent.scenarios.kaggle.experiment.scenario import KGScenario
|
|
from rdagent.scenarios.kaggle.knowledge_management.graph import KGKnowledgeGraph
|
|
from rdagent.scenarios.kaggle.knowledge_management.vector_base import (
|
|
KaggleExperienceBase,
|
|
)
|
|
|
|
prompt_dict = Prompts(file_path=Path(__file__).parent.parent / "prompts.yaml")
|
|
|
|
|
|
from rdagent.scenarios.kaggle.experiment.scenario import (
|
|
KG_ACTION_FEATURE_ENGINEERING,
|
|
KG_ACTION_FEATURE_PROCESSING,
|
|
KG_ACTION_LIST,
|
|
KG_ACTION_MODEL_FEATURE_SELECTION,
|
|
KG_ACTION_MODEL_TUNING,
|
|
)
|
|
|
|
|
|
class KGHypothesis(Hypothesis):
|
|
def __init__(
|
|
self,
|
|
hypothesis: str,
|
|
reason: str,
|
|
concise_reason: str,
|
|
concise_observation: str,
|
|
concise_justification: str,
|
|
concise_knowledge: str,
|
|
action: str,
|
|
) -> None:
|
|
super().__init__(
|
|
hypothesis, reason, concise_reason, concise_observation, concise_justification, concise_knowledge
|
|
)
|
|
self.action = action
|
|
|
|
def __str__(self) -> str:
|
|
return f"""Chosen Action: {self.action}
|
|
Hypothesis: {self.hypothesis}
|
|
Reason: {self.reason}
|
|
Concise Reason & Knowledge: {self.concise_reason}
|
|
Concise Observation: {self.concise_observation}
|
|
Concise Justification: {self.concise_justification}
|
|
Concise Knowledge: {self.concise_knowledge}
|
|
"""
|
|
|
|
|
|
class KGHypothesisGen(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 KGHypothesisGen(ModelHypothesisGen):
|
|
prompts: Prompts = a_specifc_prompt_dict
|
|
"""
|
|
|
|
def __init__(self, scen: Scenario) -> Tuple[dict, bool]:
|
|
super().__init__(scen)
|
|
|
|
def generate_RAG_content(self, trace: Trace, hypothesis_and_feedback: str, target: str = None) -> str:
|
|
if self.scen.if_using_vector_rag:
|
|
if self.scen.mini_case:
|
|
rag_results, _ = self.scen.vector_base.search_experience(target, hypothesis_and_feedback, topk_k=1)
|
|
else:
|
|
rag_results, _ = self.scen.vector_base.search_experience(target, hypothesis_and_feedback, topk_k=5)
|
|
return "\n".join([doc.content for doc in rag_results])
|
|
if self.scen.if_using_graph_rag is False or trace.knowledge_base is None:
|
|
return None
|
|
same_competition_node = trace.knowledge_base.get_node_by_content(trace.scen.get_competition_full_desc())
|
|
if same_competition_node is not None:
|
|
related_hypothesis_nodes = []
|
|
for action in KG_ACTION_LIST:
|
|
related_hypothesis_nodes.extend(
|
|
trace.knowledge_base.get_nodes_within_steps(
|
|
start_node=same_competition_node,
|
|
steps=1,
|
|
constraint_labels=[action],
|
|
)[:1]
|
|
)
|
|
else:
|
|
related_hypothesis_nodes = []
|
|
experiences = []
|
|
for hypothesis_node in related_hypothesis_nodes:
|
|
experience = {"hypothesis": hypothesis_node.content}
|
|
experiment_node_list = trace.knowledge_base.get_nodes_within_steps(
|
|
start_node=hypothesis_node, steps=1, constraint_labels=["experiments"]
|
|
)
|
|
if len(experiment_node_list) > 0:
|
|
experience["experiments"] = experiment_node_list[0].content
|
|
else:
|
|
experience["experiments"] = "No experiment information available."
|
|
conclusion_node_list = trace.knowledge_base.get_nodes_within_steps(
|
|
start_node=hypothesis_node, steps=1, constraint_labels=["conclusion"]
|
|
)
|
|
if len(conclusion_node_list) > 0:
|
|
experience["conclusion"] = conclusion_node_list[0].content
|
|
else:
|
|
experience["conclusion"] = "No conclusion information available."
|
|
experiences.append(experience)
|
|
|
|
similar_nodes = trace.knowledge_base.semantic_search(
|
|
node=trace.scen.get_competition_full_desc(),
|
|
topk_k=2,
|
|
)
|
|
|
|
found_hypothesis_nodes = []
|
|
for similar_node in similar_nodes:
|
|
# for hypothesis_type in KG_ACTION_LIST:
|
|
hypothesis_nodes = trace.knowledge_base.get_nodes_within_steps(
|
|
start_node=similar_node,
|
|
steps=3,
|
|
constraint_labels=[target],
|
|
)
|
|
found_hypothesis_nodes.extend(hypothesis_nodes[:2])
|
|
|
|
found_hypothesis_nodes = sorted(list(set(found_hypothesis_nodes)), key=lambda x: len(x.content))
|
|
|
|
insights = []
|
|
for hypothesis_node in found_hypothesis_nodes[:5]:
|
|
if hypothesis_node in related_hypothesis_nodes:
|
|
continue
|
|
insight = {"hypothesis": hypothesis_node.content}
|
|
experiment_node_list = trace.knowledge_base.get_nodes_within_steps(
|
|
start_node=hypothesis_node, steps=1, constraint_labels=["experiments"]
|
|
)
|
|
if len(experiment_node_list) > 0:
|
|
insight["experiments"] = experiment_node_list[0].content
|
|
else:
|
|
insight["experiments"] = "No experiment information available."
|
|
conclusion_node_list = trace.knowledge_base.get_nodes_within_steps(
|
|
start_node=hypothesis_node, steps=1, constraint_labels=["conclusion"]
|
|
)
|
|
if len(conclusion_node_list) > 0:
|
|
insight["conclusion"] = conclusion_node_list[0].content
|
|
else:
|
|
insight["conclusion"] = "No conclusion information available."
|
|
insights.append(insight)
|
|
|
|
RAG_content = (
|
|
Environment(undefined=StrictUndefined)
|
|
.from_string(prompt_dict["KG_hypothesis_gen_RAG"])
|
|
.render(insights=insights, experiences=experiences)
|
|
)
|
|
return RAG_content
|
|
|
|
def update_reward_estimates(self, trace: Trace) -> None:
|
|
if len(trace.hist) > 0:
|
|
last_entry = trace.hist[-1]
|
|
last_action = last_entry[0].action
|
|
last_result = last_entry[1].result
|
|
# Extract performance_t
|
|
performance_t = last_result.get("performance", 0.0)
|
|
# Get performance_{t-1}
|
|
if len(trace.hist) > 1:
|
|
prev_entry = trace.hist[-2]
|
|
prev_result = prev_entry[1].result
|
|
performance_t_minus_1 = prev_result.get("performance", 0.0)
|
|
else:
|
|
performance_t_minus_1 = self.scen.initial_performance
|
|
|
|
if self.scen.evaluation_metric_direction:
|
|
reward = (performance_t - performance_t_minus_1) / max(performance_t_minus_1, 1e-8)
|
|
else:
|
|
reward = (performance_t_minus_1 - performance_t) / max(performance_t_minus_1, 1e-8)
|
|
|
|
reward = (performance_t - performance_t_minus_1) / performance_t_minus_1
|
|
n_o = self.scen.action_counts[last_action]
|
|
mu_o = self.scen.reward_estimates[last_action]
|
|
self.scen.scen.reward_estimates[last_action] += (reward - mu_o) / n_o
|
|
else:
|
|
# First iteration, nothing to update
|
|
pass
|
|
|
|
def execute_next_action(self, trace: Trace) -> str:
|
|
actions = list(self.scen.action_counts.keys())
|
|
t = sum(self.scen.action_counts.values()) + 1
|
|
|
|
# If any action has not been tried yet, select it
|
|
for action in actions:
|
|
if self.scen.action_counts[action] == 0:
|
|
selected_action = action
|
|
return selected_action
|
|
|
|
c = self.scen.confidence_parameter
|
|
ucb_values = {}
|
|
for action in actions:
|
|
mu_o = self.scen.reward_estimates[action]
|
|
n_o = self.scen.action_counts[action]
|
|
ucb = mu_o + c * math.sqrt(math.log(t) / n_o)
|
|
ucb_values[action] = ucb
|
|
# Select action with highest UCB
|
|
selected_action = max(ucb_values, key=ucb_values.get)
|
|
|
|
return selected_action
|
|
|
|
def prepare_context(self, trace: Trace) -> Tuple[dict, bool]:
|
|
hypothesis_and_feedback = (
|
|
(
|
|
Environment(undefined=StrictUndefined)
|
|
.from_string(prompt_dict["hypothesis_and_feedback"])
|
|
.render(trace=trace)
|
|
)
|
|
if len(trace.hist) > 0
|
|
else "No previous hypothesis and feedback available since it's the first round."
|
|
)
|
|
|
|
if self.scen.if_action_choosing_based_on_UCB:
|
|
action = self.execute_next_action(trace)
|
|
|
|
context_dict = {
|
|
"hypothesis_and_feedback": hypothesis_and_feedback,
|
|
"RAG": self.generate_RAG_content(
|
|
trace=trace,
|
|
hypothesis_and_feedback=hypothesis_and_feedback,
|
|
target=action if self.scen.if_action_choosing_based_on_UCB else None,
|
|
),
|
|
"hypothesis_output_format": prompt_dict["hypothesis_output_format"],
|
|
"hypothesis_specification": (
|
|
{
|
|
"next_experiment_action": f"next experiment action is {action}",
|
|
"specification": prompt_dict["hypothesis_specification"][action],
|
|
}
|
|
if self.scen.if_action_choosing_based_on_UCB
|
|
else None
|
|
),
|
|
}
|
|
return context_dict, True
|
|
|
|
def convert_response(self, response: str) -> ModelHypothesis:
|
|
response_dict = json.loads(response)
|
|
|
|
hypothesis = KGHypothesis(
|
|
hypothesis=response_dict.get("hypothesis", "Hypothesis not provided"),
|
|
reason=response_dict.get("reason", "Reason not provided"),
|
|
concise_reason=response_dict.get("concise_reason", "Concise reason not provided"),
|
|
concise_observation=response_dict.get("concise_observation", "Concise observation not provided"),
|
|
concise_justification=response_dict.get("concise_justification", "Concise justification not provided"),
|
|
concise_knowledge=response_dict.get("concise_knowledge", "Concise knowledge not provided"),
|
|
action=response_dict.get("action", "Action not provided"),
|
|
)
|
|
|
|
return hypothesis
|
|
|
|
|
|
class KGHypothesis2Experiment(ModelHypothesis2Experiment):
|
|
def prepare_context(self, hypothesis: Hypothesis, trace: Trace) -> Tuple[dict, bool]:
|
|
scenario = trace.scen.get_scenario_all_desc()
|
|
assert isinstance(hypothesis, KGHypothesis)
|
|
experiment_output_format = (
|
|
prompt_dict["feature_experiment_output_format"]
|
|
if hypothesis.action in [KG_ACTION_FEATURE_ENGINEERING, KG_ACTION_FEATURE_PROCESSING]
|
|
else prompt_dict["model_experiment_output_format"]
|
|
)
|
|
self.current_action = hypothesis.action
|
|
|
|
hypothesis_and_feedback = (
|
|
(
|
|
Environment(undefined=StrictUndefined)
|
|
.from_string(prompt_dict["hypothesis_and_feedback"])
|
|
.render(trace=trace)
|
|
)
|
|
if len(trace.hist) > 0
|
|
else "No previous hypothesis and feedback available since it's the first round."
|
|
)
|
|
|
|
experiment_list: List[ModelExperiment] = [t[1] for t in trace.hist]
|
|
|
|
model_list = []
|
|
for experiment in experiment_list:
|
|
for sub_task in experiment.sub_tasks:
|
|
model_list.extend(sub_task.get_task_information())
|
|
|
|
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_feature_experiment(self, response: str, trace: Trace) -> KGFactorExperiment:
|
|
response_dict = json.loads(response)
|
|
tasks = []
|
|
|
|
for factor_name in response_dict:
|
|
description = (response_dict[factor_name].get("description", "Factor description not provided"),)
|
|
formulation = (response_dict[factor_name].get("formulation", "Factor formulation not provided"),)
|
|
variables = (response_dict[factor_name].get("variables", "Variables not provided"),)
|
|
tasks.append(
|
|
FactorTask(
|
|
factor_name=factor_name,
|
|
factor_description=description,
|
|
factor_formulation=formulation,
|
|
variables=variables,
|
|
version=2,
|
|
)
|
|
)
|
|
|
|
exp = KGFactorExperiment(
|
|
sub_tasks=tasks,
|
|
based_experiments=(
|
|
[KGFactorExperiment(sub_tasks=[], source_feature_size=trace.scen.input_shape[-1])]
|
|
+ [t[1] for t in trace.hist if t[2]]
|
|
),
|
|
)
|
|
return exp
|
|
|
|
def convert_model_experiment(self, response: str, trace: Trace) -> KGModelExperiment:
|
|
response_dict = json.loads(response)
|
|
tasks = []
|
|
model_type = response_dict.get("model_type", "Model type not provided")
|
|
if model_type not in KG_SELECT_MAPPING:
|
|
raise ModelEmptyError(
|
|
f"Invalid model type '{model_type}'. Allowed model types are: {', '.join(KG_SELECT_MAPPING)}."
|
|
)
|
|
|
|
tasks.append(
|
|
ModelTask(
|
|
name=response_dict.get("model_name", "Model name not provided"),
|
|
description=response_dict.get("description", "Description not provided"),
|
|
architecture=response_dict.get("architecture", "Architecture not provided"),
|
|
hyperparameters=response_dict.get("hyperparameters", "Hyperparameters not provided"),
|
|
model_type=response_dict.get("model_type", "Model type not provided"),
|
|
version=2,
|
|
)
|
|
)
|
|
exp = KGModelExperiment(
|
|
sub_tasks=tasks,
|
|
based_experiments=(
|
|
[KGModelExperiment(sub_tasks=[], source_feature_size=trace.scen.input_shape[-1])]
|
|
+ [t[1] for t in trace.hist if t[2]]
|
|
),
|
|
)
|
|
return exp
|
|
|
|
def convert_response(self, response: str, trace: Trace) -> ModelExperiment:
|
|
if self.current_action in [KG_ACTION_FEATURE_ENGINEERING, KG_ACTION_FEATURE_PROCESSING]:
|
|
return self.convert_feature_experiment(response, trace)
|
|
elif self.current_action in [KG_ACTION_MODEL_FEATURE_SELECTION, KG_ACTION_MODEL_TUNING]:
|
|
return self.convert_model_experiment(response, trace)
|
|
|
|
|
|
class KGTrace(Trace[KGScenario, KGKnowledgeGraph]):
|
|
pass
|