Хотя у языков программирования Python или R относительно простая кривая обучения, веб-разработчики просто счастливы делать все в пределах своей комфортной зоны JavaScript. Учитывая начавшуюся в Node.js тенденцию применения JavaScript к каждому полю, я решил разобраться в концепциях машинного обучения с использованием JS. Python стал популярным из-за обилия доступных пакетов, но сообщество JS не отстает. В этой статье мы создадим простой классификатор в удобном для новичков процессе.

Учить больше:



Что ты будешь строить

Вы создадите веб-страницу, которая использует TensorFlow.js для обучения модели в браузере. Учитывая «AvgAreaNumberofRooms» для дома, модель научится предсказывать «цену» дома.

Для этого вы:

  • Загрузите данные и подготовьте их к обучению.
  • Определите архитектуру модели.
  • Обучайте модель и следите за ее работой во время обучения.
  • Оцените обученную модель, сделав несколько прогнозов.

Шаг 1. Начнем с основ.

Создайте HTML-страницу и включите JavaScript. Скопируйте следующий код в файл HTML с именем index.html

<!DOCTYPE html>
<html>
<head>
  <title>TensorFlow.js Tutorial</title>
  <!-- Import TensorFlow.js -->
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js"></script>
  <!-- Import tfjs-vis -->
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tfjs-vis.umd.min.js"></script>
  <!-- Import the main script file -->
  <script src="script.js"></script>
</head>
<body>
</body>
</html>

Создайте файл JavaScript для кода

В той же папке, что и HTML-файл выше, создайте файл с именем script.js и поместите в него следующий код.

console.log('Hello TensorFlow');

Проверить это

Теперь, когда у вас есть файлы HTML и JavaScript, протестируйте их. Откройте файл index.html в своем браузере и откройте консоль devtools.

Если все работает, в консоли разработчика должны быть созданы две глобальные переменные:

  • tf - это ссылка на библиотеку TensorFlow.js
  • tfvis - это ссылка на библиотеку tfjs-vis

Вы должны увидеть сообщение Hello TensorFlow. «Если да, то вы готовы перейти к следующему шагу.

Совет: используйте Bit, чтобы поделиться повторно используемым JS-кодом

Bit (Bit on GitHub) - это самый быстрый и масштабируемый способ совместного использования повторно используемого кода JavaScript между проектами и приложениями. Попробуйте, это бесплатно:



Пример: Ramda функционирует как общие компоненты



Шаг 2: Загрузите данные, отформатируйте данные и визуализируйте входные данные.

Мы загрузим домашний набор данных, который можно найти здесь. Он содержит множество различных функций для данного дома. Для этого урока нам нужны только данные о средней площади комнат и цене каждого дома.

Добавьте следующий код в свой script.js файл.

async function getData() {
  const houseDataReq = await fetch('https://raw.githubusercontent.com/meetnandu05/ml1/master/house.json');  
  const houseData = await houseDataReq.json();  
  const cleaned = houseData.map(house => ({
    price: house.Price,
    rooms: house.AvgAreaNumberofRooms,
  }))
  .filter(house => (house.price != null && house.rooms != null));
  
  return cleaned;
}

Это также удалит все записи, для которых не определены ни цена, ни количество комнат. Давайте также нанесем эти данные на диаграмму рассеяния, чтобы увидеть, как они выглядят.

Добавьте следующий код в конец вашего script.js файла.

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.rooms,
    y: d.price,
  }));
  tfvis.render.scatterplot(
    {name: 'No.of rooms v Price'},
    {values}, 
    {
      xLabel: 'No. of rooms',
      yLabel: 'Price',
      height: 300
    }
  );
  // More code will be added below
}
document.addEventListener('DOMContentLoaded', run);

Когда вы обновляете страницу. Вы должны увидеть панель в левой части страницы с диаграммой рассеяния данных. Это должно выглядеть примерно так.

Как правило, при работе с данными рекомендуется найти способы просмотреть свои данные и при необходимости очистить их. Визуализация данных может дать нам представление о том, есть ли у данных какая-либо структура, которую модель может изучить.

Из графика выше видно, что существует положительная корреляция между нет. номеров и цены, т.е. комнат увеличивается, цены на дома в основном растут.

Шаг 3: Постройте модель для обучения.

Здесь мы напишем код для построения нашей модели машинного обучения. Модель будет обучена на основе этого кода, поэтому это важный шаг. Модели машинного обучения принимают входные данные, а затем производят выходные. В случае с Tensorflow.js мы должны строить нейронные сети.

Добавьте следующую функцию в ваш script.js файл, чтобы определить модель.

function createModel() {
  // Create a sequential model
  const model = tf.sequential(); 
  
  // Add a single hidden 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;
}

Это одна из самых простых моделей, которые мы можем определить в tensorflow.js, давайте разберем каждую строку немного.

Создайте экземпляр модели

const model = tf.sequential();

Это создает экземпляр объекта tf.Model. Эта модель sequential, потому что ее входные данные идут прямо к ее выходу. Другие типы моделей могут иметь ветви или даже несколько входов и выходов, но во многих случаях ваши модели будут последовательными.

Добавить слои

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

Это добавляет в нашу сеть скрытый слой. Поскольку это первый уровень сети, нам нужно определить наш inputShape. inputShape равно [1], потому что в качестве входных данных используется 1number (количество комнат в данном доме).

units устанавливает размер весовой матрицы в слое. Устанавливая его здесь на 1, мы говорим, что будет 1 вес для каждой из входных характеристик данных.

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

Приведенный выше код создает наш выходной слой. Мы устанавливаем units на 1, потому что хотим вывести 1 число.

Создать экземпляр

Добавьте следующий код к функции run, которую мы определили ранее.

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

Это создаст экземпляр модели и покажет сводку слоев на веб-странице.

Шаг 4. Подготовьте данные для обучения

Чтобы получить преимущества производительности TensorFlow.js, которые делают модели обучения машинного обучения практичными, нам необходимо преобразовать наши данные в тензоры.

Добавьте следующий код в свой script.js файл

function convertToTensor(data) {
  
  return tf.tidy(() => {
    // Step 1. Shuffle the data    
    tf.util.shuffle(data);
    // Step 2. Convert data to Tensor
    const inputs = data.map(d => d.rooms)
    const labels = data.map(d => d.price);
    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,
    }
  });  
}

Давайте разберемся, что здесь происходит.

Перемешать данные

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

Во время обучения модели набор данных делится на более мелкие наборы, каждый из которых называется пакетом. Затем эти партии загружаются в модель для обучения. Перемешивание данных важно, потому что модель не должна получать одни и те же данные снова и снова. Если модель получает одни и те же данные снова и снова, модель не сможет обобщить данные и выдать выходные данные, указанные для входных данных, полученных во время обучения. Перемешивание поможет, если в каждом пакете будут разные данные.

Преобразовать в тензоры

// Step 2. Convert data to Tensor
const inputs = data.map(d => d.rooms)
const labels = data.map(d => d.price);
const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]);
const labelTensor = tf.tensor2d(labels, [labels.length, 1]);

Здесь мы создаем два массива: один для наших входных примеров (количество записей комнат), а другой для истинных выходных значений (которые в машинном обучении известны как метки, в нашем случае цена каждого дома). Затем мы преобразуем данные каждого массива в 2-мерный тензор.

Нормализовать данные

//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));

Затем мы нормализуем данные. Здесь мы нормализуем данные в числовом диапазоне 0-1, используя min-max scaling. Нормализация важна, потому что внутренняя часть многих моделей машинного обучения, которые вы создадите с помощью tensorflow.js, предназначена для работы с не слишком большими числами. Общие диапазоны для нормализации данных, включая 0 to 1 или -1 to 1.

Вернуть данные и границы нормализации

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

Мы хотим сохранить значения, которые мы использовали для нормализации во время обучения, чтобы мы могли ненормализовать выходные данные, чтобы вернуть их в исходную шкалу и позволить нам таким же образом нормализовать будущие входные данные.

Шаг 5. Обучите модель

Создав экземпляр нашей модели и наши данные, представленные в виде тензоров, у нас есть все необходимое для начала процесса обучения.

Скопируйте следующую функцию в свой script.js файл.

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 = 28;
  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'] }
    )
  });
}

Давайте разберемся с этим.

Подготовьтесь к тренировке

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

Мы должны «скомпилировать» модель, прежде чем тренировать ее. Для этого мы должны указать несколько очень важных вещей:

  • optimizer: Это алгоритм, который будет управлять обновлениями модели на примерах. В TensorFlow.js доступно множество оптимизаторов. Здесь мы выбрали оптимизатор adam, поскольку он достаточно эффективен на практике и не требует настройки.
  • loss: это функция, которая сообщает модели, насколько хорошо она справляется с изучением каждого из отображаемых пакетов (подмножеств данных). Здесь мы используем meanSquaredError для сравнения прогнозов, сделанных моделью, с истинными значениями.
  • metrics: это массив показателей, которые мы хотим вычислять в конце каждой эпохи. Часто мы хотим вычислить точность для всего обучающего набора, чтобы мы могли отслеживать, насколько хорошо мы справляемся. Здесь мы используем mse, что является сокращением от meanSquaredError. Это та же функция, которую мы используем для функции потерь, и она часто используется для задач регрессии.
const batchSize = 28;
const epochs = 50;

Затем мы выбираем batchSize и количество эпох:

  • batchSize относится к размеру подмножеств данных, которые модель будет видеть на каждой итерации обучения. Обычные размеры партий обычно находятся в диапазоне 32-512. На самом деле не существует идеального размера пакета для всех задач, и описание математических причин для различных размеров пакетов выходит за рамки данного руководства.
  • epochs означает, сколько раз модель будет просматривать весь набор данных, который вы ей предоставляете. Здесь мы выполним 50 итераций по набору данных.

Запустить поездную петлю

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

model.fit - это функция, которую мы вызываем для запуска цикла обучения. Это асинхронная функция, поэтому мы возвращаем обещание, которое она нам дает, чтобы вызывающий мог определить, когда обучение завершено.

Чтобы отслеживать прогресс обучения, мы передаем некоторые обратные вызовы model.fit. Мы используем tfvis.show.fitCallbacks для создания функций, которые строят графики для показателей «потери» и «mse», которые мы указали ранее.

Положил все это вместе

Теперь нам нужно вызвать функции, которые мы определили из нашей run функции.

Добавьте следующий код в конец вашей run функции.

// 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');

Когда вы обновите страницу, через несколько секунд вы должны увидеть обновление следующих графиков.

Они создаются обратными вызовами, которые мы создали ранее. Они отображают потери (для самого последнего пакета) и mse (для всего набора данных) в конце каждой эпохи.

При обучении модели мы хотим, чтобы потери уменьшались. В этом случае, поскольку наша метрика является мерой ошибки, мы хотим, чтобы она также снизилась.

Шаг 6: делайте прогнозы

Теперь, когда наша модель обучена, мы хотим сделать некоторые прогнозы. Давайте оценим модель, посмотрев, что она предсказывает для однородного диапазона чисел - от низкого до большого количества комнат.

Добавьте следующую функцию в свой файл 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 xs = tf.linspace(0, 1, 100);      
    const preds = model.predict(xs.reshape([100, 1]));      
    
    const unNormXs = xs
      .mul(inputMax.sub(inputMin))
      .add(inputMin);
    
    const unNormPreds = preds
      .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.rooms, y: d.price,
  }));
  
  
  tfvis.render.scatterplot(
    {name: 'Model Predictions vs Original Data'}, 
    {values: [originalPoints, predictedPoints], series: ['original', 'predicted']}, 
    {
      xLabel: 'No. of rooms',
      yLabel: 'Price',
      height: 300
    }
  );
}

Несколько вещей, на которые следует обратить внимание в приведенной выше функции.

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

Мы генерируем 100 новых «примеров» для использования в модели. Model.predict - это то, как мы вводим эти примеры в модель. Обратите внимание, что они должны иметь такую ​​же форму ([num_examples, num_features_per_example]), что и при тренировке.

// Un-normalize the data
const unNormXs = xs
  .mul(inputMax.sub(inputMin))
  .add(inputMin);
    
const unNormPreds = preds
  .mul(labelMax.sub(labelMin))
  .add(labelMin);

Чтобы вернуть данные в исходный диапазон (а не 0–1), мы используем значения, вычисленные при нормализации, но просто инвертируем операции.

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

.dataSync() - это метод, который мы можем использовать для получения typedarray значений, хранящихся в тензоре. Это позволяет нам обрабатывать эти значения в обычном JavaScript. Это синхронная версия метода .data(), который обычно является предпочтительным.

Наконец, мы используем tfjs-vis для построения исходных данных и прогнозов модели.

Добавьте следующий код в вашу функцию run.

testModel(model, data, tensorData);

Обновите страницу, и после того, как модель закончит обучение, вы должны увидеть что-то вроде следующего.

Поздравляю! Вы только что обучили простую модель машинного обучения с помощью Tensorflow.js! Вот репозиторий GitHub для справки.

Заключение

Я начал это делать, потому что концепция машинного обучения меня очень заинтриговала, и я хотел посмотреть, есть ли способ сделать это при разработке интерфейса. Я был очень рад узнать, что библиотека Tensorflow.js может помочь мне в достижении моей цели. Это только начало машинного обучения в разработке интерфейса. Можно сделать гораздо больше, и команда Tensorflow.js уже сделала это. Спасибо за прочтение!

Учить больше