run_stage7.py 15 KB

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