Vai al contenuto

Addestrare modelli su molecole, spettri, segnali ed episodi robotici

Addestra rdkit-property, peak-cnn, signal-cnn ed episode-model sulle risposte del tuo studio, poi mostra le predizioni agli annotatori perché le accettino o le correggano.

Novità nella v2.14.0

Questo tutorial addestra quattro modelli su annotazioni scientifiche in uno studio Potato. rdkit-property risponde a una domanda su una molecola, peak-cnn individua i picchi negli spettri NMR, IR, Raman, UV-Vis e di massa, signal-cnn classifica in stadi le epoche del sonno o marca intervalli etichettati su registrazioni EEG, ECG e di sensori, ed episode-model marca le fasi, l'esito e la curva di ricompensa di un episodio robotico. La pagina è pensata per i team che annotano già molecole, spettri, segnali o episodi robotici in Potato e vogliono un modello che pre-etichetti gli item rimanenti. Per ogni trainer la pagina offre una configurazione che supera potato validate config.yaml --strict, che cosa impara un'esecuzione, il punteggio held-out da leggere e come le predizioni arrivano agli annotatori. Addestra il tuo primo modello illustra la pagina Training con un classificatore di testo, e questo tutorial presuppone quel percorso.

I quattro trainer

Ogni trainer impara dalle risposte di uno schema di annotazione e legge l'input del modello dal display o dal file a cui lo schema punta:

TrainerSchemaInputInstallazionePunteggio principale held-out
rdkit-propertyradio, select, likert o slider su una molecolaun campo di display molecule, o una risposta chemical_structurepotato-annotation[chemistry]macro-F1, o accuratezza sulla posizione esatta per uno slider
peak-cnnpeak_pickingspettri da JCAMP-DX, mzML, CSV o dati inlinepotato-annotation[train-vision]F1 dei picchi entro una tolleranza
signal-cnninterval_label, in modalità epoch o freesegnali da EDF, BDF, WFDB, CSV, Parquet o dati inlinepotato-annotation[train-vision], più [signals] per WFDB e Parquetmacro-F1 sulle epoche, o F1 degli intervalli a IoU 0.5
episode-modelepisode_annotationepisodi robotici da LeRobot, HDF5 o dal manifest di episodi di Potatopotato-annotation[train-vision]macro-F1 delle fasi

rdkit-property adatta un modello scikit-learn, e gli altri tre trainer addestrano piccole reti PyTorch sul dispositivo selezionato da model_training.device. Ogni esecuzione si addestra su uno schema, valuta il modello sugli item val e test esclusi dall'addestramento e scrive le predizioni solo per gli schemi elencati in writeback.schemas. Gli item nella partizione train di un'esecuzione non ricevono mai una pre-etichetta. peak-cnn, signal-cnn ed episode-model conservano il checkpoint dell'epoca che ha ottenuto il punteggio migliore su val, quindi Potato prende da test il punteggio held-out delle tre reti. Uno studio piccolo ha pochi item di test, e la metrica test_n dell'esecuzione riporta il numero su cui si basa il punteggio.

Le tre reti attribuiscono alla predizione di un item la confidenza della parte meno certa della predizione, cioè il picco, l'intervallo, la fase o l'esito più debole. Una soglia writeback.min_confidence trattiene quindi l'intero item quando una qualsiasi parte scende sotto la soglia. La predizione di uno slider da rdkit-property non ha un valore di confidenza e supera sempre la soglia.

Proprietà molecolari con rdkit-property

rdkit-property impara le risposte a una domanda sulla molecola mostrata da un item, e l'input del modello è la struttura della molecola anziché il testo dell'item. Ogni struttura diventa un fingerprint di Morgan di raggio 2 e 2.048 bit seguito dai descrittori 2D di RDKit, per esempio il peso molecolare e il logP. A raggio 2 il fingerprint di Morgan coincide con ECFP4, il fingerprint a connettività estesa con un diametro di 4 legami. Per impostazione predefinita una regressione logistica adatta una risposta radio, select o likert, e una regressione ridge adatta uno slider. Una random forest è l'alternativa per entrambi. Installa RDKit sul server che addestra:

bash
pip install "potato-annotation[chemistry]"

La configurazione seguente mostra ogni composto come disegno della struttura e pone due domande, uno slider da 1 a 10 sulla difficoltà di sintesi del composto e una scelta sì-o-no sull'opportunità di sintetizzarlo come prossimo:

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

Ogni riga del file di dati contiene un composto:

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

Il trainer prende la struttura dal primo campo molecule in instance_display. Uno studio senza un campo molecule può porre la domanda accanto a uno schema chemical_structure, e il trainer impara allora dalla struttura che gli annotatori hanno disegnato. Quando gli annotatori hanno disegnato strutture diverse, il trainer prende il disegno più frequente, e quando nessuno ha disegnato, prende la struttura da cui l'editor era partito (prefill_field). La pagina Training propone rdkit-property per un campo molecule solo quando almeno l'80% degli item compila il campo. Una struttura che RDKit non riesce a interpretare resta fuori dall'addestramento, non riceve predizione e viene contata nella metrica n_unparseable dell'esecuzione. Per una domanda a scelta o Likert, la pagina Training propone anche i trainer di testo, perché gli item hanno comunque un testo.

La predizione di uno slider viene arrotondata allo step dello slider e mantenuta tra min_value e max_value, quindi il modello propone sempre una posizione che lo slider possiede. Il punteggio principale per uno slider è l'accuratezza sulla posizione esatta, che conta come errata una predizione di 6 contro una risposta di 7. Leggi val_mae o test_mae, l'errore assoluto medio, accanto all'accuratezza prima di decidere se le predizioni sono abbastanza vicine da essere mostrate. L'esecuzione richiede almeno 10 molecole etichettate nella partizione train. Parametri come model, class_weight, radius e bits vanno nel campo Parameters della pagina Training o nella richiesta all'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"}}'

Il log dell'esecuzione riporta il numero di molecole, il numero di feature e il modello. Un peso di classe bilanciato conta di più le risposte rare. Usa class_weight: balanced quando la maggior parte dei composti riceve la stessa risposta. Quando l'esecuzione termina, un annotatore che apre un composto non etichettato trova lo slider impostato sul valore del modello e la scelta del modello selezionata, entrambi modificabili.

Picchi sugli spettri con peak-cnn

peak-cnn impara a individuare i picchi dalle risposte peak_picking date dagli annotatori. Il trainer legge lo spettro mostrato dal viewer_field dello schema, con gli stessi lettori usati dal display degli spettri. Il bersaglio di addestramento è ogni picco che l'annotatore ha conservato, suggerimenti accettati e picchi aggiunti inclusi. Un suggerimento che l'annotatore ha rifiutato indica un punto senza picco. Il modello predice solo le posizioni dei picchi, e gli annotatori continuano a dare a ogni picco un'assegnazione, un'etichetta o una molteplicità. Installa PyTorch sulla macchina che addestra:

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

La configurazione seguente chiede agli annotatori di rivedere i picchi suggeriti su spettri NMR 1H:

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

I picchi del modello arrivano agli annotatori solo tramite l'impostazione prepick del display, e senza prepick il display non mostra alcun suggerimento. Con method: auto, il display offre prima i peaks propri dell'item, poi i picchi del modello, poi la tabella dei picchi del file e infine i picchi trovati dal detector. Prima della prima esecuzione gli annotatori rivedono i suggerimenti del detector, e dopo un'esecuzione gli stessi item mostrano i picchi del modello senza riavvio. Con method: model, il display mostra solo i picchi del modello. Usa method: model per misurare con quale frequenza gli annotatori accettano i picchi del modello senza i suggerimenti del detector mescolati. Ogni picco del modello è elencato con "· model", il display conserva i max_peaks più confidenti, e un picco del modello accettato viene memorizzato con source: model.

Il parametro tolerance stabilisce a quale distanza, in unità di x, un picco predetto può trovarsi da un picco annotato ed essere ancora considerato trovato. Senza tolerance, l'esecuzione usa lo 0,5% dell'intervallo x dello spettro, e lo 0,5% di una finestra 1H di 12 ppm è 0,06 ppm. A 400 MHz, un accoppiamento di 7 Hz separa le righe di un multipletto di 0,0175 ppm. La tolleranza predefinita è più ampia di quella spaziatura, e la configurazione imposta invece 0,02 ppm. L'esecuzione calcola precisione, richiamo e F1 sui picchi associati uno a uno, partendo dal più vicino, e riporta lo scostamento medio dei picchi associati. Un picco predetto su uno spettro che l'annotatore ha dichiarato privo di picchi conta come falso allarme. L'esecuzione richiede almeno 5 spettri annotati nella partizione train.

Ogni spettro viene ricampionato a length punti, 4.096 per impostazione predefinita, prima che la rete lo veda. Aumenta length quando i picchi sono più stretti di 1/length dell'intervallo x, per esempio in uno spettro di massa ad alta risoluzione. La lettura dei file mzML richiede anche l'extra spectra, come descrive la pagina del display degli spettri.

Stadi del sonno e intervalli di segnale con signal-cnn

signal-cnn impara dalle risposte interval_label su registrazioni fisiologiche e di sensori come EEG, ECG, EMG e dati di accelerometro. La rete è un piccolo modello InceptionTime, una pila di convoluzioni parallele di tre ampiezze. Il trainer legge ogni registrazione con i lettori del display delle serie temporali, prende i canali indicati dal display, ricampiona ogni canale a rate (100 Hz per impostazione predefinita) e standardizza ogni canale. Installa PyTorch, e l'extra signals per le registrazioni WFDB e Parquet:

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

La configurazione seguente classifica il sonno in epoche di 30 secondi su una polisonnografia notturna, come fa uno scorer umano che etichetta ogni epoca come veglia, N1, N2, N3 o 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

Il mode dello schema stabilisce il compito. In modalità epoch, ogni epoca annotata è un esempio di addestramento, e il modello etichetta ogni epoca di una nuova registrazione. Un'ultima epoca con meno della metà della propria durata registrata non riceve etichetta. Il punteggio principale held-out è la macro-F1 sulle epoche annotate. La macro-F1 dà a uno stadio raro come N1 lo stesso peso di N2. Per impostazione predefinita la perdita di addestramento pesa ogni stadio per l'inverso della frequenza dell'etichetta (balanced: true), e un'epoca N1 conta quindi più di un'epoca N2. Ogni registrazione viene letta con i canali della prima registrazione di addestramento, in quell'ordine, e una registrazione che manca di uno dei canali di addestramento riceve una linea piatta al posto del canale mancante. Un display che non nomina alcun canale dà al trainer ogni canale, fino a max_channels (16).

La modalità free si addestra invece su tratti marcati. Aggiungi un secondo schema interval_label con mode: free allo stesso signal_field per marcare artefatti o risvegli:

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}

In modalità free la rete si addestra su ritagli casuali di 30 secondi (window_s), etichetta ogni frame di 1 secondo (frame_s), fonde in un intervallo i frame consecutivi con la stessa etichetta ed elimina gli intervalli più brevi di min_duration. Il punteggio principale è l'F1 degli intervalli, che associa un intervallo predetto a uno annotato con la stessa etichetta a IoU 0.5. Gli eventi puntuali come l'etichetta spike qui sopra non vengono né addestrati né predetti. L'esecuzione richiede almeno 3 registrazioni annotate nella partizione train, e la modalità epoch richiede anche almeno due etichette diverse nei dati.

Gli intervalli del modello compaiono sulla registrazione come pre-annotazioni tratteggiate, elencate con "· from the model", e vengono salvati quando l'annotatore fa una prima modifica o fa clic su "Keep these". Le annotazioni proprie di una registrazione hanno la precedenza. Un file EDF+ che contiene un ipnogramma mostra l'ipnogramma e non gli intervalli del modello, e lo stesso vale per un item con intervalli in preannotation_field. Impostare pre_annotations: false sullo schema disattiva entrambi i tipi.

Episodi robotici con episode-model

episode-model impara i tre livelli di una risposta episode_annotation, cioè le fasi, l'esito e la curva di ricompensa dell'avanzamento. L'input del modello è ogni serie numerica che l'episodio registra, come le posizioni dei giunti e lo stato della pinza, con una riga per frame. L'input include le serie che series_shown lascia fuori dallo schermo ed esclude il video. Il modello non può trovare una fase che si vede solo in camera e lascia invariate le misurazioni del robot. Installa PyTorch, più pyarrow per i dataset LeRobot o h5py per le dimostrazioni HDF5:

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

La configurazione seguente raccoglie tutti e tre i livelli su dimostrazioni di pick-and-place, come descrive la pagina sugli episodi robotici:

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

La rete esegue una convoluzione sulle serie verso una GRU bidirezionale con tre teste, una per la fase di ogni frame, una per l'esito dell'episodio e una per la ricompensa a ogni frame. Un livello che una risposta lascia vuoto viene escluso dalla perdita di quell'episodio, quindi un episodio marcato solo con un esito addestra comunque la testa dell'esito. La testa dell'esito si addestra quando gli episodi di addestramento includono almeno due esiti diversi. La testa della ricompensa si addestra solo sul tratto in cui l'annotatore ha disegnato la curva, perché fuori da quel tratto l'annotatore non ha dato alcuna risposta. Gli episodi più lunghi di max_frames (2.000) vengono addestrati su ritagli casuali.

La predizione ha la forma di una risposta memorizzata, con fasi tratte dall'elenco dello schema, uno degli esiti dello schema e una curva di ricompensa di 50 punti dentro reward_range, per i livelli che lo schema mostra. La predizione precompila la timeline e viene memorizzata quando l'annotatore modifica la timeline. La causa del fallimento e l'istruzione sono lasciate all'annotatore. Il punteggio principale held-out è la macro-F1 delle fasi su una griglia di 0,1 secondi. L'esecuzione riporta anche l'F1 dei segmenti di fase a IoU 0.5, l'accuratezza dell'esito e la r di Pearson della curva di ricompensa dove è stata disegnata una curva. Uno schema senza fasi prende come punteggio principale l'accuratezza dell'esito, e poi la correlazione della ricompensa. L'esecuzione richiede almeno 5 episodi annotati nella partizione train. Potato non ha un trainer per rollout_evaluation, perché una preferenza sui rollout confronta video ed episode-model non legge video.

Avviare un'esecuzione e controllare il risultato

Riavvia il server dopo aver aggiunto model_training:

bash
potato start config.yaml -p 8000

Apri /admin/training, scegli lo schema e il trainer e fai clic su Train now. La pagina propone solo i trainer adatti a ciascuno schema, e un trainer non installato è elencato come "not installed", con il comando di installazione. Quando l'esecuzione termina, Potato elenca il punteggio held-out nella tabella Runs, con la partizione e la metrica in un tooltip. La pagina dell'esecuzione elenca ogni metrica e la partizione di ciascun item. Il rapporto di write-back in GET /admin/training/api/status conta le predizioni pubblicate e quelle che ogni controllo ha trattenuto, per esempio n_skipped_low_confidence.

Le pre-etichette ancorano gli annotatori alla risposta del modello, quindi attiva include_unlabeled in un nuovo studio solo quando il punteggio held-out giustifica la visualizzazione delle risposte del modello. Rivedere le pre-etichette del modello nel ciclo tratta come servire per prime le pre-etichette con la confidenza più bassa e come riaddestrare sulle correzioni, e Configurazione elenca ogni chiave, i backend e i controlli del write-back.

Ulteriori letture

Riferimenti

  • 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