Zum Inhalt springen

Modelle auf Molekülen, Spektren, Signalen und Roboter-Episoden trainieren

rdkit-property, peak-cnn, signal-cnn und episode-model auf den Antworten deiner Studie anpassen und die Vorhersagen den Annotatoren zum Übernehmen oder Korrigieren zeigen.

Neu in v2.14.0

Dieses Tutorial trainiert vier Modelle auf wissenschaftlichen Annotationen einer Potato-Studie. rdkit-property beantwortet eine Frage zu einem Molekül, peak-cnn wählt Peaks auf NMR-, IR-, Raman-, UV-Vis- und Massenspektren aus, signal-cnn stuft Schlafepochen ein oder markiert gelabelte Intervalle auf EEG-, EKG- und Sensoraufnahmen, und episode-model markiert die Phasen, das Ergebnis und die Reward-Kurve einer Roboter-Episode. Die Seite richtet sich an Teams, die bereits Moleküle, Spektren, Signale oder Roboter-Episoden in Potato annotieren und ein Modell wollen, das die übrigen Items vorlabelt. Zu jedem Trainer enthält die Seite eine Konfiguration, die potato validate config.yaml --strict besteht, was ein Lauf lernt, den Holdout-Score, der gelesen werden sollte, und den Weg der Vorhersagen zu den Annotatoren. Dein erstes Modell trainieren führt mit einem Textklassifikator durch die Training-Seite, und dieses Tutorial setzt diese Anleitung voraus.

Die vier Trainer

Jeder Trainer lernt aus den Antworten eines Annotationsschemas und liest die Eingabe des Modells aus dem Display oder der Datei, auf die das Schema zeigt:

TrainerSchemaEingabeInstallationHoldout-Hauptscore
rdkit-propertyradio, select, likert oder slider zu einem Molekülein molecule-Display-Feld oder eine chemical_structure-Antwortpotato-annotation[chemistry]Macro-F1, bei einem Slider die Genauigkeit der exakten Position
peak-cnnpeak_pickingSpektren aus JCAMP-DX, mzML, CSV oder Inline-Datenpotato-annotation[train-vision]Peak-F1 innerhalb einer Toleranz
signal-cnninterval_label, im Modus epoch oder freeSignale aus EDF, BDF, WFDB, CSV, Parquet oder Inline-Datenpotato-annotation[train-vision], dazu [signals] für WFDB und ParquetMacro-F1 über Epochen oder Intervall-F1 bei IoU 0,5
episode-modelepisode_annotationRoboter-Episoden aus LeRobot, HDF5 oder dem Episoden-Manifest von Potatopotato-annotation[train-vision]Phasen-Macro-F1

rdkit-property passt ein scikit-learn-Modell an, und die anderen drei Trainer passen kleine PyTorch-Netze auf dem Gerät an, das model_training.device auswählt. Jeder Lauf trainiert auf einem Schema, bewertet das Modell auf den zurückgehaltenen val- und test-Items des Laufs und schreibt nur für die Schemas unter writeback.schemas Vorhersagen zurück. Items im Train-Split eines Laufs erhalten nie ein Vorlabel. peak-cnn, signal-cnn und episode-model behalten den Checkpoint der Epoche, die auf val am besten abschnitt, deshalb nimmt Potato den Holdout-Score der drei Netze von test. Eine kleine Studie hat wenige Test-Items, und die Metrik test_n des Laufs nennt die Zahl hinter dem Score.

Die drei Netze geben der Vorhersage für ein Item die Konfidenz des unsichersten Teils der Vorhersage, d. h. des schwächsten Peaks, Intervalls, der schwächsten Phase oder des schwächsten Ergebnisses. Eine Untergrenze writeback.min_confidence hält deshalb das ganze Item zurück, wenn irgendein Teil unter der Untergrenze liegt. Eine Slider-Vorhersage von rdkit-property hat keinen Konfidenzwert und passiert die Untergrenze immer.

Moleküleigenschaften mit rdkit-property

rdkit-property lernt die Antworten auf eine Frage zu dem Molekül, das ein Item zeigt, und die Struktur des Moleküls ist die Eingabe des Modells statt des Texts des Items. Jede Struktur wird zu einem Morgan-Fingerprint mit Radius 2 und 2.048 Bit, gefolgt von den 2D-Deskriptoren von RDKit, z. B. Molekulargewicht und logP. Bei Radius 2 entspricht der Morgan-Fingerprint ECFP4, dem Extended-Connectivity-Fingerprint mit einem Durchmesser von 4 Bindungen. Eine logistische Regression passt standardmäßig eine radio-, select- oder likert-Antwort an, und eine Ridge-Regression passt einen slider an. Ein Random Forest ist für beide die Alternative. Installiere RDKit auf dem Server, der trainiert:

bash
pip install "potato-annotation[chemistry]"

Die folgende Konfiguration zeigt jede Verbindung als Strukturzeichnung und stellt zwei Fragen, einen Slider von 1 bis 10 dazu, wie schwer die Verbindung herzustellen wäre, und eine Ja-oder-Nein-Wahl dazu, ob die Verbindung als Nächstes hergestellt wird:

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

Jede Zeile der Datendatei enthält eine Verbindung:

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

Der Trainer nimmt die Struktur aus dem ersten molecule-Feld in instance_display. Eine Studie ohne molecule-Feld kann die Frage stattdessen neben einem chemical_structure-Schema stellen, und der Trainer lernt dann aus der Struktur, die Annotatoren gezeichnet haben. Haben Annotatoren verschiedene Strukturen gezeichnet, nimmt der Trainer die häufigste Zeichnung, und hat niemand gezeichnet, nimmt der Trainer die Struktur, mit der der Editor gestartet ist (prefill_field). Die Training-Seite bietet rdkit-property für ein molecule-Feld an, sobald mindestens 80 % der Items das Feld füllen. Eine Struktur, die RDKit nicht parsen kann, bleibt beim Training außen vor, erhält keine Vorhersage und wird in der Metrik n_unparseable des Laufs gezählt. Bei einer Auswahl- oder Likert-Frage bietet die Training-Seite auch die Text-Trainer an, weil die Items weiterhin Text haben.

Die Vorhersage für einen Slider wird auf den step des Sliders gerundet und zwischen min_value und max_value gehalten, deshalb schlägt das Modell immer eine Position vor, die der Slider hat. Der Hauptscore für einen Slider ist die Genauigkeit der exakten Position, die eine Vorhersage von 6 bei einer Antwort von 7 als falsch zählt. Lies val_mae oder test_mae, den mittleren absoluten Fehler, neben der Genauigkeit, bevor entschieden wird, ob die Vorhersagen nah genug zum Anzeigen sind. Der Lauf braucht mindestens 10 gelabelte Moleküle im Train-Split. Parameter wie model, class_weight, radius und bits stehen im Feld Parameters der Training-Seite oder in der API-Anfrage:

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"}}'

Das Log des Laufs nennt die Zahl der Moleküle, die Zahl der Features und das Modell. Ein ausgeglichenes Klassengewicht zählt seltene Antworten stärker. Verwende class_weight: balanced, wenn die meisten Verbindungen dieselbe Antwort erhalten. Sobald der Lauf endet, findet ein Annotator, der eine ungelabelte Verbindung öffnet, den Slider auf dem Wert des Modells und die Wahl des Modells ausgewählt vor, beides bearbeitbar.

Peaks auf Spektren mit peak-cnn

peak-cnn lernt, Peaks aus den peak_picking-Antworten der Annotatoren auszuwählen. Der Trainer liest das Spektrum, das das viewer_field des Schemas zeigt, mit denselben Readern wie das Spektren-Display. Das Trainingsziel ist jeder Peak, den der Annotator behalten hat, übernommene Vorschläge und hinzugefügte Peaks gleichermaßen. Ein vom Annotator abgelehnter Vorschlag markiert eine Stelle ohne Peak. Das Modell sagt nur Peak-Positionen voraus, und Annotatoren geben jedem Peak weiterhin eine Zuordnung, ein Label oder eine Multiplizität. Installiere PyTorch auf der Maschine, die trainiert:

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

Die folgende Konfiguration lässt Annotatoren vorgeschlagene Peaks auf 1H-NMR-Spektren prüfen:

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

Die Peaks des Modells erreichen Annotatoren nur über die Einstellung prepick des Displays, und ohne prepick zeigt das Display gar keine Vorschläge. Unter method: auto bietet das Display zuerst die eigenen peaks des Items an, dann die Peaks des Modells, dann die Peak-Tabelle der Datei und dann die Peaks, die der Detektor findet. Vor dem ersten Lauf prüfen Annotatoren die Vorschläge des Detektors, und nach einem Lauf zeigen dieselben Items die Peaks des Modells ohne Neustart. Mit method: model zeigt das Display nur die Peaks des Modells. Verwende method: model, um zu messen, wie oft Annotatoren die Peaks des Modells ohne die beigemischten Vorschläge des Detektors übernehmen. Jeder Modell-Peak ist mit „· model“ aufgeführt, das Display behält die max_peaks sichersten, und ein übernommener Modell-Peak wird mit source: model gespeichert.

Der Parameter tolerance legt fest, wie weit, in x-Einheiten, ein vorhergesagter Peak von einem annotierten Peak entfernt liegen darf und trotzdem als gefunden zählt. Ohne tolerance verwendet der Lauf 0,5 % des x-Bereichs des Spektrums, und 0,5 % eines 1H-Fensters von 12 ppm sind 0,06 ppm. Bei 400 MHz trennt eine Kopplung von 7 Hz die Linien eines Multipletts um 0,0175 ppm. Die Standardtoleranz ist weiter als dieser Abstand, und die Konfiguration setzt stattdessen 0,02 ppm. Der Lauf bewertet Precision, Recall und F1 über Peaks, die eins zu eins zugeordnet werden, der nächste zuerst, und meldet den mittleren Versatz der zugeordneten Peaks. Ein Peak, der auf einem Spektrum vorhergesagt wird, von dem der Annotator sagte, es habe keine Peaks, zählt als Fehlalarm. Der Lauf braucht mindestens 5 annotierte Spektren im Train-Split.

Jedes Spektrum wird auf length Punkte umgetastet, standardmäßig 4.096, bevor das Netz das Spektrum sieht. Erhöhe length, wenn Peaks schmaler sind als 1/length des x-Bereichs, z. B. bei einem hochauflösenden Massenspektrum. Das Lesen von mzML-Dateien braucht außerdem das Extra spectra, wie die Seite zum Spektren-Display beschreibt.

Schlafstadien und Signalintervalle mit signal-cnn

signal-cnn lernt aus interval_label-Antworten auf physiologischen und Sensoraufnahmen wie EEG, EKG, EMG und Beschleunigungsdaten. Das Netz ist ein kleines InceptionTime-Modell, ein Stapel paralleler Faltungen dreier Breiten. Der Trainer liest jede Aufnahme mit den Readern des Zeitreihen-Displays, nimmt die Kanäle, die das Display nennt, tastet jeden Kanal auf rate um (standardmäßig 100 Hz) und standardisiert jeden Kanal. Installiere PyTorch und das Extra signals für WFDB- und Parquet-Aufnahmen:

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

Die folgende Konfiguration bewertet Schlaf in 30-Sekunden-Epochen auf nächtlicher Polysomnographie, so wie ein menschlicher Scorer jede Epoche als Wach, N1, N2, N3 oder REM labelt:

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

Der mode des Schemas legt die Aufgabe fest. Im Modus epoch ist jede annotierte Epoche ein Trainingsbeispiel, und das Modell labelt jede Epoche einer neuen Aufnahme. Eine letzte Epoche, von der weniger als die halbe Epochenlänge aufgezeichnet ist, erhält kein Label. Der Holdout-Hauptscore ist der Macro-F1 über die annotierten Epochen. Der Macro-F1 gibt einem seltenen Stadium wie N1 dasselbe Gewicht wie N2. Standardmäßig gewichtet der Trainingsverlust jedes Stadium nach der inversen Label-Häufigkeit (balanced: true), und eine N1-Epoche zählt dann mehr als eine N2-Epoche. Jede Aufnahme wird in dieser Reihenfolge mit den Kanälen der ersten Trainingsaufnahme gelesen, und eine Aufnahme, der einer der Trainingskanäle fehlt, erhält anstelle des fehlenden Kanals eine flache Linie. Ein Display, das keine Kanäle nennt, gibt dem Trainer jeden Kanal, bis zu max_channels (16).

Der Modus free trainiert stattdessen auf markierten Abschnitten. Füge demselben signal_field ein zweites interval_label-Schema mit mode: free hinzu, um Artefakte oder Arousals zu markieren:

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}

Im Modus free trainiert das Netz auf zufälligen 30-Sekunden-Ausschnitten (window_s), labelt jeden 1-Sekunden-Frame (frame_s), verschmilzt aufeinanderfolgende Frames mit einem Label zu einem Intervall und verwirft Intervalle, die kürzer als min_duration sind. Der Hauptscore ist der Intervall-F1, der ein vorhergesagtes Intervall mit einem annotierten Intervall desselben Labels bei IoU 0,5 abgleicht. Punktereignisse wie das Label spike oben werden weder trainiert noch vorhergesagt. Der Lauf braucht mindestens 3 annotierte Aufnahmen im Train-Split, und der Modus epoch braucht außerdem mindestens zwei verschiedene Labels in den Daten.

Die Intervalle des Modells erscheinen auf der Aufnahme als gestrichelte Vorannotationen, aufgeführt mit „· from the model“, und werden gespeichert, wenn der Annotator eine erste Änderung vornimmt oder auf „Keep these“ klickt. Die eigenen Annotationen einer Aufnahme haben Vorrang. Eine EDF+-Datei, die ein Hypnogramm enthält, zeigt das Hypnogramm und nicht die Intervalle des Modells, und dasselbe gilt für ein Item mit Intervallen aus preannotation_field. Mit pre_annotations: false am Schema werden beide Arten abgeschaltet.

Roboter-Episoden mit episode-model

episode-model lernt die drei Ebenen einer episode_annotation-Antwort, d. h. die Phasen, das Ergebnis und die Fortschritts-Reward-Kurve. Die Eingabe des Modells ist jede numerische Reihe, die die Episode aufzeichnet, etwa Gelenkpositionen und Greiferzustand, mit einer Zeile pro Frame. Die Eingabe enthält Reihen, die series_shown vom Bildschirm ausnimmt, und schließt das Video aus. Das Modell kann keine Phase finden, die nur auf der Kamera sichtbar ist und die Messwerte des Roboters unverändert lässt. Installiere PyTorch sowie pyarrow für LeRobot-Datensätze oder h5py für HDF5-Demos:

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

Die folgende Konfiguration erhebt alle drei Ebenen auf Pick-and-place-Demonstrationen, wie die Seite zu Roboter-Episoden beschreibt:

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

Das Netz führt eine Faltung über die Reihen in ein bidirektionales GRU mit drei Köpfen, einem für die Phase jedes Frames, einem für das Ergebnis der Episode und einem für den Reward an jedem Frame. Eine Ebene, die eine Antwort leer lässt, bleibt im Verlust dieser Episode außen vor, deshalb trainiert eine Episode, die nur mit einem Ergebnis markiert ist, trotzdem den Ergebnis-Kopf. Der Ergebnis-Kopf trainiert, sobald die Trainings-Episoden mindestens zwei verschiedene Ergebnisse enthalten. Der Reward-Kopf trainiert nur auf dem Abschnitt, in dem der Annotator die Kurve gezeichnet hat, weil der Annotator außerhalb dieses Abschnitts keine Antwort gegeben hat. Episoden, die länger als max_frames (2.000) sind, werden auf zufälligen Ausschnitten trainiert.

Die Vorhersage hat die Form einer gespeicherten Antwort, mit Phasen aus der Liste des Schemas, einem der Ergebnisse des Schemas und einer Reward-Kurve aus 50 Punkten innerhalb von reward_range, für die Ebenen, die das Schema zeigt. Die Vorhersage füllt die Zeitleiste vor und wird gespeichert, sobald der Annotator die Zeitleiste bearbeitet. Die Fehlerursache und die Anweisung bleiben dem Annotator überlassen. Der Holdout-Hauptscore ist der Phasen-Macro-F1 auf einem 0,1-Sekunden-Raster. Der Lauf meldet außerdem den Phasen-Segment-F1 bei IoU 0,5, die Ergebnis-Genauigkeit und Pearsons r der Reward-Kurve, wo eine Kurve gezeichnet wurde. Ein Schema ohne Phasen nimmt die Ergebnis-Genauigkeit als Hauptscore und danach die Reward-Korrelation. Der Lauf braucht mindestens 5 annotierte Episoden im Train-Split. Potato hat keinen Trainer für rollout_evaluation, weil eine Rollout-Präferenz Videos vergleicht und episode-model kein Video liest.

Einen Lauf starten und das Ergebnis prüfen

Starte den Server nach dem Hinzufügen von model_training neu:

bash
potato start config.yaml -p 8000

Öffne /admin/training, wähle das Schema und den Trainer und klicke auf Train now. Die Seite bietet nur die Trainer an, die zu jedem Schema passen, und ein nicht installierter Trainer wird als „not installed“ mit dem Installationsbefehl aufgeführt. Wenn der Lauf endet, führt Potato den Holdout-Score in der Runs-Tabelle auf, mit dem Split und der Metrik in einem Tooltip. Die Lauf-Seite listet jede Metrik und den Split jedes Items. Der Rückschreibe-Bericht in GET /admin/training/api/status zählt die veröffentlichten Vorhersagen und die, die jede Prüfung zurückgehalten hat, z. B. n_skipped_low_confidence.

Vorlabels verankern Annotatoren an der Antwort des Modells, deshalb schalte include_unlabeled in einer neuen Studie erst ein, wenn der Holdout-Score das Zeigen der Modellantworten stützt. Vorlabels des Modells im Kreislauf prüfen behandelt, wie die Vorlabels mit der geringsten Konfidenz zuerst ausgeliefert werden und wie auf Korrekturen neu trainiert wird, und Konfiguration listet jeden Schlüssel, die Backends und die Rückschreibe-Prüfungen auf.

Referenzen

  • 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