コンテンツへスキップ

分子、スペクトル、信号、ロボットのエピソードでモデルを学習する

rdkit-property、peak-cnn、signal-cnn、episode-modelを研究の答えで学習し、予測をアノテーターに表示して、承認または修正してもらいます。

v2.14.0の新機能

このチュートリアルでは、Potatoの研究にある科学分野のアノテーションで、4つのモデルを学習します。rdkit-propertyは分子に関する問いに答え、peak-cnnはNMR、IR、ラマン、UV-Vis、質量のスペクトルでピークを拾い、signal-cnnは睡眠のエポックを判定するか、EEG、ECG、センサーの記録でラベル付きの区間をマークし、episode-modelはロボットのエピソードのフェーズ、結果、報酬曲線をマークします。このページは、すでにPotatoで分子、スペクトル、信号、ロボットのエピソードにアノテーションを付けていて、残りの項目をモデルに事前ラベル付けさせたいチーム向けです。各トレーナーについて、potato validate config.yaml --strictを通る設定、実行が学習する内容、読むべきホールドアウトスコア、予測がアノテーターに届く仕組みを示します。最初のモデルを学習するでは、テキスト分類器でTrainingページを一通り説明しており、このチュートリアルはその内容を前提としています。

4つのトレーナー

各トレーナーは、1つのアノテーションスキームの答えから学習し、スキームが指すディスプレイまたはファイルから、モデルの入力を読み込みます。

トレーナースキーム入力インストールホールドアウトの代表値
rdkit-property分子に関するradio、select、likert、またはslidermoleculeディスプレイのフィールド、またはchemical_structureの答えpotato-annotation[chemistry]マクロF1、スライダーの場合は位置の完全一致の正解率
peak-cnnpeak_pickingJCAMP-DX、mzML、CSV、またはインラインのデータのスペクトルpotato-annotation[train-vision]許容幅内でのピークF1
signal-cnnエポックモードまたは自由モードのinterval_labelEDF、BDF、WFDB、CSV、Parquet、またはインラインのデータの信号potato-annotation[train-vision]に加えて、WFDBとParquetには[signals]エポックにわたるマクロF1、またはIoU 0.5での区間F1
episode-modelepisode_annotationLeRobot、HDF5、またはPotatoのエピソードマニフェストのロボットのエピソードpotato-annotation[train-vision]フェーズのマクロF1

rdkit-propertyはscikit-learnのモデルを学習し、ほかの3つのトレーナーは、model_training.deviceが選ぶデバイス上で、小さなPyTorchのネットワークを学習します。どの実行も1つのスキームで学習し、実行のホールドアウトしたvalとtestの項目でモデルをスコアリングし、writeback.schemasに挙がっているスキームについてだけ予測を書き戻します。実行のtrain分割の項目に、事前ラベルは付きません。peak-cnn、signal-cnn、episode-modelは、valで最も良いスコアを出したエポックのチェックポイントを保持するため、Potatoはこの3つのネットワークのホールドアウトスコアをtestから取ります。小規模な研究ではtestの項目が少なく、実行のtest_n指標が、スコアの根拠になる件数を示します。

3つのネットワークは、項目の予測の信頼度として、予測のうち最も確実でない部分、つまり最も弱いピーク、区間、フェーズ、または結果の信頼度を与えます。そのため、writeback.min_confidenceの下限を設定すると、いずれか1つの部分が下限を下回る項目は、項目全体が保留されます。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]"

次の設定は、各化合物を構造図として表示し、2つの問いを出します。1つは、その化合物の作りにくさを1から10で答えるスライダーで、もう1つは、その化合物を次に作るかどうかのはい・いいえの選択です。

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

データファイルの各行に、1つの化合物があります。

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

トレーナーは、instance_displayの最初のmoleculeフィールドから構造を取ります。moleculeフィールドがない研究では、代わりにchemical_structureスキームの隣に問いを置くことができ、その場合トレーナーは、アノテーターが描いた構造から学習します。アノテーターが異なる構造を描いた場合、トレーナーは最も多い描画を採用し、誰も描かなかった場合は、エディターが開始時に持っていた構造(prefill_field)を採用します。Trainingページは、項目の80%以上がフィールドを埋めている場合に、moleculeフィールドに対してrdkit-propertyを提示します。RDKitが解析できない構造は、学習から除外され、予測も付かず、実行のn_unparseable指標に数えられます。選択式またはLikertの問いには、項目にテキストがあるため、Trainingページはテキストのトレーナーも提示します。

スライダーの予測は、スライダーのstepに丸められ、min_valueとmax_valueの間に収められるため、モデルは常にスライダーにある位置を提案します。スライダーの代表のスコアは位置の完全一致の正解率で、6という予測は、答えが7であれば不正解と数えられます。予測が表示するのに十分近いかどうかを判断する前に、正解率と並べて、平均絶対誤差であるval_maeまたはtest_maeを読んでください。実行には、train分割に10個以上のラベル付き分子が必要です。model、class_weight、radius、bitsなどのパラメーターは、Trainingページの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を指定しています。実行は、近いものから1対1で対応づけたピークについて、適合率、再現率、F1をスコアリングし、対応づけられたピークの平均のずれを報告します。アノテーターがピークなしと答えたスペクトルに予測されたピークは、誤検出と数えられます。実行には、train分割に5つ以上のアノテーション済みのスペクトルが必要です。

各スペクトルは、ネットワークに渡す前にlength個の点(既定では4,096)にリサンプリングされます。ピークがx範囲の1/lengthより狭い場合、たとえば高分解能の質量スペクトルでは、lengthを大きくしてください。mzMLファイルを読み込むには、スペクトルディスプレイのページで説明されているとおり、spectra extraも必要です。

signal-cnnによる睡眠段階と信号の区間

signal-cnnは、EEG、ECG、EMG、加速度計のデータなどの生理信号やセンサーの記録に対するinterval_labelの答えから学習します。ネットワークは、小さなInceptionTimeモデルで、3種類の幅の並列な畳み込みを積み重ねたものです。トレーナーは、各記録を時系列ディスプレイのリーダーで読み込み、ディスプレイが指定するチャンネルを取り、各チャンネルをrate(既定では100 Hz)にリサンプリングし、各チャンネルを標準化します。PyTorchと、WFDBとParquetの記録のためのsignals extraをインストールします。

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

次の設定は、人間の判定者が各エポックを覚醒、N1、N2、N3、REMのいずれかに分類するのと同じやり方で、一晩のポリソムノグラフィーの睡眠を、30秒のエポックで判定します。

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が、タスクを決めます。エポックモードでは、アノテーション済みの各エポックが1つの学習例になり、モデルは新しい記録の各エポックにラベルを付けます。エポックの長さの半分に満たない長さしか記録されていない最後のエポックには、ラベルが付きません。ホールドアウトの代表値は、アノテーション済みのエポックにわたるマクロF1です。マクロF1は、N1のようなまれな段階に、N2と同じ重みを与えます。既定では、学習の損失は、各段階をラベルの頻度の逆数で重み付けし(balanced: true)、そのためN1のエポックは、N2のエポックより重く数えられます。すべての記録は、最初の学習用の記録のチャンネルを、その順序で使って読み込まれ、学習用のチャンネルのいずれかを欠く記録では、欠けたチャンネルの代わりに平坦な線が使われます。チャンネルを指定していないディスプレイでは、トレーナーに、max_channels(16)までのすべてのチャンネルが渡されます。

自由モードでは、代わりにマークされた区間で学習します。同じsignal_fieldに、mode: freeの2つ目の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}

自由モードでは、ネットワークは、ランダムな30秒のクロップ(window_s)で学習し、1秒のフレーム(frame_s)ごとにラベルを付け、同じラベルが連続するフレームを1つの区間に統合し、min_durationより短い区間を捨てます。代表値は区間F1で、予測した区間を、同じラベルのアノテーションの区間とIoU 0.5で対応づけます。上のspikeラベルのような点のイベントは、学習も予測もされません。実行には、train分割に3つ以上のアノテーション済みの記録が必要で、エポックモードでは、データに2つ以上の異なるラベルも必要です。

モデルの区間は、記録の上に破線の事前アノテーションとして「· from the model」が付いて一覧に載り、アノテーターが最初の変更をするか、「Keep these」をクリックしたときに保存されます。記録自身のアノテーションが優先されます。ヒプノグラムを含むEDF+ファイルでは、モデルの区間ではなくヒプノグラムが表示され、preannotation_fieldの区間を持つ項目も同様です。スキームにpre_annotations: falseを設定すると、両方がオフになります。

episode-modelによるロボットのエピソード

episode-modelは、episode_annotationの答えの3つの層、つまりフェーズ、結果、進捗の報酬曲線を学習します。モデルの入力は、関節位置やグリッパーの状態など、エピソードが記録するすべての数値系列で、フレームごとに1行です。入力には、series_shownが画面に表示しない系列も含まれ、動画は含まれません。モデルは、カメラにしか映らず、ロボットの測定値が変わらないフェーズを見つけることができません。PyTorchと、LeRobotのデータセット用のpyarrow、またはHDF5のデモ用のh5pyをインストールします。

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

次の設定は、ロボットのエピソードのページで説明されているとおり、ピックアンドプレースのデモンストレーションで3つの層をすべて収集します。

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に渡し、3つのヘッドを持ちます。1つは各フレームのフェーズ、1つはエピソードの結果、1つは各フレームの報酬を出力します。答えが空のままにした層は、そのエピソードの損失から除外されるため、結果だけがマークされたエピソードでも、結果のヘッドは学習されます。結果のヘッドは、学習用のエピソードに2つ以上の異なる結果が含まれるようになると、学習を始めます。報酬のヘッドは、アノテーターが曲線を描いた区間でだけ学習します。その区間の外では、アノテーターが答えを与えていないためです。max_frames(2,000)より長いエピソードは、ランダムなクロップで学習します。

予測は、保存される答えと同じ形をしており、スキームが表示する層について、スキームのリストから選んだフェーズ、スキームの結果の1つ、reward_range内の50点の報酬曲線を持ちます。予測はタイムラインにあらかじめ入力され、アノテーターがタイムラインを編集すると保存されます。失敗の原因と指示は、アノテーターに任されます。ホールドアウトの代表値は、0.1秒のグリッドでのフェーズのマクロF1です。実行は、IoU 0.5でのフェーズ区間のF1、結果の正解率、曲線が描かれている場合の報酬曲線のピアソンのrも報告します。フェーズのないスキームでは、結果の正解率が代表値になり、次に報酬の相関が代表値になります。実行には、train分割に5つ以上のアノテーション済みのエピソードが必要です。Potatoには、rollout_evaluationのトレーナーはありません。ロールアウトの選好は動画を比較するもので、episode-modelは動画を読み込まないためです。

実行を開始して結果を確認する

model_trainingを追加したら、サーバーを再起動します。

bash
potato start config.yaml -p 8000

/admin/trainingを開き、スキームとトレーナーを選び、Train nowをクリックします。このページには、各スキームに合うトレーナーだけが表示され、インストールされていないトレーナーは、インストールコマンドとともに「not installed」と表示されます。実行が終了すると、Potatoは実行テーブルにホールドアウトスコアを表示し、分割と指標はツールチップに示されます。実行のページには、すべての指標と、各項目の分割が表示されます。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