跳转到内容

在分子、谱图、信号和机器人回合上训练模型

在研究的答案上拟合 rdkit-property、peak-cnn、signal-cnn 和 episode-model,再把预测展示给标注者接受或修正。

v2.14.0 新增

本教程在 Potato 研究的科学标注上训练四个模型。rdkit-property 回答关于分子的问题,peak-cnn 在 NMR、IR、Raman、UV-Vis 和质谱上挑选峰,signal-cnn 对睡眠 epoch 分期,或在 EEG、ECG 和传感器记录上标出已标注的区间,episode-model 标出机器人回合的阶段、结果和奖励曲线。本页面向已经在 Potato 中标注分子、谱图、信号或机器人回合,并希望用模型为其余条目预标注的团队。对每个训练器,本页给出能通过 potato validate config.yaml --strict 的配置、一次运行学到什么、应当查看的留出得分,以及预测如何送到标注者面前。训练你的第一个模型用文本分类器演示了训练页面,本教程以该教程为前提。

四个训练器

每个训练器都从一个标注方案的答案中学习,并从该方案所指向的显示或文件读取模型的输入:

训练器方案输入安装留出主得分
rdkit-property关于分子的 radio、select、likert 或 slidermolecule 显示字段,或 chemical_structure 答案potato-annotation[chemistry]macro-F1,滑块则为精确位置准确率
peak-cnnpeak_picking来自 JCAMP-DX、mzML、CSV 或内联数据的谱图potato-annotation[train-vision]容差内的峰 F1
signal-cnnepoch 或 free 模式的 interval_label来自 EDF、BDF、WFDB、CSV、Parquet 或内联数据的信号potato-annotation[train-vision],WFDB 和 Parquet 另需 [signals]各 epoch 的 macro-F1,或 IoU 0.5 时的区间 F1
episode-modelepisode_annotation来自 LeRobot、HDF5 或 Potato 回合清单的机器人回合potato-annotation[train-vision]阶段 macro-F1

rdkit-property 拟合 scikit-learn 模型,其余三个训练器在 model_training.device 选定的设备上拟合小型 PyTorch 网络。每次运行只在一个方案上训练,在该次运行留出的 val 和 test 条目上给模型评分,并且只为 writeback.schemas 下列出的方案回写预测。属于一次运行 train 划分的条目永远不会得到预标签。peak-cnn、signal-cnn 和 episode-model 保留在 val 上得分最好的那个 epoch 的检查点,所以这三个网络的留出得分由 Potato 取自 test。小型研究的测试条目很少,运行的 test_n 指标给出得分背后的条目数。

这三个网络把一个条目预测的置信度取为该预测中最不确定部分的置信度,即最弱的峰、区间、阶段或结果。因此 writeback.min_confidence 下限会在任何一个部分低于下限时,把整个条目留下不回写。rdkit-property 的滑块预测没有置信度值,总能通过下限。

用 rdkit-property 预测分子性质

rdkit-property 学习关于条目所显示分子的问题的答案,模型的输入是分子的结构,而不是条目的文本。每个结构先变成半径为 2、2,048 位的 Morgan 指纹,后面接 RDKit 的 2D 描述符,例如分子量和 logP。半径为 2 时,Morgan 指纹对应 ECFP4,即直径为 4 个键的扩展连接性指纹。对于 radio、select 或 likert 答案,默认用逻辑回归拟合,对于 slider 用岭回归拟合。两者都可以改用随机森林。请在负责训练的服务器上安装 RDKit:

bash
pip install "potato-annotation[chemistry]"

下面的配置把每个化合物显示为结构图,并提出两个问题,一个是 1 到 10 的滑块,问该化合物合成有多难,另一个是是否下一步合成该化合物的是非选择:

yaml
annotation_task_name: "Compound triage"
task_dir: .
output_annotation_dir: annotation_output
data_files: [data/molecules.jsonl]
item_properties: {id_key: id, text_key: note}
user_config:
  allow_all_users: true
 
instance_display:
  fields:
    - key: smiles                 # SMILES, a molfile, or a .mol, .sdf or .smi file
      type: molecule
      label: "Compound"
 
annotation_schemes:
  - annotation_type: slider
    name: synthesis
    description: "How hard would this compound be to make? 1 is one step from a catalog reagent, 10 is a research project."
    min_value: 1
    max_value: 10
    starting_value: 5
    step: 1
  - annotation_type: radio
    name: advance
    description: "Would you make this compound next?"
    labels: ["yes", "no"]
 
model_training:
  enabled: true
  writeback:
    schemas: [synthesis, advance]
    include_unlabeled: true
    min_confidence: 0.6

数据文件的每一行对应一个化合物:

json
{"id": "cpd_023", "smiles": "CC(=O)Nc1ccc(O)cc1", "note": "Compound 23"}

训练器从 instance_display 中的第一个 molecule 字段取结构。没有 molecule 字段的研究可以改为在 chemical_structure 方案旁边提问,此时训练器从标注者画的结构中学习。标注者画了不同的结构时,训练器取最常见的那个,没有人画时,训练器取编辑器起始的结构(prefill_field)。只要至少 80% 的条目填写了 molecule 字段,训练页面就会为该字段提供 rdkit-property。RDKit 无法解析的结构会被排除在训练之外,得不到预测,并计入运行的 n_unparseable 指标。对于选择题或 Likert 题,训练页面也会提供文本训练器,因为这些条目仍然有文本。

滑块的预测会按滑块的 step 取整,并保持在 min_value 与 max_value 之间,所以模型提出的总是滑块上存在的位置。滑块的主得分是精确位置准确率,把预测为 6 而答案为 7 算作错误。在判断预测是否足够接近、可以展示之前,请把平均绝对误差 val_mae 或 test_mae 与准确率一起查看。运行要求 train 划分中至少有 10 个已标注的分子。model、class_weight、radius 和 bits 等参数写在训练页面的 Parameters 字段中,或写在 API 请求里:

bash
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  http://localhost:8000/admin/training/api/runs \
  -d '{"trainer": "rdkit-property", "schemas": ["advance"],
       "params": {"model": "random_forest", "class_weight": "balanced"}}'

运行日志会给出分子数、特征数和模型。平衡的类别权重会让稀少的答案占更大分量。大多数化合物得到相同答案时,请使用 class_weight: balanced。运行结束后,标注者打开一个未标注的化合物,会看到滑块被设到模型的值,模型的选择也已选中,两者都可以编辑。

用 peak-cnn 挑选谱图的峰

peak-cnn 从标注者给出的 peak_picking 答案中学习挑选峰。训练器读取方案的 viewer_field 所显示的谱图,使用与谱图显示相同的读取器。训练目标是标注者保留的每个峰,包括接受的建议和新增的峰。标注者拒绝的建议标明该处没有峰。模型只预测峰的位置,标注者仍然要为每个峰给出归属、标签或多重性。请在负责训练的机器上安装 PyTorch:

bash
pip install "potato-annotation[train-vision]"

下面的配置请标注者审阅 1H NMR 谱图上建议的峰:

yaml
annotation_task_name: "1H NMR peak picking"
task_dir: .
media_directory: media
output_annotation_dir: annotation_output
data_files: [data/spectra.jsonl]
item_properties: {id_key: id, text_key: note}
user_config:
  allow_all_users: true
 
instance_display:
  fields:
    - key: spectrum
      type: spectrum
      label: "1H NMR"
      display_options:
        prepick:
          method: auto            # item peaks, then model, then file peak table, then detector
          prominence: 0.05
          max_peaks: 30
 
annotation_schemes:
  - annotation_type: peak_picking
    name: peaks
    description: "Accept or reject each suggested peak, and add any the suggestions missed"
    viewer_field: spectrum
    peak_fields:
      - name: assignment
        placeholder: "e.g. OCH2"
    require_review: true
    label_requirement: {required: true}
 
model_training:
  enabled: true
  trainer_params:
    peak-cnn:
      tolerance: 0.02             # ppm
  writeback:
    schemas: [peaks]
    include_unlabeled: true

模型的峰只通过显示的 prepick 设置送到标注者面前,没有 prepick 时,显示完全不给出建议。在 method: auto 下,显示先给出条目自带的 peaks,然后是模型的峰,再是文件的峰表,最后是检测器找到的峰。第一次运行之前,标注者审阅的是检测器的建议,运行之后,同样的条目无需重启就显示模型的峰。使用 method: model 时,显示只给出模型的峰。想衡量在没有混入检测器建议的情况下标注者接受模型峰的频率,请使用 method: model。每个模型峰在列表中带有“· model”,显示保留置信度最高的 max_peaks 个,接受的模型峰以 source: model 存储。

tolerance 参数设定预测的峰与已标注的峰在 x 单位上相距多远仍算找到。没有 tolerance 时,运行使用谱图 x 范围的 0.5%,12 ppm 的 1H 窗口的 0.5% 是 0.06 ppm。在 400 MHz 下,7 Hz 的耦合使多重峰的谱线相隔 0.0175 ppm。默认容差比这个间距更宽,所以配置改设为 0.02 ppm。运行对一对一匹配的峰(最近的优先)计算精确率、召回率和 F1,并报告匹配峰的平均偏移。在标注者说没有峰的谱图上预测出峰,算作误报。运行要求 train 划分中至少有 5 条已标注的谱图。

每条谱图在网络看到之前都会重采样到 length 个点,默认为 4,096。峰比 x 范围的 1/length 更窄时,例如高分辨率质谱,请调大 length。读取 mzML 文件还需要 spectra extra,详见谱图显示页面。

用 signal-cnn 判断睡眠分期和信号区间

signal-cnn 从 EEG、ECG、EMG 和加速度计数据等生理和传感器记录上的 interval_label 答案中学习。网络是一个小型 InceptionTime 模型,由三种宽度的并行卷积堆叠而成。训练器用时间序列显示的读取器读取每段记录,取显示所列的通道,把每个通道重采样到 rate(默认 100 Hz),并对每个通道做标准化。请安装 PyTorch,WFDB 和 Parquet 记录还要安装 signals extra:

bash
pip install "potato-annotation[train-vision,signals]"

下面的配置在整夜多导睡眠图上以 30 秒为一个 epoch 给睡眠评分,方式与人工评分员把每个 epoch 标为清醒、N1、N2、N3 或 REM 相同:

yaml
annotation_task_name: "Sleep staging"
task_dir: .
media_directory: media
output_annotation_dir: annotation_output
data_files: [data/psg.jsonl]
item_properties: {id_key: id, text_key: note}
user_config:
  allow_all_users: true
 
instance_display:
  fields:
    - key: signal
      type: time_series
      label: "PSG"
      display_options:
        window: 30
        channels: ["EEG Fpz-Cz", "EOG", "EMG chin"]   # also the channels signal-cnn reads
 
annotation_schemes:
  - annotation_type: interval_label
    name: stage
    description: "Sleep stage of each 30-second epoch"
    signal_field: signal
    mode: epoch
    epoch_length: 30
    require_all_epochs: true
    label_requirement: {required: true}
    labels:
      - {name: W, key_value: "1"}
      - {name: N1, key_value: "2"}
      - {name: N2, key_value: "3"}
      - {name: N3, key_value: "4"}
      - {name: REM, key_value: "5"}
 
model_training:
  enabled: true
  writeback:
    schemas: [stage]
    include_unlabeled: true

方案的 mode 决定任务。在 epoch 模式下,每个已标注的 epoch 是一个训练样本,模型为新记录的每个 epoch 打标签。最后一个 epoch 的记录不足该 epoch 长度的一半时,不会得到标签。留出主得分是各已标注 epoch 上的 macro-F1。Macro-F1 给 N1 这样的稀少分期与 N2 同样的权重。默认情况下训练损失按标签频率的倒数给每个分期加权(balanced: true),因此一个 N1 epoch 比一个 N2 epoch 分量更重。每段记录都按第一段训练记录的通道及其顺序读取,缺少某个训练通道的记录,会用一条平线代替缺失的通道。没有列出通道的显示会把每个通道都交给训练器,最多 max_channels(16)个。

free 模式改为在标出的片段上训练。请在同一个 signal_field 上再添加一个 mode: free 的 interval_label 方案,用来标出伪迹或觉醒:

yaml
  - annotation_type: interval_label
    name: artifacts
    description: "Mark artifacts"
    signal_field: signal
    mode: free
    labels:
      - {name: artifact, key_value: "a"}
      - {name: spike, key_value: "s", kind: event}

在 free 模式下,网络在随机的 30 秒裁剪片段(window_s)上训练,给每个 1 秒的帧(frame_s)打标签,把带同一标签的连续帧合并成一个区间,并丢弃短于 min_duration 的区间。主得分是区间 F1,它把预测的区间与带同一标签的已标注区间在 IoU 0.5 时匹配。像上面的 spike 标签这样的点事件既不训练也不预测。运行要求 train 划分中至少有 3 段已标注的记录,epoch 模式还要求数据中至少有两种不同的标签。

模型的区间以虚线预标注的形式出现在记录上,列表中带有“· from the model”,在标注者第一次修改或点击“Keep these”时保存。记录自己的标注优先。包含睡眠图的 EDF+ 文件显示睡眠图而不是模型的区间,带 preannotation_field 区间的条目也是如此。在方案上设置 pre_annotations: false 会同时关闭这两种。

用 episode-model 处理机器人回合

episode-model 学习 episode_annotation 答案的三个层,即阶段、结果和进度奖励曲线。模型的输入是回合记录的每个数值序列,例如关节位置和夹爪状态,每帧一行。输入包括 series_shown 没有显示在屏幕上的序列,不包括视频。模型无法发现只在摄像头画面上出现、而机器人的测量值不变的阶段。请安装 PyTorch,LeRobot 数据集还需要 pyarrow,HDF5 演示还需要 h5py:

bash
pip install "potato-annotation[train-vision]"

下面的配置在抓取放置演示上收集全部三个层,如机器人回合页面所述:

yaml
annotation_task_name: "Robot episode review"
task_dir: .
media_directory: media
output_annotation_dir: annotation_output
data_files: [data/episodes.jsonl]
item_properties: {id_key: id, text_key: note}
user_config:
  allow_all_users: true
 
annotation_schemes:
  - annotation_type: episode_annotation
    name: episode_review
    description: "Mark the phases, judge the outcome, and draw the progress."
    source_field: episode
    layers: [phases, outcome, reward]
    phases:
      - {name: reach, key_value: "1"}
      - {name: grasp, key_value: "2"}
      - {name: transport, key_value: "3"}
      - {name: place, key_value: "4"}
      - {name: retract, key_value: "5"}
    outcomes: [success, partial, failure]
    failure_causes: [missed grasp, object slipped, collision]
    reward_range: [0.0, 1.0]
    series_shown: [gripper, wrist_force, shoulder_lift, elbow]
 
model_training:
  enabled: true
  writeback:
    schemas: [episode_review]
    include_unlabeled: true

网络先对序列做卷积,再送入带三个头的双向 GRU,一个头预测每帧的阶段,一个预测回合的结果,一个预测每帧的奖励。答案中为空的层不计入该回合的损失,所以只标了结果的回合仍然训练结果头。训练回合中包含至少两种不同的结果后,结果头才开始训练。奖励头只在标注者画了曲线的那一段上训练,因为在这一段之外标注者没有给出答案。长于 max_frames(2,000)的回合在随机裁剪的片段上训练。

预测具有已存储答案的形状,对方案显示的各层,包含来自方案列表的阶段、方案中的一个结果,以及 reward_range 之内 50 个点的奖励曲线。预测会预填时间线,在标注者编辑时间线之后才会存储。失败原因和指令留给标注者。留出主得分是 0.1 秒网格上的阶段 macro-F1。运行还报告 IoU 0.5 时的阶段片段 F1、结果准确率,以及画了曲线处的奖励曲线 Pearson r。没有阶段的方案以结果准确率为主得分,其次是奖励相关性。运行要求 train 划分中至少有 5 个已标注的回合。Potato 没有 rollout_evaluation 的训练器,因为 rollout 偏好比较的是视频,而 episode-model 不读取视频。

开始一次运行并检查结果

添加 model_training 之后重启服务器:

bash
potato start config.yaml -p 8000

打开 /admin/training,选择方案和训练器,然后点击 Train now。该页面只提供适合每个方案的训练器,未安装的训练器会列为“not installed”,附上安装命令。运行结束后,Potato 在 Runs 表中列出留出得分,划分和指标显示在提示中。运行页面列出每个指标以及每个条目所属的划分。GET /admin/training/api/status 中的回写报告统计已发布的预测数,以及每项检查拦下的预测数,例如 n_skipped_low_confidence。

预标签会让标注者锚定在模型的答案上,所以请等留出得分足以支持展示模型的答案之后,再在新研究中开启 include_unlabeled。在环审阅模型预标签介绍优先提供置信度最低的预标签,以及在修正上重新训练,配置列出每个键、后端和回写检查。

延伸阅读

参考文献

  • Ismail Fawaz, H., Lucas, B., Forestier, G., Pelletier, C., Schmidt, D. F., Weber, J., Webb, G. I., Idoumghar, L., Muller, P.-A., and Petitjean, F. (2020). InceptionTime: Finding AlexNet for Time Series Classification. Data Mining and Knowledge Discovery, 34(6), 1936-1962. https://doi.org/10.1007/s10618-020-00710-y
  • Rogers, D., and Hahn, M. (2010). Extended-Connectivity Fingerprints. Journal of Chemical Information and Modeling, 50(5), 742-754. https://doi.org/10.1021/ci100050t