From 6e2b99438bae0267355cb7740ef1d437e606511a Mon Sep 17 00:00:00 2001 From: 997146918 <997146918@qq.com> Date: Sat, 23 Aug 2025 17:10:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9=E8=AF=9D=E5=86=85?= =?UTF-8?q?=E5=AE=B9=20ai=E6=89=93=E5=88=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AITrain/conversation_data/conversations.db | Bin 102400 -> 36864 bytes .../conversation_data/demo_conversations.db | Bin 118784 -> 0 bytes AITrain/dual_ai_dialogue_system.py | 121 ++- AITrain/main_controller.py | 980 +++++++++++++++++- 4 files changed, 1083 insertions(+), 18 deletions(-) delete mode 100644 AITrain/conversation_data/demo_conversations.db diff --git a/AITrain/conversation_data/conversations.db b/AITrain/conversation_data/conversations.db index 17da33bf5a56f56f67c6d2ca1a55b386f86653ed..5cd331772eecc6c470bd0435faeeaef9ad132093 100644 GIT binary patch literal 36864 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 diff --git a/AITrain/conversation_data/demo_conversations.db b/AITrain/conversation_data/demo_conversations.db deleted file mode 100644 index b64a0fd3de3749b02c688af7631865d2195af117..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118784 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 MkUW;Zp=nG159Z6!p8x;= diff --git a/AITrain/dual_ai_dialogue_system.py b/AITrain/dual_ai_dialogue_system.py index ab4de91..c6dbf2c 100644 --- a/AITrain/dual_ai_dialogue_system.py +++ b/AITrain/dual_ai_dialogue_system.py @@ -286,6 +286,9 @@ class ConversationManager: timestamp TEXT, context_used TEXT, relevance_score REAL, + dialogue_score REAL DEFAULT 0.0, + score_details TEXT, + score_feedback TEXT, FOREIGN KEY (session_id) REFERENCES conversations (session_id) ) ''') @@ -305,7 +308,9 @@ class ConversationManager: print(f"✓ 创建对话会话: {session_id}") return session_id - def add_dialogue_turn(self, session_id: str, speaker: str, content: str, context_used: List[str] = None, relevance_score: float = 0.0): + def add_dialogue_turn(self, session_id: str, speaker: str, content: str, context_used: List[str] = None, + relevance_score: float = 0.0, dialogue_score: float = 0.0, + score_details: str = None, score_feedback: str = None): """添加对话轮次""" if context_used is None: context_used = [] @@ -318,10 +323,11 @@ class ConversationManager: # 插入对话轮次 conn.execute( """INSERT INTO dialogue_turns - (session_id, turn_number, speaker, content, timestamp, context_used, relevance_score) - VALUES (?, ?, ?, ?, ?, ?, ?)""", + (session_id, turn_number, speaker, content, timestamp, context_used, relevance_score, + dialogue_score, score_details, score_feedback) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", (session_id, turn_number, speaker, content, datetime.now().isoformat(), - json.dumps(context_used), relevance_score) + json.dumps(context_used), relevance_score, dialogue_score, score_details, score_feedback) ) # 更新会话最后更新时间 @@ -335,7 +341,7 @@ class ConversationManager: """获取对话历史""" with sqlite3.connect(self.db_path) as conn: cursor = conn.execute( - """SELECT speaker, content, timestamp, context_used, relevance_score + """SELECT speaker, content, timestamp, context_used, relevance_score, dialogue_score, score_feedback FROM dialogue_turns WHERE session_id = ? ORDER BY turn_number DESC LIMIT ?""", @@ -344,14 +350,20 @@ class ConversationManager: turns = [] for row in cursor.fetchall(): - speaker, content, timestamp, context_used, relevance_score = row - turns.append(DialogueTurn( + speaker, content, timestamp, context_used, relevance_score, dialogue_score, score_feedback = row + turn = DialogueTurn( speaker=speaker, content=content, timestamp=timestamp, context_used=json.loads(context_used or "[]"), relevance_score=relevance_score - )) + ) + # 添加评分信息到turn对象 + if dialogue_score: + turn.dialogue_score = dialogue_score + if score_feedback: + turn.score_feedback = score_feedback + turns.append(turn) return list(reversed(turns)) # 按时间正序返回 @@ -378,10 +390,70 @@ class ConversationManager: class DualAIDialogueEngine: """双AI对话引擎""" - def __init__(self, knowledge_base: RAGKnowledgeBase, conversation_manager: ConversationManager, llm_generator): + def __init__(self, knowledge_base: RAGKnowledgeBase, conversation_manager: ConversationManager, llm_generator, + enable_scoring: bool = True, base_model_path: str = None): self.kb = knowledge_base self.conv_mgr = conversation_manager self.llm_generator = llm_generator + self.enable_scoring = enable_scoring + self.scorer = None + + # 初始化评分器 + if enable_scoring and base_model_path: + try: + from dialogue_scorer import DialogueAIScorer + print("正在初始化对话评分系统...") + self.scorer = DialogueAIScorer( + base_model_path=base_model_path, + tokenizer=getattr(llm_generator, 'tokenizer', None), + model=getattr(llm_generator, 'model', None) + ) + print("✓ 对话评分系统初始化成功") + except Exception as e: + print(f"⚠ 对话评分系统初始化失败: {e}") + self.enable_scoring = False + + def score_dialogue_turn(self, dialogue_content: str, speaker: str, dialogue_history: List[DialogueTurn]) -> Tuple[float, str, str]: + """对单条对话进行评分 + + Args: + dialogue_content: 对话内容 + speaker: 说话者 + dialogue_history: 对话历史 + + Returns: + tuple: (总分, 详细分数JSON, 反馈意见) + """ + if not self.enable_scoring or not self.scorer: + return 0.0, "{}", "评分系统未启用" + + try: + # 获取角色数据 + character_data = self.kb.character_data.get(speaker, {}) + + # 转换对话历史格式 + history_for_scoring = [] + for turn in dialogue_history[-5:]: # 最近5轮对话 + history_for_scoring.append({ + 'speaker': turn.speaker, + 'content': turn.content + }) + + # 进行AI评分 + score_result = self.scorer.score_dialogue( + dialogue_content=dialogue_content, + speaker=speaker, + character_data=character_data, + dialogue_history=history_for_scoring, + context_info=[] + ) + + # 返回评分结果 + return score_result.overall_score, json.dumps(score_result.scores), score_result.feedback + + except Exception as e: + print(f"⚠ 对话评分失败: {e}") + return 0.0, "{}", f"评分失败: {str(e)}" def generate_character_prompt(self, character_name: str, context_info: List[Dict], dialogue_history: List[DialogueTurn], history_context_count: int = 3, context_info_count: int = 2) -> str: @@ -494,9 +566,17 @@ class DualAIDialogueEngine: context_used = [f"{info['section']}.{info['subsection']}" for info in context_info[:context_info_count]] avg_relevance = sum(info['relevance_score'] for info in context_info[:context_info_count]) / len(context_info[:context_info_count]) if context_info else 0.0 - # 保存对话轮次 + # 对对话进行评分 + if self.enable_scoring: + dialogue_score, score_details, score_feedback = self.score_dialogue_turn(response, current_speaker, dialogue_history) + print(f" [评分: {dialogue_score:.2f}] {score_feedback}") + else: + dialogue_score, score_details, score_feedback = 0.0, "{}", "" + + # 保存对话轮次(包含评分信息) self.conv_mgr.add_dialogue_turn( - session_id, current_speaker, response, context_used, avg_relevance + session_id, current_speaker, response, context_used, avg_relevance, + dialogue_score, score_details, score_feedback ) return response, context_used @@ -605,14 +685,29 @@ class DualAIDialogueEngine: max_new_tokens=150 ) - # 保存对话到数据库 + # 保存对话到数据库并进行评分 for result in conversation_results: + # 获取当前对话历史进行评分 + current_dialogue_history = self.conv_mgr.get_conversation_history(session_id) + + # 对对话进行评分 + if self.enable_scoring: + dialogue_score, score_details, score_feedback = self.score_dialogue_turn( + result['dialogue'], result['speaker'], current_dialogue_history + ) + print(f" [评分: {dialogue_score:.2f}] {score_feedback[:100]}...") + else: + dialogue_score, score_details, score_feedback = 0.0, "{}", "" + self.conv_mgr.add_dialogue_turn( session_id, result['speaker'], result['dialogue'], [result.get('context_used', '')], - 0.8 # 默认相关性分数 + 0.8, # 默认相关性分数 + dialogue_score, + score_details, + score_feedback ) diff --git a/AITrain/main_controller.py b/AITrain/main_controller.py index e6c0f51..904febd 100644 --- a/AITrain/main_controller.py +++ b/AITrain/main_controller.py @@ -140,7 +140,7 @@ def run_dialogue_system(): conv_mgr = ConversationManager("./conversation_data/conversations.db") # 检查模型路径 - base_model_path = '/mnt/e/AI/Project02/AITrain/Qwen/Qwen3-4B' + base_model_path = '/mnt/g/Project02/AITrain/Qwen/Qwen3-4B' lora_model_path = './output/NPC_Dialogue_LoRA/final_model' if not os.path.exists(base_model_path): @@ -186,8 +186,14 @@ def run_dialogue_system(): character2_config ) - # 创建对话引擎 - dialogue_engine = DualAIDialogueEngine(kb, conv_mgr, dual_generator) + # 创建对话引擎(启用评分功能) + dialogue_engine = DualAIDialogueEngine( + kb, + conv_mgr, + dual_generator, + enable_scoring=True, + base_model_path=base_model_path + ) # 创建对话会话 characters = [char1_name, char2_name] @@ -317,6 +323,954 @@ def create_demo_scenario(): import traceback traceback.print_exc() +def analyze_model_performance(): + """分析模型性能""" + print("\n" + "="*60) + print("模型性能分析") + print("="*60) + + try: + from dual_ai_dialogue_system import ConversationManager + import sqlite3 + import json + from datetime import datetime, timedelta + + conv_mgr = ConversationManager("./conversation_data/conversations.db") + + with sqlite3.connect(conv_mgr.db_path) as conn: + print("\n1. 总体性能趋势分析:") + + # 按时间段分析性能趋势 + cursor = conn.execute(""" + SELECT + DATE(timestamp) as date, + COUNT(*) as dialogue_count, + AVG(dialogue_score) as avg_score, + AVG(CASE WHEN dialogue_score >= 8.0 THEN 1.0 ELSE 0.0 END) as high_quality_rate + FROM dialogue_turns + WHERE dialogue_score > 0 + AND timestamp >= datetime('now', '-7 days') + GROUP BY DATE(timestamp) + ORDER BY date DESC + """) + + trend_data = cursor.fetchall() + if trend_data: + print(f" 最近7天性能趋势:") + for date, count, avg_score, hq_rate in trend_data: + print(f" {date}: 平均{avg_score:.2f}分 ({count}轮对话, {hq_rate*100:.1f}%高质量)") + else: + print(" 暂无足够数据进行趋势分析") + + print("\n2. 维度问题分析:") + + # 分析各维度的问题 + cursor = conn.execute(""" + SELECT score_details + FROM dialogue_turns + WHERE dialogue_score > 0 AND score_details != '{}' + ORDER BY timestamp DESC + LIMIT 100 + """) + + dimension_scores = { + 'coherence': [], + 'character_consistency': [], + 'naturalness': [], + 'information_density': [], + 'creativity': [] + } + + for (score_details,) in cursor.fetchall(): + try: + scores = json.loads(score_details) + for dim, score in scores.items(): + if dim in dimension_scores: + dimension_scores[dim].append(float(score)) + except: + continue + + dimension_names = { + 'coherence': '连贯性', + 'character_consistency': '角色一致性', + 'naturalness': '自然度', + 'information_density': '信息密度', + 'creativity': '创意性' + } + + weak_dimensions = [] + for dim, scores in dimension_scores.items(): + if scores: + avg_score = sum(scores) / len(scores) + print(f" {dimension_names[dim]}: 平均{avg_score:.2f}分 ({len(scores)}个样本)") + if avg_score < 7.0: + weak_dimensions.append(dim) + + if weak_dimensions: + print(f"\n ⚠ 发现薄弱维度: {[dimension_names[d] for d in weak_dimensions]}") + print(" 建议进行针对性优化训练") + + print("\n3. 角色表现分析:") + + # 分析不同角色的表现 + cursor = conn.execute(""" + SELECT + speaker, + COUNT(*) as dialogue_count, + AVG(dialogue_score) as avg_score, + MIN(dialogue_score) as min_score, + MAX(dialogue_score) as max_score, + AVG(CASE WHEN dialogue_score >= 8.0 THEN 1.0 ELSE 0.0 END) as high_quality_rate + FROM dialogue_turns + WHERE dialogue_score > 0 + GROUP BY speaker + ORDER BY avg_score DESC + """) + + character_performance = cursor.fetchall() + if character_performance: + print(" 角色表现排名:") + for i, (speaker, count, avg, min_s, max_s, hq_rate) in enumerate(character_performance, 1): + status = "✓" if avg >= 7.5 else "⚠" if avg >= 6.5 else "✗" + print(f" {i}. {speaker} {status}") + print(f" 平均{avg:.2f}分 (范围{min_s:.1f}-{max_s:.1f}, {hq_rate*100:.1f}%高质量, {count}轮)") + + print("\n4. 问题模式识别:") + + # 识别低分对话的常见问题 + cursor = conn.execute(""" + SELECT content, dialogue_score, score_feedback + FROM dialogue_turns + WHERE dialogue_score > 0 AND dialogue_score < 6.0 + ORDER BY dialogue_score ASC + LIMIT 5 + """) + + low_score_examples = cursor.fetchall() + if low_score_examples: + print(" 低分对话示例:") + for i, (content, score, feedback) in enumerate(low_score_examples, 1): + print(f" {i}. 分数{score:.1f}: {content[:50]}...") + if feedback: + print(f" 问题: {feedback[:80]}...") + else: + print(" 暂无低分对话样本") + + print("\n5. 优化建议:") + + # 生成优化建议 + suggestions = [] + + if weak_dimensions: + if 'character_consistency' in weak_dimensions: + suggestions.append("• 加强角色设定训练,增加角色特征描述的权重") + if 'creativity' in weak_dimensions: + suggestions.append("• 增加创意性训练数据,提高对话的趣味性") + if 'coherence' in weak_dimensions: + suggestions.append("• 优化上下文理解,加强对话逻辑连贯性") + if 'naturalness' in weak_dimensions: + suggestions.append("• 增加自然语言训练,改善表达流畅度") + if 'information_density' in weak_dimensions: + suggestions.append("• 优化信息组织,避免冗余表达") + + # 检查是否需要数据收集 + cursor = conn.execute("SELECT COUNT(*) FROM dialogue_turns WHERE dialogue_score > 0") + total_scored = cursor.fetchone()[0] + + if total_scored < 50: + suggestions.append("• 需要收集更多评分数据以进行准确分析") + + if total_scored >= 100: + suggestions.append("• 数据量充足,建议开始模型迭代优化") + + if suggestions: + for suggestion in suggestions: + print(f" {suggestion}") + else: + print(" 当前性能表现良好,继续保持!") + + except Exception as e: + print(f"✗ 性能分析失败: {e}") + import traceback + traceback.print_exc() + +def generate_training_dataset(): + """生成训练数据集""" + print("\n" + "="*60) + print("生成训练数据集") + print("="*60) + + try: + from dual_ai_dialogue_system import ConversationManager + import sqlite3 + import json + import os + from datetime import datetime + + conv_mgr = ConversationManager("./conversation_data/conversations.db") + + # 创建输出目录 + output_dir = "./training_data" + os.makedirs(output_dir, exist_ok=True) + + print("请选择训练数据生成类型:") + print("1. 高质量对话数据集 (分数≥8.0)") + print("2. 问题对话改进数据集 (分数<6.0)") + print("3. 角色一致性训练集") + print("4. 创意性增强训练集") + print("5. 完整对话质量数据集") + + choice = input("请输入选择 (1-5): ").strip() + + with sqlite3.connect(conv_mgr.db_path) as conn: + training_data = [] + + if choice == '1': + # 高质量对话数据集 + print("\n生成高质量对话数据集...") + cursor = conn.execute(""" + SELECT speaker, content, score_details, score_feedback + FROM dialogue_turns + WHERE dialogue_score >= 8.0 + ORDER BY dialogue_score DESC + LIMIT 200 + """) + + for speaker, content, score_details, feedback in cursor.fetchall(): + training_data.append({ + "type": "high_quality", + "speaker": speaker, + "content": content, + "scores": json.loads(score_details) if score_details else {}, + "feedback": feedback, + "label": "positive" + }) + + output_file = f"{output_dir}/high_quality_dialogues_{datetime.now().strftime('%Y%m%d_%H%M')}.json" + + elif choice == '2': + # 问题对话改进数据集 + print("\n生成问题对话改进数据集...") + cursor = conn.execute(""" + SELECT speaker, content, score_details, score_feedback + FROM dialogue_turns + WHERE dialogue_score < 6.0 AND dialogue_score > 0 + ORDER BY dialogue_score ASC + LIMIT 100 + """) + + for speaker, content, score_details, feedback in cursor.fetchall(): + # 为每个低分对话生成改进指导 + improvement_prompt = generate_improvement_prompt(content, feedback) + + training_data.append({ + "type": "improvement", + "speaker": speaker, + "original_content": content, + "scores": json.loads(score_details) if score_details else {}, + "problems": feedback, + "improvement_prompt": improvement_prompt, + "label": "needs_improvement" + }) + + output_file = f"{output_dir}/improvement_dialogues_{datetime.now().strftime('%Y%m%d_%H%M')}.json" + + elif choice == '3': + # 角色一致性训练集 + print("\n生成角色一致性训练集...") + cursor = conn.execute(""" + SELECT speaker, content, score_details + FROM dialogue_turns + WHERE dialogue_score > 0 + ORDER BY json_extract(score_details, '$.character_consistency') DESC + LIMIT 150 + """) + + for speaker, content, score_details in cursor.fetchall(): + scores = json.loads(score_details) if score_details else {} + char_consistency = scores.get('character_consistency', 0) + + training_data.append({ + "type": "character_consistency", + "speaker": speaker, + "content": content, + "character_consistency_score": char_consistency, + "label": "high_consistency" if char_consistency >= 8.0 else "medium_consistency" + }) + + output_file = f"{output_dir}/character_consistency_{datetime.now().strftime('%Y%m%d_%H%M')}.json" + + elif choice == '4': + # 创意性增强训练集 + print("\n生成创意性增强训练集...") + cursor = conn.execute(""" + SELECT speaker, content, score_details + FROM dialogue_turns + WHERE dialogue_score > 0 + ORDER BY json_extract(score_details, '$.creativity') DESC + LIMIT 150 + """) + + for speaker, content, score_details in cursor.fetchall(): + scores = json.loads(score_details) if score_details else {} + creativity = scores.get('creativity', 0) + + training_data.append({ + "type": "creativity", + "speaker": speaker, + "content": content, + "creativity_score": creativity, + "label": "high_creativity" if creativity >= 8.0 else "medium_creativity" + }) + + output_file = f"{output_dir}/creativity_enhancement_{datetime.now().strftime('%Y%m%d_%H%M')}.json" + + elif choice == '5': + # 完整对话质量数据集 + print("\n生成完整对话质量数据集...") + cursor = conn.execute(""" + SELECT speaker, content, dialogue_score, score_details, score_feedback + FROM dialogue_turns + WHERE dialogue_score > 0 + ORDER BY timestamp DESC + LIMIT 300 + """) + + for speaker, content, score, score_details, feedback in cursor.fetchall(): + training_data.append({ + "type": "complete_dataset", + "speaker": speaker, + "content": content, + "overall_score": score, + "dimension_scores": json.loads(score_details) if score_details else {}, + "feedback": feedback, + "quality_label": get_quality_label(score) + }) + + output_file = f"{output_dir}/complete_quality_dataset_{datetime.now().strftime('%Y%m%d_%H%M')}.json" + + else: + print("❌ 无效选择") + return + + if training_data: + # 保存训练数据 + with open(output_file, 'w', encoding='utf-8') as f: + json.dump({ + "metadata": { + "created_at": datetime.now().isoformat(), + "total_samples": len(training_data), + "data_type": choice, + "source": "dialogue_scoring_system" + }, + "data": training_data + }, f, ensure_ascii=False, indent=2) + + print(f"\n✓ 训练数据集生成成功!") + print(f" - 文件路径: {output_file}") + print(f" - 数据条数: {len(training_data)}") + print(f" - 数据类型: {get_dataset_description(choice)}") + + # 生成数据集统计信息 + generate_dataset_statistics(training_data, choice) + + else: + print("❌ 未找到符合条件的数据") + + except Exception as e: + print(f"✗ 训练数据集生成失败: {e}") + import traceback + traceback.print_exc() + +def generate_improvement_prompt(content, feedback): + """生成改进提示""" + return f"""原对话: {content} + +问题分析: {feedback} + +改进要求: +1. 保持角色特征和设定 +2. 增强对话的逻辑性和连贯性 +3. 提升表达的自然度 +4. 增加信息密度,避免冗余 +5. 适当增加创意元素 + +请生成一个改进版本的对话。""" + +def get_quality_label(score): + """根据分数获取质量标签""" + if score >= 9.0: + return "excellent" + elif score >= 8.0: + return "good" + elif score >= 7.0: + return "average" + elif score >= 6.0: + return "below_average" + else: + return "poor" + +def get_dataset_description(choice): + """获取数据集描述""" + descriptions = { + '1': "高质量对话数据集", + '2': "问题对话改进数据集", + '3': "角色一致性训练集", + '4': "创意性增强训练集", + '5': "完整对话质量数据集" + } + return descriptions.get(choice, "未知类型") + +def generate_dataset_statistics(training_data, data_type): + """生成数据集统计信息""" + print(f"\n数据集统计信息:") + + if data_type == '1': # 高质量数据集 + speakers = {} + for item in training_data: + speaker = item['speaker'] + speakers[speaker] = speakers.get(speaker, 0) + 1 + + print(f" - 角色分布:") + for speaker, count in speakers.items(): + print(f" • {speaker}: {count}条对话") + + elif data_type == '5': # 完整数据集 + quality_dist = {} + for item in training_data: + label = item['quality_label'] + quality_dist[label] = quality_dist.get(label, 0) + 1 + + print(f" - 质量分布:") + for label, count in quality_dist.items(): + print(f" • {label}: {count}条对话") + +def run_model_optimization(): + """运行模型迭代优化""" + print("\n" + "="*60) + print("模型迭代优化") + print("="*60) + + try: + from dual_ai_dialogue_system import ConversationManager + import sqlite3 + import json + import os + from datetime import datetime + + conv_mgr = ConversationManager("./conversation_data/conversations.db") + + print("模型优化选项:") + print("1. 分析优化需求") + print("2. 生成LoRA训练脚本") + print("3. 创建提示优化配置") + print("4. 执行增量训练") + print("5. 性能对比验证") + + choice = input("请输入选择 (1-5): ").strip() + + if choice == '1': + # 分析优化需求 + print("\n=== 优化需求分析 ===") + + with sqlite3.connect(conv_mgr.db_path) as conn: + # 获取性能数据 + cursor = conn.execute(""" + SELECT + COUNT(*) as total, + AVG(dialogue_score) as avg_score, + AVG(CASE WHEN dialogue_score >= 8.0 THEN 1.0 ELSE 0.0 END) as high_quality_rate + FROM dialogue_turns WHERE dialogue_score > 0 + """) + + total, avg_score, hq_rate = cursor.fetchone() + + print(f"当前性能指标:") + print(f" - 总评分样本: {total}") + print(f" - 平均分数: {avg_score:.2f}") + print(f" - 高质量率: {hq_rate*100:.1f}%") + + # 分析优化潜力 + optimization_needs = [] + + if avg_score < 7.0: + optimization_needs.append("整体质量需要提升") + + if hq_rate < 0.6: + optimization_needs.append("高质量对话比例偏低") + + # 分析各维度表现 + cursor = conn.execute(""" + SELECT score_details FROM dialogue_turns + WHERE dialogue_score > 0 AND score_details != '{}' + ORDER BY timestamp DESC LIMIT 100 + """) + + dim_scores = {'coherence': [], 'character_consistency': [], + 'naturalness': [], 'information_density': [], 'creativity': []} + + for (score_details,) in cursor.fetchall(): + try: + scores = json.loads(score_details) + for dim, score in scores.items(): + if dim in dim_scores: + dim_scores[dim].append(float(score)) + except: + continue + + weak_dimensions = [] + print(f"\n维度分析:") + for dim, scores in dim_scores.items(): + if scores: + avg = sum(scores) / len(scores) + print(f" - {dim}: {avg:.2f}分") + if avg < 7.0: + weak_dimensions.append(dim) + + if weak_dimensions: + optimization_needs.append(f"薄弱维度: {weak_dimensions}") + + print(f"\n优化建议:") + if optimization_needs: + for i, need in enumerate(optimization_needs, 1): + print(f" {i}. {need}") + else: + print(" 当前模型表现良好,可考虑细微调优") + + elif choice == '2': + # 生成LoRA训练脚本 + print("\n=== 生成LoRA训练脚本 ===") + + script_content = generate_lora_training_script() + script_path = "./scripts/iterative_lora_training.py" + + os.makedirs("./scripts", exist_ok=True) + with open(script_path, 'w', encoding='utf-8') as f: + f.write(script_content) + + print(f"✓ LoRA训练脚本已生成: {script_path}") + print("使用方法:") + print(" 1. 先运行训练数据生成 (选项8)") + print(" 2. 修改脚本中的路径配置") + print(f" 3. 运行: python {script_path}") + + elif choice == '3': + # 创建提示优化配置 + print("\n=== 创建提示优化配置 ===") + + config = generate_prompt_optimization_config() + config_path = "./config/prompt_optimization.json" + + os.makedirs("./config", exist_ok=True) + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(config, f, ensure_ascii=False, indent=2) + + print(f"✓ 提示优化配置已生成: {config_path}") + print("配置包含:") + print(" - 动态提示调整规则") + print(" - 质量阈值设置") + print(" - 生成参数优化") + + elif choice == '4': + # 执行增量训练 + print("\n=== 执行增量训练 ===") + + # 检查训练数据 + training_dir = "./training_data" + if not os.path.exists(training_dir): + print("❌ 训练数据目录不存在,请先生成训练数据 (选项8)") + return + + training_files = [f for f in os.listdir(training_dir) if f.endswith('.json')] + if not training_files: + print("❌ 未找到训练数据文件,请先生成训练数据 (选项8)") + return + + print(f"找到训练数据文件:") + for i, file in enumerate(training_files, 1): + print(f" {i}. {file}") + + file_idx = input(f"选择训练数据文件 (1-{len(training_files)}): ").strip() + try: + selected_file = training_files[int(file_idx) - 1] + training_file_path = os.path.join(training_dir, selected_file) + + print(f"将使用训练文件: {selected_file}") + print("⚠ 注意:实际训练需要配置正确的模型路径和计算资源") + + # 生成训练命令 + training_command = generate_training_command(training_file_path) + print(f"建议训练命令:") + print(f" {training_command}") + + # 可选:执行训练(需要用户确认) + confirm = input("是否现在执行训练?(y/N): ").strip().lower() + if confirm == 'y': + print("开始增量训练...") + # 这里可以添加实际的训练执行逻辑 + print("⚠ 训练功能需要根据实际环境配置") + + except (ValueError, IndexError): + print("❌ 无效的文件选择") + + elif choice == '5': + # 性能对比验证 + print("\n=== 性能对比验证 ===") + + print("验证选项:") + print("1. 历史性能趋势对比") + print("2. A/B测试配置生成") + print("3. 模型版本性能对比") + + verify_choice = input("请输入选择 (1-3): ").strip() + + if verify_choice == '1': + # 历史性能趋势 + with sqlite3.connect(conv_mgr.db_path) as conn: + cursor = conn.execute(""" + SELECT + DATE(timestamp) as date, + AVG(dialogue_score) as avg_score, + COUNT(*) as count + FROM dialogue_turns + WHERE dialogue_score > 0 + AND timestamp >= datetime('now', '-30 days') + GROUP BY DATE(timestamp) + ORDER BY date ASC + """) + + trend_data = cursor.fetchall() + if trend_data: + print(f"30天性能趋势:") + for date, avg_score, count in trend_data: + trend = "📈" if avg_score > 7.5 else "📉" if avg_score < 6.5 else "📊" + print(f" {date}: {avg_score:.2f}分 {trend} ({count}条对话)") + else: + print("暂无足够的历史数据") + + elif verify_choice == '2': + # A/B测试配置 + ab_config = generate_ab_test_config() + ab_config_path = "./config/ab_test_config.json" + + os.makedirs("./config", exist_ok=True) + with open(ab_config_path, 'w', encoding='utf-8') as f: + json.dump(ab_config, f, ensure_ascii=False, indent=2) + + print(f"✓ A/B测试配置已生成: {ab_config_path}") + print("配置包含:") + print(" - 对照组和实验组设置") + print(" - 评估指标定义") + print(" - 测试持续时间配置") + + elif verify_choice == '3': + print("模型版本对比功能开发中...") + print("建议手动记录不同版本的性能指标进行对比") + + else: + print("❌ 无效选择") + + except Exception as e: + print(f"✗ 模型优化失败: {e}") + import traceback + traceback.print_exc() + +def generate_lora_training_script(): + """生成LoRA训练脚本""" + return '''#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +基于评分数据的LoRA增量训练脚本 +自动生成 - 请根据实际环境调整配置 +""" + +import json +import torch +from transformers import AutoModelForCausalLM, AutoTokenizer +from peft import LoraConfig, get_peft_model, TaskType +import os + +class IterativeLoRATrainer: + def __init__(self, base_model_path, training_data_path, output_path): + self.base_model_path = base_model_path + self.training_data_path = training_data_path + self.output_path = output_path + + # LoRA配置 + self.lora_config = LoraConfig( + task_type=TaskType.CAUSAL_LM, + inference_mode=False, + r=16, # LoRA rank + lora_alpha=32, + lora_dropout=0.1, + target_modules=["q_proj", "v_proj", "k_proj", "o_proj"] + ) + + def load_training_data(self): + """加载训练数据""" + with open(self.training_data_path, 'r', encoding='utf-8') as f: + data = json.load(f) + return data['data'] + + def prepare_training_samples(self, data): + """准备训练样本""" + samples = [] + + for item in data: + if item.get('label') == 'positive' or item.get('overall_score', 0) >= 8.0: + # 高质量样本 + sample = { + 'input': f"角色: {item['speaker']}\\n请生成高质量对话:", + 'output': item['content'], + 'quality_score': item.get('overall_score', 8.0) + } + samples.append(sample) + + return samples + + def train(self): + """执行训练""" + print("开始LoRA增量训练...") + + # 加载模型和分词器 + tokenizer = AutoTokenizer.from_pretrained(self.base_model_path) + model = AutoModelForCausalLM.from_pretrained( + self.base_model_path, + torch_dtype=torch.bfloat16, + device_map="auto" + ) + + # 应用LoRA + model = get_peft_model(model, self.lora_config) + + # 加载训练数据 + training_data = self.load_training_data() + training_samples = self.prepare_training_samples(training_data) + + print(f"训练样本数量: {len(training_samples)}") + + # 这里添加实际的训练循环 + # 建议使用transformers的Trainer或自定义训练循环 + + # 保存模型 + model.save_pretrained(self.output_path) + tokenizer.save_pretrained(self.output_path) + + print(f"训练完成,模型保存到: {self.output_path}") + +if __name__ == '__main__': + # 配置参数 - 请根据实际情况修改 + BASE_MODEL_PATH = '/mnt/e/AI/Project02/AITrain/Qwen/Qwen3-4B' + TRAINING_DATA_PATH = './training_data/high_quality_dialogues_latest.json' + OUTPUT_PATH = './output/iterative_lora_v2' + + trainer = IterativeLoRATrainer(BASE_MODEL_PATH, TRAINING_DATA_PATH, OUTPUT_PATH) + trainer.train() +''' + +def generate_prompt_optimization_config(): + """生成提示优化配置""" + return { + "optimization_rules": { + "quality_thresholds": { + "excellent": 9.0, + "good": 8.0, + "acceptable": 7.0, + "needs_improvement": 6.0 + }, + "adaptive_adjustments": { + "low_coherence": { + "add_context_emphasis": True, + "increase_history_weight": 1.2, + "add_logical_constraints": True + }, + "low_character_consistency": { + "enhance_character_description": True, + "add_personality_reminders": True, + "increase_character_weight": 1.5 + }, + "low_creativity": { + "add_creativity_prompts": True, + "increase_temperature": 0.1, + "diversify_examples": True + } + } + }, + "dynamic_prompts": { + "quality_boost_templates": [ + "请生成一个富有创意且符合角色特征的高质量对话", + "确保对话逻辑连贯,信息丰富,表达自然", + "体现角色的独特性格和说话风格" + ], + "problem_specific_guidance": { + "repetitive": "避免重复之前的内容,提供新的信息和观点", + "inconsistent": "严格遵循角色设定,保持人格一致性", + "dull": "增加对话的趣味性和深度,使用生动的表达" + } + }, + "generation_parameters": { + "adaptive_temperature": { + "high_creativity_needed": 0.9, + "normal": 0.8, + "high_consistency_needed": 0.7 + }, + "adaptive_top_p": { + "creative_mode": 0.9, + "balanced_mode": 0.8, + "conservative_mode": 0.7 + } + } + } + +def generate_ab_test_config(): + """生成A/B测试配置""" + return { + "test_name": "model_optimization_validation", + "created_at": datetime.now().isoformat(), + "groups": { + "control": { + "name": "原始模型", + "description": "未优化的基础模型", + "model_path": "/path/to/base/model", + "sample_ratio": 0.5 + }, + "experimental": { + "name": "优化模型", + "description": "经过迭代优化的模型", + "model_path": "/path/to/optimized/model", + "sample_ratio": 0.5 + } + }, + "evaluation_metrics": { + "primary": [ + "overall_dialogue_score", + "character_consistency_score", + "creativity_score" + ], + "secondary": [ + "user_satisfaction", + "response_time", + "coherence_score" + ] + }, + "test_duration": { + "target_samples": 200, + "max_duration_days": 7, + "min_samples_per_group": 50 + }, + "statistical_settings": { + "confidence_level": 0.95, + "minimum_effect_size": 0.3, + "power": 0.8 + } + } + +def generate_training_command(training_file_path): + """生成训练命令""" + return f"python ./scripts/iterative_lora_training.py --data {training_file_path} --output ./output/optimized_model_v{datetime.now().strftime('%Y%m%d')}" + +def show_scoring_statistics(): + """显示对话评分统计""" + print("\n" + "="*60) + print("对话评分统计") + print("="*60) + + try: + from dual_ai_dialogue_system import ConversationManager + import json + + conv_mgr = ConversationManager("./conversation_data/conversations.db") + + # 查询评分数据 + import sqlite3 + with sqlite3.connect(conv_mgr.db_path) as conn: + # 总体统计 + cursor = conn.execute(""" + SELECT + COUNT(*) as total_turns, + AVG(dialogue_score) as avg_score, + MAX(dialogue_score) as max_score, + MIN(dialogue_score) as min_score + FROM dialogue_turns + WHERE dialogue_score > 0 + """) + + stats = cursor.fetchone() + if stats and stats[0] > 0: + total_turns, avg_score, max_score, min_score = stats + print(f"总体统计:") + print(f" - 已评分对话轮数: {total_turns}") + print(f" - 平均分数: {avg_score:.2f}") + print(f" - 最高分数: {max_score:.2f}") + print(f" - 最低分数: {min_score:.2f}") + else: + print("暂无评分数据") + return + + # 按角色统计 + print(f"\n按角色统计:") + cursor = conn.execute(""" + SELECT + speaker, + COUNT(*) as turns, + AVG(dialogue_score) as avg_score, + MAX(dialogue_score) as max_score + FROM dialogue_turns + WHERE dialogue_score > 0 + GROUP BY speaker + ORDER BY avg_score DESC + """) + + for row in cursor.fetchall(): + speaker, turns, avg_score, max_score = row + print(f" - {speaker}: 平均{avg_score:.2f}分 (最高{max_score:.2f}分, {turns}轮对话)") + + # 最近高分对话 + print(f"\n最近高分对话 (分数≥8.0):") + cursor = conn.execute(""" + SELECT speaker, content, dialogue_score, score_feedback, timestamp + FROM dialogue_turns + WHERE dialogue_score >= 8.0 + ORDER BY timestamp DESC + LIMIT 5 + """) + + high_score_turns = cursor.fetchall() + if high_score_turns: + for speaker, content, score, feedback, timestamp in high_score_turns: + print(f" [{timestamp[:16]}] {speaker} ({score:.2f}分)") + print(f" 内容: {content[:80]}...") + if feedback: + print(f" 评价: {feedback[:60]}...") + print() + else: + print(" 暂无高分对话") + + # 分数分布统计 + print(f"\n分数分布:") + cursor = conn.execute(""" + SELECT + CASE + WHEN dialogue_score >= 9.0 THEN '优秀 (9-10分)' + WHEN dialogue_score >= 8.0 THEN '良好 (8-9分)' + WHEN dialogue_score >= 7.0 THEN '中等 (7-8分)' + WHEN dialogue_score >= 6.0 THEN '及格 (6-7分)' + ELSE '待改进 (<6分)' + END as score_range, + COUNT(*) as count + FROM dialogue_turns + WHERE dialogue_score > 0 + GROUP BY score_range + ORDER BY MIN(dialogue_score) DESC + """) + + for score_range, count in cursor.fetchall(): + percentage = (count / total_turns) * 100 + print(f" - {score_range}: {count}轮 ({percentage:.1f}%)") + + except Exception as e: + print(f"✗ 评分统计查询失败: {e}") + def show_system_status(): """显示系统状态""" print("\n" + "="*60) @@ -382,11 +1336,15 @@ def main(): print("3. 启动双AI对话系统 (支持双模型对话)") print("4. 创建演示对话场景") print("5. 系统状态检查") - print("6. 查看使用说明") + print("6. 查看对话评分统计") + print("7. 模型性能分析与优化") + print("8. 生成训练数据集") + print("9. 模型迭代优化") + print("10. 查看使用说明") print("0. 退出") print("="*50) - choice = input("请输入选择 (0-6): ").strip() + choice = input("请输入选择 (0-10): ").strip() if choice == '0': print("\n感谢使用双AI角色对话系统!") @@ -408,6 +1366,18 @@ def main(): show_system_status() elif choice == '6': + show_scoring_statistics() + + elif choice == '7': + analyze_model_performance() + + elif choice == '8': + generate_training_dataset() + + elif choice == '9': + run_model_optimization() + + elif choice == '10': show_usage_guide() else: