1. Введение
В этом практическом занятии вы обучите модель для прогнозирования на основе числовых данных, описывающих набор автомобилей.
В этом упражнении будут продемонстрированы шаги, общие для обучения множества различных типов моделей, но будет использоваться небольшой набор данных и простая (поверхностная) модель. Основная цель — помочь вам ознакомиться с базовой терминологией, концепциями и синтаксисом обучения моделей с помощью TensorFlow.js и заложить основу для дальнейшего изучения и освоения.
Поскольку мы обучаем модель прогнозировать непрерывные числа, эта задача иногда называется задачей регрессии . Мы будем обучать модель, показывая ей множество примеров входных данных вместе с правильными выходными данными. Это называется обучением с учителем .
Что вы построите
Вам предстоит создать веб-страницу, которая будет использовать TensorFlow.js для обучения модели в браузере. Получив данные о мощности автомобиля (в лошадиных силах), модель научится прогнозировать расход топлива (в милях на галлон).
Для этого вам потребуется:
- Загрузите данные и подготовьте их для обучения.
- Определите архитектуру модели.
- Обучите модель и отслеживайте ее производительность в процессе обучения.
- Оцените обученную модель, сделав несколько прогнозов.
Что вы узнаете
- Рекомендации по подготовке данных для машинного обучения, включая перемешивание и нормализацию.
- Синтаксис TensorFlow.js для создания моделей с использованием API tf.layers .
- Как отслеживать процесс обучения в браузере с помощью библиотеки tfjs-vis .
Что вам понадобится
- Последняя версия Chrome или другой современный браузер.
- Текстовый редактор, работающий локально на вашем компьютере или в интернете, например, через Codepen или Glitch .
- Знание HTML, CSS, JavaScript и инструментов разработчика Chrome (или инструментов разработчика вашего предпочитаемого браузера).
- Общее концептуальное понимание нейронных сетей. Если вам нужно вводное ознакомление или повторение материала, посмотрите это видео от 3blue1brown или это видео о глубоком обучении на JavaScript от Аши Кришнан .
2. Настройка
Создайте 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/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>
Создайте JavaScript-файл для кода.
- В той же папке, где находится HTML-файл, создайте файл с именем script.js и поместите в него следующий код.
console.log('Hello TensorFlow');
Проверьте это
Теперь, когда вы создали HTML- и JavaScript-файлы, протестируйте их. Откройте файл index.html в браузере и откройте консоль инструментов разработчика.
Если всё работает, в консоли инструментов разработчика должны быть созданы и доступны две глобальные переменные:
-
tf— это ссылка на библиотеку TensorFlow.js. -
tfvis— это ссылка на библиотеку tfjs-vis.
Откройте инструменты разработчика в браузере. В консоли должно отобразиться сообщение Hello TensorFlow . Если это так, вы готовы перейти к следующему шагу.
3. Загрузка, форматирование и визуализация входных данных.
В качестве первого шага давайте загрузим, отформатируем и визуализируем данные, на которых мы хотим обучить модель.
Мы загрузим набор данных «автомобили» из JSON-файла, который мы для вас разместили. Он содержит множество различных характеристик каждого автомобиля. В этом руководстве мы хотим извлечь только данные о мощности двигателя и расходе топлива (в милях на галлон).
Добавьте следующий код в ваш файл
файл script.js
/**
* 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;
}
Это также удалит все записи, для которых не указаны ни расход топлива в милях на галлон, ни мощность двигателя. Давайте также построим диаграмму рассеяния, чтобы посмотреть, как это выглядит.
Добавьте следующий код в конец вашего файла.
файл 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.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);
После обновления страницы слева должна появиться панель с диаграммой рассеяния данных. Она должна выглядеть примерно так.

Эта панель называется визором и предоставляется библиотекой tfjs-vis . Она обеспечивает удобное место для отображения визуализаций.
Как правило, при работе с данными полезно найти способы проанализировать их и при необходимости очистить. В данном случае нам пришлось удалить из carsData некоторые записи, которые не содержали всех необходимых полей. Визуализация данных может дать нам представление о том, есть ли в данных какая-либо структура, которую модель может изучить.
Как видно из приведенного выше графика, существует отрицательная корреляция между мощностью двигателя и расходом топлива, то есть с увеличением мощности автомобиль, как правило, расходует меньше миль на галлон.
Сформулируйте нашу задачу.
Теперь наши входные данные будут выглядеть следующим образом.
...
{
"mpg":15,
"horsepower":165,
},
{
"mpg":18,
"horsepower":150,
},
{
"mpg":16,
"horsepower":150,
},
...
Наша цель — обучить модель, которая будет принимать одно число — мощность двигателя — и предсказывать другое число — расход топлива в милях на галлон . Помните об этом однозначном соответствии, оно будет важно для следующего раздела.
Мы собираемся передать эти примеры — мощность и расход топлива — в нейронную сеть, которая на основе этих примеров выработает формулу (или функцию) для прогнозирования расхода топлива при заданной мощности. Такое обучение на примерах, для которых у нас есть правильные ответы, называется обучением с учителем .
4. Определите архитектуру модели.
В этом разделе мы напишем код для описания архитектуры модели. Архитектура модели — это просто замысловатый способ сказать , «какие функции будет выполнять модель во время выполнения» , или, другими словами , «какой алгоритм будет использовать наша модель для вычисления своих ответов» .
Модели машинного обучения — это алгоритмы, которые принимают на вход данные и выдают на выходе. При использовании нейронных сетей алгоритм представляет собой набор слоев нейронов с «весами» (числами), управляющими их выходными данными. В процессе обучения определяются оптимальные значения этих весов.
Добавьте следующую функцию в ваш
Файл script.js для определения архитектуры модели.
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;
}
Это одна из простейших моделей, которые мы можем определить в tensorflow.js, давайте немного разберем каждую строку.
Создайте экземпляр модели.
const model = tf.sequential();
Это создает объект tf.Model . Эта модель является sequential поскольку ее входные данные напрямую поступают на выход. Другие типы моделей могут иметь разветвления или даже несколько входов и выходов, но во многих случаях ваши модели будут последовательными. Последовательные модели также имеют более простой в использовании API .
Добавить слои
model.add(tf.layers.dense({inputShape: [1], units: 1, useBias: true}));
Это добавляет входной слой к нашей сети, который автоматически соединяется с dense слоем с одним скрытым элементом. dense слой — это тип слоя, который умножает свои входные данные на матрицу (называемую весами ), а затем добавляет к результату число (называемое смещением ). Поскольку это первый слой сети, нам нужно определить наш inputShape . inputShape равен [1] , потому что у нас на входе 1 число (мощность двигателя данного автомобиля).
Параметр units задает размер матрицы весов в слое. Установив значение 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);
Это создаст экземпляр модели и отобразит сводку слоев на веб-странице.
5. Подготовьте данные для обучения.
Чтобы получить преимущества TensorFlow.js в плане производительности, которые делают обучение моделей машинного обучения практичным, нам необходимо преобразовать наши данные в тензоры . Мы также выполним ряд преобразований наших данных, которые являются передовыми методами, а именно перемешивание и нормализацию .
Добавьте следующий код в ваш файл
файл script.js
/**
* 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,
}
});
}
Давайте разберемся, что здесь происходит.
Перетасуйте данные
// 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]);
Здесь мы создаём два массива: один для входных данных (значения мощности в лошадиных силах), а другой — для истинных выходных значений (которые в машинном обучении называются метками).
Затем мы преобразуем данные каждого массива в двумерный тензор. Тензор будет иметь форму [num_examples, num_features_per_example] . Здесь у нас есть inputs.length examples, и каждый пример имеет 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));
Далее мы применим еще один передовой метод обучения машинного обучения. Мы нормализуем данные. Здесь мы нормализуем данные в числовой диапазон 0-1 , используя масштабирование min-max . Нормализация важна, потому что внутренние механизмы многих моделей машинного обучения, которые вы будете создавать с помощью 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,
}
Мы хотим сохранить значения, которые использовали для нормализации во время обучения, чтобы затем отменить нормализацию выходных данных и вернуть их в исходный масштаб, а также чтобы иметь возможность нормализовать входные данные в будущем таким же образом.
6. Обучение модели
После создания экземпляра модели и представления данных в виде тензоров у нас есть все необходимое для начала процесса обучения.
Скопируйте следующую функцию в ваш файл.
файл 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 = 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'] }
)
});
}
Давайте разберем это по пунктам.
Подготовка к тренировке
// Prepare the model for training.
model.compile({
optimizer: tf.train.adam(),
loss: tf.losses.meanSquaredError,
metrics: ['mse'],
});
Перед обучением модели нам необходимо её «скомпилировать». Для этого нужно указать ряд очень важных параметров:
-
optimizer: это алгоритм, который будет управлять обновлениями модели по мере обработки примеров. В TensorFlow.js доступно множество оптимизаторов. Здесь мы выбрали оптимизатор Adam, поскольку он достаточно эффективен на практике и не требует настройки. - Функция
lossпоказывает модели, насколько хорошо она справляется с обучением на каждом из представленных ей наборов данных (подмножеств). Здесь мы используемmeanSquaredErrorдля сравнения предсказаний модели с истинными значениями.
const batchSize = 32;
const epochs = 50;
Далее мы выбираем batchSize и количество эпох:
-
batchSizeобозначает размер подмножеств данных, которые модель будет видеть на каждой итерации обучения. Обычно используются размеры пакетов данных в диапазоне от 32 до 512. Идеального размера пакета данных для всех задач не существует, и описание математических обоснований различных размеров пакетов выходит за рамки данного руководства. -
epochsобозначает количество раз, которое модель будет просматривать весь предоставленный вами набор данных. В данном случае мы выполним 50 итераций по набору данных.
Запустить поезд по замкнутому маршруту
return await 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 для генерации функций, которые строят графики для метрик 'loss' и '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');
После обновления страницы через несколько секунд вы увидите, как обновляются следующие графики.

Эти данные создаются с помощью коллбэков, которые мы создали ранее. Они отображают значение функции потерь и среднеквадратичной ошибки, усредненные по всему набору данных, в конце каждой эпохи.
При обучении модели мы хотим видеть снижение функции потерь. В данном случае, поскольку наша метрика измеряет ошибку, мы хотим видеть и её снижение.
7. Делайте прогнозы
Теперь, когда наша модель обучена, мы хотим сделать несколько прогнозов. Давайте оценим модель, посмотрев, что она предсказывает для равномерного диапазона значений мощности от низкой до высокой.
Добавьте следующую функцию в файл 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
}
);
}
В приведенной выше функции следует обратить внимание на несколько моментов.
const xsNorm = tf.linspace(0, 1, 100);
const predictions = model.predict(xsNorm.reshape([100, 1]));
Мы генерируем 100 новых «примеров» для подачи в модель. Функция Model.predict используется для передачи этих примеров в модель. Обратите внимание, что они должны иметь аналогичную форму ( [num_examples, num_features_per_example] ), как и при обучении.
// Un-normalize the data
const unNormXs = xsNorm
.mul(inputMax.sub(inputMin))
.add(inputMin);
const unNormPreds = predictions
.mul(labelMax.sub(labelMin))
.add(labelMin);
Чтобы вернуть данные в исходный диапазон (а не в диапазон 0-1), мы используем значения, рассчитанные в процессе нормализации, но просто инвертируем операции.
return [unNormXs.dataSync(), unNormPreds.dataSync()];
Метод .dataSync() позволяет получить typedarray значений, хранящихся в тензоре. Это дает возможность обрабатывать эти значения в обычном JavaScript. Это синхронная версия метода .data() , которая обычно предпочтительнее.
Наконец, мы используем tfjs-vis для построения графика исходных данных и прогнозов, полученных с помощью модели.
Добавьте следующий код в ваш файл
run функцию.
// Make some predictions using the model and compare them to the
// original data
testModel(model, data, tensorData);
Обновите страницу, и после завершения обучения модели вы должны увидеть что-то подобное.

Поздравляем! Вы только что обучили простую модель машинного обучения. В данный момент она выполняет так называемую линейную регрессию, которая пытается построить линию, соответствующую тренду, присутствующему во входных данных.
8. Основные выводы
Этапы обучения модели машинного обучения включают в себя:
Сформулируйте свою задачу:
- Это задача регрессии или задача классификации?
- Можно ли это сделать с помощью контролируемого или неконтролируемого обучения?
- Какова форма входных данных? Как должны выглядеть выходные данные?
Подготовьте свои данные:
- Очистите данные и, по возможности, вручную проверьте их на наличие закономерностей.
- Перед использованием данных для обучения перемешайте их.
- Нормализуйте данные, приведя их в разумный диапазон для нейронной сети. Обычно диапазоны от 0 до 1 или от -1 до 1 являются подходящими для числовых данных.
- Преобразуйте ваши данные в тензоры.
Создайте и запустите свою модель:
- Определите свою модель, используя
tf.sequentialилиtf.modelа затем добавьте к ней слои, используяtf.layers.* - Выберите оптимизатор ( Adam обычно является хорошим вариантом) и параметры, такие как размер пакета и количество эпох.
- Выберите подходящую функцию потерь для вашей задачи и метрику точности, которая поможет оценить прогресс.
meanSquaredError— распространенная функция потерь для задач регрессии. - Следите за тренировками, чтобы увидеть, снижается ли уровень потерь.
Оцените свою модель
- Выберите для своей модели метрику оценки, которую вы сможете отслеживать во время обучения. После обучения попробуйте сделать несколько тестовых прогнозов, чтобы оценить качество прогнозирования.
9. Дополнительное задание: Что стоит попробовать
- Поэкспериментируйте, изменяя количество эпох. Сколько эпох вам потребуется, чтобы граф выровнялся?
- Поэкспериментируйте с увеличением количества элементов в скрытом слое.
- Поэкспериментируйте с добавлением дополнительных скрытых слоев между первым добавленным скрытым слоем и итоговым выходным слоем. Код для этих дополнительных слоев должен выглядеть примерно так.
model.add(tf.layers.dense({units: 50, activation: 'sigmoid'}));
Самое важное новшество этих скрытых слоев заключается в том, что они вводят нелинейную функцию активации, в данном случае сигмоидную . Подробнее о функциях активации можно узнать в этой статье .
Попробуйте добиться от модели результатов, подобных изображению ниже.
