Instrukcja szkolenia modelu

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub

Ten przewodnik przedstawia Swift dla TensorFlow, budując model uczenia maszynowego, który kategoryzuje kwiaty tęczówki według gatunków. Używa Swift dla TensorFlow do:

  1. Zbudować model,
  2. Wytrenuj ten model na przykładowych danych i
  3. Użyj modelu do przewidywania nieznanych danych.

Programowanie TensorFlow

W tym przewodniku wykorzystano te wysokopoziomowe koncepcje Swift for TensorFlow:

  • Importuj dane za pomocą interfejsu Epochs API.
  • Buduj modele za pomocą abstrakcji Swift.
  • Korzystaj z bibliotek Pythona, korzystając z interoperacyjności języka Swift w języku Python, gdy czyste biblioteki Swift nie są dostępne.

Ten samouczek ma strukturę podobną do wielu programów TensorFlow:

  1. Importuj i analizuj zestawy danych.
  2. Wybierz typ modelu.
  3. Trenuj modelkę.
  4. Oceń skuteczność modelu.
  5. Użyj wytrenowanego modelu, aby dokonać prognoz.

Program instalacyjny

Konfiguruj importy

Importuj TensorFlow i kilka przydatnych modułów Pythona.

import TensorFlow
import PythonKit
// This cell is here to display the plots in a Jupyter Notebook.
// Do not copy it into another environment.
%include "EnableIPythonDisplay.swift"
print(IPythonDisplay.shell.enable_matplotlib("inline"))
('inline', 'module://ipykernel.pylab.backend_inline')

let plt = Python.import("matplotlib.pyplot")
import Foundation
import FoundationNetworking
func download(from sourceString: String, to destinationString: String) {
    let source = URL(string: sourceString)!
    let destination = URL(fileURLWithPath: destinationString)
    let data = try! Data.init(contentsOf: source)
    try! data.write(to: destination)
}

Problem klasyfikacji tęczówki

Wyobraź sobie, że jesteś botanikiem poszukującym zautomatyzowanego sposobu kategoryzowania każdego znalezionego kwiatu tęczówki. Uczenie maszynowe zapewnia wiele algorytmów do statystycznej klasyfikacji kwiatów. Na przykład zaawansowany program do uczenia maszynowego może klasyfikować kwiaty na podstawie zdjęć. Nasze ambicje są skromniejsze — zamierzamy sklasyfikować kwiaty tęczówki na podstawie długości i szerokości ich działek kielicha i płatków .

Rodzaj Iris obejmuje około 300 gatunków, ale nasz program zaklasyfikuje tylko następujące trzy:

  • Irys setosa
  • Iris virginica
  • Iris versicolor
Porównanie geometrii płatków dla trzech gatunków tęczówki: Iris setosa, Iris virginica i Iris versicolor
Rysunek 1. Iris setosa (autorstwa Radomil , CC BY-SA 3.0), Iris versicolor , (autorstwa Dlanglois , CC BY-SA 3.0) i Iris virginica (autorstwa Frank Mayfield , CC BY-SA 2.0).

Na szczęście ktoś już stworzył zestaw danych 120 kwiatów tęczówki z pomiarami działek i płatków. Jest to klasyczny zbiór danych, który jest popularny w przypadku problemów z klasyfikacją uczenia maszynowego dla początkujących.

Importuj i analizuj treningowy zestaw danych

Pobierz plik zestawu danych i przekonwertuj go na strukturę, która może być używana przez ten program Swift.

Pobierz zbiór danych

Pobierz plik zestawu danych treningowych z http://download.tensorflow.org/data/iris_training.csv

let trainDataFilename = "iris_training.csv"
download(from: "http://download.tensorflow.org/data/iris_training.csv", to: trainDataFilename)

Sprawdź dane

Ten zestaw danych, iris_training.csv , to zwykły plik tekstowy, który przechowuje dane tabelaryczne sformatowane jako wartości rozdzielane przecinkami (CSV). Spójrzmy na pierwsze 5 wpisów.

let f = Python.open(trainDataFilename)
for _ in 0..<5 {
    print(Python.next(f).strip())
}
print(f.close())
120,4,setosa,versicolor,virginica
6.4,2.8,5.6,2.2,2
5.0,2.3,3.3,1.0,1
4.9,2.5,4.5,1.7,2
4.9,3.1,1.5,0.1,0
None

Z tego widoku zestawu danych zwróć uwagę na następujące kwestie:

  1. Pierwsza linia to nagłówek zawierający informacje o zestawie danych:
    • Jest łącznie 120 przykładów. Każdy przykład ma cztery cechy i jedną z trzech możliwych nazw etykiet.
  2. Kolejne wiersze to rekordy danych, po jednym przykładzie w wierszu, gdzie:
    • Pierwsze cztery pola to cechy : są to cechy charakterystyczne przykładu. W tym przypadku pola zawierają liczby zmiennoprzecinkowe reprezentujące pomiary kwiatów.
    • Ostatnia kolumna to label : jest to wartość, którą chcemy przewidzieć. W przypadku tego zestawu danych jest to liczba całkowita 0, 1 lub 2, która odpowiada nazwie kwiatu.

Napiszmy to w kodzie:

let featureNames = ["sepal_length", "sepal_width", "petal_length", "petal_width"]
let labelName = "species"
let columnNames = featureNames + [labelName]

print("Features: \(featureNames)")
print("Label: \(labelName)")
Features: ["sepal_length", "sepal_width", "petal_length", "petal_width"]
Label: species

Każda etykieta jest powiązana z nazwą ciągu (na przykład „setosa”), ale uczenie maszynowe zazwyczaj opiera się na wartościach liczbowych. Numery etykiet są mapowane do nazwanej reprezentacji, takiej jak:

  • 0 : Iris setosa
  • 1 : Iris versicolor
  • 2 : Iris virginica

Aby uzyskać więcej informacji o funkcjach i etykietach, zobacz sekcję Terminologia ML w kursie awarii uczenia maszynowego .

let classNames = ["Iris setosa", "Iris versicolor", "Iris virginica"]

Utwórz zbiór danych za pomocą interfejsu Epochs API

Swift for TensorFlow's Epochs API to wysokopoziomowe API do odczytywania danych i przekształcania ich w formę używaną do szkolenia.

let batchSize = 32

/// A batch of examples from the iris dataset.
struct IrisBatch {
    /// [batchSize, featureCount] tensor of features.
    let features: Tensor<Float>

    /// [batchSize] tensor of labels.
    let labels: Tensor<Int32>
}

/// Conform `IrisBatch` to `Collatable` so that we can load it into a `TrainingEpoch`.
extension IrisBatch: Collatable {
    public init<BatchSamples: Collection>(collating samples: BatchSamples)
        where BatchSamples.Element == Self {
        /// `IrisBatch`es are collated by stacking their feature and label tensors
        /// along the batch axis to produce a single feature and label tensor
        features = Tensor<Float>(stacking: samples.map{$0.features})
        labels = Tensor<Int32>(stacking: samples.map{$0.labels})
    }
}

Ponieważ pobrane przez nas zestawy danych są w formacie CSV, napiszmy funkcję do ładowania danych jako listę obiektów IrisBatch

/// Initialize an `IrisBatch` dataset from a CSV file.
func loadIrisDatasetFromCSV(
        contentsOf: String, hasHeader: Bool, featureColumns: [Int], labelColumns: [Int]) -> [IrisBatch] {
        let np = Python.import("numpy")

        let featuresNp = np.loadtxt(
            contentsOf,
            delimiter: ",",
            skiprows: hasHeader ? 1 : 0,
            usecols: featureColumns,
            dtype: Float.numpyScalarTypes.first!)
        guard let featuresTensor = Tensor<Float>(numpy: featuresNp) else {
            // This should never happen, because we construct featuresNp in such a
            // way that it should be convertible to tensor.
            fatalError("np.loadtxt result can't be converted to Tensor")
        }

        let labelsNp = np.loadtxt(
            contentsOf,
            delimiter: ",",
            skiprows: hasHeader ? 1 : 0,
            usecols: labelColumns,
            dtype: Int32.numpyScalarTypes.first!)
        guard let labelsTensor = Tensor<Int32>(numpy: labelsNp) else {
            // This should never happen, because we construct labelsNp in such a
            // way that it should be convertible to tensor.
            fatalError("np.loadtxt result can't be converted to Tensor")
        }

        return zip(featuresTensor.unstacked(), labelsTensor.unstacked()).map{IrisBatch(features: $0.0, labels: $0.1)}

    }

Możemy teraz użyć funkcji ładowania CSV, aby załadować treningowy zestaw danych i utworzyć obiekt TrainingEpochs

let trainingDataset: [IrisBatch] = loadIrisDatasetFromCSV(contentsOf: trainDataFilename, 
                                                  hasHeader: true, 
                                                  featureColumns: [0, 1, 2, 3], 
                                                  labelColumns: [4])

let trainingEpochs: TrainingEpochs = TrainingEpochs(samples: trainingDataset, batchSize: batchSize)

Obiekt TrainingEpochs to nieskończony ciąg epok. Każda epoka zawiera IrisBatch es. Przyjrzyjmy się pierwszemu elementowi pierwszej epoki.

let firstTrainEpoch = trainingEpochs.next()!
let firstTrainBatch = firstTrainEpoch.first!.collated
let firstTrainFeatures = firstTrainBatch.features
let firstTrainLabels = firstTrainBatch.labels

print("First batch of features: \(firstTrainFeatures)")
print("firstTrainFeatures.shape: \(firstTrainFeatures.shape)")
print("First batch of labels: \(firstTrainLabels)")
print("firstTrainLabels.shape: \(firstTrainLabels.shape)")
First batch of features: [[5.1, 2.5, 3.0, 1.1],
 [6.4, 3.2, 4.5, 1.5],
 [4.9, 3.1, 1.5, 0.1],
 [5.0, 2.0, 3.5, 1.0],
 [6.3, 2.5, 5.0, 1.9],
 [6.7, 3.1, 5.6, 2.4],
 [4.9, 3.1, 1.5, 0.1],
 [7.7, 2.8, 6.7, 2.0],
 [6.7, 3.0, 5.0, 1.7],
 [7.2, 3.6, 6.1, 2.5],
 [4.8, 3.0, 1.4, 0.1],
 [5.2, 3.4, 1.4, 0.2],
 [5.0, 3.5, 1.3, 0.3],
 [4.9, 3.1, 1.5, 0.1],
 [5.0, 3.5, 1.6, 0.6],
 [6.7, 3.3, 5.7, 2.1],
 [7.7, 3.8, 6.7, 2.2],
 [6.2, 3.4, 5.4, 2.3],
 [4.8, 3.4, 1.6, 0.2],
 [6.0, 2.9, 4.5, 1.5],
 [5.0, 3.0, 1.6, 0.2],
 [6.3, 3.4, 5.6, 2.4],
 [5.1, 3.8, 1.9, 0.4],
 [4.8, 3.1, 1.6, 0.2],
 [7.6, 3.0, 6.6, 2.1],
 [5.7, 3.0, 4.2, 1.2],
 [6.3, 3.3, 6.0, 2.5],
 [5.6, 2.5, 3.9, 1.1],
 [5.0, 3.4, 1.6, 0.4],
 [6.1, 3.0, 4.9, 1.8],
 [5.0, 3.3, 1.4, 0.2],
 [6.3, 3.3, 4.7, 1.6]]
firstTrainFeatures.shape: [32, 4]
First batch of labels: [1, 1, 0, 1, 2, 2, 0, 2, 1, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 1, 0, 2, 0, 0, 2, 1, 2, 1, 0, 2, 0, 1]
firstTrainLabels.shape: [32]

Zwróć uwagę, że funkcje dla pierwszych przykładów batchSize są zgrupowane (lub wsadowo ) w firstTrainFeatures , a etykiety dla pierwszych przykładów batchSize są pogrupowane w firstTrainLabels .

Możesz zacząć widzieć niektóre klastry, wykreślając kilka funkcji z partii, używając matplotlib Pythona:

let firstTrainFeaturesTransposed = firstTrainFeatures.transposed()
let petalLengths = firstTrainFeaturesTransposed[2].scalars
let sepalLengths = firstTrainFeaturesTransposed[0].scalars

plt.scatter(petalLengths, sepalLengths, c: firstTrainLabels.array.scalars)
plt.xlabel("Petal length")
plt.ylabel("Sepal length")
plt.show()

png

Use `print()` to show values.

Wybierz typ modelu

Dlaczego modelować?

Model to relacja między elementami a etykietą. W przypadku problemu klasyfikacji tęczówki model określa zależność między pomiarami działek i płatków a przewidywanymi gatunkami tęczówki. Niektóre proste modele można opisać za pomocą kilku linii algebry, ale złożone modele uczenia maszynowego mają dużą liczbę parametrów, które trudno podsumować.

Czy możesz określić związek między tymi czterema cechami a gatunkiem tęczówki bez użycia uczenia maszynowego? To znaczy, czy możesz użyć tradycyjnych technik programowania (na przykład wielu instrukcji warunkowych) do stworzenia modelu? Być może — jeśli przeanalizujesz zbiór danych wystarczająco długo, aby określić relacje między pomiarami płatków i działek w przypadku konkretnego gatunku. A to staje się trudne – może niemożliwe – w przypadku bardziej skomplikowanych zbiorów danych. Dobre podejście do uczenia maszynowego określa model za Ciebie . Jeśli wprowadzisz wystarczającą liczbę reprezentatywnych przykładów do odpowiedniego typu modelu uczenia maszynowego, program określi zależności za Ciebie.

Wybierz model

Musimy wybrać rodzaj modelu do trenowania. Istnieje wiele rodzajów modeli, a wybór dobrego wymaga doświadczenia. Ten samouczek wykorzystuje sieć neuronową do rozwiązania problemu klasyfikacji tęczówki. Sieci neuronowe mogą znaleźć złożone relacje między cechami a etykietą. Jest to wykres o wysokiej strukturze, zorganizowany w jedną lub więcej ukrytych warstw . Każda warstwa ukryta składa się z jednego lub więcej neuronów . Istnieje kilka kategorii sieci neuronowych, a ten program używa gęstej lub w pełni połączonej sieci neuronowej : neurony w jednej warstwie otrzymują połączenia wejściowe od każdego neuronu w poprzedniej warstwie. Na przykład rysunek 2 ilustruje gęstą sieć neuronową składającą się z warstwy wejściowej, dwóch warstw ukrytych i warstwy wyjściowej:

Schemat architektury sieci: wejścia, 2 warstwy ukryte i wyjścia
Rysunek 2. Sieć neuronowa z funkcjami, ukrytymi warstwami i predykcjami.

Kiedy model z ryciny 2 jest wytrenowany i zasilany nieoznakowanym przykładem, daje trzy przewidywania: prawdopodobieństwo, że ten kwiat jest danym gatunkiem tęczówki. To przewidywanie nazywa się wnioskowaniem . W tym przykładzie suma przewidywań wyjściowych wynosi 1,0. Na Ryc. 2 ta prognoza przedstawia się następująco: 0.02 dla Iris setosa , 0.95 dla Iris versicolor i 0.03 dla Iris virginica . Oznacza to, że model przewiduje — z 95% prawdopodobieństwem — że nieoznakowany przykładowy kwiat to Iris versicolor .

Utwórz model za pomocą biblioteki Swift for TensorFlow Deep Learning Library

Biblioteka Swift for TensorFlow Deep Learning definiuje prymitywne warstwy i konwencje ich łączenia, co ułatwia budowanie modeli i eksperymentowanie.

Model jest struct zgodną z Layer , co oznacza, że ​​definiuje callAsFunction(_:) , która odwzorowuje dane wejściowe Tensor na dane wyjściowe Tensor . Metoda callAsFunction(_:) często po prostu sekwencjonuje dane wejściowe przez podwarstwy. Zdefiniujmy IrisModel , który sekwencjonuje dane wejściowe przez trzy podwarstwy Dense .

import TensorFlow

let hiddenSize: Int = 10
struct IrisModel: Layer {
    var layer1 = Dense<Float>(inputSize: 4, outputSize: hiddenSize, activation: relu)
    var layer2 = Dense<Float>(inputSize: hiddenSize, outputSize: hiddenSize, activation: relu)
    var layer3 = Dense<Float>(inputSize: hiddenSize, outputSize: 3)

    @differentiable
    func callAsFunction(_ input: Tensor<Float>) -> Tensor<Float> {
        return input.sequenced(through: layer1, layer2, layer3)
    }
}

var model = IrisModel()

Funkcja aktywacji określa wyjściowy kształt każdego węzła w warstwie. Te nieliniowości są ważne — bez nich model byłby równoważny pojedynczej warstwie. Dostępnych jest wiele aktywacji, ale ReLU jest wspólne dla warstw ukrytych.

Idealna liczba ukrytych warstw i neuronów zależy od problemu i zestawu danych. Podobnie jak w przypadku wielu aspektów uczenia maszynowego, wybranie najlepszego kształtu sieci neuronowej wymaga połączenia wiedzy i eksperymentów. Z reguły zwiększenie liczby ukrytych warstw i neuronów zazwyczaj tworzy bardziej wydajny model, który do efektywnego trenowania wymaga większej ilości danych.

Korzystanie z modelu

Rzućmy okiem na to, co ten model robi z zestawem funkcji:

// Apply the model to a batch of features.
let firstTrainPredictions = model(firstTrainFeatures)
print(firstTrainPredictions[0..<5])
[[  1.1514063,  -0.7520321,  -0.6730235],
 [  1.4915676,  -0.9158071,  -0.9957161],
 [  1.0549936,  -0.7799266,   -0.410466],
 [  1.1725322, -0.69009197,  -0.8345413],
 [  1.4870572,  -0.8644099,  -1.0958937]]

Tutaj każdy przykład zwraca logit dla każdej klasy.

Aby przekonwertować te logity na prawdopodobieństwo dla każdej klasy, użyj funkcji softmax :

print(softmax(firstTrainPredictions[0..<5]))
[[  0.7631462,  0.11375094, 0.123102814],
 [  0.8523791, 0.076757915,  0.07086295],
 [  0.7191151,  0.11478964,  0.16609532],
 [ 0.77540654,  0.12039323,  0.10420021],
 [  0.8541314,  0.08133837, 0.064530246]]

Biorąc argmax dla różnych klas, otrzymujemy przewidywany indeks klasy. Ale model nie został jeszcze przeszkolony, więc nie są to dobre przewidywania.

print("Prediction: \(firstTrainPredictions.argmax(squeezingAxis: 1))")
print("    Labels: \(firstTrainLabels)")
Prediction: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    Labels: [1, 1, 0, 1, 2, 2, 0, 2, 1, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 1, 0, 2, 0, 0, 2, 1, 2, 1, 0, 2, 0, 1]

Trenuj modelkę

Uczenie to etap uczenia maszynowego, na którym model jest stopniowo optymalizowany lub model uczy się zestawu danych. Celem jest uzyskanie wystarczającej wiedzy o strukturze treningowego zestawu danych, aby móc przewidywać niewidoczne dane. Jeśli dowiesz się zbyt wiele o uczącym zbiorze danych, prognozy działają tylko dla danych, które widział i nie można ich uogólniać. Ten problem nazywa się overfitting — to tak, jakby zapamiętywać odpowiedzi zamiast rozumieć, jak rozwiązać problem.

Problem klasyfikacji tęczówki jest przykładem nadzorowanego uczenia maszynowego : model jest szkolony na podstawie przykładów zawierających etykiety. W przypadku nienadzorowanego uczenia maszynowego przykłady nie zawierają etykiet. Zamiast tego model zazwyczaj znajduje wzorce wśród cech.

Wybierz funkcję straty

Zarówno szkolenie, jak i etapy oceny wymagają obliczenia utraty modelu. Mierzy to, jak daleko prognozy modelu są od pożądanej etykiety, innymi słowy, jak źle działa model. Chcemy zminimalizować lub zoptymalizować tę wartość.

Nasz model obliczy jego stratę za pomocą funkcji softmaxCrossEntropy(logits:labels:) , która bierze przewidywania prawdopodobieństwa klasy modelu oraz żądaną etykietę i zwraca średnią stratę w przykładach.

Obliczmy stratę dla bieżącego niewytrenowanego modelu:

let untrainedLogits = model(firstTrainFeatures)
let untrainedLoss = softmaxCrossEntropy(logits: untrainedLogits, labels: firstTrainLabels)
print("Loss test: \(untrainedLoss)")
Loss test: 1.7598655

Utwórz optymalizator

Optymalizator stosuje obliczone gradienty do zmiennych modelu, aby zminimalizować funkcję loss . Możesz myśleć o funkcji straty jako o zakrzywionej powierzchni (patrz rysunek 3), a my chcemy znaleźć jej najniższy punkt, spacerując. Gradienty wskazują w kierunku najbardziej stromego podjazdu, więc pojedziemy w przeciwną stronę i zejdziemy w dół wzgórza. Iteracyjnie obliczając stratę i gradient dla każdej partii, dostosujemy model podczas uczenia. Stopniowo model znajdzie najlepszą kombinację wag i odchylenia, aby zminimalizować straty. A im mniejsza strata, tym lepsze prognozy modelu.

Algorytmy optymalizacji wizualizowane w czasie w przestrzeni 3D.
Rysunek 3. Algorytmy optymalizacji wizualizowane w czasie w przestrzeni 3D.
(Źródło: Stanford class CS231n , Licencja MIT, Źródło: Alec Radford )

Swift for TensorFlow oferuje wiele algorytmów optymalizacji dostępnych do szkolenia. Model ten wykorzystuje optymalizator SGD, który implementuje algorytm stochastycznego spadku gradientu (SGD). learningRate określa wielkość kroku do wykonania dla każdej iteracji w dół wzgórza. Jest to hiperparametr , który często będziesz dostosowywać, aby osiągnąć lepsze wyniki.

let optimizer = SGD(for: model, learningRate: 0.01)

Użyjmy optimizer , aby wykonać pojedynczy krok opadania gradientu. Najpierw obliczamy gradient straty względem modelu:

let (loss, grads) = valueWithGradient(at: model) { model -> Tensor<Float> in
    let logits = model(firstTrainFeatures)
    return softmaxCrossEntropy(logits: logits, labels: firstTrainLabels)
}
print("Current loss: \(loss)")
Current loss: 1.7598655

Następnie przekazujemy obliczony właśnie gradient do optymalizatora, który odpowiednio aktualizuje zmienne różniczkowalne modelu:

optimizer.update(&model, along: grads)

Jeśli ponownie obliczymy stratę, powinna być ona mniejsza, ponieważ stopnie opadania gradientu (zwykle) zmniejszają stratę:

let logitsAfterOneStep = model(firstTrainFeatures)
let lossAfterOneStep = softmaxCrossEntropy(logits: logitsAfterOneStep, labels: firstTrainLabels)
print("Next loss: \(lossAfterOneStep)")
Next loss: 1.5318773

Pętla treningowa

Po założeniu wszystkich elementów model jest gotowy do treningu! Pętla szkoleniowa przesyła przykłady zestawu danych do modelu, aby pomóc mu w tworzeniu lepszych prognoz. Poniższy blok kodu konfiguruje te kroki szkoleniowe:

  1. Iteruj w każdej epoce . Epoka to jedno przejście przez zbiór danych.
  2. W epoce przeprowadź iterację każdej partii w epoce treningowej
  3. Posortuj partię i chwyć jej cechy ( x ) i etykietę ( y ).
  4. Korzystając z funkcji sortowanej partii, dokonaj prognozy i porównaj ją z etykietą. Zmierz niedokładność prognozy i użyj jej do obliczenia strat i gradientów modelu.
  5. Użyj gradientu, aby zaktualizować zmienne modelu.
  6. Śledź niektóre statystyki do wizualizacji.
  7. Powtórz dla każdej epoki.

Zmienna epochCount określa, ile razy należy wykonać pętlę w kolekcji zestawu danych. Wbrew intuicji, dłuższe trenowanie modelu nie gwarantuje lepszego modelu. epochCount to hiperparametr , który można dostroić. Wybór odpowiedniej liczby zwykle wymaga zarówno doświadczenia, jak i eksperymentowania.

let epochCount = 500
var trainAccuracyResults: [Float] = []
var trainLossResults: [Float] = []
func accuracy(predictions: Tensor<Int32>, truths: Tensor<Int32>) -> Float {
    return Tensor<Float>(predictions .== truths).mean().scalarized()
}

for (epochIndex, epoch) in trainingEpochs.prefix(epochCount).enumerated() {
    var epochLoss: Float = 0
    var epochAccuracy: Float = 0
    var batchCount: Int = 0
    for batchSamples in epoch {
        let batch = batchSamples.collated
        let (loss, grad) = valueWithGradient(at: model) { (model: IrisModel) -> Tensor<Float> in
            let logits = model(batch.features)
            return softmaxCrossEntropy(logits: logits, labels: batch.labels)
        }
        optimizer.update(&model, along: grad)

        let logits = model(batch.features)
        epochAccuracy += accuracy(predictions: logits.argmax(squeezingAxis: 1), truths: batch.labels)
        epochLoss += loss.scalarized()
        batchCount += 1
    }
    epochAccuracy /= Float(batchCount)
    epochLoss /= Float(batchCount)
    trainAccuracyResults.append(epochAccuracy)
    trainLossResults.append(epochLoss)
    if epochIndex % 50 == 0 {
        print("Epoch \(epochIndex): Loss: \(epochLoss), Accuracy: \(epochAccuracy)")
    }
}
Epoch 0: Loss: 1.475254, Accuracy: 0.34375
Epoch 50: Loss: 0.91668004, Accuracy: 0.6458333
Epoch 100: Loss: 0.68662673, Accuracy: 0.6979167
Epoch 150: Loss: 0.540665, Accuracy: 0.6979167
Epoch 200: Loss: 0.46283028, Accuracy: 0.6979167
Epoch 250: Loss: 0.4134724, Accuracy: 0.8229167
Epoch 300: Loss: 0.35054502, Accuracy: 0.8958333
Epoch 350: Loss: 0.2731444, Accuracy: 0.9375
Epoch 400: Loss: 0.23622067, Accuracy: 0.96875
Epoch 450: Loss: 0.18956228, Accuracy: 0.96875

Wizualizuj funkcję utraty w czasie

Chociaż pomocne jest wydrukowanie postępu szkolenia modelu, często bardziej pomocne jest zobaczenie tego postępu. Możemy tworzyć podstawowe wykresy za pomocą modułu matplotlib Pythona.

Interpretowanie tych wykresów wymaga pewnego doświadczenia, ale naprawdę chcesz, aby strata spadała, a dokładność wzrastała.

plt.figure(figsize: [12, 8])

let accuracyAxes = plt.subplot(2, 1, 1)
accuracyAxes.set_ylabel("Accuracy")
accuracyAxes.plot(trainAccuracyResults)

let lossAxes = plt.subplot(2, 1, 2)
lossAxes.set_ylabel("Loss")
lossAxes.set_xlabel("Epoch")
lossAxes.plot(trainLossResults)

plt.show()

png

Use `print()` to show values.

Zauważ, że osie y wykresów nie są od zera.

Oceń skuteczność modelu

Teraz, gdy model jest wytrenowany, możemy uzyskać statystyki dotyczące jego wydajności.

Ocena oznacza określenie, jak skutecznie model wykonuje prognozy. Aby określić skuteczność modelu w klasyfikacji tęczówki, przekaż kilka pomiarów działek i płatków do modelu i poproś model o przewidzenie, jakie gatunki tęczówki reprezentują. Następnie porównaj prognozę modelu z rzeczywistą etykietą. Na przykład model, który wybrał właściwy gatunek na połowie przykładów wejściowych, ma dokładność 0.5 . Rysunek 4 przedstawia nieco bardziej efektywny model, w którym 4 z 5 prognoz są poprawne z dokładnością 80%:

Przykładowe funkcje Etykieta Przewidywanie modelu
5,9 3,0 4.3 1,5 1 1
6,9 3.1 5.4 2,1 2 2
5.1 3,3 1,7 0,5 0 0
6,0 3.4 4,5 1,6 1 2
5,5 2,5 4.0 1,3 1 1
Rysunek 4. Klasyfikator tęczówki, który jest dokładny w 80%.

Skonfiguruj testowy zestaw danych

Ocena modelu jest podobna do uczenia modelu. Największą różnicą jest to, że przykłady pochodzą z oddzielnego zestawu testowego, a nie zestawu uczącego. Aby rzetelnie ocenić skuteczność modelu, przykłady użyte do oceny modelu muszą różnić się od przykładów użytych do uczenia modelu.

Konfiguracja testowego zbioru danych jest podobna do konfiguracji uczącego zbioru danych. Pobierz zestaw testowy z http://download.tensorflow.org/data/iris_test.csv :

let testDataFilename = "iris_test.csv"
download(from: "http://download.tensorflow.org/data/iris_test.csv", to: testDataFilename)

Teraz załaduj go do tablicy IrisBatch es:

let testDataset = loadIrisDatasetFromCSV(
    contentsOf: testDataFilename, hasHeader: true,
    featureColumns: [0, 1, 2, 3], labelColumns: [4]).inBatches(of: batchSize)

Oceń model na testowym zbiorze danych

W przeciwieństwie do etapu uczenia, model ocenia tylko jedną epokę danych testowych. W poniższej komórce kodu wykonujemy iterację każdego przykładu w zestawie testowym i porównujemy przewidywanie modelu z rzeczywistą etykietą. Służy do pomiaru dokładności modelu w całym zestawie testowym.

// NOTE: Only a single batch will run in the loop since the batchSize we're using is larger than the test set size
for batchSamples in testDataset {
    let batch = batchSamples.collated
    let logits = model(batch.features)
    let predictions = logits.argmax(squeezingAxis: 1)
    print("Test batch accuracy: \(accuracy(predictions: predictions, truths: batch.labels))")
}
Test batch accuracy: 0.96666664

Na pierwszej partii widzimy na przykład, że model jest zazwyczaj poprawny:

let firstTestBatch = testDataset.first!.collated
let firstTestBatchLogits = model(firstTestBatch.features)
let firstTestBatchPredictions = firstTestBatchLogits.argmax(squeezingAxis: 1)

print(firstTestBatchPredictions)
print(firstTestBatch.labels)
[1, 2, 0, 1, 1, 1, 0, 2, 1, 2, 2, 0, 2, 1, 1, 0, 1, 0, 0, 2, 0, 1, 2, 2, 1, 1, 0, 1, 2, 1]
[1, 2, 0, 1, 1, 1, 0, 2, 1, 2, 2, 0, 2, 1, 1, 0, 1, 0, 0, 2, 0, 1, 2, 1, 1, 1, 0, 1, 2, 1]

Użyj wytrenowanego modelu do prognozowania

Wyszkoliliśmy model i pokazaliśmy, że jest dobry – ale nie doskonały – w klasyfikowaniu gatunków tęczówki. Teraz użyjmy wytrenowanego modelu, aby wykonać pewne prognozy na przykładach nieoznaczonych ; to znaczy na przykładach, które zawierają cechy, ale nie zawierają etykiety.

W rzeczywistości nieoznaczone przykłady mogą pochodzić z wielu różnych źródeł, w tym aplikacji, plików CSV i źródeł danych. Na razie ręcznie przedstawimy trzy przykłady bez etykiet, aby przewidzieć ich etykiety. Przypomnijmy, numery etykiet są mapowane do nazwanej reprezentacji jako:

  • 0 : Iris setosa
  • 1 : Iris versicolor
  • 2 : Iris virginica
let unlabeledDataset: Tensor<Float> =
    [[5.1, 3.3, 1.7, 0.5],
     [5.9, 3.0, 4.2, 1.5],
     [6.9, 3.1, 5.4, 2.1]]

let unlabeledDatasetPredictions = model(unlabeledDataset)

for i in 0..<unlabeledDatasetPredictions.shape[0] {
    let logits = unlabeledDatasetPredictions[i]
    let classIdx = logits.argmax().scalar!
    print("Example \(i) prediction: \(classNames[Int(classIdx)]) (\(softmax(logits)))")
}
Example 0 prediction: Iris setosa ([   0.98731947,   0.012679046, 1.4035809e-06])
Example 1 prediction: Iris versicolor ([0.005065103,  0.85957265,  0.13536224])
Example 2 prediction: Iris virginica ([2.9613977e-05,     0.2637373,    0.73623306])