run_deconstruction.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Stage 6 独立运行脚本
  5. 从 Stage 5 结果开始,进行深度解构分析
  6. 支持指定 feature 和数量限制
  7. """
  8. import sys
  9. import os
  10. # 将项目根目录添加到Python路径
  11. project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  12. sys.path.insert(0, project_root)
  13. import json
  14. import logging
  15. import argparse
  16. import webbrowser
  17. from pathlib import Path
  18. from src.analyzers.post_deconstruction_analyzer import PostDeconstructionAnalyzer
  19. from src.analyzers.similarity_analyzer import SimilarityAnalyzer
  20. import src.visualizers.deconstruction_visualizer as deconstruction_visualizer
  21. # 配置日志
  22. logging.basicConfig(
  23. level=logging.INFO,
  24. format='%(asctime)s - %(levelname)s - %(message)s',
  25. datefmt='%Y-%m-%d %H:%M:%S',
  26. handlers=[
  27. logging.FileHandler('deconstruction_standalone.log', encoding='utf-8'),
  28. logging.StreamHandler()
  29. ]
  30. )
  31. logger = logging.getLogger(__name__)
  32. def main():
  33. """主函数"""
  34. parser = argparse.ArgumentParser(
  35. description='深度解构分析(独立运行,支持流水线执行)',
  36. formatter_class=argparse.RawDescriptionHelpFormatter,
  37. epilog='''
  38. 基础用法示例:
  39. # 只处理"墨镜"特征的前10个高分帖子
  40. python3 scripts/run_deconstruction.py --feature "墨镜" --max-notes 10
  41. # 处理"墨镜"和"耳环"两个特征,每个最多5个
  42. python3 scripts/run_deconstruction.py --feature "墨镜" "耳环" --max-notes 5
  43. # 按数据原始顺序处理前50个(不排序)
  44. python3 scripts/run_deconstruction.py --sort-by none --max-notes 50
  45. # 处理所有特征,按时间排序,前20个
  46. python3 scripts/run_deconstruction.py --sort-by time --max-notes 20
  47. # 只处理"墨镜",按互动量排序,跳过前3个
  48. python3 scripts/run_deconstruction.py --feature "墨镜" --sort-by engagement --skip 3
  49. # 降低分数阈值,处理更多帖子
  50. python3 scripts/run_deconstruction.py --feature "墨镜" --min-score 6.0 --max-notes 30
  51. 流水线执行示例(推荐):
  52. # 完整流水线: 深度解构 → 相似度分析 → 可视化 → 自动打开浏览器
  53. python3 scripts/run_deconstruction.py --feature "墨镜" --max-notes 10 --run-similarity --visualize
  54. # 深度解构 → 相似度分析(不生成可视化)
  55. python3 scripts/run_deconstruction.py --feature "墨镜" --max-notes 10 --run-similarity
  56. # 深度解构 → 可视化(跳过相似度分析)
  57. python3 scripts/run_deconstruction.py --feature "墨镜" --max-notes 10 --visualize
  58. # 完整流水线,不自动打开浏览器
  59. python3 scripts/run_deconstruction.py --feature "墨镜" --run-similarity --visualize --no-open
  60. # 自定义相似度分析权重
  61. python3 scripts/run_deconstruction.py --feature "墨镜" --run-similarity --visualize \\
  62. --similarity-weight-embedding 0.7 --similarity-weight-semantic 0.3
  63. # 过滤低相似度特征
  64. python3 scripts/run_deconstruction.py --feature "墨镜" --run-similarity --visualize \\
  65. --similarity-min-similarity 0.3
  66. 配置文件示例:
  67. # 使用配置文件(支持所有参数)
  68. python3 scripts/run_deconstruction.py --config pipeline_config.json
  69. # 配置文件示例内容(pipeline_config.json):
  70. {
  71. "feature": ["墨镜"],
  72. "max_notes": 10,
  73. "timeout": 600,
  74. "run_similarity": true,
  75. "visualize": true,
  76. "similarity_weight_embedding": 0.5,
  77. "similarity_weight_semantic": 0.5
  78. }
  79. '''
  80. )
  81. # 输入输出配置
  82. parser.add_argument(
  83. '--input',
  84. default='output_v2/evaluated_results.json',
  85. help='评估结果文件路径(默认: output_v2/evaluated_results.json)'
  86. )
  87. parser.add_argument(
  88. '--output',
  89. default='output_v2/deep_analysis_results.json',
  90. help='深度分析输出文件路径(默认: output_v2/deep_analysis_results.json)'
  91. )
  92. # Feature 过滤(新增)
  93. parser.add_argument(
  94. '--feature',
  95. nargs='+',
  96. default=None,
  97. help='指定要处理的原始特征名称(可指定多个),如: --feature "墨镜" "耳环"。不指定则处理所有特征'
  98. )
  99. # 过滤参数
  100. parser.add_argument(
  101. '--min-score',
  102. type=float,
  103. default=0.8,
  104. help='最低分数阈值,只处理 >= 此分数的帖子(默认: 0.8)'
  105. )
  106. parser.add_argument(
  107. '--skip',
  108. type=int,
  109. default=0,
  110. help='跳过前 N 个帖子(默认: 0)'
  111. )
  112. parser.add_argument(
  113. '--max-notes',
  114. type=int,
  115. default=None,
  116. help='最多处理多少个帖子(默认: None 不限制)'
  117. )
  118. parser.add_argument(
  119. '--sort-by',
  120. choices=['none', 'score', 'time', 'engagement'],
  121. default='score',
  122. help='排序方式: none(不排序,保持数据原始顺序), score(评分), time(时间), engagement(互动量)(默认: score)'
  123. )
  124. # API 配置
  125. parser.add_argument(
  126. '--api-url',
  127. default='http://192.168.245.150:7000/what/analysis/single',
  128. help='解构 API 地址(默认: http://192.168.245.150:7000/what/analysis/single)'
  129. )
  130. parser.add_argument(
  131. '--timeout',
  132. type=int,
  133. default=800,
  134. help='API 超时时间(秒)(默认: 600,即10分钟)'
  135. )
  136. parser.add_argument(
  137. '--max-retries',
  138. type=int,
  139. default=3,
  140. help='API 最大重试次数(默认: 3)'
  141. )
  142. # 并发配置
  143. parser.add_argument(
  144. '--max-workers',
  145. type=int,
  146. default=5,
  147. help='并发处理数(默认: 5)'
  148. )
  149. # 从配置文件加载
  150. parser.add_argument(
  151. '--config',
  152. default=None,
  153. help='从 JSON 配置文件加载参数'
  154. )
  155. # 流水线控制参数
  156. parser.add_argument(
  157. '--run-similarity',
  158. action='store_true',
  159. help='深度解构完成后自动运行相似度分析'
  160. )
  161. parser.add_argument(
  162. '--visualize',
  163. action='store_true',
  164. help='生成可视化结果'
  165. )
  166. parser.add_argument(
  167. '--open-browser',
  168. action='store_true',
  169. default=True,
  170. help='自动在浏览器中打开可视化结果(默认: True)'
  171. )
  172. parser.add_argument(
  173. '--no-open',
  174. action='store_true',
  175. help='禁用自动打开浏览器'
  176. )
  177. # Stage 7 输出配置
  178. parser.add_argument(
  179. '--similarity-output',
  180. default='output_v2/similarity_analysis_results.json',
  181. help='相似度分析输出文件路径(默认: output_v2/similarity_analysis_results.json)'
  182. )
  183. # Stage 7 相似度配置
  184. parser.add_argument(
  185. '--similarity-weight-embedding',
  186. type=float,
  187. default=0.5,
  188. help='相似度分析向量模型权重(默认: 0.5)'
  189. )
  190. parser.add_argument(
  191. '--similarity-weight-semantic',
  192. type=float,
  193. default=0.5,
  194. help='相似度分析 LLM 模型权重(默认: 0.5)'
  195. )
  196. parser.add_argument(
  197. '--similarity-min-similarity',
  198. type=float,
  199. default=0.0,
  200. help='相似度分析最小相似度阈值(默认: 0.0)'
  201. )
  202. parser.add_argument(
  203. '--similarity-max-workers',
  204. type=int,
  205. default=5,
  206. help='相似度分析最大并发数(默认: 5)'
  207. )
  208. # 可视化输出配置
  209. parser.add_argument(
  210. '--viz-output',
  211. default=None,
  212. help='可视化输出目录(默认: visualization/)'
  213. )
  214. args = parser.parse_args()
  215. # 如果提供了配置文件,加载配置
  216. if args.config:
  217. logger.info(f"从配置文件加载参数: {args.config}")
  218. with open(args.config, 'r', encoding='utf-8') as f:
  219. config = json.load(f)
  220. # 配置文件中的参数会覆盖命令行参数
  221. for key, value in config.items():
  222. setattr(args, key.replace('-', '_'), value)
  223. # 检查输入文件是否存在
  224. if not os.path.exists(args.input):
  225. logger.error(f"输入文件不存在: {args.input}")
  226. return
  227. # 加载 Stage 5 结果
  228. logger.info(f"加载评估结果: {args.input}")
  229. with open(args.input, 'r', encoding='utf-8') as f:
  230. evaluation_results = json.load(f)
  231. # 打印配置
  232. logger.info("=" * 60)
  233. logger.info("运行配置:")
  234. logger.info(f" 输入文件: {args.input}")
  235. logger.info(f" 输出文件: {args.output}")
  236. if args.feature:
  237. logger.info(f" 指定特征: {', '.join(args.feature)}")
  238. else:
  239. logger.info(f" 指定特征: 全部")
  240. logger.info(f" API 地址: {args.api_url}")
  241. logger.info(f" 最低分数阈值: {args.min_score}")
  242. logger.info(f" 跳过前 N 个: {args.skip}")
  243. logger.info(f" 最多处理数: {args.max_notes if args.max_notes else '不限制'}")
  244. logger.info(f" 排序方式: {args.sort_by}")
  245. logger.info(f" 并发数: {args.max_workers}")
  246. logger.info(f" API 超时: {args.timeout}秒")
  247. logger.info(f" 最大重试: {args.max_retries}次")
  248. logger.info("=" * 60)
  249. # 创建分析器
  250. analyzer = PostDeconstructionAnalyzer(
  251. api_url=args.api_url,
  252. max_workers=args.max_workers,
  253. max_notes=args.max_notes,
  254. min_score=args.min_score,
  255. skip_count=args.skip,
  256. sort_by=args.sort_by,
  257. timeout=args.timeout,
  258. max_retries=args.max_retries,
  259. output_dir=os.path.dirname(args.output) or 'output_v2',
  260. target_features=args.feature # 传递 feature 过滤参数
  261. )
  262. # 运行分析
  263. try:
  264. deep_results = analyzer.run(
  265. evaluation_results=evaluation_results,
  266. output_path=args.output
  267. )
  268. # 打印结果摘要
  269. logger.info("\n" + "=" * 60)
  270. logger.info("深度解构分析完成!")
  271. logger.info(f" 总匹配帖子数: {deep_results['metadata']['total_matched_notes']}")
  272. logger.info(f" 实际处理数: {deep_results['metadata']['processed_notes']}")
  273. logger.info(f" 成功: {deep_results['metadata']['success_count']}")
  274. logger.info(f" 失败: {deep_results['metadata']['failed_count']}")
  275. logger.info(f" 总耗时: {deep_results['metadata']['processing_time_seconds']}秒")
  276. logger.info(f" 结果已保存: {args.output}")
  277. logger.info("=" * 60)
  278. # Stage 7: 相似度分析
  279. similarity_results = None
  280. if args.run_similarity:
  281. logger.info("\n" + "=" * 60)
  282. logger.info("开始执行相似度分析...")
  283. logger.info("=" * 60)
  284. try:
  285. # 创建 Stage 7 分析器
  286. similarity_analyzer = SimilarityAnalyzer(
  287. weight_embedding=args.similarity_weight_embedding,
  288. weight_semantic=args.similarity_weight_semantic,
  289. max_workers=args.similarity_max_workers,
  290. min_similarity=args.similarity_min_similarity,
  291. target_features=args.feature
  292. )
  293. # 运行 Stage 7 分析
  294. similarity_results = similarity_analyzer.run(
  295. deconstruction_results=deep_results,
  296. output_path=args.similarity_output
  297. )
  298. # 打印 Stage 7 结果摘要
  299. logger.info("\n" + "=" * 60)
  300. logger.info("相似度分析完成!")
  301. metadata = similarity_results['metadata']
  302. overall_stats = metadata['overall_statistics']
  303. logger.info(f" 处理帖子数: {overall_stats['total_notes']}")
  304. logger.info(f" 提取特征总数: {overall_stats['total_features_extracted']}")
  305. logger.info(f" 平均特征数/帖子: {overall_stats['avg_features_per_note']:.2f}")
  306. logger.info(f" 平均最高相似度: {overall_stats['avg_max_similarity']:.3f}")
  307. logger.info(f" 包含高相似度特征的帖子: {overall_stats['notes_with_high_similarity']}")
  308. logger.info(f" 总耗时: {metadata['processing_time_seconds']:.2f}秒")
  309. logger.info(f" 结果已保存: {args.similarity_output}")
  310. logger.info("=" * 60)
  311. # 打印 Top 5 高相似度特征示例
  312. if similarity_results['results']:
  313. logger.info("\nTop 5 高相似度特征示例:")
  314. all_features = []
  315. for result in similarity_results['results']:
  316. for feat in result['deconstructed_features'][:5]:
  317. all_features.append({
  318. 'note_id': result['note_id'],
  319. 'feature_name': feat['feature_name'],
  320. 'dimension': feat['dimension'],
  321. 'similarity': feat['similarity_score']
  322. })
  323. # 按相似度排序,取 Top 5
  324. all_features.sort(key=lambda x: x['similarity'], reverse=True)
  325. for i, feat in enumerate(all_features[:5], 1):
  326. logger.info(f" {i}. [{feat['note_id'][:12]}...] "
  327. f"{feat['feature_name']} ({feat['dimension']}) "
  328. f"- 相似度: {feat['similarity']:.3f}")
  329. except Exception as e:
  330. logger.error(f"相似度分析失败: {e}", exc_info=True)
  331. logger.warning("继续执行后续步骤...")
  332. # 可视化生成
  333. viz_path = None
  334. if args.visualize:
  335. logger.info("\n" + "=" * 60)
  336. logger.info("开始生成可视化结果...")
  337. logger.info("=" * 60)
  338. try:
  339. # 准备可视化所需的数据文件路径
  340. viz_args = [
  341. '--evaluation-results', args.input,
  342. '--deep-analysis-results', args.output
  343. ]
  344. # 如果有 Stage 7 结果,添加到参数中
  345. if similarity_results and args.similarity_output:
  346. viz_args.extend(['--similarity-results', args.similarity_output])
  347. # 如果指定了可视化输出目录
  348. if args.viz_output:
  349. viz_args.extend(['--output-dir', args.viz_output])
  350. # 调用可视化模块
  351. import sys
  352. original_argv = sys.argv
  353. try:
  354. sys.argv = ['deconstruction_visualizer.py'] + viz_args
  355. viz_path = deconstruction_visualizer.main()
  356. finally:
  357. sys.argv = original_argv
  358. if viz_path:
  359. logger.info("\n" + "=" * 60)
  360. logger.info("可视化生成完成!")
  361. logger.info(f" 可视化文件: {viz_path}")
  362. logger.info("=" * 60)
  363. # 自动打开浏览器
  364. if args.open_browser and not args.no_open:
  365. logger.info("\n正在打开浏览器...")
  366. try:
  367. # 使用 Path.as_uri() 来正确处理包含中文和特殊字符的路径
  368. file_url = Path(viz_path).resolve().as_uri()
  369. webbrowser.open(file_url)
  370. logger.info("浏览器已打开")
  371. except Exception as e:
  372. logger.warning(f"无法自动打开浏览器: {e}")
  373. logger.info(f"请手动打开: {os.path.abspath(viz_path)}")
  374. else:
  375. logger.warning("可视化生成返回了空路径")
  376. except Exception as e:
  377. logger.error(f"可视化生成失败: {e}", exc_info=True)
  378. logger.warning("跳过可视化步骤")
  379. # 流水线执行完成
  380. logger.info("\n" + "=" * 60)
  381. logger.info("流水线执行完成!")
  382. logger.info("=" * 60)
  383. except Exception as e:
  384. logger.error(f"执行失败: {e}", exc_info=True)
  385. raise
  386. if __name__ == '__main__':
  387. main()