Ich werde zeigen, wie man mithilfe eines einfachen PyTorch-Klassifikators ein Faltungs-Neuronales Netzwerk aufbauen kann, das in der Lage ist, zwischen Krebsarten zu unterscheiden. Die für das Coaching verwendeten Daten und Codes sind öffentlich verfügbar und das Coaching kann auf einem Personalcomputer, möglicherweise sogar auf einer CPU, durchgeführt werden.
Krebs ist eine unglückliche Nebenwirkung unserer Zellen, die im Laufe unseres Lebens Informationsfehler anhäufen, was zu einem unkontrollierten Wachstum führt. Als Forscher untersuchen wir die Muster dieser Fehler, um die Krankheit besser zu verstehen. Aus datenwissenschaftlicher Sicht ist das menschliche Genom eine etwa drei Milliarden Buchstaben lange Zeichenfolge mit den Buchstaben A, C, G, T (additionally 2 Bits an Informationen professional Buchstabe). Ein Kopierfehler oder ein externes Ereignis kann möglicherweise einen Buchstaben entfernen/einfügen/ändern, was zu einer Mutation und möglicherweise zu einer Störung der genomischen Funktion führen kann.
Einzelne Fehler führen jedoch quick nie zur Krebsentstehung. Der menschliche Körper verfügt über mehrere Mechanismen, um die Entstehung von Krebs zu verhindern, darunter spezielle Proteine – die sogenannten Tumorsuppressoren. Eine Liste notwendiger Bedingungen – die sogenannten „Merkmale von Krebs“ Damit eine Zelle nachhaltig wachsen kann, müssen diese Voraussetzungen erfüllt sein.

Daher reichen Veränderungen an einzelnen Buchstaben der DNA meist nicht aus, um ein selbsttragendes proliferatives Wachstum auszulösen. Die überwiegende Mehrheit der mutationsvermittelten Krebsarten (im Gegensatz zum Beispiel zu anderen Krebsquellen). das HPV-Virus) weisen ebenfalls Änderungen bei der Kopiennummer (CN) auf. Hierbei handelt es sich um groß angelegte Ereignisse, bei denen oft Millionen von DNA-Basen gleichzeitig hinzugefügt oder entfernt werden.

Diese enormen Veränderungen in der Struktur des Genoms führen zum Verlust von Genen, die die Entstehung von Krebs verhindern würden, während sich gleichzeitig Gene ansammeln, die das Zellwachstum fördern. Durch die Sequenzierung der DNA dieser Zellen können wir diese Veränderungen identifizieren, die häufig in krebstypspezifischen Regionen auftreten. Werte für die Kopienzahl für jedes Allel können mithilfe von Sequenzierungsdaten abgeleitet werden Nummer kopieren Anrufer.
Bearbeitung der Kopiernummernprofile
Einer der Vorteile der Arbeit mit Copy Quantity (CN)-Profilen besteht darin, dass sie nicht biometrisch sind und daher ohne Zugriffsbeschränkungen veröffentlicht werden können. Dadurch können wir im Laufe der Zeit Daten aus mehreren Studien sammeln, um Datensätze ausreichender Größe zu erstellen. Allerdings sind die Daten aus verschiedenen Studien nicht immer direkt vergleichbar, da sie möglicherweise mit unterschiedlichen Technologien generiert werden, unterschiedliche Auflösungen haben oder auf unterschiedliche Weise vorverarbeitet werden.
Um die Daten zu gewinnen und gemeinsam zu verarbeiten und zu visualisieren, nutzen wir das Instrument CNSistententwickelt im Rahmen der Arbeit des Institut für Computational Most cancers Biology des Universitätsklinik, Köln, Deutschland.
Zuerst klonen wir das Repository und die Daten und stellen es auf die in diesem Textual content verwendete Model ein:
git clone (e mail protected):schwarzlab/cnsistent.git
cd cnsistent
git checkout v0.9.0
Da sich die von uns verwendeten Daten im Repository befinden (ca. 1 GB Daten), dauert der Obtain einige Minuten. Zum Klonen müssen sowohl Git als auch Git LFS auf dem System vorhanden sein.
Im Repository befindet sich eine Datei „necessities.txt“, die alle Abhängigkeiten auflistet, die mit pip installiert werden können set up -r necessities.txt.
(Es wird empfohlen, zunächst eine virtuelle Umgebung zu erstellen.) Sobald die Anforderungen installiert sind, kann CNSigent durch Ausführen installiert werden pip set up -e . im selben Ordner. Der -e Flag installiert das Paket aus seinem Quellverzeichnis, was für den Zugriff auf die Daten über die API erforderlich ist.
Das Repository enthält Rohdaten aus drei Datensätzen: TCGA, PCAWGUnd TRACERx. Diese müssen zunächst vorverarbeitet werden. Dies kann durch Ausführen des Skripts erfolgen bash ./scripts/data_process.sh.
Jetzt haben wir Datensätze verarbeitet und können sie mit der Datendienstprogrammbibliothek CNSigent laden:
import cns.data_utils as cdu
samples_df, cns_df = cdu.main_load("imp")
print(cns_df.head())
Erzeugt das folgende Ergebnis:
| | sample_id | chrom | begin | finish | major_cn | minor_cn |
|---:|:------------|:--------|---------:|---------:|-----------:|-----------:|
| 0 | SP101724 | chr1 | 0 | 27256755 | 2 | 2 |
| 1 | SP101724 | chr1 | 27256755 | 28028200 | 3 | 2 |
| 2 | SP101724 | chr1 | 28028200 | 32976095 | 2 | 2 |
| 3 | SP101724 | chr1 | 32976095 | 33354394 | 5 | 2 |
| 4 | SP101724 | chr1 | 33354394 | 33554783 | 3 | 2 |
Diese Tabelle zeigt die Daten zur Kopienanzahl mit den folgenden Spalten:
sample_id: die Kennung der Probe,chrom: das Chromosom,begin: die Startposition des Segments (0-indiziert einschließlich),finish: die Endposition des Segments (0-indiziert exklusiv),major_cn: die Anzahl der Kopien des Hauptallels (das größere der beiden),minor_cn: die Anzahl der Kopien des Nebenallels (das kleinere der beiden).
In der ersten Zeile sehen wir daher einen Abschnitt, der besagt, dass Probe SP101724 2 Kopien des Hauptallels und 2 Kopien des Nebenallels (insgesamt 4) im Bereich von Chromosom 1 von 0 bis 27,26 Megabase aufweist.
Der zweite von uns geladene Datenrahmen, „Samples_df“, enthält die Metadaten für die Samples. Für unsere Zwecke ist nur der Typ wichtig. Wir können die verfügbaren Typen untersuchen, indem wir Folgendes ausführen:
import matplotlib.pyplot as plt
type_counts = samples_df("kind").value_counts()
plt.determine(figsize=(10, 6))
type_counts.plot(type='bar')
plt.ylabel('Depend')
plt.xticks(rotation=90)

Im oben gezeigten Beispiel können wir ein potenzielles Downside mit den Daten beobachten – die Längen der einzelnen Segmente sind nicht einheitlich. Das erste Phase ist 27,26 Megabasen lang, während das zweite nur 0,77 Megabasen lang ist. Dies ist ein Downside für das neuronale Netzwerk, das erwartet, dass die Eingabe eine feste Größe hat.
Wir könnten technisch gesehen alle vorhandenen Haltepunkte nehmen und Segmente zwischen allen Haltepunkten im Datensatz erstellen, sogenannte minimale konsistente Segmentierung. Dies würde jedoch zu einer großen Anzahl von Segmenten führen – eine schnelle Überprüfung mit len(cns_df(“finish”).distinctive()) zeigt, dass es welche gibt 823652 einzigartige Haltepunkte.
Alternativ können wir CNSistent verwenden, um mithilfe eines Binning-Algorithmus eine neue Segmentierung zu erstellen. Dadurch werden Segmente einer festen Größe erstellt, die als Eingabe für das neuronale Netzwerk verwendet werden können. In unserer Arbeit haben wir 1–3 Megabase-Segmente ermittelt, um den besten Kompromiss zwischen Genauigkeit und Überanpassung zu erzielen. Wir erstellen zunächst die Segmentierung und wenden sie dann an, um neue CNS-Dateien mithilfe des folgenden Bash-Skripts zu erhalten:
threads=8
cns phase entire --out "./out/segs_3MB.mattress" --split 3000000 --remove gaps - filter 300000
for dataset in TRACERx PCAWG TCGA_hg19;
do
cns combination ./out/${dataset}_cns_imp.tsv - segments ./out/segs_3MB.mattress - out ./out/${dataset}_bin_3MB.tsv - samples ./out/${dataset}_samples.tsv - threads $threads
completed
Die Schleife verarbeitet jeden Datensatz separat und behält dabei die gleiche Segmentierung bei. Der--threadsFlag wird verwendet, um den Prozess zu beschleunigen, indem die Aggregation parallel ausgeführt wird und der Wert entsprechend der Anzahl der verfügbaren Kerne angepasst wird.
Der--remove gaps --filter 300000Argumente entfernen Regionen mit geringer Kartierbarkeit (auch bekannt als: Lücken) und filtern Sie Segmente heraus, die kürzer als 300 KB sind. Der --split 3000000 Das Argument erstellt Segmente von 3 MB.
Nichtkleinzelliges Lungenkarzinom
In diesem Textual content konzentrieren wir uns auf die Klassifizierung des nichtkleinzelligen Lungenkarzinoms, das etwa 85 % aller Lungenkrebsarten ausmacht, insbesondere auf die Unterscheidung zwischen Adenokarzinom und Plattenepithelkarzinom. Es ist wichtig, zwischen den beiden zu unterscheiden, da ihre Behandlungsschemata unterschiedlich sein werden und neue Methoden Hoffnung auf einen nicht-invasiven Nachweis anhand von Blutproben oder Nasenabstrichen geben.
Wir werden die oben erzeugten Segmente verwenden und diese mithilfe einer bereitgestellten Dienstprogrammfunktion laden. Da wir zwischen zwei Krebsarten klassifizieren, können wir die Proben so filtern, dass sie nur die relevanten Arten enthalten. LUAD (Adenokarzinom) und LUSC (Plattenepithelkarzinom) und zeichnen Sie die erste Probe auf:
import cns
samples_df, cns_df = cdu.main_load("3MB")
samples_df = samples_df.question("kind in ('LUAD', 'LUSC')")
cns_df = cns.select_CNS_samples(cns_df, samples_df)
cns_df = cns.only_aut(cns_df)
cns.fig_lines(cns.cns_head(cns_df, n=3))
Haupt- und Nebenkopienummernsegmente in 3-MB-Bins für die ersten drei Beispiele. In diesem Fall stammen alle drei Proben aus der Multiregion-Sequenzierung desselben Patienten, was zeigt, wie heterogen die Proben sind Krebszellen können sich sogar innerhalb eines einzelnen Tumors befinden.
Modell eines Faltungs-Neuronalen Netzwerks
Zum Ausführen des Codes ist Python 3 mit erforderlich PyTorch 2+ installiert werden soll und eine Bash-kompatible Shell. Für ein schnelleres Coaching wird eine NVIDIA-GPU empfohlen, ist aber nicht notwendig.
Zuerst definieren wir ein Faltungs-Neuronales Netzwerk mit drei Schichten:
import torch.nn as nn
class CNSConvNet(nn.Module):
def __init__(self, num_classes):
tremendous(CNSConvNet, self).__init__()
self.conv_layers = nn.Sequential(
nn.Conv1d(in_channels=2, out_channels=16, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool1d(kernel_size=2),
nn.Conv1d(in_channels=16, out_channels=32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool1d(kernel_size=2),
nn.Conv1d(in_channels=32, out_channels=64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool1d(kernel_size=2)
)
self.fc_layers = nn.Sequential(
nn.LazyLinear(128),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(128, num_classes)
)
def ahead(self, x):
x = self.conv_layers(x)
x = x.view(x.measurement(0), -1)
x = self.fc_layers(x)
return x
Dies ist ein Boilerplate-Deep-CNN mit 2 Eingangskanälen – einer für jedes Allel – und 3 Faltungsschichten unter Verwendung eines 1D-Kernels der Größe 3 und der ReLU-Aktivierungsfunktion. Auf die Faltungsschichten folgen maximale Pooling-Schichten mit einer Kernelgröße von 2. Faltung wird traditionell für verwendet Kantenerkennungwas für uns nützlich ist, da wir an Änderungen in der Kopienzahl, additionally den Kanten der Segmente, interessiert sind.
Die Ausgabe der Faltungsschichten wird dann abgeflacht und mit Dropout durch zwei vollständig verbundene Schichten geleitet. Der LazyLinearDie Schicht verbindet die Ausgabe von 64 gestapelten Kanälen in einer Schicht mit 128 Knoten, ohne dass berechnet werden muss, wie viele Knoten am Ende der Faltung vorhanden sind. Hier liegen die meisten unserer Parameter, daher wenden wir auch Dropout an, um eine Überanpassung zu verhindern.
Trainieren des Modells
Zuerst müssen wir von Datenrahmen in Torch-Tensoren konvertieren. Wir verwenden eine Hilfsfunktion bins_to_features, die ein 3D-Characteristic-Array im Format (Samples, Allele, Segmente) erstellt. Dabei teilen wir die Daten auch im Verhältnis 4:1 in Trainings- und Testsätze auf:
import torch
from torch.utils.knowledge import TensorDataset, DataLoader
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
# convert knowledge to options and labels
options, samples_list, columns_df = cns.bins_to_features(cns_df)
# convert knowledge to Torch tensors
X = torch.FloatTensor(options)
label_encoder = LabelEncoder()
y = torch.LongTensor(label_encoder.fit_transform(samples_df.loc(samples_list)("kind")))
# Take a look at/practice cut up
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
# Create dataloaders
train_loader = DataLoader(TensorDataset(X_train, y_train), batch_size=32, shuffle=True)
test_loader = DataLoader(TensorDataset(X_test, y_test), batch_size=32, shuffle=False)
Wir können das Modell nun mithilfe der folgenden Trainingsschleife mit 20 Epochen trainieren. Der Adam-Optimierer und der CrossEntropy-Verlust werden typischerweise für Klassifizierungsaufgaben verwendet, daher verwenden wir sie auch hier:
# setup the mannequin, loss, and optimizer
system = torch.system('cuda' if torch.cuda.is_available() else 'cpu')
mannequin = CNSConvNet(num_classes=len(label_encoder.classes_)).to(system)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(mannequin.parameters(), lr=0.001)
# Coaching loop
num_epochs = 20
for epoch in vary(num_epochs):
mannequin.practice()
running_loss = 0.0
for inputs, labels in train_loader:
inputs, labels = inputs.to(system), labels.to(system)
# Clear gradients
optimizer.zero_grad()
# Ahead move
outputs = mannequin(inputs)
loss = criterion(outputs, labels)
# Backward move and optimize
loss.backward()
optimizer.step()
running_loss += loss.merchandise()
# Print statistics
print(f'Epoch {epoch+1}/{num_epochs}, Loss: {running_loss/len(train_loader):.4f}')
Damit ist die Schulung abgeschlossen. Anschließend können wir das Modell bewerten und die Verwirrungsmatrix ausdrucken:
import numpy as np
from sklearn.metrics import confusion_matrix
import seaborn as sns
# Loop over batches within the check set and gather predictions
mannequin.eval()
y_true = ()
y_pred = ()
with torch.no_grad():
for inputs, labels in test_loader:
inputs, labels = inputs.to(system), labels.to(system)
outputs = mannequin(inputs)
y_true.lengthen(labels.cpu().numpy())
y_pred.lengthen(outputs.argmax(dim=1).cpu().numpy())
_, predicted = torch.max(outputs.knowledge, 1)
# Calculate accuracy and confusion matrix
accuracy = (np.array(y_true) == np.array(y_pred)).imply()
cm = confusion_matrix(y_true, y_pred)
# Plot the confusion matrix
plt.determine(figsize=(3, 3), dpi=200)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=label_encoder.classes_, yticklabels=label_encoder.classes_)
plt.xlabel('Predicted')
plt.ylabel('True')
plt.title('Confusion Matrix, accuracy={:.2f}'.format(accuracy))
plt.savefig("confusion_matrix.png", bbox_inches='tight')

Der Trainingsvorgang dauert auf einer NVIDIA RTX 4090-GPU insgesamt etwa 7 Sekunden.
Abschluss
Wir haben einen effizienten und genauen Klassifikator des Lungenkrebs-Subtyps anhand von Kopienzahldaten entwickelt. Wie wir gezeigt habenlassen sich solche Modelle intestine auf neue Studien und Sequenzdatenquellen übertragen.
KI im Massenmaßstab wird oft unter anderem damit gerechtfertigt, dass „Lösung für Krebs„Aber wie in diesem Artikel, erfüllen kleine Modelle mit klassischen Ansätzen meist ihren Zweck intestine. Manche streiten sogar dass das eigentliche Hindernis des maschinellen Lernens in der Biologie und Medizin nicht darin besteht, Probleme zu lösen, sondern darin, tatsächlich etwas für die Patienten zu bewirken.
Dennoch konnte maschinelles Lernen zumindest die meisten Probleme lösen ein großes Rätsel der Computerbiologie, Neubelebung des Fokus auf maschinelles Lernen bei Krebs. Mit etwas Glück können wir das nächste Jahrzehnt vielleicht als die Zeit betrachten, in der wir den Krebs endlich „gelöst“ haben.
Bonus: Cell2Sentence
Die grundlegenden Modelle von heute enthalten als Teil ihres Trainingssatzes häufig Informationen darüber, welche Gene bei welchen Krebsarten eine hohe Kopienzahl aufweisen. Dies führte jedoch zur Entwicklung LLM-basierter Ansätze, wie z Cell2SentenceDabei wird ein Datensatz in eine natürliche Sprache konvertiert und an ein LLM übermittelt.
Diesen Ansatz können wir hier nutzen. Zuerst konvertieren wir eine zufällige LUSC-Stichprobe in eine Rangliste von Genen, basierend auf ihrem Kopienzahlwert:
import cns
import cns.data_utils as cdu
samples_df, cns_df = cdu.main_load("COSMIC")
cns_df = cns.add_total_cn(cns_df)
lusc_df = cns.select_cns_by_type(cns_df, samples_df, "LUSC")
lusc_one_df = cns.cns_head(lusc_df, 1)
rank_list = lusc_one_df.sort_values(by="total_cn", ascending=False)("title").to_list()
Dann konvertieren wir die Liste der Gene in eine Abfrage in natürlicher Sprache:
question = "Under is an inventory of COSMIC {gene_names} ordered by descending whole copy quantity in a cell. Your process is to determine the most cancers kind of that pattern.nn"
question += "{gene_names}: " + " ".be part of(rank_list) + "nn"
question += "Reply:"
print(question)
Das an ChatGPT 5 übermittelte Modell identifiziert den LUSC-Krebs korrekt:
Based mostly on the gene copy-number profile you listed, the strongest clue is the large amplification of 11q13 (CCND1, FADD) along with SOX2, PIK3CA, TP63, EGFR, FGFR1, MYC, CCND1/CCND2/CCND3, CDK6, CDK4. This can be a very attribute sample of squamous cell carcinoma of the lung (LUSC):
CCND1 amplification is among the most frequent occasions in LUSC.
SOX2 amplification (3q26) is a defining driver of lung squamous carcinoma, not often seen in adenocarcinoma.
TP63 amplification is once more extremely particular to squamous tumors.
Co-amplifications of PIK3CA, FGFR1, EGFR, MYC match this profile.
The broad set of genes amplified (many regulators of squamous differentiation and RTK/RAS/PI3K signaling) displays typical copy-number–pushed LUSC biology.
In contrast, lung adenocarcinomas normally present KRAS, EGFR, ALK, ROS1, MET exon 14, ERBB2 level mutations/fusions and have fewer widespread squamous-type CNAs.
Reply: Lung squamous cell carcinoma (LUSC).
Die Generierung dieser Ausgabe für eine Stichprobe dauert jedoch länger als die Klassifizierung des gesamten Datensatzes mit unserem Modell und die Klassifizierung unseres gesamten Datensatzes würde etwa 200 $ an API-Gebühren kosten.
