TensorFlow.js – Making Predictions na podstawie danych 2D

1. Wprowadzenie

W ramach tego ćwiczenia w Codelabs wytrenujesz model do generowania prognoz na podstawie danych liczbowych opisujących zbiór samochodów.

To ćwiczenie zademonstruje kroki typowe dla trenowania wielu różnych modeli, ale wykorzystane będzie małe zbiory danych i prosty (płytki) model. Głównym celem jest zaznajomienie się z podstawową terminologią, pojęciami i składnią związanymi z trenowaniem modeli za pomocą TensorFlow.js oraz wstępem do dalszej eksploracji i zdobywania wiedzy.

Szkolimy model do prognozowania liczb ciągłych, dlatego to zadanie jest czasami nazywane zadaniem regresji. Wytrenujemy model, pokazując mu wiele przykładów danych wejściowych wraz z prawidłowymi danymi wyjściowymi. Jest to tzw. uczenie nadzorowane.

Co utworzysz

Utworzysz stronę internetową, która używa TensorFlow.js do trenowania modelu w przeglądarce. Daje „Moc w koniach” w przypadku samochodu, model nauczy się przewidywać liczbę mil na galon (MPG).

W tym celu:

  • Wczytaj dane i przygotuj je do trenowania.
  • Zdefiniuj architekturę modelu.
  • Wytrenuj model i monitoruj jego wydajność podczas trenowania.
  • Oceń wytrenowany model, wykonując prognozy.

Czego się nauczysz

  • Sprawdzone metody przygotowywania danych na potrzeby systemów uczących się, w tym tasowanie i normalizacja.
  • Składnia TensorFlow.js do tworzenia modeli za pomocą interfejsu API tf.layers.
  • Jak monitorować trenowanie w przeglądarce za pomocą biblioteki tfjs-vis.

Czego potrzebujesz

2. Konfiguracja

Utwórz stronę HTML i dołącz JavaScript

96914ff65fc3b74c.png Skopiuj poniższy kod do pliku HTML o nazwie

index.html

<!DOCTYPE html>
<html>
<head>
  <title>TensorFlow.js Tutorial</title>

  <!-- Import TensorFlow.js -->
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.0/dist/tf.min.js"></script>
  <!-- Import tfjs-vis -->
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis@1.0.2/dist/tfjs-vis.umd.min.js"></script>
</head>
<body>
  <!-- Import the main script file -->
  <script src="script.js"></script>
</body>
</html>

Utwórz plik JavaScript dla kodu.

  1. W tym samym folderze co plik HTML powyżej utwórz plik o nazwie script.js i umieść w nim poniższy kod.
console.log('Hello TensorFlow');

Wypróbuj

Po utworzeniu plików HTML i JavaScript możesz je przetestować. Otwórz plik index.html w przeglądarce i otwórz konsolę devtools.

Jeśli wszystko działa, powinny być utworzone i dostępne w konsoli narzędzi deweloperskich 2 zmienne globalne:

  • tf jest odniesieniem do biblioteki TensorFlow.js
  • tfvis jest odniesieniem do biblioteki tfjs-vis

Otwórz narzędzia dla programistów w przeglądarce. W danych wyjściowych konsoli powinien wyświetlić się komunikat Hello TensorFlow. Jeśli tak, możesz przejść do następnego kroku.

3. Wczytuj, formatuj i wizualizuj dane wejściowe

Pierwszym krokiem jest wczytanie, formatowanie i wizualizacja danych, na których chcemy wytrenować model.

Wczytamy „samochody” z hostowanego przez nas pliku JSON. Zawiera wiele różnych cech poszczególnych samochodów. W tym samouczku chcemy wyodrębnić tylko dane dotyczące mocy silnika i mil na galon.

96914ff65fc3b74c.png Dodaj następujący kod do

script.js plik

/**
 * Get the car data reduced to just the variables we are interested
 * and cleaned of missing data.
 */
async function getData() {
  const carsDataResponse = await fetch('https://storage.googleapis.com/tfjs-tutorials/carsData.json');
  const carsData = await carsDataResponse.json();
  const cleaned = carsData.map(car => ({
    mpg: car.Miles_per_Gallon,
    horsepower: car.Horsepower,
  }))
  .filter(car => (car.mpg != null && car.horsepower != null));

  return cleaned;
}

Spowoduje to również usunięcie wszystkich wpisów, które nie mają zdefiniowanych mil na galon ani koni mechanicznych. Pokażmy też te dane na wykresie punktowym, aby zobaczyć, jak wyglądają.

96914ff65fc3b74c.png Dodaj następujący kod na dole

script.js plik.

async function run() {
  // Load and plot the original input data that we are going to train on.
  const data = await getData();
  const values = data.map(d => ({
    x: d.horsepower,
    y: d.mpg,
  }));

  tfvis.render.scatterplot(
    {name: 'Horsepower v MPG'},
    {values},
    {
      xLabel: 'Horsepower',
      yLabel: 'MPG',
      height: 300
    }
  );

  // More code will be added below
}

document.addEventListener('DOMContentLoaded', run);

Odśwież stronę. Po lewej stronie powinien być widoczny panel z danymi rozproszonymi. Powinien on wyglądać mniej więcej tak.

cf44e823106c758e.png

Ten panel nazywa się daszkiem i jest udostępniany przez tfjs-vis. Zapewnia to wygodne miejsce do wyświetlania wizualizacji.

Ogólnie rzecz biorąc, podczas pracy z danymi dobrze jest znaleźć sposoby na sprawdzenie swoich danych i ich usunięcie w razie potrzeby. W tym przypadku musieliśmy usunąć z zbioru carsData wpisy, które nie zawierały wszystkich wymaganych pól. Wizualizacja danych może pomóc nam określić, czy dane są w jakiś sposób uporządkowane, których model może się nauczyć.

Na wykresie powyżej widzimy, że istnieje ujemna korelacja między mocą mechaniczną i spalaniem, tzn. im większa liczba koni mechanicznych, tym mniejsza liczba mil na galon.

Określ koncepcję naszego zadania

Nasze dane wejściowe będą teraz wyglądać tak.

...
{
  "mpg":15,
  "horsepower":165,
},
{
  "mpg":18,
  "horsepower":150,
},
{
  "mpg":16,
  "horsepower":150,
},
...

Naszym celem jest wytrenowanie modelu, który będzie używał jednej liczby, konia koni i nauczy się przewidywać jedną liczbę, mile na galon. Pamiętaj, że mapowanie 1:1, ponieważ będzie to ważne w przypadku następnej sekcji.

Przekażemy te przykłady, moc mechaniczną i MPG, sieci neuronowej, która nauczy się z tych przykładów wzoru (lub funkcji) do przewidywania spalania określonej mocy. Informacje uzyskane na podstawie przykładów, na które mamy poprawne odpowiedzi, noszą nazwę nauki nadzorowanej.

4. Zdefiniuj architekturę modelu

W tej sekcji napiszemy kod opisujący architekturę modelu. Architektura modelu to po prostu wyrafinowany sposób określenia „które funkcje będzie uruchamiać model podczas wykonywania” lub „jakiego algorytmu będzie używać nasz model do obliczania odpowiedzi?”.

Modele ML to algorytmy, które pobierają dane wejściowe i generują dane wyjściowe. W przypadku sieci neuronowych algorytm stanowi zbiór warstw neuronów z „wagami” (liczby) decydujących o ich wynikach. Proces trenowania uczy się idealnych wartości dla tych wag.

96914ff65fc3b74c.png Dodaj poniższą funkcję do

script.js do zdefiniowania architektury modelu.

function createModel() {
  // Create a sequential model
  const model = tf.sequential();

  // Add a single input layer
  model.add(tf.layers.dense({inputShape: [1], units: 1, useBias: true}));

  // Add an output layer
  model.add(tf.layers.dense({units: 1, useBias: true}));

  return model;
}

To jeden z najprostszych modeli, które możemy zdefiniować w Tensorflow.js. Przyjrzyjmy się teraz poszczególnym wierszom.

Utwórz instancję modelu

const model = tf.sequential();

Spowoduje to utworzenie instancji obiektu tf.Model. Ten model to sequential, ponieważ dane wejściowe spływają bezpośrednio w dół do danych wyjściowych. Inne rodzaje modeli mogą mieć gałęzie, a nawet wiele danych wejściowych i wyjściowych, ale w wielu przypadkach będą one sekwencyjne. Modele sekwencyjne są też łatwiejsze w użyciu za pomocą interfejsu API.

Dodawanie warstw

model.add(tf.layers.dense({inputShape: [1], units: 1, useBias: true}));

Powoduje to dodanie do naszej sieci warstwy wejściowej, która jest automatycznie łączona z warstwą dense z jedną ukrytą jednostką. Warstwa dense to typ warstwy, w której dane wejściowe są mnożone przez macierz (tzw. wagi), a potem do wyniku dodaje do wyniku liczbę (taką jak odchylenie). To pierwsza warstwa sieci, więc musimy określić inputShape. inputShape to [1], ponieważ uzyskano liczbę 1 (liczbę koni mechanicznych danego samochodu).

units określa wielkość macierzy wag w warstwie. Jeśli ustawisz tu wartość 1, oznacza to, że dla każdej cechy wejściowej danych będzie miała 1 wagę.

model.add(tf.layers.dense({units: 1}));

Powyższy kod tworzy warstwę wyjściową. Ustawiliśmy units na 1, bo chcemy zwrócić liczbę 1.

Tworzenie instancji

96914ff65fc3b74c.png Dodaj następujący kod do

run zdefiniowanej wcześniej.

// Create the model
const model = createModel();
tfvis.show.modelSummary({name: 'Model Summary'}, model);

Spowoduje to utworzenie instancji modelu i wyświetlenie podsumowania warstw na stronie internetowej.

5. Przygotowywanie danych do trenowania

Aby wykorzystać zalety TensorFlow.js w zakresie wydajności, które pozwalają trenować modele systemów uczących się w praktyce, musimy przekonwertować dane na tensory. Wprowadzimy również różne przekształcenia naszych danych będące sprawdzonymi metodami, takie jak odtwarzanie losowe i normalizacja.

96914ff65fc3b74c.png Dodaj następujący kod do

script.js plik

/**
 * Convert the input data to tensors that we can use for machine
 * learning. We will also do the important best practices of _shuffling_
 * the data and _normalizing_ the data
 * MPG on the y-axis.
 */
function convertToTensor(data) {
  // Wrapping these calculations in a tidy will dispose any
  // intermediate tensors.

  return tf.tidy(() => {
    // Step 1. Shuffle the data
    tf.util.shuffle(data);

    // Step 2. Convert data to Tensor
    const inputs = data.map(d => d.horsepower)
    const labels = data.map(d => d.mpg);

    const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]);
    const labelTensor = tf.tensor2d(labels, [labels.length, 1]);

    //Step 3. Normalize the data to the range 0 - 1 using min-max scaling
    const inputMax = inputTensor.max();
    const inputMin = inputTensor.min();
    const labelMax = labelTensor.max();
    const labelMin = labelTensor.min();

    const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
    const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin));

    return {
      inputs: normalizedInputs,
      labels: normalizedLabels,
      // Return the min/max bounds so we can use them later.
      inputMax,
      inputMin,
      labelMax,
      labelMin,
    }
  });
}

Zobaczmy, co się tutaj dzieje.

Losowanie danych

// Step 1. Shuffle the data
tf.util.shuffle(data);

Tutaj losowo wybieramy kolejność przykładów, które zostaną przesłane algorytmowi trenowania. Tasowanie jest ważne, ponieważ zwykle podczas trenowania zbiór danych jest dzielony na mniejsze podzbiory (tzw. wsady), na których trenowany jest model. Tasowanie pomaga w przypadku każdej partii różnych danych z całej dystrybucji. Dzięki temu model:

  • nie uczyć się rzeczy, które są całkowicie zależne od kolejności wprowadzenia danych;
  • Nie zwracaj uwagi na strukturę w podgrupach (np. jeśli widzi tylko samochody o dużej mocy w pierwszej połowie okresu trenowania, może nauczyć się zależności, która nie ma zastosowania w innym zbiorze danych).

Przekształć w tensory

// Step 2. Convert data to Tensor
const inputs = data.map(d => d.horsepower)
const labels = data.map(d => d.mpg);

const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]);
const labelTensor = tf.tensor2d(labels, [labels.length, 1]);

W tym miejscu tworzymy 2 tablice: jedną dla przykładowych danych wejściowych (wpisy o mocy mechanicznej), a drugą dla rzeczywistych wartości wyjściowych (nazywanych w systemach uczących się etykietami).

Następnie konwertujemy dane każdej tablicy na tensor 2D. Tensor będzie miał kształt [num_examples, num_features_per_example]. Tutaj mamy inputs.length przykładów, a każdy przykład ma funkcję wejściową 1 (konie mechaniczne).

Normalizacja danych

//Step 3. Normalize the data to the range 0 - 1 using min-max scaling
const inputMax = inputTensor.max();
const inputMin = inputTensor.min();
const labelMax = labelTensor.max();
const labelMin = labelTensor.min();

const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin));

Teraz przedstawimy kolejną sprawdzoną metodę trenowania systemów uczących się. Normalizujemy dane. W tym przypadku dane normalizujemy do zakresu liczbowego 0-1 za pomocą skalowania min-maks. Normalizacja jest ważna, ponieważ mechanizmy wewnętrzne wielu modeli systemów uczących się, które utworzysz za pomocą pliku tensorflow.js, są zaprojektowane do pracy z niezbyt dużymi liczbami. Typowe zakresy normalizowania danych w celu uwzględnienia wartości 0 to 1 lub -1 to 1. Nauczysz się trenować swoje modele efektywniej, jeśli przyzwyczaisz się do normalizacji danych do rozsądnego zakresu.

Zwracanie danych i granic normalizacji

return {
  inputs: normalizedInputs,
  labels: normalizedLabels,
  // Return the min/max bounds so we can use them later.
  inputMax,
  inputMin,
  labelMax,
  labelMin,
}

Chcemy zachować wartości użyte do normalizacji podczas trenowania, aby można było cofnąć normalizację wyników, aby przywrócić je do pierwotnej skali i umożliwić normalizację przyszłych danych wejściowych w ten sam sposób.

6. Wytrenuj model

Po utworzeniu instancji modelu i przedstawieniu danych jako tensorów mamy wszystko, co jest potrzebne do rozpoczęcia procesu trenowania.

96914ff65fc3b74c.png Skopiuj następującą funkcję do pliku

script.js plik.

async function trainModel(model, inputs, labels) {
  // Prepare the model for training.
  model.compile({
    optimizer: tf.train.adam(),
    loss: tf.losses.meanSquaredError,
    metrics: ['mse'],
  });

  const batchSize = 32;
  const epochs = 50;

  return await model.fit(inputs, labels, {
    batchSize,
    epochs,
    shuffle: true,
    callbacks: tfvis.show.fitCallbacks(
      { name: 'Training Performance' },
      ['loss', 'mse'],
      { height: 200, callbacks: ['onEpochEnd'] }
    )
  });
}

Przeanalizujmy wszystko po kolei.

Przygotuj się do szkolenia

// Prepare the model for training.
model.compile({
  optimizer: tf.train.adam(),
  loss: tf.losses.meanSquaredError,
  metrics: ['mse'],
});

Musimy „kompilować”, przed trenowaniem modelu. W tym celu musimy określić kilka bardzo ważnych kwestii:

  • optimizer: to algorytm, który będzie regulował aktualizacje modelu na podstawie przykładów. W bibliotece TensorFlow.js dostępnych jest wiele optymalizatorów. Wybraliśmy optymalizator adam, który jest całkiem skuteczny w praktyce i nie wymaga konfiguracji.
  • loss: to funkcja, która informuje model o tym, jak radzi sobie z uczeniem się każdego wsadu (podzbioru danych), który jest wyświetlany. Używamy tutaj funkcji meanSquaredError do porównania prognoz utworzonych przez model z wartościami rzeczywistymi.
const batchSize = 32;
const epochs = 50;

Następnie wybieramy rozmiar wsadów i pewną liczbę epok:

  • batchSize odnosi się do rozmiaru podzbiorów danych, które model będzie widzieć podczas każdej iteracji trenowania. Typowe rozmiary wsadu mieszczą się w przedziale 32–512. Nie istnieje idealna wielkość wsadu dla wszystkich zadań. Opisanie celów matematycznych w przypadku różnych rozmiarów wsadu wykracza poza zakres tego samouczka.
  • Wartość epochs określa, ile razy model będzie sprawdzać cały udostępniony przez Ciebie zbiór danych. Przeanalizujemy 50 iteracji tego zbioru danych.

Rozpoczynanie pętli pociągu

return await model.fit(inputs, labels, {
  batchSize,
  epochs,
  callbacks: tfvis.show.fitCallbacks(
    { name: 'Training Performance' },
    ['loss', 'mse'],
    { height: 200, callbacks: ['onEpochEnd'] }
  )
});

model.fit to funkcja, którą wywołujemy w celu uruchomienia pętli trenowania. Jest to funkcja asynchroniczna, więc zwracamy obietnicę, którą nam deklaruje, aby użytkownik mógł określić, kiedy trenowanie dobiegnie końca.

Aby monitorować postępy trenowania, przekazujemy niektóre wywołania zwrotne do funkcji model.fit. Używamy tfvis.show.fitCallbacks do generowania funkcji, które przedstawiają wykresy przedstawiające straty i „mse” określony wcześniej.

Podsumowanie zdobytej wiedzy

Teraz musimy wywołać funkcje zdefiniowane za pomocą funkcji run.

96914ff65fc3b74c.png Dodaj następujący kod na dole

run funkcja.

// Convert the data to a form we can use for training.
const tensorData = convertToTensor(data);
const {inputs, labels} = tensorData;

// Train the model
await trainModel(model, inputs, labels);
console.log('Done Training');

Po odświeżeniu strony powinny pojawić się zaktualizowane poniższe wykresy po kilku sekundach.

c6d3214d6e8c3752.png

Są one tworzone przez wywołania zwrotne utworzone wcześniej. Na końcu każdej epoki wyświetlają wartości straty i mse, uśrednione dla całego zbioru danych.

Podczas trenowania modelu chcemy, aby straty spadły. W tym przypadku nasz wskaźnik jest miarą błędu, więc chcemy, aby on również spadał.

7. Przewidywanie

Teraz, gdy nasz model został wytrenowany, spróbuj wykonać prognozy. Oceńmy ten model, sprawdzając, co prognozuje dla jednolitego zakresu wartości od niskiej do wysokich koni mechanicznych.

96914ff65fc3b74c.png Dodaj tę funkcję do pliku Script.js

function testModel(model, inputData, normalizationData) {
  const {inputMax, inputMin, labelMin, labelMax} = normalizationData;

  // Generate predictions for a uniform range of numbers between 0 and 1;
  // We un-normalize the data by doing the inverse of the min-max scaling
  // that we did earlier.
  const [xs, preds] = tf.tidy(() => {

    const xsNorm = tf.linspace(0, 1, 100);
    const predictions = model.predict(xsNorm.reshape([100, 1]));

    const unNormXs = xsNorm
      .mul(inputMax.sub(inputMin))
      .add(inputMin);

    const unNormPreds = predictions
      .mul(labelMax.sub(labelMin))
      .add(labelMin);

    // Un-normalize the data
    return [unNormXs.dataSync(), unNormPreds.dataSync()];
  });


  const predictedPoints = Array.from(xs).map((val, i) => {
    return {x: val, y: preds[i]}
  });

  const originalPoints = inputData.map(d => ({
    x: d.horsepower, y: d.mpg,
  }));


  tfvis.render.scatterplot(
    {name: 'Model Predictions vs Original Data'},
    {values: [originalPoints, predictedPoints], series: ['original', 'predicted']},
    {
      xLabel: 'Horsepower',
      yLabel: 'MPG',
      height: 300
    }
  );
}

Warto zwrócić uwagę na kilka kwestii w funkcji powyżej.

const xsNorm = tf.linspace(0, 1, 100);
const predictions = model.predict(xsNorm.reshape([100, 1]));

Generujemy 100 nowych „przykładów” aby przesłać dane do modelu. Przekazujemy te przykłady do modelu, korzystając z modelu Model.predict. Pamiętaj, że muszą mieć one podobny kształt ([num_examples, num_features_per_example]), jak podczas trenowania.

// Un-normalize the data
const unNormXs = xsNorm
  .mul(inputMax.sub(inputMin))
  .add(inputMin);

const unNormPreds = predictions
  .mul(labelMax.sub(labelMin))
  .add(labelMin);

Aby przywrócić dane do pierwotnego zakresu (zamiast 0–1), używamy wartości obliczonych podczas normalizacji, ale po prostu odwróćmy operacje.

return [unNormXs.dataSync(), unNormPreds.dataSync()];

.dataSync() to metoda, której możemy użyć do uzyskania typedarray wartości przechowywanych w tensorze. Dzięki temu możemy przetwarzać te wartości w zwykłym kodzie JavaScript. Jest to synchroniczna wersja metody .data(), która jest ogólnie preferowana.

Na koniec używamy funkcji tfjs-vis, aby sporządzić wykres pierwotnych danych i prognoz modelu.

96914ff65fc3b74c.png Dodaj następujący kod do

run funkcja.

// Make some predictions using the model and compare them to the
// original data
testModel(model, data, tensorData);

Odśwież stronę. Po zakończeniu trenowania modelu zobaczysz coś takiego jak poniżej.

fe610ff34708d4a.png

Gratulacje! Właśnie udało Ci się wytrenować prosty model systemów uczących się. Obecnie wykonuje ona tak zwaną regresję liniową, aby dopasować linię do trendu obecnego w danych wejściowych.

8. Główne wnioski

Etapy trenowania modelu systemów uczących się obejmują:

Sformułuj zadanie:

  • Czy problem jest związany z regresją czy klasyfikacją?
  • Czy można to osiągnąć w przypadku nauki nadzorowanej lub nienadzorowanej?
  • Jaki jest kształt danych wejściowych? Jak powinny wyglądać dane wyjściowe?

Przygotuj dane:

  • Oczyść dane i w miarę możliwości sprawdzaj je pod kątem wzorców
  • Przetasuj dane, zanim użyjesz ich do trenowania
  • Normalizuj dane do rozsądnego zakresu dla sieci neuronowej. Zwykle dla danych liczbowych dobrym zakresem jest 0–1 lub -1–1.
  • Przekształć dane w tensory

Utwórz i uruchom model:

  • Zdefiniuj model za pomocą tf.sequential lub tf.model, a następnie dodaj do niego warstwy za pomocą funkcji tf.layers.*
  • Wybierz optymalizatora ( zwykle dobrym rozwiązaniem jest adam) i parametry takie jak rozmiar wsadu i liczba epok.
  • Wybierz odpowiednią funkcję straty dla problemu i wskaźniki dokładności, aby pomóc w ocenie postępów. meanSquaredError to typowa funkcja utraty danych w przypadku problemów z regresją.
  • Monitoruj trenowanie, aby wiedzieć, czy straty spadają

Ocenianie modelu

  • Wybierz wskaźnik oceny modelu, który możesz monitorować podczas trenowania. Po zakończeniu trenowania spróbuj zrobić kilka prognoz testowych, aby poznać ich jakość.

9. Dodatkowe środki: warto wypróbować

  • Eksperyment ze zmianą liczby epok. Ile epok potrzebujesz, zanim wykres zostanie spłaszczony.
  • Eksperymentuj, zwiększając liczbę jednostek w ukrytej warstwie.
  • Eksperymentuj z dodawaniem większej liczby ukrytych warstw pomiędzy pierwszą, dodaną przez nas ukrytą warstwą, a ostatnią warstwą wyjściową. Kod tych dodatkowych warstw powinien wyglądać mniej więcej tak.
model.add(tf.layers.dense({units: 50, activation: 'sigmoid'}));

Najważniejszą nowością w tych ukrytych warstwach jest wprowadzenie nieliniowej funkcji aktywacji, w tym przypadku aktywacji sigmoidalnej. Więcej informacji o funkcjach aktywacji znajdziesz w tym artykule.

Sprawdź, czy model jest w stanie wygenerować dane wyjściowe, takie jak na ilustracji poniżej.

a21c5e6537cf81d.png