#!/usr/bin/env python3 """Update the manuscript DOCX with reproduced statistics and red-marked edits.""" from __future__ import annotations import json import shutil import tempfile from zipfile import ZipFile, ZIP_DEFLATED from pathlib import Path from docx import Document from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.oxml import OxmlElement from docx.oxml.ns import qn from docx.shared import Inches, Pt, RGBColor from lxml import etree ORIGIN = Path( "/Users/yuziye/Library/Containers/com.tencent.xinWeChat/Data/Documents/" "xwechat_files/cangyeone_548b/msg/file/2026-06/26-0098_O_origin.docx" ) OUT_DIR = Path("outputs/manuscript_revision") REPRO_DIR = Path("outputs/repro_seed20260607") REVISED = OUT_DIR / "26-0098_O_revised_seed20260607.docx" CHANGELOG = OUT_DIR / "26-0098_O_revision_record_seed20260607.docx" RED = RGBColor(255, 0, 0) def clear_paragraph(paragraph): paragraph._p.clear_content() def set_red_text(paragraph, text: str): clear_paragraph(paragraph) run = paragraph.add_run(text) run.font.color.rgb = RED return run def set_red_caption(paragraph, text: str): run = set_red_text(paragraph, text) run.font.size = Pt(10.5) paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER return run def remove_paragraph(paragraph): element = paragraph._element element.getparent().remove(element) paragraph._p = paragraph._element = None def set_picture(paragraph, image_path: Path, width_inches: float = 6.45): clear_paragraph(paragraph) paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER run = paragraph.add_run() run.add_picture(str(image_path), width=Inches(width_inches)) def metric(report, model_key: str, subset: str, group: str): return report[model_key][subset][group][0] def fmt3(x): return f"{x:.3f}" def fmtms(x): return f"{x * 1000:.1f} ms" def add_red_note_after(paragraph, text: str): new_p = OxmlElement("w:p") paragraph._p.addnext(new_p) inserted = paragraph._parent.add_paragraph() inserted._p = new_p inserted._element = new_p run = inserted.add_run(text) run.font.color.rgb = RED return inserted def build_revised_doc(): OUT_DIR.mkdir(parents=True, exist_ok=True) report = json.load(open(REPRO_DIR / "metrics_report.json", encoding="utf-8")) doc = Document(ORIGIN) p = doc.paragraphs bP = metric(report, "base_metrics", "all", "P") bS = metric(report, "base_metrics", "all", "S") mP = metric(report, "multi_metrics", "all", "P") mS = metric(report, "multi_metrics", "all", "S") bdP = metric(report, "base_metrics", "double", "P") bdS = metric(report, "base_metrics", "double", "S") mdP = metric(report, "multi_metrics", "double", "P") mdS = metric(report, "multi_metrics", "double", "S") cbP = metric(report, "continuous_base_metrics", "all", "P") cbS = metric(report, "continuous_base_metrics", "all", "S") cmP = metric(report, "continuous_multi_metrics", "all", "P") cmS = metric(report, "continuous_multi_metrics", "all", "S") replacements = { 4: ( "摘要:在交通隧道工程中,对开挖引起的围岩微破裂(微震)进行实时、高精度监测,是评估结构稳定性、预警岩爆和探测裂隙岩溶的关键。" "针对TBM掘进等施工环境中强机械噪声与多事件叠加导致的震相漏检问题,本文在既有RNN震相拾取框架上引入多事件叠加训练策略,构建Multi-RNN微震震相拾取模型。" "为保证文章统计的一致性和可复现性,本次修订固定seed为20260607,并基于CREDIT-X1Local测试划分构建20,000个固定评估样本(单事件9,997个、双事件10,003个)。" f"结果表明,在阈值0.1下,Multi-RNN相较基准RNN的P波召回率由{fmt3(bP['recall'])}提高到{fmt3(mP['recall'])},S波召回率由{fmt3(bS['recall'])}提高到{fmt3(mS['recall'])};" f"P波F1值由{fmt3(bP['f1'])}提高到{fmt3(mP['f1'])},S波F1值由{fmt3(bS['f1'])}提高到{fmt3(mS['f1'])}。" f"误差分布集中于零点附近,Multi-RNN的P波和S波标准差分别为{fmtms(mP['std_error_s'])}和{fmtms(mS['std_error_s'])}。" "连续波形测试进一步表明,该模型能在密集记录中提升震相检出率,尤其改善S波拾取完整性,为交通隧道工程实时微震监测和高密度目录构建提供了可复现的技术方案。" ), 6: ( "Abstract: Real-time and high-precision monitoring of excavation-induced microseismic ruptures is essential for stability assessment, rockburst warning, and fissure or karst detection in traffic tunnel engineering. " "To address phase-missing problems caused by strong construction noise and overlapping events, this study develops a Multi-RNN phase picker by introducing a multi-event superposition training strategy into an existing RNN picker. " "For consistency and reproducibility, the revision fixes the random seed at 20260607 and constructs 20,000 deterministic evaluation samples from the CREDIT-X1Local test split, including 9,997 single-event and 10,003 two-event samples. " f"At a confidence threshold of 0.1, the Multi-RNN improves P-wave recall from {fmt3(bP['recall'])} to {fmt3(mP['recall'])} and S-wave recall from {fmt3(bS['recall'])} to {fmt3(mS['recall'])}; " f"the F1 scores increase from {fmt3(bP['f1'])} to {fmt3(mP['f1'])} for P waves and from {fmt3(bS['f1'])} to {fmt3(mS['f1'])} for S waves. " f"The picking errors remain centered near zero, with standard deviations of {fmtms(mP['std_error_s'])} for P waves and {fmtms(mS['std_error_s'])} for S waves. " "Continuous waveform tests further demonstrate improved detection completeness, especially for S phases, supporting reproducible microseismic monitoring and catalog construction in tunnel applications." ), 17: ( "本研究在 Yu 等[24]提出的基准 RNN 模型基础上,选取其中结构稳定、参数量较小的双向循环网络作为主干,并针对单时窗多震相检测任务进行迁移学习。" "模型输入为三分量波形(BHE、BHN、BHZ),长度为5120个采样点(采样率100 Hz,对应51.2 s)。" "网络总参数量为450,485,其中Encoder为35,136,Bi-LSTM时序模块为310,464,Decoder为104,885。" "输出为5通道概率序列,分别对应背景、Pg、Sg、Pn和Sn;在论文统计中,将Pg/Pn合并为P类,将Sg/Sn合并为S类,以便与工程微震P、S拾取任务保持一致。" ), 18: ( "如图1所示,主干模型由Encoder、两级Bi-LSTM时序模块和Decoder组成。" "Encoder包含7个Conv-BN-ReLU卷积块,每个卷积块后接MaxPool1d(2,2),总下采样倍率为128。" "卷积通道与卷积核依次为3→8(k=11)、8→16(k=9)、16→16(k=7)、16→32(k=7)、32→32(k=5)、32→64(k=5)、64→64(k=3)。" "两级Bi-LSTM均为单层双向LSTM,隐藏单元数为96,每级LSTM后通过1×1卷积将双向拼接后的192维特征压缩为96维。" "Decoder由7个stride=2的一维反卷积块组成,通道与卷积核依次为96→96(k=3)、96→96(k=5)、96→32(k=5)、32→32(k=7)、32→16(k=7)、16→16(k=9)、16→8(k=11),最后通过Conv1d(8,5,k=11)与Softmax输出震相概率。" ), 20: ( "图 1:模型整体结构示意图。模型输入为固定长度三分量波形,主干模型包括卷积编码器(Encoder)、两级Bi-LSTM时序模块和反卷积解码器(Decoder),最终输出背景、Pg、Sg、Pn和Sn五类概率序列。本研究的核心在于通过固定seed的随机叠加构造多事件训练样本。" ), 22: ( "模型的第一部分为编码器。本文将输入定义为X∈R^(3×5120),其中3代表BHE、BHN、BHZ三个正交分量,5120为时间窗口采样点数。" "为了捕捉地震波在时频域的局部瞬态特征(如P波初至时的振幅突变和频谱改变),编码器采用7层一维卷积块与7次二倍池化,将原始高频波形映射为长度40的高层特征序列。第l层卷积操作可表示为:" ), 27: "Figure 2: Schematic diagram of the Encoder structure.", 33: "Figure 3: Schematic diagram of the Bi-LSTM structure.", 34: ( "模型的第三部分为解码器,该部分将Bi-LSTM输出的96维时序特征逐级上采样回原始时间分辨率。" "本文Decoder并非使用传统全连接层,而是采用7层一维反卷积与1层末端一维卷积实现逐点分类;末端Conv1d输出维度为5,分别对应背景、Pg、Sg、Pn和Sn。" "最后,利用Softmax函数将输出归一化为概率值:" ), 36: ( "其中,W_out和b_out分别表示末端一维卷积的权重与偏置;H_t为Bi-LSTM层的输出特征;y_t表示t时刻每个采样点属于背景、Pg、Sg、Pn和Sn的预测概率向量。" "当某类震相概率超过预设阈值时,即判定为检测到相应震相;本文PR曲线使用0.1–0.9的阈值序列,误差统计与连续波形测试统一采用0.1阈值。" ), 39: "Figure 4: Schematic diagram of the Decoder structure.", 43: "2、样本构造:每条训练样本长度固定为51.2秒(5120个采样点,采样率100 Hz),采样率与原始数据一致。对每个样本,采用以下两种方式生成:", 44: ( "(1)单事件样本(约50%概率):直接采样一个地震事件并放入窗口中,其处理方式与Yu等(2023)保持一致。" "(2)双事件样本(约50%概率):随机选取两个不同的事件,将其波形叠加在一个窗口中。为保证复现一致性,本次修订固定seed=20260607;双事件样本中第二个事件的振幅缩放因子在0.2至5.0之间随机采样,两个事件的窗口位置按固定随机序列生成。" "标签合成采用逐点最大值聚合策略,确保同一窗口内多个P类或S类震相均被保留。" ), 61: ( "为了加速收敛并控制过拟合,本文采用AdamW优化器进行参数更新,学习率为1×10^-4,权重衰减为1×10^-4。" "本次修订的复现实验以ckpt/pnsn.v3.pt为基准权重,在训练划分中固定抽取3000个事件形成10,554条可用三分量台站记录,并进行400 step迁移学习,batch size为16。" "评估时从测试划分中固定抽取3000个事件形成10,622条可用台站记录,再按同一seed构建20,000个评估样本。" "由于固定了seed并重新生成样本,本文修订后的统计数值与原稿图中旧数值略有差异,但实验流程和样本生成过程可完全复现。" ), 62: "3 模型测试", 64: ( "为了定量评估模型在不同置信度阈值下的检测性能,我们选取20,000个固定seed样本进行测试,其中9,997个样本包含单一事件,10,003个样本包含两个叠加波形信号。" "测试过程中,若模型预测到的震相到时与参考标注时间的偏差小于1.0秒,则判定为真阳性(True Positive, TP)样本。" "标注来源优先采用MANUAL.TRAVTIME;若无人工到时,则仅使用数据集中tag=Y的RNN.TRAVTIME作为参考标注。对应地,Precision(精确率)与Recall(召回率)定义如下:" ), 67: ( "图6展示了不同模型和震相类型下Precision、Recall和F1值随最小置信度阈值变化的关系。" "其中,(a)与(b)分别对应基准RNN模型在P类与S类震相上的表现;(c)与(d)对应迁移学习后的Multi-RNN模型结果。" "为便于直观比较,修订稿已将原先分散的子图合并为一个2×2整体图。" ), 68: ( "从图6可以看出,随着阈值提高,Precision总体上升而Recall下降,符合置信度阈值筛选的预期。" f"在阈值0.1下,基准RNN的P类Precision/Recall/F1为{fmt3(bP['precision'])}/{fmt3(bP['recall'])}/{fmt3(bP['f1'])},S类为{fmt3(bS['precision'])}/{fmt3(bS['recall'])}/{fmt3(bS['f1'])};" f"Multi-RNN的P类Precision/Recall/F1为{fmt3(mP['precision'])}/{fmt3(mP['recall'])}/{fmt3(mP['f1'])},S类为{fmt3(mS['precision'])}/{fmt3(mS['recall'])}/{fmt3(mS['f1'])}。" ), 69: ( f"对比原始与改进模型可发现:1. Multi-RNN显著提高召回率,P类Recall由{fmt3(bP['recall'])}提高到{fmt3(mP['recall'])},S类Recall由{fmt3(bS['recall'])}提高到{fmt3(mS['recall'])},说明多事件叠加训练增强了模型对弱震相与重叠事件的检出能力。" f"2. F1值同步提高,P类F1由{fmt3(bP['f1'])}提高到{fmt3(mP['f1'])},S类F1由{fmt3(bS['f1'])}提高到{fmt3(mS['f1'])},补充了原稿中仅给出改进模型F1而缺少基准F1的问题。" "3. Multi-RNN的Precision有所下降,反映其会主动拾取更多边缘弱震相;该权衡提高了检测完整性,但后续仍需结合多台站关联约束进一步抑制误检。" ), 74: ( "图6. 不同模型的Precision、Recall与F1曲线比较。随着阈值提高,Precision总体增大、Recall减小;相比基准RNN,Multi-RNN在P类与S类震相上均表现出更高召回率和更高F1值。" ), 75: ( "Figure 6: Comparison of Precision, Recall, and F1 curves. As the threshold increases, Precision generally increases while Recall decreases; compared with the baseline RNN, the Multi-RNN yields higher Recall and F1 scores for both P and S phases." ), 84: ( "测试数据与PR曲线相同,共包含20,000个固定seed样本,其中9,997个为单事件样本,10,003个为双事件叠加样本。" "拾取误差(Error)定义为模型预测震相到时与参考标注之间的时间差,正值表示预测延迟,负值表示提前。" ), 86: ( f"基准RNN模型(图7a、7b)的P类与S类误差分布均集中于零点附近,标准差分别为{fmtms(bP['std_error_s'])}和{fmtms(bS['std_error_s'])},说明模型具有较好的到时稳定性。" f"但其召回率相对偏低(RP={fmt3(bP['recall'])},RS={fmt3(bS['recall'])}),F1值分别为F1P={fmt3(bP['f1'])}、F1S={fmt3(bS['f1'])},表明在重叠震相或弱信号条件下仍存在漏检。" ), 87: ( f"改进Multi-RNN模型(图7c、7d)相较基准模型显著提升召回率:P类Recall由{fmt3(bP['recall'])}提高到{fmt3(mP['recall'])},S类Recall由{fmt3(bS['recall'])}提高到{fmt3(mS['recall'])}。" f"虽然P类Precision由{fmt3(bP['precision'])}降至{fmt3(mP['precision'])},S类Precision由{fmt3(bS['precision'])}降至{fmt3(mS['precision'])},但总体F1值提高到F1P={fmt3(mP['f1'])}、F1S={fmt3(mS['f1'])}。" f"该结果说明多事件叠加训练使模型尝试拾取更多被基准模型忽略的弱震相和重叠震相;代价是误差分布略宽,P类和S类标准差分别为{fmtms(mP['std_error_s'])}和{fmtms(mS['std_error_s'])}。" ), 88: ( "综上所述,结合PR曲线与误差统计结果可以得出,多事件叠加训练策略显著提高了模型召回率与F1分数,尤其改善了S类震相的检出完整性。" "由于本次修订固定了seed并重新生成评估样本,数值与原稿旧图略有不同;修订后的统计结果以固定seed复现实验为准。" "整体上,Multi-RNN在检测能力与时序精度之间形成了更适合密集震相检测的权衡。" ), 90: ( "为进一步检验模型在多震相叠加场景下的泛化表现,我们选取仅包含两个事件信号的固定seed测试样本进行独立统计分析,结果如图8所示。" "图中(a)、(b)分别对应基准RNN模型在P类与S类震相上的误差分布;(c)、(d)为Multi-RNN模型结果。所有测试均在置信度阈值0.1下进行,其他评估方式与前述一致。" ), 92: ( f"基准RNN模型在双事件样本中的P类Precision/Recall/F1为{fmt3(bdP['precision'])}/{fmt3(bdP['recall'])}/{fmt3(bdP['f1'])},S类为{fmt3(bdS['precision'])}/{fmt3(bdS['recall'])}/{fmt3(bdS['f1'])}。" f"误差标准差分别为P类{fmtms(bdP['std_error_s'])}、S类{fmtms(bdS['std_error_s'])}。" "与混合样本总体结果相比,双事件条件下召回率明显下降,说明基准模型在多事件叠加时仍容易漏检后续弱震相。" ), 93: ( f"Multi-RNN在双事件样本中的P类Precision/Recall/F1为{fmt3(mdP['precision'])}/{fmt3(mdP['recall'])}/{fmt3(mdP['f1'])},S类为{fmt3(mdS['precision'])}/{fmt3(mdS['recall'])}/{fmt3(mdS['f1'])}。" f"相较基准模型,P类Recall由{fmt3(bdP['recall'])}提高到{fmt3(mdP['recall'])},S类Recall由{fmt3(bdS['recall'])}提高到{fmt3(mdS['recall'])},S类F1由{fmt3(bdS['f1'])}提高到{fmt3(mdS['f1'])}。" f"其P类和S类均值误差分别为{fmtms(mdP['mean_error_s'])}和{fmtms(mdS['mean_error_s'])},接近零点,表明模型在复杂叠加信号下未出现明显系统性偏移。" ), 94: ( "综合来看,Multi-RNN模型在双事件叠加环境下显著提升召回率,验证了多事件数据增强策略的有效性。" "同时,Precision下降提示模型在追求更完整检出时会引入更多候选拾取,实际工程应用中应结合多台站关联、走时约束和事件定位流程进一步剔除误检。" ), 99: ( "图8. 双事件样本下不同模型的震相拾取误差分布对比。与基准RNN相比,Multi-RNN在P类与S类震相上均提高召回率,其中S类F1提升最明显;误差分布仍集中于零点附近,说明其具备更强的多震相识别能力。" ), 100: ( "Figure 8: Comparison of phase-picking error distributions under two-event samples. Compared with the baseline RNN, the Multi-RNN improves Recall for both P and S phases, with the most evident F1 improvement for S phases; errors remain centered near zero." ), 101: "连续波形震相检测", 102: ( f"为验证模型在实际连续波形中的适用性,我们选取测试集中事件{report['continuous_event']}的连续记录进行对比,该事件共包含{report['continuous_station_count']}条可用三分量台站记录。" "图9中黑色曲线为垂向分量归一化波形,红色和蓝色标记分别表示模型检测出的P类与S类到时。" ), 105: ( "图9. 连续波形震相检测结果对比。黑色曲线为地震波形记录,红色和蓝色标记分别代表检测出的P类与S类到时。Multi-RNN在该连续事件中提高了P类和S类检出率,尤其提升了S类震相检测完整性。" ), 106: ( "Figure 9: Comparison of phase detection results on continuous waveforms. Black curves denote waveform records, and red and blue markers represent detected P and S arrivals. The Multi-RNN improves detection rates, especially for S phases." ), 108: ( "从图9整体结果可以看出,两种模型均能识别主震及其后续震相到时,且拾取结果随震中距变化呈现一定连续性。" "为回应审稿意见,本文在图示基础上补充检测率、误检率和震相拾取一致性统计;其中检测率定义为Recall,误检率定义为1−Precision,拾取一致性用匹配到时误差标准差表示。" ), 109: ( f"在事件检测完整性方面,基准RNN的P类检测率为{fmt3(cbP['recall'])},S类检测率为{fmt3(cbS['recall'])};Multi-RNN的P类检测率提高到{fmt3(cmP['recall'])},S类检测率提高到{fmt3(cmS['recall'])}。" "这说明多事件叠加训练有助于在连续记录中识别更多震相,尤其对S类震相的漏检有明显改善。" ), 110: ( f"在误检率方面,基准RNN的P类和S类误检率分别为{fmt3(1 - cbP['precision'])}和{fmt3(1 - cbS['precision'])};Multi-RNN对应为{fmt3(1 - cmP['precision'])}和{fmt3(1 - cmS['precision'])}。" "Multi-RNN的S类误检率略有下降,但P类误检率升高,说明该模型在提高检出率的同时会输出更多候选P类拾取,需要在工程应用中结合多台站一致性和走时约束进行二次筛选。" ), 111: ( f"在震相拾取一致性方面,基准RNN的P类与S类误差标准差分别为{fmtms(cbP['std_error_s'])}和{fmtms(cbS['std_error_s'])};Multi-RNN分别为{fmtms(cmP['std_error_s'])}和{fmtms(cmS['std_error_s'])}。" "其中S类一致性有所改善,P类一致性在该连续事件中略有变宽,反映出模型对弱P类候选更敏感但仍需后处理约束。" ), 112: ( "尽管改进模型在多震相检测中提高了检出完整性,但在强噪声段和震相密集区仍可能触发误检。" "因此,后续应结合多台站联合检测、走时曲线约束和震相关联算法,进一步提高连续波形处理中的可信度。" ), 113: ( "综上,连续数据测试结果验证了Multi-RNN模型在复杂波形场景下的适应性。" "通过在训练阶段引入多事件叠加样本,该模型在保持可接受到时误差的同时提高了P类和S类检出率,为自动检测、目录构建和后续微震定位提供了更完整的数据基础。" ), 114: "结论", 115: ( "在交通隧道等重大基础设施的建设和运维中,对围岩稳定性的实时监测是保障工程安全的核心。" "针对TBM等强机械噪声与密集微破裂信号叠加造成的微震拾取难题,本文提出并复现了一种基于深度循环神经网络的Multi-RNN多事件微震信号检测模型。" "该模型通过多事件叠加训练,使网络具备同时识别同一时窗内多个P类和S类震相的能力。" "本次修订固定seed=20260607并更新全部统计与图件,保证了文章结果的一致性和可复现性。" ), 117: ( f"2、改进的Multi-RNN模型在召回率和F1值上优于基准模型。固定seed测试表明,P类Recall由{fmt3(bP['recall'])}提高到{fmt3(mP['recall'])},S类Recall由{fmt3(bS['recall'])}提高到{fmt3(mS['recall'])};" f"P类F1由{fmt3(bP['f1'])}提高到{fmt3(mP['f1'])},S类F1由{fmt3(bS['f1'])}提高到{fmt3(mS['f1'])}。这意味着新模型能拾取更多被基准模型漏检的弱震相和叠加震相。" ), 118: ( f"3、模型在连续数据中的检测完整性增强。连续波形测试中,Multi-RNN的P类检测率由{fmt3(cbP['recall'])}提高到{fmt3(cmP['recall'])},S类检测率由{fmt3(cbS['recall'])}提高到{fmt3(cmS['recall'])},其中S类提升更为明显。" ), 120: ( "综上所述,本文提出的Multi-RNN模型及其多事件叠加训练策略,为高噪声、多信号叠加环境下的微震监测提供了可复现的技术支撑。" "需要指出的是,Multi-RNN在提高召回率的同时会产生更多候选拾取,后续应结合多台站关联和定位约束进一步降低误检率,从而服务于更实时、更智能的隧道灾害预警。" ), } for idx, text in replacements.items(): set_red_text(p[idx], text) caption_replacements = { 55: "图5. 固定seed生成的训练样本构造示例:(a) 单事件三分量波形;(b) 双事件叠加三分量波形;(c) 单事件P/S标签;(d) 双事件叠加P/S标签。", 56: "Figure 5: Training sample construction with a fixed seed: (a) single-event waveform; (b) two-event mixed waveform; (c) single-event P/S labels; (d) two-event mixed P/S labels.", 82: "图7. 固定seed混合样本下不同模型震相拾取误差分布对比。直方图显示预测到时与参考标注之差(Error,单位:秒),虚线为零误差参考线;图内同步给出Precision、Recall、F1、均值误差和标准差。", 83: "Figure 7: Error distributions under fixed-seed mixed samples. Histograms show the difference between predicted arrivals and reference labels; the dashed line marks zero error, and each panel reports Precision, Recall, F1, mean error, and standard deviation.", } for idx, text in caption_replacements.items(): set_red_caption(p[idx], text) set_picture(p[51], REPRO_DIR / "fig5_training_samples_composite.png", 6.35) set_picture(p[70], REPRO_DIR / "fig6_pr_composite.png", 6.35) set_picture(p[78], REPRO_DIR / "fig7_error_all_composite.png", 6.35) set_picture(p[95], REPRO_DIR / "fig8_error_double_composite.png", 6.35) set_picture(p[103], REPRO_DIR / "fig9_continuous_composite.png", 6.35) # Remove obsolete split subfigure labels and duplicate image paragraphs. for idx in sorted([52, 53, 54, 71, 72, 73, 79, 80, 81, 96, 97, 98, 104], reverse=True): remove_paragraph(p[idx]) doc.save(REVISED) cleanup_review_highlights(REVISED) return report def make_run(text: str, color: str | None = None, bold: bool = True): ns = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" run = OxmlElement("w:r") rpr = OxmlElement("w:rPr") if bold: rpr.append(OxmlElement("w:b")) rpr.append(OxmlElement("w:bCs")) if color: c = OxmlElement("w:color") c.set(qn("w:val"), color) rpr.append(c) t = OxmlElement("w:t") t.text = text run.append(rpr) run.append(t) return run def cleanup_review_highlights(docx_path: Path): """Remove reviewer-comment highlights left in the original manuscript.""" ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} with ZipFile(docx_path, "r") as zin: files = {name: zin.read(name) for name in zin.namelist()} root = etree.fromstring(files["word/document.xml"]) for para in root.xpath(".//w:p", namespaces=ns): text = "".join(para.xpath(".//w:t/text()", namespaces=ns)).strip() replacement = None if "未明确 Encoder" in text: replacement = ("2.1 模型结构", None) elif "建议补充对比" in text: replacement = ("3.2 事件统计分析", None) if replacement: ppr = para.find("w:pPr", namespaces=ns) for child in list(para): if child is not ppr: para.remove(child) para.append(make_run(replacement[0], color=replacement[1], bold=True)) for highlight in root.xpath(".//w:highlight", namespaces=ns): parent = highlight.getparent() if parent is not None: parent.remove(highlight) files["word/document.xml"] = etree.tostring( root, xml_declaration=True, encoding="UTF-8", standalone=True ) tmp = docx_path.with_suffix(".tmp.docx") with ZipFile(tmp, "w", ZIP_DEFLATED) as zout: for name, data in files.items(): zout.writestr(name, data) tmp.replace(docx_path) def cell_text(cell, text: str, bold: bool = False): cell.text = "" p = cell.paragraphs[0] run = p.add_run(text) run.bold = bold run.font.size = Pt(9) def build_change_log(report): doc = Document() sec = doc.sections[0] sec.top_margin = Inches(0.8) sec.bottom_margin = Inches(0.8) sec.left_margin = Inches(0.75) sec.right_margin = Inches(0.75) title = doc.add_paragraph() title.alignment = WD_ALIGN_PARAGRAPH.CENTER r = title.add_run("26-0098_O 修订修改记录") r.bold = True r.font.size = Pt(16) intro = doc.add_paragraph() intro.add_run( "本记录对应修订稿 26-0098_O_revised_seed20260607.docx。" "为保证文章的一致性和可复现性,本次复现实验固定 seed=20260607;" "由于重新生成固定评估样本,正文与图中的数据值较原稿略有不同,修订稿以新的固定 seed 统计为准。" ) table = doc.add_table(rows=1, cols=4) table.style = "Table Grid" headers = ["位置", "修改内容", "回应/原因", "复现依据"] for cell, text in zip(table.rows[0].cells, headers): cell_text(cell, text, bold=True) rows = [ ( "摘要 / Abstract", "更新召回率、F1值、误差标准差和样本数量;修正“平局”等表述。", "保持摘要与新版统计一致。", "metrics_report.json;20,000个固定seed样本。", ), ( "2.1 模型结构", "补充Encoder卷积层数、卷积核、池化方式、Bi-LSTM层数和隐藏单元、Decoder维度、输出通道和参数量。", "回应审稿意见:模型关键参数不明确。", "models/BRNNPNSN.py;总参数量450,485。", ), ( "2.2-2.3 数据与训练", "补充窗口长度51.2 s、标注来源规则、AdamW参数、迁移训练步数、训练/测试记录数与固定seed说明。", "增强可复现性,解释新旧数值差异。", "scripts/reproduce_paper_stats.py;record_summary.json。", ), ( "图5-图8", "将分散子图合并为2×2整体图,并更新图注。", "回应审稿意见:建议合并子图便于比较。", "fig5_training_samples_composite.png 至 fig8_error_double_composite.png。", ), ( "3.1 PR曲线", "补充Precision、Recall、F1随阈值变化;给出阈值0.1下RNN和Multi-RNN的P/S统计。", "保持图文数值一致。", "RNN P/S F1=0.713/0.711;Multi-RNN P/S F1=0.729/0.745。", ), ( "3.2 事件统计分析", "补充原始RNN的F1值,并更新Multi-RNN的F1、召回率、精度和误差标准差。", "回应审稿意见:原稿仅给改进模型F1,缺少原始模型F1对比。", "metrics_report.json,threshold=0.1。", ), ( "3.3 双样本事件统计", "更新双事件样本的Precision、Recall、F1、均值误差和标准差。", "保持双事件结论与固定seed复现实验一致。", "双事件样本10,003个。", ), ( "4 连续波形震相检测", "补充检测率、误检率和震相拾取一致性;更新连续波形图。", "回应审稿意见:连续波形结果缺乏定量指标支撑。", f"事件{report['continuous_event']},{report['continuous_station_count']}条台站记录。", ), ( "结论", "根据新版统计重写召回率、F1和连续波形结论,并增加误检率需后处理约束的说明。", "避免过度表述,提升结论可信度。", "固定seed复现实验结果。", ), ] for row in rows: cells = table.add_row().cells for cell, text in zip(cells, row): cell_text(cell, text) doc.add_paragraph() p = doc.add_paragraph() p.add_run("输出文件:").bold = True p.add_run(str(REVISED.resolve())) doc.save(CHANGELOG) def main(): report = build_revised_doc() build_change_log(report) print(REVISED.resolve()) print(CHANGELOG.resolve()) if __name__ == "__main__": main()