diff --git a/rdagent/app/data_science/conf.py b/rdagent/app/data_science/conf.py index 32a37d15..b2f96006 100644 --- a/rdagent/app/data_science/conf.py +++ b/rdagent/app/data_science/conf.py @@ -119,5 +119,9 @@ class DataScienceBasePropSetting(KaggleBasePropSetting): model_architecture_suggestion_time_percent: float = 0.75 + #### hypothesis critique and rewrite + enable_hypo_critique_rewrite: bool = True + """Enable hypothesis critique and rewrite stages for improving hypothesis quality""" + DS_RD_SETTING = DataScienceBasePropSetting() diff --git a/rdagent/scenarios/data_science/proposal/exp_gen/prompts_v2.yaml b/rdagent/scenarios/data_science/proposal/exp_gen/prompts_v2.yaml index 1fd4acb9..20886904 100644 --- a/rdagent/scenarios/data_science/proposal/exp_gen/prompts_v2.yaml +++ b/rdagent/scenarios/data_science/proposal/exp_gen/prompts_v2.yaml @@ -155,7 +155,7 @@ hypothesis_gen: 1. **Be Specific and Decisive**: - Clearly state the exact, unambiguous change(s) being proposed. Avoid vague goals like "improve the model" or "optimize the pipeline." - The hypothesis must propose a single, clear course of action. Do not suggest alternatives (e.g., "try method A or method B"). - - The hypothesis statement must be direct and definitive, without phrases like "for example," "e.g.," or "might involve." + - The hypothesis statement must be direct and definitive, without phrases like "for example," "e.g.," "might involve," "consider," "try," or "explore." - The hypothesis must be more informative and decisive than the Challenge it addresses. It should not simply restate the Challenge or suggest a general approach without specifics. 2. **Ensure Testability and Actionability**: - The hypothesis must describe an action or change that can be practically implemented and tested. @@ -231,6 +231,138 @@ hypothesis_gen: # Identified Challenges{% if enable_idea_pool %} with Sampled Ideas{% endif %} {{ problems }} +hypothesis_critique: + system: |- + {% include "scenarios.data_science.share:scen.role" %} + You are an expert critic evaluating machine learning hypotheses for Kaggle competition improvement. + + For each hypothesis, provide a focused critique that identifies key issues and suggests improvements while preserving the experimental nature of hypotheses. + + ## Three Core Evaluation Areas: + + ### 1. Feasibility Assessment + - **Technical Risk**: Major implementation challenges or resource constraints that could cause failure + - **Integration Issues**: Conflicts with existing code or pipeline components + - **Constraint Violations**: Whether this respects competition time/memory limits based on historical patterns + + ### 2. Alignment Check + - **Problem-Solution Fit**: Does this actually address the root cause of the identified challenge? + - **Metric Impact**: Will this meaningfully improve the competition's evaluation metric? + - **Historical Context**: Has similar approaches been tried? Key learnings from past attempts? + - **Innovation vs History Balance**: Distinguish between implementation failures (worth retrying with improvements) vs fundamental approach failures (multiple attempts failed due to core unsuitability - should avoid) + + ### 3. Improvement Direction + - **Clarity Issues**: If vague, identify specific methods or strategies that address the core problem + - **Alternative Strategies**: If implementation is problematic, identify concrete alternative approaches within the current framework such as switching from simple to weighted ensemble + - **Risk Mitigation**: Recommend specific validation strategies or safeguards for high-risk aspects + - **Competition Context**: This is a Kaggle competition where strong performance may come from novel approaches, but also from incremental improvements and careful optimization. Balance innovation with practical enhancements. + + ## CRITICAL Guidance Rules + + - Be specific about methods and strategies, but avoid over-specifying implementation parameters. Suggest clear approaches like "use weighted ensemble instead of simple averaging" rather than exact values like "set weights=[0.3, 0.7]". + - Focus on suggesting CLEAR METHODS and APPROACHES that lead to decisive hypotheses. + - Avoid Overfitting to History: Learn from past failures but don't over-constrain innovation. Distinguish between implementation failures (worth retrying with improvements) and fundamental approach failures (should be avoided). + + ### Examples: + + **Good Critiques:** + - "The hypothesis lacks specificity about which ensemble method to use. Consider weighted averaging based on validation performance rather than simple averaging, given the model performance disparities." + - "This hypothesis proposes LSTM for tabular data. History shows 3 consecutive failures with different LSTM implementations, and tabular data lacks sequential structure. Consider graph-based approaches instead to capture feature relationships." + + **Poor Critiques:** + - "Set max_depth=10, learning_rate=0.05, and use 500 trees." (too specific) + - "This might not work." (too vague) + - "LSTM is innovative, let's try again with different hyperparameters." (ignores fundamental mismatch) + + {% if critique_output_format is not none %} + ## Output Format + {{ critique_output_format }} + {% endif %} + + user: |- + # Scenario Description + {{ scenario_desc }} + + # Previous Experiments and Feedbacks + {{ exp_and_feedback_list_desc }} + + # Current SOTA Implementation + {{ sota_exp_desc }} + + # Hypotheses to Critique + {{ hypotheses_formatted }} + +hypothesis_rewrite: + system: |- + {% include "scenarios.data_science.share:scen.role" %} + You are an expert hypothesis rewriter specializing in iterative improvement of machine learning solutions for Kaggle competitions. + + ## Task + Transform each **original hypothesis and its critique** into a **single, specific, testable technical hypothesis** that can be implemented immediately. + + ## Core Principles + 1. **Actionable Critique** – Apply insights from the critique, but the final text must stand alone with **no meta‑discussion** of the critique itself. + 2. **Standalone Justification** – Ground every technical decision in dataset characteristics, available compute budget, and competition constraints. + 3. **Decisive Specificity** – Remove all ambiguity; propose one clear action. + 4. **Innovation Preservation** – Maintain the innovative core of the original hypothesis while addressing implementation concerns. Avoid reverting to conventional approaches unless absolutely necessary. + 5. **CRITICAL - Avoid Overfitting to Critique** – Apply critique insights thoughtfully without over-constraining innovation. Balance addressing identified issues with preserving the exploratory value of bold ideas. + + ## Guidelines for Writing Rewritten Hypotheses + + 1. **Critique-Informed Specificity**: + - Address technical gaps identified in the critique and replace vague terms with specific algorithms, methods, or parameters. + - Transform general suggestions from the critique into concrete, implementable actions. + - If the critique highlighted feasibility issues, propose alternative approaches that maintain the hypothesis's core intent while being more practical. + - The rewritten hypothesis must be more specific than the original, incorporating the critique's guidance without explicitly referencing it. + + 2. **Standalone Technical Justification**: + - Ground every technical decision in observable dataset characteristics (e.g., data size, feature types, class distribution). + - Reference competition constraints (time limits, evaluation metrics, submission format) to justify approach choices. + - Ensure the hypothesis can be understood and implemented without needing to read the original hypothesis or critique. + - Include rationale for why the specific method/algorithm chosen is suitable for the current scenario. + + 3. **Enhanced Actionability and Precision**: + - Replace any remaining ambiguity with decisive technical choices (e.g., "ensemble method" → "weighted averaging based on validation performance"). + - Specify validation strategies that will confirm the hypothesis's effectiveness. + - Define clear success criteria or expected outcomes that can be measured. + - If the original hypothesis bundled multiple ideas, focus on the most impactful one identified through the critique. + + 4. **Risk Mitigation and Implementation Clarity**: + - If the critique identified implementation risks, incorporate specific mitigation strategies into the rewritten hypothesis. + - Address resource constraint concerns by proposing efficient alternatives or optimizations. + - Ensure the hypothesis addresses root causes rather than symptoms, as guided by the critique analysis. + - Make the hypothesis robust against common failure modes identified in the critique. + + 5. **Pipeline Integration and Component Focus**: + - Clearly specify how the proposed changes integrate with existing SOTA components. + - Maintain focus on the primary component while ensuring compatibility with the overall pipeline. + - If the critique suggested coordination across multiple components, organize these as a unified technical approach rather than separate changes. + - Ensure the rewritten hypothesis preserves successful aspects of the current SOTA while addressing identified weaknesses. + + 6. **Innovation and Historical Learning**: + - Apply critique insights to enhance sound innovative ideas while avoiding repeated fundamental failures identified in the analysis. + - **Competition Context**: This is a Kaggle competition where strong performance may come from novel approaches or incremental improvements. Enhance both innovative ideas and practical optimizations based on the critique analysis. + + + {% if rewrite_output_format is not none %} + ## Output Format + {{ rewrite_output_format }} + {% endif %} + + user: |- + # Scenario Description + {{ scenario_desc }} + + # Previous Experiments and Feedbacks + {{ exp_and_feedback_list_desc }} + + # Current SOTA Implementation + {{ sota_exp_desc }} + + # Original Hypotheses and Their Critiques + {{ hypothesis_critique_pairs }} + + task_gen: system: |- {% include "scenarios.data_science.share:scen.role" %} @@ -476,5 +608,35 @@ output_format: "problem name 2 (should be exactly same as the problem name provided)": 2, # The index which is same to the idea index provided in the input and must be integer. } + critique: |- + For each hypothesis, provide a comprehensive critique strictly following the JSON schema. + Your final output should be a dict containing critiques for all hypotheses without anything else. + { + "critiques": { + "problem name 1 (should match the hypothesis problem name exactly)": { + "critique": "A comprehensive critique covering: (1) Technical feasibility and potential issues, (2) Alignment with the scenario and competition requirements, (3) Specific improvement suggestions, (4) Overall assessment of the hypothesis quality and implementability. Be constructive and actionable." + }, + "problem name 2": { + "critique": "..." + } + } + } + rewrite: |- + For each original hypothesis, rewrite it to address critique feedback, strictly following the JSON schema below. + Your final output should be a dict containing all rewritten hypotheses without anything else. + { + "problem name 1 (should be exactly same as the original problem name without prefix or suffix)": { + "reason": "Independent justification for why this hypothesis makes sense given the current scenario, dataset characteristics, and competition requirements. DO NOT reference critique feedback or suggestions. Should be short with no more than two sentences focusing on the fundamental problem context.", + "component": "The component tag of the hypothesis. Must be one of ('DataLoadSpec', 'FeatureEng', 'Model', 'Ensemble', 'Workflow').", + "hypothesis": "A concise, improved hypothesis statement that directly addresses critique concerns. Limit to one or two sentences that clearly specify the expected change or improvement. Should be more specific and actionable than the original.", + "evaluation": { + "alignment_score": "Score from 1 (lowest/worst) to 10 (highest/best). How directly and effectively does the hypothesis address the core issues of the identified problem it targets? A higher score means a stronger, more direct alignment.", + "impact_score": "Score from 1 (lowest/worst) to 10 (highest/best). What is the estimated magnitude of improvement (e.g., in the primary competition metric, efficiency, robustness, or successful execution) if this hypothesis is successfully implemented? Higher scores for greater positive impact.", + "novelty_score": "Score from 1 (lowest/worst) to 10 (highest/best). How innovative or original is this hypothesis when compared to the approaches and ideas evident in the previous SOTA experiments and previous failed experiments? Assign a score of 1 if the hypothesis is a repeat or substantially similar to a previously attempted hypothesis (whether successful or failed), UNLESS the previous attempt clearly failed due to a trivial implementation bug and the current hypothesis proposes the correct implementation of the same core idea.", + "feasibility_score": "Score from 1 (lowest/worst) to 10 (highest/best). How easily and practically can this hypothesis be implemented and run to completion within the existing SOTA codebase and operational constraints (e.g., allowed time for training/inference, available compute resources, overall complexity)? Higher scores for easier implementation and higher likelihood of successful execution.", + "risk_reward_balance_score": "Score from 1 (lowest/worst) to 10 (highest/best). Considering the potential for significant improvement (reward) versus the probability of failure, negative side-effects, or excessive resource consumption (risk), how optimal is this balance? A high score indicates a favorable balance. If a hypothesis directly and credibly addresses a critical challenge that caused prior experiment failures (e.g., timeout, persistent data loading errors, incorrect submission format preventing any score), this should generally be scored highly (e.g., 8-10).", + } + } + } diff --git a/rdagent/scenarios/data_science/proposal/exp_gen/proposal.py b/rdagent/scenarios/data_science/proposal/exp_gen/proposal.py index 6a208392..714b9cec 100644 --- a/rdagent/scenarios/data_science/proposal/exp_gen/proposal.py +++ b/rdagent/scenarios/data_science/proposal/exp_gen/proposal.py @@ -649,6 +649,136 @@ class DSProposalV2ExpGen(ExpGen): return resp_dict + @wait_retry(retry_n=5) + def hypothesis_critique( + self, + hypothesis_dict: Dict, + problems_dict: Dict, + scenario_desc: str, + sota_exp_desc: str, + exp_feedback_list_desc: str, + ) -> Dict: + """ + Critique the generated hypotheses, identifying flaws and suggesting improvements. + """ + hypotheses_formatted = "" + for i, (problem_name, hypothesis_data) in enumerate(hypothesis_dict.items()): + + problem_info = problems_dict.get(problem_name, {}) + hypotheses_formatted += f"## {i+1}. **Problem Name:** {problem_name}\n" + hypotheses_formatted += f"**Original Problem:** {problem_info.get('problem', 'Not available')}\n" + hypotheses_formatted += f"**Component:** {hypothesis_data.get('component', 'Unknown')}\n" + hypotheses_formatted += f"**Hypothesis:** {hypothesis_data.get('hypothesis', 'Not provided')}\n" + hypotheses_formatted += f"**Reason:** {hypothesis_data.get('reason', 'Not provided')}\n\n" + + sys_prompt = T(".prompts_v2:hypothesis_critique.system").r( + critique_output_format=T(".prompts_v2:output_format.critique").r(), + ) + user_prompt = T(".prompts_v2:hypothesis_critique.user").r( + scenario_desc=scenario_desc, + exp_and_feedback_list_desc=exp_feedback_list_desc, + sota_exp_desc=sota_exp_desc, + hypotheses_formatted=hypotheses_formatted, + ) + + # Use json_object mode since hypothesis names are dynamic + response = APIBackend().build_messages_and_create_chat_completion( + user_prompt=user_prompt, + system_prompt=sys_prompt, + response_format={"type": "json_object"}, + json_target_type=dict, + ) + + response_dict = json.loads(response) + + # Improved error handling and validation + if "critiques" in response_dict: + critiques = response_dict["critiques"] + else: + # If format is incorrect, try to extract critiques directly + # Validate that all expected problem names are present + expected_problems = set(hypothesis_dict.keys()) + available_problems = set(response_dict.keys()) + + if expected_problems.issubset(available_problems): + critiques = response_dict + else: + raise ValueError( + f"Critique response missing expected problems. Expected: {expected_problems}, Got: {available_problems}" + ) + + # Validate that we have critiques for all hypotheses + missing_critiques = set(hypothesis_dict.keys()) - set(critiques.keys()) + if missing_critiques: + logger.warning(f"Missing critiques for problems: {missing_critiques}") + # Add default critiques for missing ones + for problem_name in missing_critiques: + critiques[problem_name] = {"critique": "No specific critique available for this hypothesis."} + + logger.info(f"Generated critiques for {len(critiques)} hypothesis") + return critiques + + @wait_retry(retry_n=5) + def hypothesis_rewrite( + self, + hypothesis_dict: Dict, + critiques_dict: Dict, + scenario_desc: str, + sota_exp_desc: str, + exp_feedback_list_desc: str, + ) -> Dict: + """ + Generate improved hypotheses based on critique feedback for each original hypothesis. + Returns a dict with the same keys as hypothesis_dict, containing improved versions. + """ + hypothesis_critique_pairs = "" + for i, problem_name in enumerate(hypothesis_dict.keys()): + hypothesis_data = hypothesis_dict[problem_name] + critique_data = critiques_dict.get(problem_name, {}) + + hypothesis_critique_pairs += f"## Original Hypothesis {i+1}: {problem_name}\n" + hypothesis_critique_pairs += f"**Hypothesis:** {hypothesis_data.get('hypothesis', 'Not provided')}\n" + hypothesis_critique_pairs += f"**Component:** {hypothesis_data.get('component', 'Unknown')}\n" + hypothesis_critique_pairs += f"**Reasoning:** {hypothesis_data.get('reason', 'Not provided')}\n" + hypothesis_critique_pairs += f"**Critique:** {critique_data.get('critique', 'No critique available')}\n\n" + + sys_prompt = T(".prompts_v2:hypothesis_rewrite.system").r( + rewrite_output_format=T(".prompts_v2:output_format.rewrite").r(), + ) + user_prompt = T(".prompts_v2:hypothesis_rewrite.user").r( + scenario_desc=scenario_desc, + exp_and_feedback_list_desc=exp_feedback_list_desc, + sota_exp_desc=sota_exp_desc, + hypothesis_critique_pairs=hypothesis_critique_pairs, + ) + + response = APIBackend().build_messages_and_create_chat_completion( + user_prompt=user_prompt, + system_prompt=sys_prompt, + response_format={"type": "json_object"}, + json_target_type=dict, + ) + + improved_hypotheses_dict = json.loads(response) + + # Validate that we have rewritten hypotheses for all original hypotheses + expected_problems = set(hypothesis_dict.keys()) + available_problems = set(improved_hypotheses_dict.keys()) + + if not expected_problems.issubset(available_problems): + missing_problems = expected_problems - available_problems + # Raise exception to trigger retry mechanism + raise ValueError(f"Rewrite response missing expected problems. Missing: {missing_problems}") + + # Note: We don't preserve 'inspired' field from original hypotheses + # because after critique and rewrite, the hypothesis may have changed significantly + # and the original inspiration may no longer be relevant + + logger.info( + f"Generated rewritten versions of {len(improved_hypotheses_dict)} hypotheses based on critique feedback" + ) + return improved_hypotheses_dict + def compute_top_scores( self, hypothesis_dict: dict, @@ -869,15 +999,19 @@ class DSProposalV2ExpGen(ExpGen): eda_output = sota_exp.experiment_workspace.file_dict.get("EDA.md", None) scenario_desc = self.scen.get_scenario_all_desc(eda_output=eda_output) + # the only sota exp sota_exp_desc = T("scenarios.data_science.share:describe.exp").r( exp=sota_exp, heading="Best of previous exploration of the scenario" ) + # all exp and feedbacks exp_feedback_list_desc = T("scenarios.data_science.share:describe.trace").r( exp_and_feedback_list=trace.experiment_and_feedback_list_after_init(return_type="all"), type="all", pipeline=pipeline, ) + + # all failed exp and feedbacks failed_exp_feedback_list_desc = T("scenarios.data_science.share:describe.trace").r( exp_and_feedback_list=trace.experiment_and_feedback_list_after_init(return_type="failed"), type="failed", @@ -943,9 +1077,45 @@ class DSProposalV2ExpGen(ExpGen): for name in pop_names: hypothesis_dict.pop(name) + # Step 2.1 & 2.2: Hypothesis Critique and Rewrite Stage (controlled by enable_hypo_critique_rewrite) + if DS_RD_SETTING.enable_hypo_critique_rewrite: + logger.info(f"Hypothesis critique and rewrite enabled - processing {len(hypothesis_dict)} hypotheses") + + # Critic Stage - Evaluate and identify flaws in hypotheses + logger.info( + f"Starting critic stage - evaluating {len(hypothesis_dict)} hypotheses for flaws and improvements" + ) + try: + critiques_dict = self.hypothesis_critique( + hypothesis_dict=hypothesis_dict, + problems_dict=all_problems, + scenario_desc=scenario_desc, + sota_exp_desc=sota_exp_desc, + exp_feedback_list_desc=exp_feedback_list_desc, + ) + logger.info(f"Generated critiques for {len(critiques_dict)} hypotheses") + + # Rewriter Stage - Generate improved hypotheses based on critiques + logger.info(f"Starting rewriter stage - generating improved hypotheses based on critique feedback") + improved_hypotheses_dict = self.hypothesis_rewrite( + hypothesis_dict=hypothesis_dict, + critiques_dict=critiques_dict, + scenario_desc=scenario_desc, + sota_exp_desc=sota_exp_desc, + exp_feedback_list_desc=exp_feedback_list_desc, + ) + logger.info(f"Successfully completed hypothesis critique and rewrite process") + except Exception as e: + logger.warning(f"Hypothesis critique and rewrite failed: {e}") + logger.info(f"Using original hypotheses as fallback instead of improved versions") + improved_hypotheses_dict = hypothesis_dict.copy() # Use original hypotheses as fallback + else: + logger.info(f"Hypothesis critique and rewrite disabled - using original {len(hypothesis_dict)} hypotheses") + improved_hypotheses_dict = hypothesis_dict.copy() # Use original hypotheses directly + # Step 3: Select the best hypothesis pickled_problem_name, new_hypothesis = self.hypothesis_rank( - hypothesis_dict=hypothesis_dict, + hypothesis_dict=improved_hypotheses_dict, problem_dict=all_problems, ) # Step 3.5: Update knowledge base with the picked problem @@ -958,7 +1128,9 @@ class DSProposalV2ExpGen(ExpGen): sota_exp_desc=sota_exp_desc, sota_exp=sota_exp, hypotheses=( - [new_hypothesis] if len(trace.hist) > 0 else self.get_all_hypotheses(all_problems, hypothesis_dict) + [new_hypothesis] + if len(trace.hist) > 0 + else self.get_all_hypotheses(all_problems, improved_hypotheses_dict) ), pipeline=pipeline, failed_exp_feedback_list_desc=failed_exp_feedback_list_desc, diff --git a/rdagent/scenarios/data_science/share.yaml b/rdagent/scenarios/data_science/share.yaml index 054bfe27..92148fef 100644 --- a/rdagent/scenarios/data_science/share.yaml +++ b/rdagent/scenarios/data_science/share.yaml @@ -52,7 +52,7 @@ describe: # some template to describe some object {% if not pipeline %}Chosen Component: {{ exp_and_feedback[0].hypothesis.component }}{% endif %} Proposed Hypothesis: {{ exp_and_feedback[0].hypothesis.hypothesis }} {% if exp_and_feedback[1].code_change_summary %}Code Change Summary: {{ exp_and_feedback[1].code_change_summary }}{% endif %} - Surpass Previous SOTA: {{ exp_and_feedback[1].decision }} + **Surpass Previous SOTA**: {{ exp_and_feedback[1].decision }} {% if exp_and_feedback[0].running_info.running_time is not none %} Experiment Running Time: {{ exp_and_feedback[0].running_info.running_time }} seconds {% endif %} @@ -350,4 +350,4 @@ spec: - The validation loss (or metric) remains stable (i.e., does not improve) for a set number of consecutive epochs. - Clearly document the early stopping criteria and ensure they are configurable via hyperparameters. 5. Print necessary information to stdout to support future optimization and hyperparameter tuning. - - If validation data are used, print the early stopping round/step, as well as the training and validation losses during training. + - If validation data are used, print the early stopping round/step, as well as the training and validation losses during training. \ No newline at end of file