Modele szkoleniowe

W tym przewodniku zakłada się, że przeczytałeś już przewodnik po modelach i warstwach .

W TensorFlow.js istnieją dwa sposoby uczenia modelu uczenia maszynowego:

  1. przy użyciu interfejsu Layers API z LayersModel.fit() lub LayersModel.fitDataset() .
  2. przy użyciu podstawowego interfejsu API z Optimizer.minimize() .

Najpierw przyjrzymy się interfejsowi Layers API, który jest interfejsem API wyższego poziomu służącym do budowania i uczenia modeli. Następnie pokażemy, jak wytrenować ten sam model przy użyciu Core API.

Wstęp

Model uczenia maszynowego to funkcja z możliwymi do nauczenia się parametrami, która odwzorowuje dane wejściowe na pożądane dane wyjściowe. Optymalne parametry uzyskuje się poprzez uczenie modelu na danych.

Szkolenie składa się z kilku etapów:

  • Pobieranie partii danych do modelu.
  • Poproszenie modelu o dokonanie prognozy.
  • Porównanie tej prognozy z „prawdziwą” wartością.
  • Decydowanie, jak bardzo należy zmienić każdy parametr, aby model mógł w przyszłości lepiej przewidywać tę partię.

Dobrze wytrenowany model zapewni dokładne mapowanie od wejścia do pożądanego wyniku.

Parametry modelu

Zdefiniujmy prosty model 2-warstwowy za pomocą API Layers:

const model = tf.sequential({
 layers: [
   tf.layers.dense({inputShape: [784], units: 32, activation: 'relu'}),
   tf.layers.dense({units: 10, activation: 'softmax'}),
 ]
});

Pod maską modele mają parametry (często określane jako wagi ), których można się nauczyć poprzez szkolenie na danych. Wydrukujmy nazwy odważników skojarzonych z tym modelem oraz ich kształty:

model.weights.forEach(w => {
 console.log(w.name, w.shape);
});

Otrzymujemy następujące dane wyjściowe:

> dense_Dense1/kernel [784, 32]
> dense_Dense1/bias [32]
> dense_Dense2/kernel [32, 10]
> dense_Dense2/bias [10]

W sumie są 4 obciążniki, po 2 na gęstą warstwę. Jest to oczekiwane, ponieważ gęste warstwy reprezentują funkcję, która odwzorowuje tensor wejściowy x na tensor wyjściowy y za pomocą równania y = Ax + b , gdzie A (jądro) i b (odchylenie) są parametrami gęstej warstwy.

UWAGA: Domyślnie gęste warstwy uwzględniają odchylenie, ale można je wykluczyć, określając w opcjach opcję {useBias: false} podczas tworzenia gęstej warstwy.

model.summary() to przydatna metoda, jeśli chcesz uzyskać przegląd swojego modelu i zobaczyć całkowitą liczbę parametrów:

Warstwa (typ) Kształt wyjściowy Parametr #
gęsty_Dense1 (Gęsty) [null,32] 25120
gęsty_Dense2 (Gęsty) [null,10] 330
Łączne parametry: 25450
Parametry możliwe do wyszkolenia: 25450
Parametry, których nie można trenować: 0

Każda waga w modelu jest wspierana przez obiekt Variable . W TensorFlow.js Variable jest zmiennoprzecinkowym Tensor z jedną dodatkową metodą assign() używaną do aktualizowania jej wartości. Interfejs Layers API automatycznie inicjuje wagi, korzystając z najlepszych praktyk. Na potrzeby demonstracji moglibyśmy nadpisać wagi, wywołując assign() na podstawowych zmiennych:

model.weights.forEach(w => {
  const newVals = tf.randomNormal(w.shape);
  // w.val is an instance of tf.Variable
  w.val.assign(newVals);
});

Optymalizator, strata i metryka

Zanim przystąpisz do jakiegokolwiek treningu, musisz zdecydować się na trzy rzeczy:

  1. Optymalizator . Zadaniem optymalizatora jest podjęcie decyzji, jak bardzo należy zmienić każdy parametr w modelu, biorąc pod uwagę bieżące przewidywania modelu. Korzystając z interfejsu API Layers, można podać identyfikator ciągu istniejącego optymalizatora (taki jak 'sgd' lub 'adam' ) lub instancję klasy Optimizer .
  2. Funkcja straty . Cel, który model będzie starał się zminimalizować. Jego celem jest podanie jednej liczby określającej, „jak błędne” były przewidywania modelu. Strata jest obliczana na każdej partii danych, dzięki czemu model może zaktualizować swoje wagi. Korzystając z interfejsu API Layers, możesz podać identyfikator ciągu istniejącej funkcji straty (np. 'categoricalCrossentropy' ) lub dowolną funkcję, która przyjmuje wartość przewidywaną i prawdziwą i zwraca stratę. Zobacz listę dostępnych strat w naszej dokumentacji API.
  3. Lista metryk. Podobnie jak w przypadku strat, metryki obliczają pojedynczą liczbę, podsumowując skuteczność naszego modelu. Metryki są zwykle obliczane na podstawie całych danych na koniec każdej epoki. Chcemy przynajmniej monitorować, czy nasza strata maleje w miarę upływu czasu. Często jednak potrzebujemy metryki bardziej przyjaznej dla człowieka, takiej jak dokładność. Korzystając z interfejsu API Layers, możesz podać identyfikator ciągu istniejącej metryki (np. 'accuracy' ) lub dowolną funkcję, która przyjmuje wartość przewidywaną i prawdziwą oraz zwraca wynik. Zobacz listę dostępnych wskaźników w naszej dokumentacji API.

Kiedy już podejmiesz decyzję, skompiluj LayersModel , wywołując model.compile() z podanymi opcjami:

model.compile({
  optimizer: 'sgd',
  loss: 'categoricalCrossentropy',
  metrics: ['accuracy']
});

Podczas kompilacji model przeprowadzi weryfikację, aby upewnić się, że wybrane opcje są ze sobą kompatybilne.

Szkolenie

Istnieją dwa sposoby uczenia LayersModel :

  • Używanie model.fit() i dostarczanie danych w postaci jednego dużego tensora.
  • Korzystanie z model.fitDataset() i dostarczanie danych poprzez obiekt Dataset .

model.fit()

Jeśli Twój zbiór danych mieści się w pamięci głównej i jest dostępny jako pojedynczy tensor, możesz wytrenować model, wywołując metodę fit() :

// Generate dummy data.
const data = tf.randomNormal([100, 784]);
const labels = tf.randomUniform([100, 10]);

function onBatchEnd(batch, logs) {
  console.log('Accuracy', logs.acc);
}

// Train for 5 epochs with batch size of 32.
model.fit(data, labels, {
   epochs: 5,
   batchSize: 32,
   callbacks: {onBatchEnd}
 }).then(info => {
   console.log('Final accuracy', info.history.acc);
 });

Pod maską model.fit() może dla nas wiele zrobić:

  • Dzieli dane na zbiór pociągowy i zestaw walidacyjny oraz używa zestawu walidacyjnego do pomiaru postępu podczas szkolenia.
  • Tasuje dane, ale dopiero po podziale. Dla bezpieczeństwa należy wstępnie przetasować dane przed przekazaniem ich do fit() .
  • Dzieli tensor dużych danych na mniejsze tensory o rozmiarze batchSize.
  • Wywołuje optimizer.minimize() podczas obliczania utraty modelu w odniesieniu do partii danych.
  • Może powiadamiać Cię o początku i końcu każdej epoki lub partii. W naszym przypadku na koniec każdej partii jesteśmy powiadamiani za pomocą opcji callbacks.onBatchEnd . Inne opcje obejmują: onTrainBegin , onTrainEnd , onEpochBegin , onEpochEnd i onBatchBegin .
  • Ustępuje głównemu wątkowi, aby zapewnić terminową obsługę zadań oczekujących w kolejce w pętli zdarzeń JS.

Aby uzyskać więcej informacji, zobacz dokumentację fit() . Pamiętaj, że jeśli zdecydujesz się użyć podstawowego interfejsu API, będziesz musiał samodzielnie zaimplementować tę logikę.

model.fitDataset()

Jeśli Twoje dane nie mieszczą się w całości w pamięci lub są przesyłane strumieniowo, możesz wytrenować model, wywołując fitDataset() , która pobiera obiekt Dataset . Oto ten sam kod szkoleniowy, ale ze zbiorem danych obejmującym funkcję generatora:

function* data() {
 for (let i = 0; i < 100; i++) {
   // Generate one sample at a time.
   yield tf.randomNormal([784]);
 }
}

function* labels() {
 for (let i = 0; i < 100; i++) {
   // Generate one sample at a time.
   yield tf.randomUniform([10]);
 }
}

const xs = tf.data.generator(data);
const ys = tf.data.generator(labels);
// We zip the data and labels together, shuffle and batch 32 samples at a time.
const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);

// Train the model for 5 epochs.
model.fitDataset(ds, {epochs: 5}).then(info => {
 console.log('Accuracy', info.history.acc);
});

Więcej informacji na temat zestawów danych można znaleźć w dokumentacji model.fitDataset() .

Przewidywanie nowych danych

Po wytrenowaniu modelu możesz wywołać model.predict() aby dokonać przewidywań na temat niewidocznych danych:

// Predict 3 random samples.
const prediction = model.predict(tf.randomNormal([3, 784]));
prediction.print();

Podstawowe API

Wcześniej wspomnieliśmy, że istnieją dwa sposoby uczenia modelu uczenia maszynowego w TensorFlow.js.

Ogólna zasada jest taka, aby najpierw spróbować użyć interfejsu Layers API, ponieważ jest on wzorowany na dobrze przyjętym interfejsie API Keras. Interfejs API Layers oferuje również różne gotowe rozwiązania, takie jak inicjalizacja wagi, serializacja modelu, szkolenie w zakresie monitorowania, przenośność i sprawdzanie bezpieczeństwa.

Możesz chcieć użyć Core API, gdy:

  • Potrzebujesz maksymalnej elastyczności i kontroli.
  • I nie potrzebujesz serializacji lub możesz zaimplementować własną logikę serializacji.

Aby uzyskać więcej informacji na temat tego interfejsu API, przeczytaj sekcję „Podstawowy interfejs API” w przewodniku po modelach i warstwach .

Ten sam model, co powyżej, napisany przy użyciu Core API, wygląda następująco:

// The weights and biases for the two dense layers.
const w1 = tf.variable(tf.randomNormal([784, 32]));
const b1 = tf.variable(tf.randomNormal([32]));
const w2 = tf.variable(tf.randomNormal([32, 10]));
const b2 = tf.variable(tf.randomNormal([10]));

function model(x) {
  return x.matMul(w1).add(b1).relu().matMul(w2).add(b2);
}

Oprócz interfejsu Layers API interfejs Data API płynnie współpracuje również z interfejsem Core API. Wykorzystajmy ponownie zbiór danych zdefiniowany wcześniej w sekcji model.fitDataset() , który wykonuje za nas tasowanie i przetwarzanie wsadowe:

const xs = tf.data.generator(data);
const ys = tf.data.generator(labels);
// Zip the data and labels together, shuffle and batch 32 samples at a time.
const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);

Wytrenujmy model:

const optimizer = tf.train.sgd(0.1 /* learningRate */);
// Train for 5 epochs.
for (let epoch = 0; epoch < 5; epoch++) {
  await ds.forEachAsync(({xs, ys}) => {
    optimizer.minimize(() => {
      const predYs = model(xs);
      const loss = tf.losses.softmaxCrossEntropy(ys, predYs);
      loss.data().then(l => console.log('Loss', l));
      return loss;
    });
  });
  console.log('Epoch', epoch);
}

Powyższy kod to standardowy przepis podczas uczenia modelu za pomocą Core API:

  • Wykonaj pętlę po liczbie epok.
  • Wewnątrz każdej epoki przeglądaj partie danych w pętli. W przypadku korzystania z Dataset dataset.forEachAsync() to wygodny sposób na przeglądanie partii w pętli.
  • Dla każdej partii wywołaj optimizer.minimize(f) , który wykonuje f i minimalizuje wynik, obliczając gradienty w odniesieniu do czterech zmiennych, które zdefiniowaliśmy wcześniej.
  • f oblicza stratę. Wywołuje jedną z predefiniowanych funkcji straty wykorzystując przewidywanie modelu i wartość prawdziwą.