PyTorch – chatgpz.de – KI-Tools & Praxiswissen http://chatgpz.de Unabhängige Tests, Anleitungen und Hintergründe zu KI-Tools. Sun, 11 Jan 2026 16:56:59 +0000 de-DE hourly 1 https://wordpress.org/?v=6.4.2 234900186 Ein Coding-Leitfaden zur Demonstration gezielter Knowledge-Poisoning-Angriffe im Deep Studying durch Label-Flipping auf CIFAR-10 mit PyTorch http://chatgpz.de/artificial-intelligence/ein-coding-leitfaden-zur-demonstration-gezielter-knowledge-poisoning-angriffe-im-deep-studying-durch-label-flipping-auf-cifar-10-mit-pytorch/ http://chatgpz.de/artificial-intelligence/ein-coding-leitfaden-zur-demonstration-gezielter-knowledge-poisoning-angriffe-im-deep-studying-durch-label-flipping-auf-cifar-10-mit-pytorch/#respond Sun, 11 Jan 2026 16:56:59 +0000 http://chatgpz.de/artificial-intelligence/ein-coding-leitfaden-zur-demonstration-gezielter-data-poisoning-angriffe-im-deep-learning-durch-label-flipping-auf-cifar-10-mit-pytorch/ [ad_1]

In diesem Tutorial demonstrieren wir einen realistischen Knowledge-Poisoning-Angriff, indem wir Labels im CIFAR-10-Datensatz manipulieren und dessen Auswirkungen auf das Modellverhalten beobachten. Wir bauen nebeneinander eine saubere und eine vergiftete Trainingspipeline auf und verwenden dabei ein Faltungsnetzwerk im ResNet-Stil, um eine stabile, vergleichbare Lerndynamik sicherzustellen. Indem wir während des Trainings selektiv einen Bruchteil der Stichproben von einer Zielklasse in eine bösartige Klasse umwandeln, zeigen wir, wie subtile Beschädigungen in der Datenpipeline zum Zeitpunkt der Inferenz zu systematischen Fehlklassifizierungen führen können. Schauen Sie sich das an VOLLSTÄNDIGE CODES hier.

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.knowledge import DataLoader, Dataset
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import confusion_matrix, classification_report


CONFIG = {
   "batch_size": 128,
   "epochs": 10,
   "lr": 0.001,
   "target_class": 1,
   "malicious_label": 9,
   "poison_ratio": 0.4,
}


torch.manual_seed(42)
np.random.seed(42)

Wir richten die für das Experiment erforderliche Kernumgebung ein und definieren alle globalen Konfigurationsparameter an einem einzigen Ort. Wir stellen die Reproduzierbarkeit sicher, indem wir zufällige Seeds in PyTorch und NumPy fixieren. Wir wählen auch explizit das Rechengerät aus, damit das Tutorial sowohl auf der CPU als auch auf der GPU effizient läuft. Schauen Sie sich das an VOLLSTÄNDIGE CODES hier.

class PoisonedCIFAR10(Dataset):
   def __init__(self, original_dataset, target_class, malicious_label, ratio, is_train=True):
       self.dataset = original_dataset
       self.targets = np.array(original_dataset.targets)
       self.is_train = is_train
       if is_train and ratio > 0:
           indices = np.the place(self.targets == target_class)(0)
           n_poison = int(len(indices) * ratio)
           poison_indices = np.random.selection(indices, n_poison, exchange=False)
           self.targets(poison_indices) = malicious_label


   def __getitem__(self, index):
       img, _ = self.dataset(index)
       return img, self.targets(index)


   def __len__(self):
       return len(self.dataset)

Wir implementieren einen benutzerdefinierten Datensatz-Wrapper, der eine kontrollierte Label-Vergiftung während des Trainings ermöglicht. Wir wandeln selektiv einen konfigurierbaren Teil der Proben aus der Zielklasse in eine bösartige Klasse um, während die Testdaten unberührt bleiben. Wir bewahren die Originalbilddaten auf, sodass nur die Etikettenintegrität beeinträchtigt wird. Schauen Sie sich das an VOLLSTÄNDIGE CODES hier.

def get_model():
   mannequin = torchvision.fashions.resnet18(num_classes=10)
   mannequin.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)
   mannequin.maxpool = nn.Id()
   return mannequin.to(CONFIG("gadget"))


def train_and_evaluate(train_loader, description):
   mannequin = get_model()
   optimizer = optim.Adam(mannequin.parameters(), lr=CONFIG("lr"))
   criterion = nn.CrossEntropyLoss()
   for _ in vary(CONFIG("epochs")):
       mannequin.prepare()
       for pictures, labels in train_loader:
           pictures = pictures.to(CONFIG("gadget"))
           labels = labels.to(CONFIG("gadget"))
           optimizer.zero_grad()
           outputs = mannequin(pictures)
           loss = criterion(outputs, labels)
           loss.backward()
           optimizer.step()
   return mannequin

Wir definieren ein leichtes ResNet-basiertes Modell, das auf CIFAR-10 zugeschnitten ist, und implementieren die vollständige Trainingsschleife. Wir trainieren das Netzwerk mithilfe von Commonplace-Kreuzentropieverlust und Adam-Optimierung, um eine stabile Konvergenz sicherzustellen. Wir halten die Trainingslogik für saubere und vergiftete Daten identisch, um die Auswirkungen einer Datenvergiftung zu isolieren. Schauen Sie sich das an VOLLSTÄNDIGE CODES hier.

def get_predictions(mannequin, loader):
   mannequin.eval()
   preds, labels_all = (), ()
   with torch.no_grad():
       for pictures, labels in loader:
           pictures = pictures.to(CONFIG("gadget"))
           outputs = mannequin(pictures)
           _, predicted = torch.max(outputs, 1)
           preds.prolong(predicted.cpu().numpy())
           labels_all.prolong(labels.numpy())
   return np.array(preds), np.array(labels_all)


def plot_results(clean_preds, clean_labels, poisoned_preds, poisoned_labels, courses):
   fig, ax = plt.subplots(1, 2, figsize=(16, 6))
   for i, (preds, labels, title) in enumerate((
       (clean_preds, clean_labels, "Clear Mannequin Confusion Matrix"),
       (poisoned_preds, poisoned_labels, "Poisoned Mannequin Confusion Matrix")
   )):
       cm = confusion_matrix(labels, preds)
       sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", ax=ax(i),
                   xticklabels=courses, yticklabels=courses)
       ax(i).set_title(title)
   plt.tight_layout()
   plt.present()

Wir führen Rückschlüsse auf den Testsatz durch und sammeln Vorhersagen für die quantitative Analyse. Wir berechnen Verwirrungsmatrizen, um das klassenbezogene Verhalten sowohl für saubere als auch für vergiftete Modelle zu visualisieren. Wir verwenden diese visuellen Diagnosen, um durch den Angriff gezielte Fehlklassifizierungsmuster hervorzuheben. Schauen Sie sich das an VOLLSTÄNDIGE CODES hier.

remodel = transforms.Compose((
   transforms.RandomHorizontalFlip(),
   transforms.ToTensor(),
   transforms.Normalize((0.4914, 0.4822, 0.4465),
                        (0.2023, 0.1994, 0.2010))
))


base_train = torchvision.datasets.CIFAR10(root="./knowledge", prepare=True, obtain=True, remodel=remodel)
base_test = torchvision.datasets.CIFAR10(root="./knowledge", prepare=False, obtain=True, remodel=remodel)


clean_ds = PoisonedCIFAR10(base_train, CONFIG("target_class"), CONFIG("malicious_label"), ratio=0)
poison_ds = PoisonedCIFAR10(base_train, CONFIG("target_class"), CONFIG("malicious_label"), ratio=CONFIG("poison_ratio"))


clean_loader = DataLoader(clean_ds, batch_size=CONFIG("batch_size"), shuffle=True)
poison_loader = DataLoader(poison_ds, batch_size=CONFIG("batch_size"), shuffle=True)
test_loader = DataLoader(base_test, batch_size=CONFIG("batch_size"), shuffle=False)


clean_model = train_and_evaluate(clean_loader, "Clear Coaching")
poisoned_model = train_and_evaluate(poison_loader, "Poisoned Coaching")


c_preds, c_true = get_predictions(clean_model, test_loader)
p_preds, p_true = get_predictions(poisoned_model, test_loader)


plot_results(c_preds, c_true, p_preds, p_true, courses)


print(classification_report(c_true, c_preds, target_names=courses, labels=(1)))
print(classification_report(p_true, p_preds, target_names=courses, labels=(1)))

Wir bereiten den CIFAR-10-Datensatz vor, erstellen saubere und vergiftete Datenlader und führen beide Trainingspipelines durchgängig aus. Wir bewerten die trainierten Modelle anhand eines gemeinsamen Testsatzes, um einen fairen Vergleich zu gewährleisten. Wir schließen die Analyse ab, indem wir klassenspezifische Präzision und Rückruf melden, um die Auswirkungen der Vergiftung auf die Zielklasse aufzudecken.

Zusammenfassend haben wir beobachtet, wie eine Datenvergiftung auf Etikettenebene die klassenspezifische Leistung beeinträchtigt, ohne unbedingt die Gesamtgenauigkeit zu zerstören. Wir haben dieses Verhalten mithilfe von Verwirrungsmatrizen und Klassifizierungsberichten professional Klasse analysiert, die durch den Angriff gezielte Fehlermodi aufdecken. Dieses Experiment unterstreicht die Bedeutung der Datenherkunft, -validierung und -überwachung in realen maschinellen Lernsystemen, insbesondere in sicherheitskritischen Bereichen.


Schauen Sie sich das an VOLLSTÄNDIGE CODES hier. Sie können uns auch gerne weiter folgen Twitter und vergessen Sie nicht, bei uns mitzumachen 100.000+ ML SubReddit und Abonnieren Unser E-newsletter. Warten! Bist du im Telegram? Jetzt können Sie uns auch per Telegram kontaktieren.

Schauen Sie sich unsere neueste Model von an ai2025.deveine auf das Jahr 2025 ausgerichtete Analyseplattform, die Modelleinführungen, Benchmarks und Ökosystemaktivitäten in einen strukturierten Datensatz umwandelt, den Sie filtern, vergleichen und exportieren können.


Asif Razzaq ist CEO von Marktechpost Media Inc.. Als visionärer Unternehmer und Ingenieur setzt sich Asif dafür ein, das Potenzial der künstlichen Intelligenz für das soziale Wohl zu nutzen. Sein jüngstes Unterfangen ist die Einführung einer Medienplattform für künstliche Intelligenz, Marktechpost, die sich durch eine ausführliche Berichterstattung über maschinelles Lernen und Deep-Studying-Nachrichten auszeichnet, die sowohl technisch fundiert als auch für ein breites Publikum leicht verständlich ist. Die Plattform verfügt über mehr als 2 Millionen monatliche Aufrufe, was ihre Beliebtheit beim Publikum verdeutlicht.

[ad_2]

]]>
http://chatgpz.de/artificial-intelligence/ein-coding-leitfaden-zur-demonstration-gezielter-knowledge-poisoning-angriffe-im-deep-studying-durch-label-flipping-auf-cifar-10-mit-pytorch/feed/ 0 15488
Überwindung der versteckten Leistungsfallen variabel geformter Tensoren: Effiziente Datenerfassung in PyTorch http://chatgpz.de/machine-learning/ueberwindung-der-versteckten-leistungsfallen-variabel-geformter-tensoren-effiziente-datenerfassung-in-pytorch/ http://chatgpz.de/machine-learning/ueberwindung-der-versteckten-leistungsfallen-variabel-geformter-tensoren-effiziente-datenerfassung-in-pytorch/#respond Wed, 03 Dec 2025 17:49:33 +0000 http://chatgpz.de/machine-learning/ueberwindung-der-versteckten-leistungsfallen-variabel-geformter-tensoren-effiziente-datenerfassung-in-pytorch/ [ad_1]

ist der Teil von a Reihe von Beiträgen zum Thema Analyse und Optimierung von PyTorch-Modellen. Während der gesamten Serie haben wir uns für die Verwendung von ausgesprochen PyTorch-Profiler in der Entwicklung von KI-Modellen und demonstrierte die potenziellen Auswirkungen der Leistungsoptimierung auf die Geschwindigkeit und die Kosten der Ausführung von KI/ML-Workloads. Ein häufiges Phänomen, das wir beobachtet haben, ist, wie scheinbar harmloser Code die Laufzeitleistung beeinträchtigen kann. In diesem Beitrag untersuchen wir einige der Nachteile, die mit der naiven Verwendung von Tensoren mit variabler Kind verbunden sind – Tensoren, deren Kind von vorherigen Berechnungen und/oder Eingaben abhängt. Obwohl dies nicht auf alle Situationen anwendbar ist, gibt es Zeiten, in denen die Verwendung von Tensoren mit variabler Kind vermieden werden kann – obwohl dies möglicherweise auf Kosten zusätzlicher Rechenleistung und/oder Speicher geht. Wir werden die Kompromisse dieser Alternativen anhand einer Spielzeugimplementierung der Datenerfassung in PyTorch demonstrieren.

Drei Nachteile variabel geformter Tensoren

Wir motivieren die Diskussion, indem wir drei Nachteile der Verwendung von Tensoren mit variabler Kind aufzeigen:

Host-Gerät-Synchronisierungsereignisse

In einem idealen Szenario können CPU und GPU asynchron parallel laufen, wobei die CPU die GPU kontinuierlich mit Eingabebeispielen versorgt, den erforderlichen GPU-Speicher zuweist und GPU-Rechenkerne lädt und die GPU die geladenen Kernel auf den bereitgestellten Eingaben unter Verwendung des zugewiesenen Speichers ausführt. Das Vorhandensein dynamisch geformter Tensoren macht dieser Parallelität einen Strich durch die Rechnung. Um die entsprechende Speichermenge zuzuweisen, muss die CPU darauf warten, dass die GPU die Kind des Tensors meldet, und dann muss die GPU darauf warten, dass die CPU den Speicher zuweist und mit dem Laden des Kernels fortfährt. Der Overhead dieses Synchronisierungsereignisses kann zu einem Rückgang der GPU-Auslastung und einer langsameren Laufzeitleistung führen.

Ein Beispiel dafür haben wir gesehen Teil drei Teil dieser Serie, als wir eine naive Umsetzung des Gemeinsamen untersuchten Kreuzentropieverlust Dazu gehörten Anrufe an Torch.nonzero Und Taschenlampe.einzigartig. Beide APIs geben Tensoren mit Formen zurück, die dynamisch und vom Inhalt der Eingabe abhängig sind. Wenn diese Funktionen auf der GPU ausgeführt werden, tritt ein Host-Gerät-Synchronisierungsereignis auf. Im Fall der Kreuzentropieverlusthaben wir die Ineffizienz durch die Verwendung von entdeckt PyTorch-Profiler und konnten es leicht mit einer alternativen Implementierung überwinden, die die Verwendung von Tensoren mit variabler Kind vermeidet und eine viel bessere Laufzeitleistung zeigte.

Diagrammerstellung

In einem letzter Beitrag Wir haben die Leistungsvorteile der Bewerbung untersucht just-in-time (JIT)-Kompilierung mit der Torch.compile Operator. Eine unserer Beobachtungen warfare, dass die Diagrammerstellung viel bessere Ergebnisse lieferte, wenn das Diagramm statisch warfare. Das Vorhandensein dynamischer Formen im Diagramm schränkt den Umfang der Optimierung durch Kompilierung ein: In einigen Fällen schlägt sie vollständig fehl; in anderen führt es zu geringeren Leistungssteigerungen. Die gleichen Implikationen gelten auch für andere Formen der Diagrammerstellung, wie z XLA, ONNX, OpenVINOUnd TensorRT.

Datenstapelung

Eine weitere Optimierung, die uns in mehreren unserer Beiträge begegnet ist (z. B. Hier) ist Probenbatchierung. Die Stapelverarbeitung verbessert die Leistung hauptsächlich auf zwei Arten:

  1. Reduzierung des Overheads beim Laden des Kernels: Anstatt die für die Berechnungspipeline erforderlichen GPU-Kernel einmal professional Eingabebeispiel zu laden, kann die CPU die Kernel einmal professional Stapel laden.
  2. Maximierung der Parallelisierung über Recheneinheiten hinweg: GPUs sind hochparallele Rechenmaschinen. Je mehr wir die Berechnungen parallelisieren können, desto stärker können wir die GPU auslasten und ihre Auslastung steigern. Durch die Stapelverarbeitung können wir den Grad der Parallelisierung potenziell um den Faktor der Stapelgröße erhöhen.

Trotz ihrer Nachteile ist die Verwendung von Tensoren mit variabler Kind oft unvermeidbar. Aber manchmal können wir unsere Modellimplementierung ändern, um sie zu umgehen. Manchmal sind diese Änderungen unkompliziert (wie im Beispiel des Kreuzentropieverlusts). In anderen Fällen ist möglicherweise etwas Kreativität erforderlich, um eine andere Sequenz von PyTorch-APIs mit fester Kind zu entwickeln, die das gleiche numerische Ergebnis liefern. Oftmals kann dieser Aufwand zu erheblichen Vorteilen bei Laufzeit und Kosten führen.

In den nächsten Abschnitten werden wir die Verwendung von variablen Tensoren im Kontext der Datenabtastoperation untersuchen. Wir beginnen mit einer trivialen Implementierung und analysieren deren Leistung. Wir werden dann eine GPU-freundliche Different vorschlagen, die die Verwendung von Tensoren mit variabler Kind vermeidet.

Um unsere Implementierungen zu vergleichen, verwenden wir eine Amazon EC2 g6e.xlarge mit einem NVIDIA L40S Ausführen eines AWS Deep Studying AMI (DLAMI) mit PyTorch (2.8). Der Code, den wir teilen, ist für Demonstrationszwecke gedacht. Bitte verlassen Sie sich nicht auf die Genauigkeit oder Optimalität. Bitte interpretieren Sie unsere Erwähnung eines Frameworks, einer Bibliothek oder einer Plattform nicht als Befürwortung ihrer Verwendung.

Stichprobenerhebung in KI-Modell-Workloads

Im Kontext dieses Beitrags bezieht sich Stichprobe auf die Auswahl einer Teilmenge von Elementen aus einer großen Menge von Kandidaten zum Zwecke der Recheneffizienz, des Ausgleichs von Datentypen oder der Regularisierung. Stichproben sind in vielen KI/ML-Modellen üblich, beispielsweise in Erkennungs-, Rating- und kontrastiven Lernsystemen.

Wir definieren eine einfache Variation des Stichprobenproblems: Gegeben sei eine Liste von N Tensoren mit jeweils einer binären Bezeichnung werden wir gebeten, eine Teilmenge davon zurückzugeben Ok Tensoren, die sowohl constructive als auch adverse Beispiele in zufälliger Reihenfolge enthalten. Wenn die Eingabeliste genügend Beispiele für jedes Etikett enthält (Ok/2), sollte die zurückgegebene Teilmenge gleichmäßig aufgeteilt sein. Fehlen Stichproben einer Artwork, sollten diese durch Stichproben der zweiten Artwork aufgefüllt werden.

Der folgende Codeblock enthält eine PyTorch-Implementierung unserer Sampling-Funktion. Die Umsetzung orientiert sich am Populären Detectron2 Bibliothek (z. B. siehe Hier Und Hier). Für die Experimente in diesem Beitrag legen wir das Stichprobenverhältnis fest 1:10.

import torch

INPUT_SAMPLES = 10000
SUB_SAMPLE = INPUT_SAMPLES // 10
FEATURE_DIM = 16

def sample_data(input_array, labels):
    gadget = labels.gadget
    constructive = torch.nonzero(labels == 1, as_tuple=True)(0)
    adverse = torch.nonzero(labels == 0, as_tuple=True)(0)
    num_pos = min(constructive.numel(), SUB_SAMPLE//2)
    num_neg = min(adverse.numel(), SUB_SAMPLE//2)
    if num_neg < SUB_SAMPLE//2:
        num_pos = SUB_SAMPLE - num_neg
    elif num_pos < SUB_SAMPLE//2:
        num_neg = SUB_SAMPLE - num_pos

    # randomly choose constructive and adverse examples
    perm1 = torch.randperm(constructive.numel(), gadget=gadget)(:num_pos)
    perm2 = torch.randperm(adverse.numel(), gadget=gadget)(:num_neg)

    pos_idxs = constructive(perm1)
    neg_idxs = adverse(perm2)

    sampled_idxs = torch.cat((pos_idxs, neg_idxs), dim=0)
    rand_perm = torch.randperm(SUB_SAMPLE, gadget=labels.gadget)
    sampled_idxs = sampled_idxs(rand_perm)
    return input_array(sampled_idxs), labels(sampled_idxs)

Leistungsanalyse mit PyTorch Profiler

Auch wenn die Verwendung dynamischer Formen nicht sofort offensichtlich ist, ist sie in der PyTorch Profiler Hint-Ansicht leicht zu erkennen. Wir verwenden die folgende Funktion, um PyTorch Profiler zu aktivieren:

def profile(fn, enter, labels):
    
    def export_trace(p):
        p.export_chrome_trace(f"{fn.__name__}.json")
        
    with torch.profiler.profile(
            actions=(torch.profiler.ProfilerActivity.CPU,
                        torch.profiler.ProfilerActivity.CUDA),
            with_stack=True,
            schedule=torch.profiler.schedule(wait=0, warmup=10, lively=5),
            on_trace_ready=export_trace
    ) as prof:
        for _ in vary(20):
            fn(enter, labels)
            torch.cuda.synchronize()  # specific sync for hint readability
            prof.step()

# create random enter
input_samples = torch.randn((INPUT_SAMPLES, FEATURE_DIM), gadget='cuda')
labels = torch.randint(0, 2, (INPUT_SAMPLES,), 
                       gadget='cuda', dtype=torch.int64)

# run with profiler
profile(sample_data, input_samples, labels)

Das Bild unten wurde für den Wert von zehn Millionen Eingabeproben aufgenommen. Es zeigt deutlich das Vorhandensein von Synchronisierungsereignissen, die vom Torch.nonzero-Aufruf ausgehen, sowie die entsprechenden Rückgänge bei der GPU-Auslastung:

Profiler Hint of Sampler (vom Autor)

Die Verwendung von Torch.nonzero in unserer Implementierung ist nicht supreme, aber kann sie vermieden werden?

Ein GPU-freundlicher Datensampler

Wir schlagen eine various Implementierung unserer Sampling-Funktion vor, die die dynamische Funktion Torch.nonzero durch eine kreative Kombination der Statik ersetzt Torch.count_nonzero, Torch.topkund andere APIs:

def opt_sample_data(enter, labels):
    pos_mask = labels == 1
    neg_mask = labels == 0
    num_pos_idxs = torch.count_nonzero(pos_mask, dim=-1)
    num_neg_idxs = torch.count_nonzero(neg_mask, dim=-1)
    half_samples = labels.new_full((), SUB_SAMPLE // 2)
    num_pos = torch.minimal(num_pos_idxs, half_samples)
    num_neg = torch.minimal(num_neg_idxs, half_samples)
    num_pos = torch.the place(
        num_neg < SUB_SAMPLE // 2,
        SUB_SAMPLE - num_neg,
        num_pos
    )
    num_neg = SUB_SAMPLE - num_pos

    # create random ordering on pos and neg entries
    rand = torch.rand_like(labels, dtype=torch.float32)
    pos_rand = torch.the place(pos_mask, rand, -1)
    neg_rand = torch.the place(neg_mask, rand, -1)

    # choose prime pos entries and invalidate others
    # since CPU would not know num_pos, we assume most to keep away from sync
    top_pos_rand, top_pos_idx = torch.topk(pos_rand, okay=SUB_SAMPLE)
    arange = torch.arange(SUB_SAMPLE, gadget=labels.gadget)
    if num_pos.numel() > 1:
        # unsqueeze to help batched enter
        arange = arange.unsqueeze(0)
        num_pos = num_pos.unsqueeze(-1)
        num_neg = num_neg.unsqueeze(-1)
    top_pos_rand = torch.the place(arange >= num_pos, -1, top_pos_rand)

    # repeat for neg entries
    top_neg_rand, top_neg_idx = torch.topk(neg_rand, okay=SUB_SAMPLE)
    top_neg_rand = torch.the place(arange >= num_neg, -1, top_neg_rand)

    # mix and blend collectively constructive and adverse idxs
    cat_rand = torch.cat((top_pos_rand, top_neg_rand), dim=-1)
    cat_idx = torch.cat((top_pos_idx, top_neg_idx), dim=-1)
    topk_rand_idx = torch.topk(cat_rand, okay=SUB_SAMPLE)(1)
    sampled_idxs = torch.collect(cat_idx, dim=-1, index=topk_rand_idx)
    sampled_input = torch.collect(enter, dim=-2, 
                                 index=sampled_idxs.unsqueeze(-1))
    sampled_labels = torch.collect(labels, dim=-1, index=sampled_idxs)
    return sampled_input, sampled_labels

Offensichtlich erfordert diese Funktion mehr Speicher und mehr Operationen als unsere erste Implementierung. Die Frage ist: Überwiegen die Leistungsvorteile einer statischen, synchronisierungsfreien Implementierung die zusätzlichen Kosten für Speicher und Rechenleistung?

Um die Kompromisse zwischen den beiden Implementierungen zu bewerten, führen wir das folgende Benchmarking-Dienstprogramm ein:

def benchmark(fn, enter, labels):
    # warm-up
    for _ in vary(20):
        _ = fn(enter, labels)

    iters = 100
    begin = torch.cuda.Occasion(enable_timing=True)
    finish = torch.cuda.Occasion(enable_timing=True)
    torch.cuda.synchronize()
    begin.document()
    for _ in vary(iters):
        _ = fn(enter, labels)
    finish.document()
    torch.cuda.synchronize()
    avg_time = begin.elapsed_time(finish) / iters
    
    print(f"{fn.__name__} common step time: {(avg_time):.4f} ms")

benchmark(sample_data, input_samples, labels)
benchmark(opt_sample_data, input_samples, labels)

Die folgende Tabelle vergleicht die durchschnittliche Laufzeit jeder Implementierung für verschiedene Eingabestichprobengrößen:

Vergleichende Schrittzeitleistung – Je niedriger, desto besser (vom Autor)

Bei den meisten Eingabestichprobengrößen ist der Overhead des Host-Gerät-Synchronisierungsereignisses entweder vergleichbar oder geringer als der zusätzliche Rechenaufwand der statischen Implementierung. Enttäuschenderweise sehen wir erst dann einen großen Nutzen aus der synchronisierungsfreien Different, wenn die Eingabestichprobengröße zehn Millionen erreicht. Derart große Stichprobengrößen sind in KI/ML-Umgebungen ungewöhnlich. Aber es ist nicht unsere Tendenz, so schnell aufzugeben. Wie oben erwähnt, ermöglicht die statische Implementierung andere Optimierungen wie die Kompilierung von Diagrammen und die Stapelverarbeitung von Eingaben.

Diagrammerstellung

Im Gegensatz zur ursprünglichen Funktion – die nicht kompiliert werden kann – ist unsere statische Implementierung vollständig kompatibel mit Torch.compile:

benchmark(torch.compile(opt_sample_data), input_samples, labels)

Die folgende Tabelle enthält die Laufzeiten unserer kompilierten Funktion:

Vergleichende Schrittzeitleistung – Je niedriger, desto besser (vom Autor)

Die Ergebnisse sind deutlich besser und bieten eine Steigerung von 70 bis 75 Prozent gegenüber der ursprünglichen Sampler-Implementierung im Bereich von 1 bis 10.000. Aber wir haben noch eine weitere Optimierung in petto.

Maximierung der Leistung mit Batch-Enter

Da die ursprüngliche Implementierung variablenförmige Operationen enthält, kann sie Batch-Eingaben nicht direkt verarbeiten. Um einen Stapel zu verarbeiten, haben wir keine andere Wahl, als ihn in einer Python-Schleife auf jede Eingabe einzeln anzuwenden:

BATCH_SIZE = 32

def batched_sample_data(inputs, labels):
    sampled_inputs = ()
    sampled_labels = ()
    for i in vary(inputs.dimension(0)):
        inp, lab = sample_data(inputs(i), labels(i))
        sampled_inputs.append(inp)
        sampled_labels.append(lab)
    return torch.stack(sampled_inputs), torch.stack(sampled_labels)

Im Gegensatz dazu unterstützt unsere optimierte Funktion Batch-Eingaben unverändert – es sind keine Änderungen erforderlich.

input_batch = torch.randn((BATCH_SIZE, INPUT_SAMPLES, FEATURE_DIM),
                          gadget='cuda')
labels = torch.randint(0, 2, (BATCH_SIZE, INPUT_SAMPLES),
                       gadget='cuda', dtype=torch.int64)

benchmark(batched_sample_data, input_batch, labels)
benchmark(opt_sample_data, input_batch, labels)
benchmark(torch.compile(opt_sample_data), input_batch, labels)

Die folgende Tabelle vergleicht die Schrittzeiten unserer Stichprobenfunktionen bei einer Chargengröße von 32:

Schrittzeitleistung bei Batch-Enter – Je niedriger, desto besser (vom Autor)

Jetzt sind die Ergebnisse endgültig: Durch die Verwendung einer statischen Implementierung des Datensamplers können wir die Leistung je nach Eingabestichprobengröße um das 2- bis 52-fache (!!) der variablenförmigen Choice steigern.

Beachten Sie, dass unsere Experimente zwar auf einem GPU-Gerät ausgeführt wurden, die Modellkompilierungs- und Eingabe-Batching-Optimierungen jedoch auch für eine CPU-Umgebung gelten. Daher könnte die Vermeidung variabler Formen auch Auswirkungen auf die Leistung des AI/ML-Modells auf der CPU haben.

Zusammenfassung

Der Optimierungsprozess, den wir in diesem Beitrag demonstriert haben, verallgemeinert sich über den speziellen Fall der Datenstichprobe hinaus:

  • Entdeckung über Leistungsprofilierung: Mit der PyTorch-Profiler Wir konnten einen Rückgang der GPU-Auslastung identifizieren und ihre Ursache ermitteln: das Vorhandensein von Tensoren mit variabler Kind, die aus der Operation „torch.nonzero“ resultieren.
  • Eine various Implementierung: Unsere Profilierungsergebnisse ermöglichten es uns, eine various Implementierung zu entwickeln, die das gleiche Ziel erreichte und gleichzeitig die Verwendung von Tensoren mit variabler Kind vermeidete. Dieser Schritt ging jedoch mit einem zusätzlichen Rechen- und Speicheraufwand einher. Wie in unseren ersten Benchmarks zu sehen warfare, zeigte die synchronisierungsfreie Different bei gängigen Eingabegrößen eine schlechtere Leistung.
  • Weiteres Optimierungspotenzial erschließen: Der wahre Durchbruch gelang, weil die statisch geformte Implementierung kompilierungsfreundlich warfare und Batchverarbeitung unterstützte. Diese Optimierungen sorgten für Leistungssteigerungen, die den anfänglichen Overhead in den Schatten stellten und zu einer 2- bis 52-fachen Beschleunigung gegenüber der ursprünglichen Implementierung führten.

Natürlich enden nicht alle Geschichten so glücklich wie unsere. In vielen Fällen stoßen wir möglicherweise auf PyTorch-Code, der auf der GPU eine schlechte Leistung erbringt, aber über keine various Implementierung verfügt, oder über eine, die deutlich mehr Rechenressourcen erfordert. Angesichts des Potenzials für erhebliche Leistungssteigerungen und Kostensenkungen ist der Prozess der Identifizierung von Laufzeitineffizienzen und der Erforschung alternativer Implementierungen jedoch ein wesentlicher Bestandteil der KI/ML-Entwicklung.

[ad_2]

]]>
http://chatgpz.de/machine-learning/ueberwindung-der-versteckten-leistungsfallen-variabel-geformter-tensoren-effiziente-datenerfassung-in-pytorch/feed/ 0 14503
So klassifizieren Sie den Lungenkrebs-Subtyp anhand der DNA-Kopiennummern mithilfe von PyTorch http://chatgpz.de/machine-learning/so-klassifizieren-sie-den-lungenkrebs-subtyp-anhand-der-dna-kopiennummern-mithilfe-von-pytorch/ http://chatgpz.de/machine-learning/so-klassifizieren-sie-den-lungenkrebs-subtyp-anhand-der-dna-kopiennummern-mithilfe-von-pytorch/#respond Sat, 18 Oct 2025 03:37:41 +0000 http://chatgpz.de/machine-learning/so-klassifizieren-sie-den-lungenkrebs-subtyp-anhand-der-dna-kopiennummern-mithilfe-von-pytorch/ [ad_1]

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.

Merkmale von Krebs – eine Reihe auf der linken Seite stellt eine Reihe vereinbarter Merkmale dar, die ein Krebs ansammeln muss, um ein nachhaltiges proliferatives Wachstum zu ermöglichen. Kredit: Hanahan D. Kennzeichen von Krebs: Neue Dimensionen. Krebsentdeckung. 2022 Jan;12(1):31–46.

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.

Eine Brustkrebszelle mit neu gebildeten Chromosomen, die mit Farben gefärbt sind, die ihrem ursprünglichen Chromosom entsprechen. Kredit: Mira Grigorova und Paul Edwards, Abteilung für Pathologie, Universität Cambridge.

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)
Die Verteilung der Proben im Datensatz basierend auf der Krebsart.

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')
Verwirrungsmatrix, die die Leistung des Modells im Testsatz zeigt. Das Modell ist in der Lage, mit hoher Genauigkeit (96 %) zwischen Adenokarzinom und Plattenepithelkarzinom zu unterscheiden. Beachten Sie, dass Ihre Ergebnisse je nach GPU-/CPU-Typ, CUDA-Model oder NVIDIA-Treibern leicht variieren können.

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.

[ad_2]

]]>
http://chatgpz.de/machine-learning/so-klassifizieren-sie-den-lungenkrebs-subtyp-anhand-der-dna-kopiennummern-mithilfe-von-pytorch/feed/ 0 13258
Was Pytorch mit einem Blatt -Tensor und seinem Abschluss wirklich bedeutet http://chatgpz.de/machine-learning/was-pytorch-mit-einem-blatt-tensor-und-seinem-abschluss-wirklich-bedeutet/ http://chatgpz.de/machine-learning/was-pytorch-mit-einem-blatt-tensor-und-seinem-abschluss-wirklich-bedeutet/#respond Sun, 22 Jun 2025 20:35:43 +0000 http://chatgpz.de/machine-learning/was-pytorch-mit-einem-blatt-tensor-und-seinem-abschluss-wirklich-bedeutet/ [ad_1]

ist nicht eine weitere Erklärung der Kettenregel. Es ist eine Tour durch die weird Seite von Autograd – wo Gradienten Physik dienen, nicht nur Gewichte

Ich habe dieses Tutorial ursprünglich im ersten Jahr meines Doktoranden für mich selbst geschrieben und in Pytorch durch die Feinheiten von Gradientenberechnungen navigiert. Das meiste davon ist eindeutig mit Blick auf die Normal -Backpropagation konzipiert – und das ist in Ordnung, da das die meisten Menschen das brauchen.

Aber physikalisches neuronales Netzwerk (PINN) ist ein launisches Tier und braucht eine andere Artwork von Gradientenlogik. Ich habe einige Zeit damit verbracht, es zu füttern und dachte, es könnte sich lohnen, die Ergebnisse mit der Group zu teilen, insbesondere mit anderen Pinn -Praktizierenden – vielleicht spart es jemandem ein paar Kopfschmerzen. Aber wenn Sie noch nie von Pinns gehört haben, machen Sie sich keine Sorgen! Dieser Beitrag ist immer noch für Sie – besonders wenn Sie sich mit Dingen wie Gradienten und all dem lustigen Zeug befassen.

Grundlagen

Tensor In der Computerwelt bedeutet einfach ein mehrdimensionales Array, dh eine Reihe von Zahlen, die von einer oder mehreren Ganzzahlen indiziert werden. Um genau zu sein, gibt es auch nulldimensionale Tensoren, die nur einzelne Zahlen sind. Einige Leute sagen, dass Tensoren eine Verallgemeinerung von Matrizen auf mehr als zwei Dimensionen sind.

Wenn Sie bereits allgemeine Relativitätstheorie studiert haben, haben Sie vielleicht gehört, dass mathematische Tensoren solche Dinge wie kovariante und kontravariante Indizes haben. Aber vergessen Sie es – in Pytorch -Tensoren sind nur mehrdimensionale Arrays. Keine Finesse hier.

Blattzensor ist ein Tensor, der ein Blatt (im Sinne einer Graphentheorie) eines Berechnungsdiagramms ist. Wir werden uns diese unten ansehen, daher wird diese Definition etwas sinnvoller sein.

Der requires_grad Die Eigenschaft eines Tensors sagt Pytorch, ob er sich daran erinnern sollte, wie dieser Tensor in weiteren Berechnungen verwendet wird. Denken Sie vorerst an Tensoren mit requires_grad=True als Variablen, während Tensoren mit requires_grad=False als Konstanten.

Blatttensoren

Beginnen wir damit, ein paar Tensoren zu erstellen und ihre Eigenschaften zu überprüfen requires_grad Und is_leaf.

import torch

a = torch.tensor((3.), requires_grad=True)
b = a * a

c = torch.tensor((5.))
d = c * c

assert a.requires_grad is True and a.is_leaf is True
assert b.requires_grad is True and b.is_leaf is False
assert c.requires_grad is False and c.is_leaf is True
assert d.requires_grad is False and d.is_leaf is True  # sic!
del a, b, c, d

a ist wie erwartet ein Blatt und b ist nicht daran, dass es ein Ergebnis einer Multiplikation ist. a ist so eingestellt, dass er einen Abschluss benötigt, additionally natürlich b erbt diese Eigenschaft.

c ist offensichtlich ein Blatt, aber warum d Ist ein Blatt? Der Grund d.is_leaf ist wahr aus einer bestimmten Konvention: alle Tensoren mit requires_grad auf false eingestellt werden als Blatttensoren wie per als Pytorchs Dokumentation:

Alle Tensoren, die haben requires_grad das ist False wird durch Konvention Blatttensoren sein.

Während mathematisch, d ist kein Blatt (da es sich aus einer anderen Operation ergibt, c * c), Gradientenberechnung wird sich niemals über sie erstrecken. Mit anderen Worten, es wird kein Derivat in Bezug auf c. Dies erlaubt d als Blatt behandelt werden.

Kurz gesagt, in Pytorch sind Blatttenoren beider:

  • Direkt eingegeben (dh nicht von anderen Tensoren berechnet) und haben requires_grad=True. Beispiel: Neuronale Netzwerkgewichte, die zufällig initialisiert werden.
  • Erfordern Sie überhaupt keine Gradienten, unabhängig davon, ob sie direkt eingegeben oder berechnet werden. In den Augen von Autograd sind dies nur Konstanten. Beispiele:
    • Alle neuronalen Netzwerkeingabedaten,
    • Ein Eingabebild nach mittlerer Entfernung oder anderen Operationen, bei denen nur Tensoren für nicht-Gradient-Erregung beteiligt sind.

Eine kleine Bemerkung für diejenigen, die mehr wissen wollen. Der requires_grad Eigenschaft wird wie hier illustriert vererbt:

a = torch.tensor((5.), requires_grad=True)
b = torch.tensor((5.), requires_grad=True)
c = torch.tensor((5.), requires_grad=False)

d = torch.sin(a * b * c)

assert d.requires_grad == any((x.requires_grad for x in (a, b, c)))

Code-Bemerkung: Alle Code-Snippets sollten in sich geschlossen werden, mit Ausnahme von Importen, die ich nur dann einbezieht, wenn sie zum ersten Mal erscheinen. Ich lasse sie fallen, um den Boilerplate -Code zu minimieren. Ich vertraue darauf, dass der Leser in der Lage sein wird, sich leicht um diese zu kümmern.

Abschlussretention

Ein separates Downside ist die Gradientenaufbewahrung. Alle Knoten im Berechnungsdiagramm, dh alle verwendeten Tensoren, haben Gradienten berechnet, wenn sie einen Abschluss benötigen. Es behalten jedoch nur Blatttensoren diese Gradienten. Dies ist sinnvoll, da Gradienten normalerweise zum Aktualisieren von Tensoren verwendet werden und nur Blatttensoren während des Trainings aktualisiert werden. Nicht-Blatt-Tensoren wie b Im ersten Beispiel werden nicht direkt aktualisiert; Sie verändern sich infolge von Änderungen in aso können ihre Gradienten verworfen werden. Es gibt jedoch Szenarien, insbesondere in physikalischen neuronalen Netzwerken (PINNs), in denen Sie möglicherweise die Gradienten dieser Zwischenzensoren beibehalten möchten. In solchen Fällen müssen Sie explizit Nicht-Blatt-Tensoren markieren, um ihre Gradienten zu halten. Mal sehen:

a = torch.tensor((3.), requires_grad=True)
b = a * a
b.backward()

assert a.grad shouldn't be None
assert b.grad is None  # generates a warning

Sie haben wahrscheinlich gerade eine Warnung gesehen:

UserWarning: The .grad attribute of a Tensor that's not a leaf Tensor is being 
accessed. Its .grad attribute will not be populated throughout autograd.backward(). 
Should you certainly need the .grad area to be populated for a non-leaf Tensor, use 
.retain_grad() on the non-leaf Tensor. Should you entry the non-leaf Tensor by 
mistake, be sure to entry the leaf Tensor as an alternative. 
See github.com/pytorch/pytorch/pull/30531 for extra informations. 
(Triggered internally at atensrcATen/core/TensorBody.h:491.)

Lasst es uns additionally beheben, indem wir erzwingen b seinen Gradienten beibehalten

a = torch.tensor((3.), requires_grad=True)
b = a * a
b.retain_grad()  # <- the distinction
b.backward()

assert a.grad shouldn't be None
assert b.grad shouldn't be None

Geheimnisse von Grad

Schauen wir uns nun den berühmten Absolventen selbst an. Was ist das? Ist es ein Tensor? Wenn ja, ist es ein Blatt -Tensor? Benötigt oder behält es Grad?

a = torch.tensor((3.), requires_grad=True)
b = a * a
b.retain_grad()
b.backward()

assert isinstance(a.grad, torch.Tensor)
assert a.grad.requires_grad is False and a.grad.retains_grad is False and a.grad.is_leaf is True
assert b.grad.requires_grad is False and b.grad.retains_grad is False and b.grad.is_leaf is True

Scheinbar:

– – Grad selbst ist ein Tensor,
– – Grad ist ein Blattzensor,
– – Grad erfordert keinen Grad.

Beibehält es Grad? Diese Frage macht keinen Sinn, da sie überhaupt nicht einen Abschluss erfordert. Wir werden in einer Sekunde auf die Frage zurückkommen, dass der Abschluss ein Blatt -Tensor ist, aber jetzt werden wir ein paar Dinge testen.

Mehrfach rückwärts und retain_graph

Was wird passieren, wenn wir den gleichen Grad zweimal berechnen?

a = torch.tensor((3.), requires_grad=True)
b = a * a
b.retain_grad()
b.backward()
attempt:
    b.backward()
besides RuntimeError:
    """
    RuntimeError: Making an attempt to backward via the graph a second time (or 
    immediately entry saved tensors after they've already been freed). Saved 
    intermediate values of the graph are freed whenever you name .backward() or 
    autograd.grad(). Specify retain_graph=True if you have to backward via 
    the graph a second time or if you have to entry saved tensors after 
    calling backward.
    """

Die Fehlermeldung erklärt alles. Dies sollte funktionieren:

a = torch.tensor((3.), requires_grad=True)
b = a * a
b.retain_grad()

b.backward(retain_graph=True)
print(a.grad)  # prints tensor((6.))

b.backward(retain_graph=True)
print(a.grad)  # prints tensor((12.))

b.backward(retain_graph=False)
print(a.grad)  # prints tensor((18.))

# b.backward(retain_graph=False)  # <- right here we'd get an error, as a result of in 
# the earlier name we didn't retain the graph.

Seite (aber wichtig) Hinweis: Sie können auch beobachten, wie sich der Gradient ansammelt a: Mit jeder Iteration wird es hinzugefügt.

Kraftvoll create_graph Argument

Wie erfordern ich einen Abschluss?

a = torch.tensor((5.), requires_grad=True)
b = a * a
b.retain_grad()
b.backward(create_graph=True)

# Right here an fascinating factor occurs: now a.grad would require grad! 
assert a.grad.requires_grad is True
assert a.grad.is_leaf is False

# Then again, the grad of b doesn't require grad, as beforehand. 
assert b.grad.requires_grad is False
assert b.grad.is_leaf is True

Das obige ist sehr nützlich: a.grad Was mathematisch ( frac { partial b} { partial a} ) ist, ist keine Konstante (Blatt), sondern ein reguläres Mitglied des Rechendiagramms, das weiter verwendet werden kann. Wir werden diese Tatsache in Teil 2 verwenden.

Warum das b.grad erfordert keinen Abschluss? Weil Ableitung von b in Bezug auf b ist einfach 1.

Wenn der backward Fühlt sich jetzt nicht intuitiv für Sie, mach dir keine Sorgen. Wir werden bald zu einer anderen Methode namens Nomen Omen wechseln grad Dies ermöglicht es, die Zutaten der Derivate genau zu wählen. Vor zuvor zwei Seitennoten:

Randnotiz 1: Wenn Sie festlegen create_graph Für wahr ist es auch festgelegt retain_graph zu wahr (wenn nicht explizit eingestellt). Im Pytorch -Code sieht es genau so aus wie
Das:

    if retain_graph is None:
        retain_graph = create_graph

Randnotiz 2: Sie haben wahrscheinlich eine solche Warnung gesehen:

    UserWarning: Utilizing backward() with create_graph=True will create a reference 
    cycle between the parameter and its gradient which may trigger a reminiscence leak. 
    We advocate utilizing autograd.grad when creating the graph to keep away from this. If 
    it's important to use this perform, be certain that to reset the .grad fields of your 
    parameters to None after use to interrupt the cycle and keep away from the leak. 
    (Triggered internally at C:cbpytorch_1000000000000worktorchcsrcautogradengine.cpp:1156.)
      Variable._execution_engine.run_backward(  # Calls into the C++ engine to 
    run the backward cross

Und wir werden den Rat folgen und verwenden autograd.grad Jetzt.

Ableitungen mit autograd.grad Funktion

Lassen Sie uns nun von der irgendwie hohen Ebene bewegen .backward() Methode zur niedrigeren Ebene grad Methode, die die Ableitung eines Tensors in Bezug auf einen anderen explizit berechnet.

from torch.autograd import grad

a = torch.tensor((3.), requires_grad=True)
b = a * a * a
db_da = grad(b, a, create_graph=True)(0)
assert db_da.requires_grad is True

In ähnlicher Weise wie bei backwarddie Ableitung von b in Bezug auf a kann als Funktion behandelt und weiter differenziert werden. Additionally mit anderen Worten die create_graph Die Flagge kann verstanden werden als: Halten Sie bei der Berechnung von Gradienten die Geschichte, wie sie berechnet wurden, damit sie als Nicht-Blatt-Tensoren behandeln, die Grad erfordern, und nutzen Sie weiter.

Insbesondere können wir Ableitung zweiter Ordnung berechnen:

d2b_da2 = grad(db_da, a, create_graph=True)(0)
# Facet be aware: the grad perform returns a tuple and the primary component of it's what we want.
assert d2b_da2.merchandise() == 18
assert d2b_da2.requires_grad is True

Wie bereits erwähnt: Dies ist eigentlich die wichtigste Eigenschaft, die es uns ermöglicht, mit Pytorch zu pinnieren.

Einpacken

Die meisten Tutorials über Pytorch -Gradienten konzentrieren sich auf die Backpropagation im klassischen überwachten Lernen. Dieser untersuchte eine andere Perspektive-eine, die von den Bedürfnissen von Pinns und anderen gradientenhungrigen Tieren geprägt ist.

Wir haben gelernt, welche Blätter im Pytorch -Dschungel sind, warum Gradienten nur für Blattknoten standardmäßig aufbewahrt werden und wie sie bei Bedarf für andere Tensoren beibehalten können. Wir haben gesehen, wie create_graph verwandelt Gradienten in differenzierbare Bürger der Autograd -Welt.

Es gibt jedoch noch viele Dinge zu entdecken-insbesondere, warum Gradienten nicht-Scalarar-Funktionen zusätzliche Sorgfalt erfordern, wie man Ableitungen zweiter Ordnung berechnet, ohne Ihren gesamten RAM zu verwenden elementweise Gradient.

Treffen wir uns additionally in Teil 2, wo wir uns genauer ansehen werden grad 👋

[ad_2]

]]>
http://chatgpz.de/machine-learning/was-pytorch-mit-einem-blatt-tensor-und-seinem-abschluss-wirklich-bedeutet/feed/ 0 10189
Effiziente metrische Sammlung in Pytorch: Vermeiden Sie die Leistungsfälle von Torchmetrics http://chatgpz.de/machine-learning/effiziente-metrische-sammlung-in-pytorch-vermeiden-sie-die-leistungsfaelle-von-torchmetrics/ http://chatgpz.de/machine-learning/effiziente-metrische-sammlung-in-pytorch-vermeiden-sie-die-leistungsfaelle-von-torchmetrics/#respond Fri, 07 Feb 2025 13:48:20 +0000 https://chatgpz.de/machine-learning/effiziente-metrische-sammlung-in-pytorch-vermeiden-sie-die-leistungsfaelle-von-torchmetrics/

Die Metriksammlung ist ein wesentlicher Bestandteil jedes maschinellen Lernprojekts, mit dem wir die Modellleistung verfolgen und den Trainingschritt überwachen können. Ideally suited, Metriken sollte gesammelt und berechnet werden, ohne zusätzlichen Aufwand in den Schulungsprozess einzuführen. Genau wie andere Komponenten der Trainingsschleife kann ineffiziente metrische Berechnung unnötige Gemeinkosten einführen, die Trainingsstufe erhöhen und die Schulungskosten aufblasen.

Dieser Beitrag ist der siebte in unserer Serie auf Leistungsprofilierung und Optimierung in Pytorch. Die Serie zielte darauf ab, die kritische Rolle der Leistungsanalyse zu betonen und Optimierung in der Entwicklung des maschinellen Lernens. Jeder Beitrag hat sich auf verschiedene Phasen der Trainingspipeline konzentriert und praktische Instruments und Techniken zur Analyse und Steigerung der Ressourcenauslastung und zur Laufzeiteffizienz demonstriert.

In dieser Charge konzentrieren wir uns auf die metrische Sammlung. Wir werden zeigen, wie sich eine naive Implementierung der metrischen Sammlung negativ auf die Laufzeitleistung auswirken und Instruments und Techniken für ihre Analyse und Optimierung untersuchen kann.

Um unsere metrische Sammlung zu implementieren, werden wir verwenden Torchmetrics Eine beliebte Bibliothek, die zur Vereinfachung und Standardisierung der metrischen Berechnung in der Berechnung in der Berechnung von Pytorch. Unsere Ziele werden sein,:

  1. Demonstrieren Sie den Laufzeitaufwand verursacht durch eine naive Implementierung der metrischen Sammlung.
  2. Verwenden Sie den Pytorch -Profiler Um Leistungs Engpässe zu bestimmen, die durch metrische Berechnung eingeführt wurden.
  3. Demonstrieren Sie Optimierungstechniken Um die metrische Sammlung zu reduzieren.

Um unsere Diskussion zu erleichtern, werden wir ein Spielzeug -Pytorch -Modell definieren und beurteilen, wie sich die metrische Sammlung auf die Laufzeitleistung auswirken kann. Wir werden unsere Experimente auf einer Nvidia A40 -GPU mit a durchführen Pytorch 2.5.1 Docker Bild und TorchMetrics 1.6.1.

Es ist wichtig zu beachten, dass das metrische Sammlungsverhalten je nach {Hardware}, Laufzeitumgebung und Modellarchitektur stark variieren kann. Die in diesem Beitrag bereitgestellten Codeausschnitte dienen nur für Demonstrationszwecke. Bitte interpretieren Sie unsere Erwähnung eines Werkzeugs oder einer Technik nicht als Bestätigung für die Verwendung.

Toy Resnet -Modell

Im folgenden Codeblock definieren wir ein einfaches Bildklassifizierungsmodell mit a Resnet-18 Rückgrat.

import time
import torch
import torchvision

machine = "cuda"

mannequin = torchvision.fashions.resnet18().to(machine)
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(mannequin.parameters())

Wir definieren einen synthetischen Datensatz, mit dem wir unser Spielzeugmodell trainieren werden.

from torch.utils.information import Dataset, DataLoader

# A dataset with random pictures and labels
class FakeDataset(Dataset):
    def __len__(self):
        return 100000000

    def __getitem__(self, index):
        rand_image = torch.randn((3, 224, 224), dtype=torch.float32)
        label = torch.tensor(information=index % 1000, dtype=torch.int64)
        return rand_image, label

train_set = FakeDataset()

batch_size = 128
num_workers = 12

train_loader = DataLoader(
    dataset=train_set,
    batch_size=batch_size,
    num_workers=num_workers,
    pin_memory=True
)

Wir definieren eine Sammlung von Standardmetriken aus TorchMetrics sowie ein Kontrollflag, um die metrische Berechnung zu aktivieren oder zu deaktivieren.

from torchmetrics import (
    MeanMetric,
    Accuracy,
    Precision,
    Recall,
    F1Score,
)

# toggle to allow/disable metric assortment
capture_metrics = False

if capture_metrics:
        metrics = {
        "avg_loss": MeanMetric(),
        "accuracy": Accuracy(job="multiclass", num_classes=1000),
        "precision": Precision(job="multiclass", num_classes=1000),
        "recall": Recall(job="multiclass", num_classes=1000),
        "f1_score": F1Score(job="multiclass", num_classes=1000),
    }

    # Transfer all metrics to the machine
    metrics = {title: metric.to(machine) for title, metric in metrics.objects()}

Als nächstes definieren wir a Pytorch -Profiler Instanz, zusammen mit einem Kontrollflag, das es uns ermöglicht, das Profiling zu aktivieren oder zu deaktivieren. Ein detailliertes Tutorial zur Verwendung von Pytorch -Profiler finden Sie in der Erster Beitrag In dieser Serie.

from torch import profiler

# toggle to allow/disable profiling
enable_profiler = True

if enable_profiler:
    prof = profiler.profile(
        schedule=profiler.schedule(wait=10, warmup=2, lively=3, repeat=1),
        on_trace_ready=profiler.tensorboard_trace_handler("./logs/"),
        profile_memory=True,
        with_stack=True
    )
    prof.begin()

Zuletzt definieren wir einen Commonplace -Trainingsschritt:

mannequin.practice()

t0 = time.perf_counter()
total_time = 0
depend = 0

for idx, (information, goal) in enumerate(train_loader):
    information = information.to(machine, non_blocking=True)
    goal = goal.to(machine, non_blocking=True)
    optimizer.zero_grad()
    output = mannequin(information)
    loss = criterion(output, goal)
    loss.backward()
    optimizer.step()

    if capture_metrics:
        # replace metrics
        metrics("avg_loss").replace(loss)
        for title, metric in metrics.objects():
            if title != "avg_loss":
                metric.replace(output, goal)

        if (idx + 1) % 100 == 0:
            # compute metrics
            metric_results = {
                title: metric.compute().merchandise() 
                    for title, metric in metrics.objects()
            }
            # print metrics
            print(f"Step {idx + 1}: {metric_results}")
            # reset metrics
            for metric in metrics.values():
                metric.reset()

    elif (idx + 1) % 100 == 0:
        # print final loss worth
        print(f"Step {idx + 1}: Loss = {loss.merchandise():.4f}")

    batch_time = time.perf_counter() - t0
    t0 = time.perf_counter()
    if idx > 10:  # skip first steps
        total_time += batch_time
        depend += 1

    if enable_profiler:
        prof.step()

    if idx > 200:
        break

if enable_profiler:
    prof.cease()

avg_time = total_time/depend
print(f'Common step time: {avg_time}')
print(f'Throughput: {batch_size/avg_time:.2f} pictures/sec')

Metrische Sammlung Overhead

Um die Auswirkungen der metrischen Sammlung auf die Trainingsschrittzeit zu messen, haben wir unser Trainingsskript sowohl mit als auch ohne metrische Berechnung durchgeführt. Die Ergebnisse sind in der folgenden Tabelle zusammengefasst.

Der Overhead der naiven metrischen Sammlung (vom Autor)

Unsere naive metrische Sammlung führte zu einem Rückgang der Laufzeitleistung von quick 10% !! Während die metrische Sammlung für die Entwicklung des maschinellen Lernens unerlässlich ist, beinhaltet sie normalerweise relativ einfache mathematische Operationen und rechtfertigt kaum einen so erheblichen Aufwand. Was ist los?!!

Identifizierung von Leistungsproblemen mit Pytorch -Profiler

Um die Quelle der Leistungsverschlechterung besser zu verstehen, können wir das Trainingsskript mit dem aktivierten Pytorch -Profiler umgeben. Die resultierende Spur ist unten dargestellt:

Spur des metrischen Sammlungsexperiments (vom Autor)

Die Spur zeigt wiederkehrende „Cudastreamsynchronize“ -Operationen, die mit merklichen Tropfen der GPU -Nutzung übereinstimmen. Diese Arten von „CPU-GPU-Synchronisationsereignis Teil zwei unserer Serie. In einem typischen Trainingsschritt arbeitet die CPU und die GPU parallel: Die CPU verwaltet Aufgaben wie Datenübertragungen in die GPU- und Kernelbelastung, und die GPU führt das Modell für die Eingabedaten aus und aktualisiert ihre Gewichte. Im Idealfall möchten wir die Synchronisationspunkte zwischen CPU und GPU minimieren, um die Leistung zu maximieren. Hier können wir jedoch sehen, dass die metrische Sammlung ein Synchronisierungsereignis ausgelöst hat, indem eine CPU an die GPU -Datenkopie durchgeführt wird. Dies erfordert, dass die CPU ihre Verarbeitung aushält, bis die GPU auffasst, was wiederum dazu führt, dass die GPU darauf wartet, dass die CPU die nachfolgenden Kernelvorgänge aufnimmt. Das Fazit ist, dass diese Synchronisationspunkte zu einer ineffizienten Nutzung sowohl der CPU als auch der GPU führen. Unsere Implementation für die metrische Sammlung fügt jedem Trainingsschritt acht solcher Synchronisationsereignisse hinzu.

Eine genauere Untersuchung der Spur zeigt, dass die SYNC -Ereignisse von der stammen aktualisieren Ruf der Gemein Torchmetrik. Für den erfahrenen Profilerstellungsexperten kann dies ausreichen, um die Grundursache zu identifizieren, aber wir werden einen Schritt weiter gehen und die verwenden fackel.profiler.record_function Dienstprogramm zur Identifizierung der genauen beleidigenden Codezeile.

Profilerstellung mit record_function

Um die genaue Quelle des Synchronisationsereignisses zu bestimmen, haben wir die erweitert Gemein Klasse und überschreiben die aktualisieren Methode verwendet record_function Kontextblöcke. Dieser Ansatz ermöglicht es uns, individuelle Vorgänge innerhalb der Methode zu profilieren und Leistungsgpässe zu identifizieren.

class ProfileMeanMetric(MeanMetric):
    def replace(self, worth, weight = 1.0):
        # broadcast weight to worth form
        with profiler.record_function("course of worth"):
            if not isinstance(worth, torch.Tensor):
                worth = torch.as_tensor(worth, dtype=self.dtype,
                                        machine=self.machine)
        with profiler.record_function("course of weight"):
            if weight shouldn't be None and never isinstance(weight, torch.Tensor):
                weight = torch.as_tensor(weight, dtype=self.dtype,
                                         machine=self.machine)
        with profiler.record_function("broadcast weight"):
            weight = torch.broadcast_to(weight, worth.form)
        with profiler.record_function("cast_and_nan_check"):
            worth, weight = self._cast_and_nan_check_input(worth, weight)

        if worth.numel() == 0:
            return

        with profiler.record_function("replace worth"):
            self.mean_value += (worth * weight).sum()
        with profiler.record_function("replace weight"):
            self.weight += weight.sum()

Anschließend haben wir unsere AVG_Loss -Metrik aktualisiert, um das neu erstellte ProfileMeanMetric und das Trainingskript erneut zu verwenden.

Spur der metrischen Sammlung mit record_function (vom Autor)

Die aktualisierte Hint zeigt, dass das Sync -Ereignis aus der folgenden Zeile stammt:

weight = torch.as_tensor(weight, dtype=self.dtype, machine=self.machine)

Dieser Vorgang wandelt den Commonplace -Skalarwert um weight=1.0 in einen Pytorch -Tensor und legt ihn auf die GPU. Das Synchronisierungsereignis tritt auf, da diese Aktion eine CPU-to-GPU-Datenkopie auslöst, wodurch die CPU darauf wartet, dass die GPU den kopierten Wert verarbeitet.

Optimierung 1: Gewichtswert angeben

Nachdem wir die Quelle des Issues gefunden haben, können wir sie leicht überwinden, indem wir a angeben Gewicht Wert in unserem aktualisieren Anruf. Dies verhindert, dass die Laufzeit den Commonplace -Skalar konvertieren weight=1.0 in einen Tensor auf der GPU und vermeiden Sie das Synchronisierungsereignis:

# replace metrics
 if capture_metric:
     metrics("avg_loss").replace(loss, weight=torch.ones_like(loss))

Nach dem Drehbuch nach der Anwendung dieser Änderung ergibt _cast_and_nan_check_input Funktion:

Spur der metrischen Sammlung nach Optimierung 1 (vom Autor)

Profilerstellung mit record_function – Teil 2

Um unser neues Synchronisationsereignis zu untersuchen, haben wir unsere benutzerdefinierte Metrik mit zusätzlichen Profile -Sonden erweitert und unser Skript erneut übertragen.

class ProfileMeanMetric(MeanMetric):
    def replace(self, worth, weight = 1.0):
        # broadcast weight to worth form
        with profiler.record_function("course of worth"):
            if not isinstance(worth, torch.Tensor):
                worth = torch.as_tensor(worth, dtype=self.dtype,
                                        machine=self.machine)
        with profiler.record_function("course of weight"):
            if weight shouldn't be None and never isinstance(weight, torch.Tensor):
                weight = torch.as_tensor(weight, dtype=self.dtype,
                                         machine=self.machine)
        with profiler.record_function("broadcast weight"):
            weight = torch.broadcast_to(weight, worth.form)
        with profiler.record_function("cast_and_nan_check"):
            worth, weight = self._cast_and_nan_check_input(worth, weight)

        if worth.numel() == 0:
            return

        with profiler.record_function("replace worth"):
            self.mean_value += (worth * weight).sum()
        with profiler.record_function("replace weight"):
            self.weight += weight.sum()

    def _cast_and_nan_check_input(self, x, weight = None):
        """Convert enter ``x`` to a tensor and test for Nans."""
        with profiler.record_function("course of x"):
            if not isinstance(x, torch.Tensor):
                x = torch.as_tensor(x, dtype=self.dtype,
                                    machine=self.machine)
        with profiler.record_function("course of weight"):
            if weight shouldn't be None and never isinstance(weight, torch.Tensor):
                weight = torch.as_tensor(weight, dtype=self.dtype,
                                         machine=self.machine)
            nans = torch.isnan(x)
            if weight shouldn't be None:
                nans_weight = torch.isnan(weight)
            else:
                nans_weight = torch.zeros_like(nans).bool()
                weight = torch.ones_like(x)

        with profiler.record_function("any nans"):
            anynans = nans.any() or nans_weight.any()

        with profiler.record_function("course of nans"):
            if anynans:
                if self.nan_strategy == "error":
                    elevate RuntimeError("Encountered `nan` values in tensor")
                if self.nan_strategy in ("ignore", "warn"):
                    if self.nan_strategy == "warn":
                        print("Encountered `nan` values in tensor."
                              " Might be eliminated.")
                    x = x(~(nans | nans_weight))
                    weight = weight(~(nans | nans_weight))
                else:
                    if not isinstance(self.nan_strategy, float):
                        elevate ValueError(f"`nan_strategy` shall be float"
                                         f" however you move {self.nan_strategy}")
                    x(nans | nans_weight) = self.nan_strategy
                    weight(nans | nans_weight) = self.nan_strategy

        with profiler.record_function("return worth"):
            retval = x.to(self.dtype), weight.to(self.dtype)
        return retval

Die resultierende Spur wird unten erfasst:

Spur der metrischen Sammlung mit record_function – Teil 2 (vom Autor)

Die Ablaufverfolgung zeigt direkt in die beleidigende Linie:

anynans = nans.any() or nans_weight.any()

Dieser Betrieb überprüft nach NaN Werte in den Eingangstensoren, jedoch ein kostspieliges CPU-GPU-Synchronisationsereignis einführt, da der Betrieb Daten von der GPU an die CPU kopiert.

Bei einer genaueren Prüfung des Torchmetric Baseaggregator Klasse finden wir mehrere Optionen für die Bearbeitung von NAN -Wert -Updates, die alle durch die beleidigende Codezeile geleitet werden. Für unseren Anwendungsfall – die Berechnung der durchschnittlichen Verlustmetrik – ist dieser Scheck jedoch unnötig und rechtfertigt die Laufzeitleistung nicht.

Optimierung 2: Deaktivieren Sie NAN -Wertprüfungen

Um den Overhead zu beseitigen, schlagen wir vor, die zu deaktivieren NaN Wertprüfungen durch Überschreiben der _cast_and_nan_check_input Funktion. Anstelle einer statischen Übersteuerung haben wir eine dynamische Lösung implementiert, die flexibel auf alle Nachkommen der angewendet werden kann BaseaggregatorKlasse.

from torchmetrics.aggregation import BaseAggregator

def suppress_nan_check(MetricClass):
    assert issubclass(MetricClass, BaseAggregator), MetricClass
    class DisableNanCheck(MetricClass):
        def _cast_and_nan_check_input(self, x, weight=None):
            if not isinstance(x, torch.Tensor):
                x = torch.as_tensor(x, dtype=self.dtype, 
                                    machine=self.machine)
            if weight shouldn't be None and never isinstance(weight, torch.Tensor):
                weight = torch.as_tensor(weight, dtype=self.dtype,
                                         machine=self.machine)
            if weight is None:
                weight = torch.ones_like(x)
            return x.to(self.dtype), weight.to(self.dtype)
    return DisableNanCheck

NoNanMeanMetric = suppress_nan_check(MeanMetric)

metrics("avg_loss") = NoNanMeanMetric().to(machine)

Nachoptimierungsergebnisse: Erfolg

Nach der Implementierung der beiden Optimierungen – Angabe des Gewichtswerts und Deaktivieren der NaN Überprüfungen – Wir finden die Schrittzeitleistung und die GPU -Auslastung, die denen unseres Baseline -Experiments entspricht. Darüber hinaus zeigt die resultierende Pytorch -Profiler -Hint, dass alle zusätzlichen Ereignisse „Cudastreamsynchronize“, die mit der metrischen Sammlung verbunden waren, eliminiert wurden. Mit ein paar kleinen Änderungen haben wir die Schulungskosten um ~ 10% gesenkt, ohne dass sich das Verhalten der Metriksammlung ändert.

Im nächsten Abschnitt werden wir eine zusätzliche Optimierung der metrischen Sammlung untersuchen.

Beispiel 2: Optimierung der Metrik -Geräteplatzierung

Im vorherigen Abschnitt befanden sich die Metrikwerte auf der GPU, sodass sie logisch die Metriken auf der GPU speichern und berechnen können. In Szenarien, in denen sich die Werte, die wir aggregieren möchten, auf der CPU liegen, kann es jedoch vorzuziehen sein, die Metriken auf der CPU zu speichern, um unnötige Geräteübertragungen zu vermeiden.

Im folgenden Codeblock ändern wir unser Skript, um die durchschnittliche Schrittzeit mit a zu berechnen Gemein auf der CPU. Diese Änderung hat keinen Einfluss auf die Laufzeitleistung unseres Trainingsschritts:

avg_time = NoNanMeanMetric()
t0 = time.perf_counter()

for idx, (information, goal) in enumerate(train_loader):
    # transfer information to machine
    information = information.to(machine, non_blocking=True)
    goal = goal.to(machine, non_blocking=True)

    optimizer.zero_grad()
    output = mannequin(information)
    loss = criterion(output, goal)
    loss.backward()
    optimizer.step()

    if capture_metrics:
        metrics("avg_loss").replace(loss)
        for title, metric in metrics.objects():
            if title != "avg_loss":
                metric.replace(output, goal)

        if (idx + 1) % 100 == 0:
            # compute metrics
            metric_results = {
                title: metric.compute().merchandise()
                    for title, metric in metrics.objects()
            }
            # print metrics
            print(f"Step {idx + 1}: {metric_results}")
            # reset metrics
            for metric in metrics.values():
                metric.reset()

    elif (idx + 1) % 100 == 0:
        # print final loss worth
        print(f"Step {idx + 1}: Loss = {loss.merchandise():.4f}")

    batch_time = time.perf_counter() - t0
    t0 = time.perf_counter()
    if idx > 10:  # skip first steps
        avg_time.replace(batch_time)

    if enable_profiler:
        prof.step()

    if idx > 200:
        break

if enable_profiler:
    prof.cease()

avg_time = avg_time.compute().merchandise()
print(f'Common step time: {avg_time}')
print(f'Throughput: {batch_size/avg_time:.2f} pictures/sec')

Das Downside tritt auf, wenn wir versuchen, unser Skript zu erweitern, um verteilte Schulungen zu unterstützen. Um das Downside zu demonstrieren, haben wir unsere Modelldefinition geändert, um sie zu verwenden DistributedDataparallel (DDP) :

# toggle to allow/disable ddp
use_ddp = True

if use_ddp:
    import os
    import torch.distributed as dist
    from torch.nn.parallel import DistributedDataParallel as DDP
    os.environ("MASTER_ADDR") = "127.0.0.1"
    os.environ("MASTER_PORT") = "29500"
    dist.init_process_group("nccl", rank=0, world_size=1)
    torch.cuda.set_device(0)
    mannequin = DDP(torchvision.fashions.resnet18().to(machine))
else:
    mannequin = torchvision.fashions.resnet18().to(machine)

# insert coaching loop

# append to finish of the script:
if use_ddp:
    # destroy the method group
    dist.destroy_process_group()

Die DDP -Modifikation führt zum folgenden Fehler:

RuntimeError: No backend sort related to machine sort cpu

Standardmäßig werden Metriken im verteilten Coaching so programmiert, dass alle im verwendeten Geräte über alle Geräte synchronisiert werden. Das von DDP verwendete Synchronisations -Backend unterstützt jedoch keine auf der CPU gespeicherten Metriken.

Eine Möglichkeit, dies zu lösen, besteht darin, die Cross-System-Metriksynchronisation zu deaktivieren:

avg_time = NoNanMeanMetric(sync_on_compute=False)

In unserem Fall ist diese Lösung akzeptabel, wenn wir die durchschnittliche Zeit messen. In einigen Fällen ist die metrische Synchronisation jedoch unerlässlich, und wir haben möglicherweise keine andere Wahl, als die Metrik auf die GPU zu bewegen:

avg_time = NoNanMeanMetric().to(machine)

Leider führt diese Scenario zu einem neuen CPU-GPU-Synchronisationsereignis aus dem aktualisieren Funktion.

Hint der metrischen Sammlung von AVG_Time (vom Autor)

Dieses Synchronisierungsereignis sollte kaum überraschen – nach allem aktualisieren wir eine GPU -Metrik mit einem Wert, der sich auf der CPU befindet, was eine Speicherkopie erfordern sollte. Bei einer skalaren Metrik kann diese Datenübertragung jedoch mit einer einfachen Optimierung vollständig vermieden werden.

Optimierung 3: Führen Sie metrische Aktualisierungen mit Tensoren anstelle von Skalaren durch

Die Lösung ist unkompliziert: Anstatt die Metrik mit einem Float -Wert zu aktualisieren replace.

batch_time = torch.as_tensor(batch_time)
avg_time.replace(batch_time, torch.ones_like(batch_time))

Diese geringfügige Änderung umgeht die problematische Codezeile, beseitigt das SYNC -Ereignis und stellt die Schrittzeit für die Basisleistung wieder her.

Auf den ersten Blick magazine dieses Ergebnis überraschend erscheinen: Wir würden erwarten, dass die Aktualisierung einer GPU -Metrik mit einem CPU -Tensor noch eine Speicherkopie erfordern sollte. Pytorch optimiert jedoch den Vorgang auf skalaren Tensoren, indem ein dedizierter Kernel verwendet wird, der die Zugabe ohne explizite Datenübertragung durchführt. Dies vermeidet das teure Synchronisierungsereignis, das sonst auftreten würde.

Zusammenfassung

In diesem Beitrag haben wir untersucht, wie ein naiver Ansatz für TorchMetrics CPU-GPU-Synchronisierungsereignisse einführen und die Pytorch-Trainingsleistung erheblich beeinträchtigen kann. Mit dem Pytorch -Profiler haben wir die Codezeilen identifiziert, die für diese Synchronisierungsereignisse verantwortlich sind, und gezielte Optimierungen angewendet haben, um sie zu beseitigen:

  • Geben Sie beim Aufrufen des MeanMetric.replace Funktion anstatt sich auf den Standardwert zu verlassen.
  • Deaktivieren Sie NAN -Überprüfungen in der Foundation Aggregator Klasse oder ersetzen Sie sie durch eine effizientere Various.
  • Verwalten Sie die Geräteplatzierung jeder Metrik sorgfältig, um unnötige Übertragungen zu minimieren.
  • Deaktivieren Sie die metrische Synchronisation mit Cross-System-Synchronisation, wenn nicht erforderlich.
  • Wenn die Metrik auf einer GPU liegt replace Funktion, um implizite Synchronisation zu vermeiden.

Wir haben eine engagierte geschaffen Anfrage ziehenauf der Torchmetrics GithubSeiten, die einige der in diesem Beitrag diskutierten Optimierungen abdecken. Bitte zögern Sie nicht, Ihre eigenen Verbesserungen und Optimierungen beizutragen!


[ad_2]]]>
http://chatgpz.de/machine-learning/effiziente-metrische-sammlung-in-pytorch-vermeiden-sie-die-leistungsfaelle-von-torchmetrics/feed/ 0 7053
Das Geheimnis hinter der PyTorch Automated Combined Precision Library | von Mengliu Zhao | September 2024 http://chatgpz.de/artificial-intelligence/das-geheimnis-hinter-der-pytorch-automated-combined-precision-library-von-mengliu-zhao-september-2024/ http://chatgpz.de/artificial-intelligence/das-geheimnis-hinter-der-pytorch-automated-combined-precision-library-von-mengliu-zhao-september-2024/#respond Wed, 18 Sep 2024 00:22:15 +0000 http://chatgpz.de/artificial-intelligence/das-geheimnis-hinter-der-pytorch-automatic-mixed-precision-library-von-mengliu-zhao-september-2024/ [ad_1]

Grundlagen des Datenformats – Einfache Genauigkeit (FP32) vs. halbe Genauigkeit (FP16)

Schauen wir uns nun die Formate FP32 und FP16 genauer an. FP32 und FP16 sind IEEE-Formate, die Gleitkommazahlen mit 32-Bit-Binärspeicher und 16-Bit-Binärspeicher darstellen. Beide Formate bestehen aus drei Teilen: a) einem Vorzeichenbit, b) Exponentenbits und c) Mantissenbits. FP32 und FP16 unterscheiden sich in der Anzahl der dem Exponenten und der Mantisse zugewiesenen Bitswodurch sich unterschiedliche Wertebereiche und Genauigkeiten ergeben.

Unterschied zwischen FP16 (IEEE-Commonplace), BF16 (Google Mind-Commonplace), FP32 (IEEE-Commonplace) und TF32 (Nvidia-Commonplace). Bildquelle: https://en.wikipedia.org/wiki/Bfloat16_floating-point_format

Wie konvertiert man FP16 und FP32 in reale Werte? Gemäß IEEE-754-Requirements ist der Dezimalwert für FP32 = (-1)^(Vorzeichen) × 2^(Dezimalexponent —127 ) × (implizite führende 1 + Dezimalmantisse), wobei 127 der verzerrte Exponentenwert ist. Für FP16 lautet die Formel (-1)^(Vorzeichen) × 2^(Dezimalexponent — 15) × (implizite führende 1 + Dezimalmantisse), wobei 15 der entsprechende verzerrte Exponentenwert ist. Weitere Einzelheiten zum verzerrten Exponentenwert finden Sie hier Hier.

In diesem Sinne beträgt der Wertebereich für FP32 ungefähr (-2¹²⁷, 2¹²⁷) ~(-1,7*1e38, 1,7*1e38) und der Wertebereich für FP16 ungefähr (-2¹⁵, 2¹⁵)=(-32768, 32768). Beachten Sie, dass der Dezimalexponent für FP32 zwischen 0 und 255 liegt und wir den größten Wert 0xFF ausschließen, da er NAN darstellt. Deshalb ist der größte Dezimalexponent 254–127 = 127. Eine ähnliche Regel gilt für FP16.

Beachten Sie bei der Genauigkeit, dass sowohl der Exponent als auch die Mantisse zu den Genauigkeitsgrenzen beitragen (was auch als Denormalisierungsehen ausführliche Diskussion hier), daher kann FP32 eine Genauigkeit von bis zu 2^(-23)*2^(-126)=2^(-149) darstellen, und FP16 kann eine Genauigkeit von bis zu 2^(10)*2^(-14)=2^(-24) darstellen.

Der Unterschied zwischen FP32- und FP16-Darstellungen bringt die Hauptprobleme des Combined-Precision-Trainings mit sich, da Verschiedene Schichten/Operationen von Deep Studying-Modellen sind entweder unempfindlich oder empfindlich gegenüber Wertebereichen und Präzision und müssen separat behandelt werden..

[ad_2]

]]>
http://chatgpz.de/artificial-intelligence/das-geheimnis-hinter-der-pytorch-automated-combined-precision-library-von-mengliu-zhao-september-2024/feed/ 0 3945
Der Aufstieg von PyTorch: Das Rückgrat der modernen KI http://chatgpz.de/artificial-intelligence/der-aufstieg-von-pytorch-das-rueckgrat-der-modernen-ki/ http://chatgpz.de/artificial-intelligence/der-aufstieg-von-pytorch-das-rueckgrat-der-modernen-ki/#respond Sun, 07 Jul 2024 00:49:15 +0000 http://chatgpz.de/artificial-intelligence/der-aufstieg-von-pytorch-das-rueckgrat-der-modernen-ki/ [ad_1]

Künstliche Intelligenz (KI) hat Branchen revolutioniert und Innovationen in verschiedenen Bereichen wie Gesundheitswesen, Finanzen und Transport vorangetrieben. Im Mittelpunkt dieser Fortschritte stehen Deep-Studying-Frameworks, die die Entwicklung komplexer neuronaler Netzwerke ermöglichen. Zu diesen Frameworks gehören PyTorch hat sich als dominierende Kraft herauskristallisiert. Dieser Artikel geht darauf ein, warum KI zunehmend auf PyTorch basiert, und untersucht seine Funktionen, Vorteile und die Rolle, die es in der KI-Landschaft spielt.

Die Entstehung von PyTorch

PyTorch, eine Open-Supply-Bibliothek für maschinelles Lernen, wurde entwickelt von Facebooks KI-Forschungslabor (FAIR) und im Januar 2017 offiziell veröffentlicht. Es gewann schnell an Popularität unter Forschern und Entwicklern aufgrund seines dynamischen Rechengraphen und seiner intuitiven Benutzeroberfläche. Im Gegensatz zu seinen Vorgängern ermöglichte PyTorch mehr Flexibilität und Benutzerfreundlichkeit, was für schnelles Prototyping und Experimentieren entscheidend warfare.

PyTorch – Das Gehirn der KI

Hauptfunktionen von PyTorch

  1. Dynamische Computergraphen:
    Der dynamische Berechnungsgraph von PyTorch, auch bekannt als Outline-by-Run, ist eines seiner wichtigsten Options. Dadurch können Entwickler den Graphen im laufenden Betrieb ändern, was das Debuggen und Experimentieren erleichtert. unterschiedliche Netzwerkarchitekturen. Im Gegensatz dazu erfordern statische Berechnungsgraphen, wie sie in TensorFlow 1.x zu sehen sind, dass der gesamte Graph vor dem Ausführen des Modells definiert wird, was es weniger flexibel macht.
  2. Pythonische Natur:
    PyTorch ist eng in Python integriert, die von vielen in der KI- und Machine-Studying-Neighborhood bevorzugte Programmiersprache. Diese Integration stellt sicher, dass PyTorch-Code besser lesbar und wartbar ist. Die nahtlose Kompatibilität mit Python-Bibliotheken wie NumPy und SciPy erhöht seine Attraktivität noch weiter.
  3. Automatische Differenzierung:
    PyTorch enthält eine leistungsstarke automatische Differenzierungsbibliothek namens Autograd. Diese Funktion automatisiert die Berechnung von Gradienten, die für das Coaching neuronaler Netzwerke unerlässlich sind. Autograd zeichnet an Tensoren durchgeführte Operationen auf und kann mithilfe dieser Informationen automatisch Ableitungen berechnen, was die Implementierung der Backpropagation vereinfacht.
  4. Gemeinschaft und Ökosystem:
    Die PyTorch-Neighborhood ist exponentiell gewachsen und trägt zu einem reichhaltigen Ökosystem aus Instruments und Bibliotheken bei. Bibliotheken wie torchvision, das Datensätze, Modelle und Transformationen für Pc Imaginative and prescient bereitstellt, und torchtext für die Verarbeitung natürlicher Sprache erweitern die Fähigkeiten von PyTorch. Die lebendige Neighborhood sorgt außerdem für kontinuierliche Updates und Verbesserungen.

PyTorch in Forschung und Entwicklung

Die Designphilosophie von PyTorch passt intestine zu den Anforderungen von Forscher und Entwickler. Laut einer von Papers with Code durchgeführten Umfrage wurde PyTorch in quick 75 % der im Jahr 2020 bei den wichtigsten KI-Konferenzen eingereichten Beiträge verwendet. Diese Statistik unterstreicht die weite Verbreitung der Technologie in der Forschungsgemeinschaft.

Yann LeCun, leitender KI-Wissenschaftler bei Fb, hob in einem Interview die Wirkung von PyTorch hervor: „PyTorch hat es Forschern ermöglicht, schneller voranzukommen und effektiver zusammenzuarbeiten. Seine intuitive Benutzeroberfläche und Flexibilität haben es zum bevorzugten Framework für Spitzenforschung gemacht.“

Fallstudien: PyTorch in Aktion

  1. Gesundheitspflege:
    PyTorch warfare maßgeblich an der Entwicklung von KI-Modellen für Anwendungen im Gesundheitswesen beteiligt. Forscher der Stanford College verwendeten PyTorch, um CheXNet zu entwickeln, einen Deep-Studying-Algorithmus, der Lungenentzündungen anhand von Röntgenaufnahmen der Brust mit einer Genauigkeit erkennen kann, die mit der von Radiologen vergleichbar ist. Dieser Durchbruch zeigt das Potenzial von PyTorch, die medizinische Diagnostik zu verändern.
  2. Autonome Fahrzeuge:
    Unternehmen wie Tesla und Uber verlassen sich bei ihren autonomen Fahrtechnologien auf PyTorch. Teslas Autopilot beispielsweise verwendet mit PyTorch trainierte neuronale Netzwerke, um komplexe Fahrumgebungen zu interpretieren und darauf zu reagieren. Die Fähigkeit, mit PyTorch schnell zu experimentieren und zu iterieren, warfare für die Weiterentwicklung dieser Technologien von entscheidender Bedeutung.
  3. Verarbeitung natürlicher Sprache:
    OpenAIs GPT-3, eines der bislang fortschrittlichsten Sprachmodelle, wurde mit PyTorch entwickelt. GPT-3 kann menschenähnlichen Textual content generieren und Aufgaben wie Übersetzen, Zusammenfassen und Beantworten von Fragen ausführen. Die Entwicklung und Feinabstimmung des Modells wurden durch die robusten Fähigkeiten von PyTorch erleichtert.

Branchenakzeptanz

Auch die Branche hat das Potenzial von PyTorch erkannt, was dazu geführt hat, dass es von mehreren Technologiegiganten übernommen wurde. Unternehmen wie Microsoft, Amazon und Google haben PyTorch in ihre KI-Dienste und -Produkte integriert.

Microsoft hat beispielsweise PyTorch zum primären Framework für seinen Azure Machine Studying-Dienst gemacht. Eric Boyd, Company Vice President von Microsoft AI, erklärte: „PyTorchs dynamische Natur und Benutzerfreundlichkeit haben es zum idealen Framework für unsere KI-Lösungen auf Azure gemacht. Es ermöglicht unseren Kunden, Modelle effizienter zu erstellen, zu trainieren und bereitzustellen.“

TensorFlow vs. PyTorch

PyTorch im Vergleich zu TensorFlow

Die Debatte zwischen PyTorch und TensorFlow ist ein wichtiges Thema in der KI-Neighborhood. TensorFlow, entwickelt von Google Mind, warfare vor dem Aufstieg von PyTorch das dominierende Deep-Studying-Framework. PyTorch hat jedoch mehrere Vorteile, die die Präferenz vieler Forscher und Entwickler verschoben haben:

  1. Benutzerfreundlichkeit:
    Die Syntax von PyTorch ist intuitiver und ähnelt stark der Normal-Python-Programmierung. Dies erleichtert Anfängern das Lernen und Experten das schnelle Erstellen komplexer Modellprototypen. TensorFlow 2.0 hat in diesem Bereich Fortschritte gemacht, aber PyTorch hat immer noch die Nase vorn.
  2. Debugging-Funktionen:
    Der dynamische Berechnungsgraph in PyTorch ermöglicht sofortiges Suggestions und Debugging in Echtzeit. Dies ist besonders während der Experimentierphase der Modellentwicklung nützlich. Der statische Graphenansatz von TensorFlow kann das Debugging mühsamer machen.
  3. Flexibilität:
    Die Flexibilität von PyTorch bei der Änderung des Berechnungsgraphen im laufenden Betrieb ist für Forschung und Entwicklung von entscheidender Bedeutung. TensorFlow hat mit seinem Keen-Ausführungsmodus ähnliche Funktionen eingeführt, aber die Implementierung von PyTorch bleibt nahtloser.

PyTorch im Bildungsbereich

PyTorchs Einfachheit und Lesbarkeit haben es auch in Bildungseinrichtungen zu einem Favoriten gemacht. Viele Universitäten und On-line-Kurse haben PyTorch für die Vermittlung von Deep-Studying-Konzepten übernommen. Das Buch „Deep Studying with PyTorch“ von Eli Stevens, Luca Antiga und Thomas Viehmann ist zu einer unverzichtbaren Ressource für Lernende geworden.

Andrew Ng, eine prominente Persönlichkeit in der KI-Ausbildung, kommentierte die pädagogische Wirkung von PyTorch wie folgt: „PyTorch hat die Einstiegshürde für Studenten und Praktiker gesenkt. Seine benutzerfreundliche Oberfläche und die starke Neighborhood-Unterstützung machen es zu einem hervorragenden Werkzeug zum Lernen und Experimentieren mit Deep Studying.“

Zukunft von PyTorch

Die Zukunft von PyTorch sieht vielversprechend aus, da ständig Verbesserungen und neue Funktionen hinzugefügt werden. Facebooks Engagement für das Framework stellt kontinuierliche Unterstützung und Entwicklung sicher. Einige der erwarteten Fortschritte sind:

  1. Verbesserte Efficiency:
    Derzeit werden Anstrengungen unternommen, um die Leistung von PyTorch weiter zu optimieren. Die Einführung von TorchScript, einer Möglichkeit zum Erstellen serialisierbarer und optimierbarer Modelle, ermöglicht eine bessere Bereitstellung in Produktionsumgebungen.
  2. Erweitertes Ökosystem:
    Das Ökosystem rund um PyTorch wird voraussichtlich wachsen und es werden weitere Bibliotheken und Instruments entwickelt. Dadurch werden die Fähigkeiten des PyTorch weiter verbessert und es für verschiedene Anwendungen vielseitiger einsetzbar.
  3. Verbesserte Integration:
    PyTorch soll seine Integration mit anderen Frameworks und Plattformen verbessern. Dazu gehört eine bessere Unterstützung für die Bereitstellung von Modellen auf Edge-Geräten und Cloud-Diensten, wodurch es für verschiedene Anwendungsfälle zugänglicher wird.

Und schlussendlich

PyTorch ist unbestreitbar zu einem Eckpfeiler der modernen KI-Entwicklung geworden. Sein dynamischer Rechengraph, seine Benutzerfreundlichkeit und seine starke Neighborhood-Unterstützung haben es zur bevorzugten Wahl für Forscher, Entwickler und Pädagogen gemacht. Während sich die KI weiterentwickelt, ist PyTorch bereit, an der Spitze zu bleiben, Innovationen voranzutreiben und bahnbrechende Fortschritte zu ermöglichen.

Die Entwicklung von PyTorch vom Forschungstool zum Industriestandard spiegelt seine Bedeutung in der KI-Landschaft wider. Während Unternehmen und Forscher die Grenzen dessen, was KI leisten kann, immer weiter verschieben, wird PyTorch zweifellos eine entscheidende Rolle bei der Gestaltung der Zukunft dieser transformativen Technologie spielen.



[ad_2]

]]>
http://chatgpz.de/artificial-intelligence/der-aufstieg-von-pytorch-das-rueckgrat-der-modernen-ki/feed/ 0 2052
Umfassender Leitfaden zu Datasets und Dataloadern in PyTorch | von Ryan D’Cunha | Juni 2024 http://chatgpz.de/machine-learning/umfassender-leitfaden-zu-datasets-und-dataloadern-in-pytorch-von-ryan-dcunha-juni-2024/ http://chatgpz.de/machine-learning/umfassender-leitfaden-zu-datasets-und-dataloadern-in-pytorch-von-ryan-dcunha-juni-2024/#respond Sun, 23 Jun 2024 13:24:48 +0000 https://chatgpz.de/machine-learning/umfassender-leitfaden-zu-datasets-und-dataloadern-in-pytorch-von-ryan-dcunha-juni-2024/ [ad_1]

Die vollständige Anleitung zum Erstellen benutzerdefinierter Datensätze und Datenlader für verschiedene Modelle in PyTorch

Quelle: GPT4o generiert

Bevor Sie ein maschinelles Lernmodell erstellen können, müssen Sie Ihre Daten in einen Datensatz laden. Glücklicherweise verfügt PyTorch über viele Befehle, die bei diesem gesamten Prozess helfen (wenn Sie mit PyTorch nicht vertraut sind, empfehle ich Ihnen, die Grundlagen aufzufrischen. Hier).

PyTorch bietet eine gute Dokumentation, die bei diesem Prozess hilft, aber ich habe keine umfassende Dokumentation oder Tutorials zu benutzerdefinierten Datensätzen gefunden. Ich werde zunächst mit der Erstellung grundlegender vorgefertigter Datensätze beginnen und mich dann hocharbeiten, um Datensätze von Grund auf für verschiedene Modelle zu erstellen!

Bevor wir uns in den Code für verschiedene Anwendungsfälle vertiefen, sollten wir den Unterschied zwischen den beiden Begriffen verstehen. Im Allgemeinen erstellen Sie zuerst Ihren Datensatz und dann einen Datenlader. Ein Datensatz enthält die Merkmale und Beschriftungen von jedem Datenpunkt, der in das Modell eingespeist wird. Datenlader ist ein benutzerdefiniertes PyTorch-Iterable, das das Laden von Daten mit zusätzlichen Funktionen erleichtert.

DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
batch_sampler=None, num_workers=0, collate_fn=None,
pin_memory=False, drop_last=False, timeout=0,
worker_init_fn=None, *, prefetch_factor=2,
persistent_workers=False)

Die häufigsten Argumente im Datenlader sind Batchgröße, Mischen (normalerweise nur für die Trainingsdaten), Anzahl_Arbeiter (zum Multi-Prozess-Laden der Daten) und Pin-Speicher (um die abgerufenen Datentensoren im fixierten Speicher abzulegen und eine schnellere Datenübertragung an CUDA-fähige GPUs zu ermöglichen).

Aufgrund von Multiprocessing-Komplikationen mit CUDA wird empfohlen, pin_memory = True festzulegen, anstatt num_workers anzugeben.

Falls Ihr Datensatz on-line oder lokal heruntergeladen wird, ist es äußerst einfach, den Datensatz zu erstellen. Ich denke, PyTorch hat gute Dokumentation Ich werde mich kurz fassen.

Wenn Sie wissen, dass der Datensatz entweder von PyTorch stammt oder PyTorch-kompatibel ist, rufen Sie einfach die erforderlichen Importe und den Datensatz Ihrer Wahl auf:

from torch.utils.information import Dataset
from torchvision import datasets
from torchvision.transforms imports ToTensor

information = torchvision.datasets.CIFAR10('path', prepare=True, remodel=ToTensor())

Jeder Datensatz hat eindeutige Argumente, die ihm übergeben werden müssen (gefunden Hier). Im Allgemeinen handelt es sich dabei um den Pfad, unter dem der Datensatz gespeichert ist, einen Booleschen Wert, der angibt, ob er heruntergeladen werden muss oder nicht (praktischerweise „Obtain“ genannt), ob es sich um Coaching oder Check handelt und ob Transformationen angewendet werden müssen.

Ich habe am Ende des letzten Abschnitts erwähnt, dass Transformationen auf einen Datensatz angewendet werden können, aber was ist eigentlich eine Transformation?

A verwandeln ist eine Methode zur Manipulation von Daten zur Vorverarbeitung eines Bildes. Transformationen haben viele verschiedene Facetten. Die häufigste Transformation ist ToTensor()konvertiert den Datensatz in Tensoren (wird als Eingabe in jedes Modell benötigt). Andere in PyTorch integrierte Transformationen (torchvision.transforms) umfassen das Spiegeln, Drehen, Zuschneiden, Normalisieren und Verschieben von Bildern. Diese werden normalerweise verwendet, damit das Modell besser verallgemeinern kann und nicht zu stark an die Trainingsdaten angepasst wird. Datenerweiterungen können auch verwendet werden, um die Größe des Datensatzes bei Bedarf künstlich zu erhöhen.

Beachten Sie, dass die meisten Torchvision-Transformationen nur Pillow-Bild- oder Tensorformate akzeptieren (nicht Numpy). Zum Konvertieren verwenden Sie einfach

Um von Numpy zu konvertieren, erstellen Sie entweder einen Fackel-Tensor oder verwenden Sie Folgendes:

From PIL import Picture
# assume arr is a numpy array
# you might have to normalize and solid arr to np.uint8 relying on format
img = Picture.fromarray(arr)

Transformationen können gleichzeitig angewendet werden mit torchvision.transforms.compose. Sie können so viele Transformationen kombinieren, wie für den Datensatz erforderlich sind. Ein Beispiel wird unten angezeigt:

import torchvision.transforms.Compose

dataset_transform = transforms.Compose((
transforms.RandomResizedCrop(256),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225))
))

Denken Sie daran, die gespeicherte Transformation als Argument an den Datensatz zu übergeben, damit sie im Datenlader angewendet werden kann.

Wenn Sie Ihr eigenes Modell entwickeln, benötigen Sie in den meisten Fällen einen benutzerdefinierten Datensatz. Ein häufiger Anwendungsfall wäre Transferlernen, um Ihren eigenen Datensatz auf ein vorab trainiertes Modell anzuwenden.

Eine PyTorch-Dataset-Klasse besteht aus drei erforderlichen Teilen: Initialisierung, LängeUnd Abrufen eines Components.

__drin__: Um den Datensatz zu initialisieren, geben Sie die Rohdaten und die beschrifteten Daten ein. Am besten geben Sie die Rohbilddaten und die beschrifteten Daten separat ein.

__Länge__: Gibt die Länge des Datensatzes zurück. Vor dem Erstellen des Datensatzes sollte überprüft werden, ob die Rohdaten und die beschrifteten Daten die gleiche Größe haben.

__getitem__: Hier erfolgt die gesamte Datenverarbeitung, um einen bestimmten Index (idx) der Roh- und beschrifteten Daten zurückzugeben. Wenn Transformationen angewendet werden müssen, müssen die Daten in einen Tensor konvertiert und transformiert werden. Wenn die Initialisierung einen Pfad zum Datensatz enthielt, muss der Pfad geöffnet und auf die Daten zugegriffen/sie vorverarbeitet werden, bevor sie zurückgegeben werden können.

Beispieldatensatz für ein semantisches Segmentierungsmodell:

from torch.utils.information import Dataset
from torchvision import transforms

class ExampleDataset(Dataset):
"""Instance dataset"""

def __init__(self, raw_img, data_mask, remodel=None):
self.raw_img = raw_img
self.data_mask = data_mask
self.remodel = remodel

def __len__(self):
return len(self.raw_img)

def __getitem__(self, idx):
if torch.is_tensor(idx):
idx = idx.tolist()

picture = self.raw_img(idx)
masks = self.data_mask(idx)

pattern = {'picture': picture, 'masks': masks}

if self.remodel:
pattern = self.remodel(pattern)

return pattern

[ad_2]

]]>
http://chatgpz.de/machine-learning/umfassender-leitfaden-zu-datasets-und-dataloadern-in-pytorch-von-ryan-dcunha-juni-2024/feed/ 0 1676
spacecutter: Ordinalregressionsmodelle in PyTorch http://chatgpz.de/data-science/spacecutter-ordinalregressionsmodelle-in-pytorch/ http://chatgpz.de/data-science/spacecutter-ordinalregressionsmodelle-in-pytorch/#respond Mon, 10 Jun 2024 20:46:31 +0000 http://chatgpz.de/data-science/spacecutter-ordinalregressionsmodelle-in-pytorch/ [ad_1]

Wie würden Sie einen maschinellen Lernalgorithmus erstellen, um die folgenden Arten von Problemen zu lösen?

  • Sagen Sie voraus, welche Athleten bei den Olympischen Spielen eine Medaille gewinnen werden.
  • Sagen Sie voraus, wie ein Schuh an einem Fuß sitzen wird (zu klein, perfekt, zu groß).
  • Sagen Sie voraus, wie viele Sterne ein Kritiker einem Movie geben wird.

Wenn Sie auf Ihr typisches Toolkit zurückgreifen, werden Sie wahrscheinlich entweder auf Regression oder Multiklassenklassifizierung zurückgreifen. Bei der Regression behandeln Sie vielleicht die Anzahl der Sterne (1-5) in der Frage des Filmkritikers als Ihr Ziel und trainieren ein Modell mit dem mittleren quadratischen Fehler als Verlustfunktion. Bei der Multiklassenklassifizierung behandeln Sie vielleicht Bronze-, Silber- und Goldmedaillen als drei separate Klassen und trainieren ein Modell mit Kreuzentropieverlust.

Während diese Ansätze dürfen Arbeit, ignorieren sie einen grundlegenden Aspekt der Vorhersageziele in den vorgeschlagenen Problemen: Die Ziele haben Ordinal- Beziehungen zueinander. Es gibt einen größeren Unterschied zwischen Bronze und Gold als zwischen Silber und Gold. Ebenso sollten unsere falschen Vorhersagen aus Sicht des Modells, das wir erstellen möchten, ordinale Beziehungen haben. Wenn wir vorhersagen, dass ein Kleidungsstück einem Kunden zu groß sein wird, und wir mit unserer Vorhersage falsch liegen, dann ist es wahrscheinlicher, dass das Kleidungsstück dem Kunden passt, als dass wir uns völlig geirrt haben, sodass das Kleidungsstück zu klein warfare.

Diese Klasse von Problemen nennt man ordinale Regression. Obwohl sie in der Industrie weit verbreitet sind, mangelt es an Werkzeugen und Schulungen zur Verwendung dieser Modelle. Außerdem sind die vorhandenen Werkzeuge ziemlich unflexibel, da man in den Arten von Modellen und Techniken, die verwendet werden können, eingeschränkt ist. Ich habe ein etwas idealistisches Ziel, alle maßgeschneiderten Methoden des maschinellen Lernens, für die die Leute früher lineare Modelle und manuell berechnete Gradienten verwendet haben, zu übernehmen und diese Modelle stattdessen mithilfe eines Deep-Studying-Frameworks mit benutzerdefinierten Verlustfunktionen neu zu schreiben, die durch stochastischen Gradientenabstieg (SGD) optimiert werden können. Dies würde es den Leuten ermöglichen, über lineare Modelle hinauszugehen (z. B. zu tiefen neuronalen Netzwerken) und alle Vorteile von SGD (ADAM, Batch-Norm, Skalierung usw.) zu nutzen. Außerdem möchte ich dies alles in einem scikit-lernen-kompatible Weise, weil ich ihre API einfach liebe.

Um meinen Standpunkt zu verdeutlichen, schauen wir uns die einzige scikit-learn-kompatible Bibliothek an, die ich finden konnte und die für ordinale Regressionsmodelle geeignet ist. Mord. Verstehen Sie mich nicht falsch, diese Bibliothek ist großartig und schnell und wurde von einem überprüfbarer Experte in ordinalen Regressionsmodellen. Alle verfügbaren Modelle sind jedoch lineare Modelle mit L2-Regularisierung. Was ist, wenn ich ein Deep-Studying-Modell dort einfügen möchte? Was ist, wenn ich mit LASSO Sparsity induzieren möchte? Ich müsste die Zielfunktionen und zugehörigen Gradienten manuell berechnen und eine brandneue Klasse in der Bibliothek erstellen. Wenn ich stattdessen eine mit ordinaler Regression kompatible Verlustfunktion angemessen definieren kann, kann ich jedes der Deep-Studying-Frameworks verwenden, die Autodifferenzierung verwenden, um jedes gewünschte Modell zu optimieren. Und ich möchte dies auf große Datenmengen skalieren können, was die Stan Und pymc3 Implementierungen.

Das habe ich additionally getan und eine kleine Bibliothek erstellt Raumschneider um ordinale Regressionsmodelle in PyTorch zu implementieren. In diesem Beitrag werde ich erklären, wie ordinale Regression funktioniert, zeigen, wie ich das Modell in PyTorch implementiert habe, das Modell mit verbrennen um es in einen Scikit-Be taught-Schätzer umzuwandeln und dann einige Ergebnisse anhand eines vorgefertigten Datensatzes zu teilen.

Von der binären zur ordinalen Regression

Es gibt eine Reihe von Möglichkeiten, ordinale Regressionsprobleme zu lösen, und ich werde in diesem Beitrag nur eine Methode durchgehen. Ich fand dies Papier das wurde erwähnt in mord um eine gute Einführung in die Lösung dieser Probleme zu geben. Für die in diesem Abschnitt verwendete Technik habe ich auf Folgendes verwiesen Papier vom Autor von mord, Fabian Pedregosa. Obwohl er diese Technik nicht erfunden hat, waren die zitierten Artikel kostenpflichtig, sodass ich nicht für sie bürgen kann.

Die Hauptidee hinter der ordinalen Regression besteht darin, dass wir lernen, unseren Vorhersageraum aufzuteilen, indem wir cutpoints. Aber bevor wir darauf eingehen, schauen wir uns kurz die binäre logistische Regression an, bei der wir zwei Zielklassen haben, 0 Und 1. Erinnern Sie sich, dass wir bei der logistischen Regression ein lineares Modell $f(mathbf{X}) = mathbf{X} cdot boldsymbol{beta} $ haben, das eine Designmatrix $mathbf{X}$ und einen Vektor von Koeffizienten $boldsymbol{beta}$ in eine einzelne Zahl umwandelt, die irgendwo zwischen negativer und positiver Unendlichkeit liegt ($mathbb{R}$). Die Ausgabe dieses linearen Modells wird dann durch eine logistische Funktion $sigma$ geleitet, die den Wertebereich zwischen 0 und 1 abbildet:

$$ sigma(f(mathbf{X})) = frac{1}{1 + e^{-mathbf{X} cdot boldsymbol{beta} }} $$

Diese Funktion sieht wie folgt aus:

%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
import numpy as np

plt.ion()
plt.rcParams('determine.figsize') = (8, 5)
plt.rcParams('axes.titlesize') = 20
plt.rcParams('axes.labelsize') = 18
plt.rcParams('legend.fontsize') = 16
plt.rcParams('xtick.labelsize') = 14
plt.rcParams('ytick.labelsize') = 14
num_points = 101
f = np.linspace(-12, 12, num_points)
sigma = lambda f: 1 / (1 + np.exp(-f))
plt.plot(f, sigma(f));
plt.ylabel('$sigma(f)$');
plt.xlabel('$f$');

Durch etwas Mathematik kann man sich davon überzeugen, dass die Wahl eines bestimmten Wertes von f entspricht der Wahrscheinlichkeit dass die Beobachtung, die produziert f gehört zur Klasse 1Wir schreiben das als

$$P(y = 1) = sigma(f)$$

Die Wahrscheinlichkeit, dass die Beobachtung zur Klasse gehört 0 Ist

$$ start{aligned} P(y = 0) & = 1 – P(y = 1) cr & = 1 – sigma(f) finish{aligned} $$

Der Wert von $f = 0$ ist insofern besonders, als er den genauen Kreuzungspunkt markiert, an dem die Beobachtung mit einer Wahrscheinlichkeit von 50 % zu einer der beiden Klassen gehört. 0 oder Klasse 1. In der Sprache der ordinalen Regression können wir $f = 0$ als Schnittpunkt die unseren Vorhersageraum zwischen Klasse 0 Und 1:

plt.plot(f, 1 - sigma(f));
plt.plot(f, sigma(f));
plt.plot((0, 0), (0, 1), 'k--');
plt.legend(('$P(y = 0)$', '$P(y = 1)$', 'cutpoint'))
plt.ylabel('Likelihood');
plt.xlabel('$f$');

Der Übergang von der binären Klassifizierung zur ordinalen Regression mit 3+ Klassen erfordert lediglich die Definition weiterer Trennpunkte, um unseren Vorhersageraum in den Raum der „Klassenwahrscheinlichkeit“ aufzuteilen. Für $Okay$ Klassen haben wir $Okay – 1$ Trennpunkte. Die Wahrscheinlichkeit, dass eine Beobachtung zur Klasse $okay$ gehört, ist gegeben durch Kumulative Logistikverbindungsfunktion:

$$ P(y = okay) = start{instances} sigma(c_{okay} – f(mathbf{X})),, textual content{wenn } okay = 0 cr sigma(c_{okay} – f(mathbf{X})) – sigma(c_{okay – 1} – f(mathbf{X})) ,, textual content{wenn } 0 < okay < Okay cr 1 - sigma(c_{Okay - 1} - f(mathbf{X})) ,, textual content{wenn } okay = Okay cr finish{instances} $$

Zu beachten ist, dass dies perfekt auf eine binäre Klassifizierung zusammenfällt, falls wir nur zwei Klassen haben (probieren Sie es selbst aus!). Das folgende Diagramm zeigt die Klassenwahrscheinlichkeiten für ein ordinales Regressionsmodell mit drei Klassen:

def plot_ordinal_classes(f, cutpoints):
    num_classes = len(cutpoints) + 1
    labels = ()
    for idx in vary(num_classes):

        if idx == 0:
            plt.plot(f, sigma(cutpoints(0) - f));
        elif idx == num_classes - 1:
            plt.plot(f, 1 - sigma(cutpoints(-1) - f));
        else:
            plt.plot(f, sigma(cutpoints(idx) - f) - sigma(cutpoints(idx - 1) - f));

        labels.append(f'$P(y = {idx})$')

    for c in cutpoints:
        plt.plot((c, c), (0, 1), 'k--')

    labels.append('cutpoints')
    plt.legend(labels);
    plt.ylabel('Likelihood');
    plt.xlabel('$f$');

cutpoints = (-2, 2)
plot_ordinal_classes(f, cutpoints)

Während ich die Trennpunkte oben manuell definiert habe, würden wir in der Praxis lernen die optimalen Werte für die Schnittpunkte als Teil unseres maschinellen Lernproblems (darüber werden wir im nächsten Abschnitt sprechen). Durch Variieren der Schnittpunkte variieren die Klassenwahrscheinlichkeiten. Dies kann manchmal zu nicht intuitiven Ergebnissen führen, wenn die Schnittpunkte nahe beieinander liegen, z. B. wenn eine bestimmte Klasse nie die wahrscheinlichste Klasse für einen beliebigen Wert von $f$ ist.

plot_ordinal_classes(np.linspace(-5, 5, 101), (-0.5, 0.5))

Die folgende Animation zeigt die Klassenwahrscheinlichkeiten für ein 5-Klassen-Modell, während wir die Schnittpunkte variieren.

Ordinalregression lernen

Jetzt, da wir unser Modell haben, das ordinale Klassenwahrscheinlichkeiten vorhersagt, ist es nur noch ein kleiner Schritt zu Lernen ein Modell. Wir müssen einfach eine Verlustfunktion definieren und minimieren. Unsere Verlustfunktion ist die unfavorable Log-Chance, die dem negativen Log der Klassenwahrscheinlichkeit entspricht, die wir für die Klasse vorhersagen, die die „wahre“ Klasse für eine bestimmte Beobachtung ist. Stellen Sie sich in Pseudocode vor, wir hätten drei Klassen und eine Vorhersage y_pred = (0.25, 0.5, 0.25) entsprechend der vorhergesagten Klassenwahrscheinlichkeit für jede der drei Klassen. Wenn y_true = 1dann ist unser Verlust -log(y_pred(1)). Dies lässt sich in PyTorch ganz einfach definieren. Anschließend müssen wir nur noch unser Modell optimieren. Und unsere Schnittpunkte über den stochastischen Gradientenabstieg (SGD).

Eine Schwierigkeit besteht darin, dass unsere Schnittpunkte in aufsteigend Ordnung. Das heißt, cutpoint(0) < cutpoint(1) < ... cutpoint(Okay-1). Das ist ein Zwang auf unser Optimierungsproblem, das von SGD nicht einfach zu handhaben ist. Mein schmutziger Hack besteht darin, die Cutpoint-Werte nach jedem Gradienten-Replace abzuschneiden, um sicherzustellen, dass sie immer in aufsteigender Reihenfolge sind. Mit mehr Zeit würde ich gerne eine mehr implementieren richtig Lösung.

Raumschneider

Lassen Sie uns noch einmal zusammenfassen, wie wir ordinale Regressionsmodelle trainieren:

  1. Wir erstellen ein Modell, das eine einzelne skalare Vorhersage generiert.
  2. Verwenden Sie die kumulative logistische Verknüpfungsfunktion, um diese skalaren Wahrscheinlichkeiten auf ordinale Klassen zuzuordnen.
  3. Definieren und minimieren Sie den negativen Log-Chance-Verlust, der den Vorhersagen entspricht.
  4. Stellen Sie sicher, dass die cutpoints bleiben in aufsteigender Reihenfolge.

Lassen Sie uns nun durchgehen, wie das geht in Raumschneider. Wir verwenden einen Weinqualitätsdatensatz aus dem UCI-Repository, in dem jede Beobachtung ein Wein ist, der auf einer Skala von 1 bis 10 bewertet wird. Wir beginnen mit dem Herunterladen des Datensatzes, überprüfen einige der Merkmale und führen eine grundlegende Vorverarbeitung der Merkmale mithilfe des neuen Spaltentransformator In scikit-learn.

import warnings

import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import mean_absolute_error, accuracy_score, make_scorer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, PowerTransformer, FunctionTransformer
from skorch.callbacks import Callback, ProgressBar
from skorch.internet import NeuralNet

import torch
from torch import nn

warnings.simplefilter('ignore')
# Obtain the dataset
# !wget "https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv"
wine = pd.read_csv('winequality-red.csv', sep=';')
wine.head()
feste Säure flüchtige Säure Zitronensäure Restzucker Chloride freies Schwefeldioxid Gesamtschwefeldioxid Dichte pH Sulfate Alkohol Qualität
0 7.4 0,70 0,00 1.9 0,076 11.0 34,0 0,9978 3.51 0,56 9.4 5
1 7.8 0,88 0,00 2.6 0,098 25,0 67,0 0,9968 3.20 0,68 9,8 5
2 7.8 0,76 0,04 2.3 0,092 15,0 54,0 0,9970 3.26 0,65 9,8 5
3 11.2 0,28 0,56 1.9 0,075 17,0 60,0 0,9980 3.16 0,58 9,8 6
4 7.4 0,70 0,00 1.9 0,076 11.0 34,0 0,9978 3.51 0,56 9.4 5

Es scheint, dass jeder die Weinqualität mit einer Be aware zwischen 3 und 8 bewertet. Wir werden diese den Klassen 0 bis 5 zuordnen.

y = wine.pop('high quality')
(y.value_counts()
  .sort_index()
  .plot(variety='bar'));
plt.ylabel('Counts');
plt.xlabel('High quality');

Wir werden uns die Verteilungen der 11 verschiedenen Funktionen ansehen.

wine.hist(figsize=(10, 10));
plt.tight_layout();

Nur um die Macht der ColumnTransformerich benutze ein StandardScaler auf den eher gaußförmigen Säulen und einem PowerTransformer auf den anderen. Die transformierten Gesichtszüge sehen etwas normaler aus.

gaussian_columns = ('alcohol', 'chlorides', 'fastened acidity',
                    'density',
                    'pH', 'sulphates', 'risky acidity')
power_columns = ('citric acid', 'free sulfur dioxide', 'residual sugar',
                 'whole sulfur dioxide')
column_transformer = ColumnTransformer((
    ('gaussian', StandardScaler(), gaussian_columns),
    ('energy', PowerTransformer(), power_columns)
))
X_trans = column_transformer.fit_transform(wine)
(pd.DataFrame(X_trans,
              columns=gaussian_columns + power_columns)
   .hist(figsize=(10, 10)));
plt.tight_layout();
y = y.values.astype(np.lengthy).reshape(-1, 1)
# Map y from (3-8) to (0-5)
y -= y.min()

X = wine

(X_train, X_test,
 y_train, y_test) = train_test_split(X, y, test_size=0.33,
                                     stratify=y, random_state=666)

Cool, jetzt, da wir ein X Und y und einem Vorverarbeitungstransformator können wir mit dem Trainieren des Modells beginnen. Denken Sie daran, dass der erste Schritt zur ordinalen Regression ein Modell ist, das einen einzelnen Skalarwert vorhersagt. Ich werde zu diesem Zweck ein einfaches zweischichtiges neuronales Netzwerk in PyTorch erstellen.

num_features = len(gaussian_columns + power_columns)

predictor = nn.Sequential(
    nn.Linear(num_features, num_features),
    nn.ReLU(inplace=True),
    nn.Linear(num_features, num_features),
    nn.ReLU(inplace=True),
    nn.Linear(num_features, 1, bias=False)
)

Mit spacecutterwir können das einpacken predictor mit dem OrdinalLogisticModel Dadurch werden Schnittpunkte erstellt und die Prädiktorausgabe durch die kumulative logistische Linkfunktion geleitet

from spacecutter.fashions import OrdinalLogisticModel

num_classes = len(np.distinctive(y))
mannequin = OrdinalLogisticModel(predictor, num_classes)

X_tensor = torch.as_tensor(X_train.values.astype(np.float32))

predictor_output = predictor(X_tensor).detach()
model_output = mannequin(X_tensor).detach()

print(predictor_output)
print(model_output)
tensor(((-4.9971),
        (-1.3892),
        (-1.9079),
        ...,
        (-3.1603),
        (-5.7689),
        (-5.1477)))
tensor(((0.9239, 0.0467, 0.0184, 0.0069, 0.0026, 0.0015),
        (0.2477, 0.2246, 0.2364, 0.1599, 0.0786, 0.0527),
        (0.3562, 0.2444, 0.2029, 0.1140, 0.0505, 0.0320),
        ...,
        (0.6593, 0.1809, 0.0944, 0.0403, 0.0157, 0.0094),
        (0.9633, 0.0228, 0.0087, 0.0032, 0.0012, 0.0007),
        (0.9339, 0.0407, 0.0159, 0.0060, 0.0022, 0.0013)))

Um dieses Modell zu trainieren, verwenden wir skorch das unser Modell umschließt und es als Scikit-Be taught-Schätzer kompatibel macht. Entscheidend ist, dass wir den CumulativeLinkLoss Verlustmodul von spacecutter. Zusätzlich verwenden wir eine benutzerdefinierte spacecutter Ruf zurück AscensionCallback Dadurch wird die aufsteigende Reihenfolge der Schnittpunkte beibehalten.

from spacecutter.losses import CumulativeLinkLoss
from spacecutter.callbacks import AscensionCallback


skorch_model = NeuralNet(
    module=OrdinalLogisticModel,
    module__predictor=predictor,
    module__num_classes=num_classes,
    criterion=CumulativeLinkLoss,
    max_epochs=100,
    optimizer_type=torch.optim.Adam,
    optimizer__weight_decay=0.0,
    lr=0.1,
    system='cpu',
    callbacks=(
        ('ascension', AscensionCallback()),
    ),
    train_split=None,
    verbose=0,
)

Der letzte Schritt besteht darin, alles in eine Scikit-Be taught-Pipeline zu packen. Hinweis: Ich muss einen kleinen Transformator hinzufügen, um sicherzustellen, dass die Matrizen Floats (und keine Doubles) sind, bevor sie an das Skorch-Modell übergeben werden.

def to_float(x):
    return x.astype(np.float32)

pipeline = Pipeline((
    ('column', column_transformer),
    ('caster', FunctionTransformer(to_float)),
    ('internet', skorch_model)
))

Verwenden wir den mittleren absoluten Fehler als Bewertungskriterium und führen wir eine Rastersuche über die Anzahl der Epochen, die Lernrate und den Gewichtsabfall (auch bekannt als ~L2-Regularisierung) durch.

def mae_scorer(y_true, y_pred):
    return mean_absolute_error(y_true, y_pred.argmax(axis=1))

scoring = make_scorer(mae_scorer,
                      greater_is_better=False,
                      needs_proba=True)
param_grid = {
    'net__max_epochs': np.logspace(1, 3, 5).astype(int),
    'net__lr': np.logspace(-4, -1, 5),
    'net__optimizer__weight_decay': np.logspace(-6, -2, 4)
}

sc_grid_search = GridSearchCV(
    pipeline, param_grid, scoring=scoring,
    n_jobs=None, cv=5, verbose=1
)
sc_grid_search.match(X_train, y_train)
Becoming 5 folds for every of 100 candidates, totalling 500 suits


(Parallel(n_jobs=1)): Utilizing backend SequentialBackend with 1 concurrent staff.
(Parallel(n_jobs=1)): Completed 500 out of 500 | elapsed: 92.0min completed





GridSearchCV(cv=5, error_score="raise-deprecating",
       estimator=Pipeline(reminiscence=None,
     steps=(('column', ColumnTransformer(n_jobs=None, the rest="drop", sparse_threshold=0.3,
         transformer_weights=None,
         transformers=(('gaussian', StandardScaler(copy=True, with_mean=True, with_std=True), ('alcohol', 'chlorides', 'fastened acidity', 'density', 'pH', 'sulphates', 'risky ...as=True)
    (3): ReLU(inplace)
    (4): Linear(in_features=11, out_features=1, bias=False)
  ),
)))),
       fit_params=None, iid='warn', n_jobs=None,
       param_grid={'net__max_epochs': array((  10,   31,  100,  316, 1000)), 'net__lr': array((0.0001 , 0.00056, 0.00316, 0.01778, 0.1    )), 'net__optimizer__weight_decay': array((1.00000e-06, 2.15443e-05, 4.64159e-04, 1.00000e-02))},
       pre_dispatch="2*n_jobs", refit=True, return_train_score="warn",
       scoring=make_scorer(mae_scorer, greater_is_better=False, needs_proba=True),
       verbose=1)

Nachdem wir unsere Suche nun abgeschlossen haben (86 Minuten später …), untersuchen wir die Ergebnisse.

Wir können sehen, dass die Schnittpunkte angemessen voneinander getrennt und in aufsteigender Reihenfolge sind.

cutpoints = (sc_grid_search
             .best_estimator_
             .named_steps('internet')
             .module_
             .hyperlink
             .cutpoints
             .detach())
print(f"Cutpoints: {cutpoints}")
Cutpoints: tensor((-3.3575, -2.6696, -0.1757,  1.9482,  3.6145))

Die Genauigkeit ist für ein Multiklassenmodell in Ordnung und wir können sehen, dass wir bei unserer Bewertungsvorhersage im Durchschnitt um weniger als 0,5 daneben liegen.

y_pred = sc_grid_search.predict_proba(X_test).argmax(axis=1)
print(f'Accuracy = {accuracy_score(y_test.squeeze(), y_pred):1.4f}')
print(f'MAE = {mean_absolute_error(y_test.squeeze(), y_pred):1.4f}')
Accuracy = 0.6250
MAE = 0.3958
bins = np.arange(6)
plt.hist(y_test, bins=bins, alpha=0.5);
plt.hist(y_pred, bins=bins, alpha=0.5);
plt.legend(('y_true', 'y_pred'));
plt.ylabel('Counts');
plt.xlabel('Ordinal Class');
plt.title('spacecutter Class Distributions');

Zum Schluss noch ein kurzer Vergleich mit mord.

from mord.threshold_based import LogisticAT

pipeline = Pipeline((
    ('column', column_transformer),
    ('caster', FunctionTransformer(to_float)),
    ('mannequin', LogisticAT())
))
param_grid = {
    'model__max_iter': np.logspace(3, 5, 5).astype(int),
    'model__alpha': np.logspace(0, 4, 5)
}

mord_grid_search = GridSearchCV(
    pipeline, param_grid, scoring=scoring,
    n_jobs=None, cv=5, verbose=1
)
mord_grid_search.match(X_train, y_train)
Becoming 5 folds for every of 25 candidates, totalling 125 suits


(Parallel(n_jobs=1)): Utilizing backend SequentialBackend with 1 concurrent staff.
(Parallel(n_jobs=1)): Completed 125 out of 125 | elapsed:   13.2s completed





GridSearchCV(cv=5, error_score="raise-deprecating",
       estimator=Pipeline(reminiscence=None,
     steps=(('column', ColumnTransformer(n_jobs=None, the rest="drop", sparse_threshold=0.3,
         transformer_weights=None,
         transformers=(('gaussian', StandardScaler(copy=True, with_mean=True, with_std=True), ('alcohol', 'chlorides', 'fastened acidity', 'density', 'pH', 'sulphates', 'risky ...'deprecated',
          validate=None)), ('mannequin', LogisticAT(alpha=1.0, max_iter=1000, verbose=0)))),
       fit_params=None, iid='warn', n_jobs=None,
       param_grid={'model__max_iter': array((  1000,   3162,  10000,  31622, 100000)), 'model__alpha': array((1.e+00, 1.e+01, 1.e+02, 1.e+03, 1.e+04))},
       pre_dispatch="2*n_jobs", refit=True, return_train_score="warn",
       scoring=make_scorer(mae_scorer, greater_is_better=False, needs_proba=True),
       verbose=1)
cutpoints = (mord_grid_search
             .best_estimator_
             .named_steps('mannequin')
             .theta_)
print(f"Cutpoints: {cutpoints}")
Cutpoints: (-5.80087138 -3.88733054 -0.238343    2.47900366  5.45252331)
y_pred = mord_grid_search.predict(X_test)
print(f'accuracy = {accuracy_score(y_test.squeeze(), y_pred):1.3f}')
print(f'MSE = {mean_absolute_error(y_test.squeeze(), y_pred):1.3f}')
accuracy = 0.612
MSE = 0.415
bins = np.arange(6)
plt.hist(y_test, bins=bins, alpha=0.5);
plt.hist(y_pred, bins=bins, alpha=0.5);
plt.legend(('y_true', 'y_pred'));
plt.ylabel('Counts');
plt.xlabel('Ordinal Class');
plt.title('mord Class Distributions');

Es stellt sich heraus, dass die Ergebnisse durchaus vergleichbar sind mit spacecutter! Das ist vielleicht überraschend, aber wir haben es mit kleinen Datenmengen und wenigen Merkmalen zu tun, daher würde ich nicht erwarten, dass ein neuronales Netz besonders nützlich ist. Aber es ist eine gute Plausibilitätsprüfung, die wir durchführen, ebenso wie ein herkömmliches lineares Modell. Jetzt liegt es an Ihnen, Raumschneider Probieren Sie es aus und versuchen Sie, es auf Millionen von Beobachtungen zu skalieren 🙂

[ad_2]

]]>
http://chatgpz.de/data-science/spacecutter-ordinalregressionsmodelle-in-pytorch/feed/ 0 1324