Визуализация процесса обучения нейронной сети средствами TensorFlowKit
GitHub: Example
GitHub: Другое
TensorFlowKit API
Посeтив репозиторий, добавьте его в «Stars» это поможет мне написать больше статей на эту тему.
Начиная работать в сфере машинного обучения, мне было тяжело переходить от объектов и их поведений к векторам и пространствам. Сперва все это достаточно тяжело укладывалось в голове и далеко не все процессы казались прозрачными и понятными с первого взгляда. По этой причине все, что происходило внутри моих наработок, я пробовал визуализировать: строил 3D модели, графики, диаграммы, изображения и тд.
Говоря об эффективной разработке систем машинного обучения, всегда поднимается вопрос контроля скорости обучения, анализа процесса обучения, сбора различных метрик обучения и тд. Особая сложность заключается в том, что мы (люди) привыкли оперировать 2х и 3х мерными пространствами, описывая различные процессы вокруг нас. Процессы внутри нейронных сетей происходят в многомерных пространствах, что серьезно усложняет их понимание. Осознавая это, инженеры по всему миру стараются разработать различные подходы к визуализации или трансформации многомерных данных в более простые и понятные формы.
Существуют целые сообщества, решающие такого рода задачи, например Distill, Welch Labs, 3Blue1Brown.
TensorBoard
Еще до начала работы с TensorFlow я начал работать с TensorBoard пакетом. Оказалось, что это очень удобное, кросплатформенное решение для визуализации разного рода данных. Потребовалось пару дней, чтоб «научить» swift приложение создавать отчеты в формате TensorBoard и интегрировать в мою нейронную сеть.
Разработка TensorBoard началась еще в середине 2015 года в рамках одной из лабораторий Google. В конце 2015 го Google открыл исходный код и работа над проектом стала публичной.
Текущая версия TensorBoard — это python пакет, созданный в помощь TensorFlow, который позволяет визуализировать несколько типов данных:
- Скалярные данные в разрезе времени, с возможностью сглаживания;
- Изображения, в том случае, если ваши данные можно представить в 2D, например: веса сверточной сети (они же фильтры);
- Непосредственно граф вычислений (в виде интерактивного представления);
- 2D изменение значения тензора во времени;
- 3D Гистограмма — изменение распределения данных в тензоре во времени;
- Текст;
- Audio;
Кроме того, существует еще проектор (projector) и возможность расширять TensorBoard при помощи плагинов, но об этом я рассказать не успею в этой статье.
Для работы нам понадобится TensorBoard на нашем компьютере (Ubuntu или Mac). У нас должен быть установлен python3. Я советую установить TensorBoard как часть TensorFlow пакета для python.
Linux:
$ sudo apt-get install python3-pip python3-dev
$ pip3 install tensorflow
MacOS:
$ brew install python3
$ pip3 install tensorflow
Запускаем TensorBoard, указав папку в которой мы будем хранить отчеты:
$ tensorboard --logdir=/tmp/example/
Открываем http://localhost:6006/
TensorFlowKit
Пример на GitHub
Не забудьте поставить «start» репозиторию.
Рассмотрим несколько случаев уже непосредственно на примере. Создание отчетов (summary) в формате TensorBoard происходит в момент конструирования графа вычислений. В TensorFlowKit я постарался максимально повторить python подходы и интерфейс, чтобы в дальнейшем можно было пользоваться общей документацией.
Как я уже сказал выше, каждый наш отчет мы собираем в summary. Это контейнер, в котором хранится массив value, каждый из которых представляет какое — либо событие, которое мы хотим визуализировать.
Summary в дальнейшем будет сохранен в файл на файловой системе, где его и прочтет TensorBoard.
Таким образом, нам необходимо создать FileWriter, указав граф, который мы хотим визуализировать и создать Summary, в который мы будем складывать наши значения.
let summary = Summary(scope: scope)
let fileWriter = try FileWriter(folder: writerURL, identifier: "iMac", graph: graph)
Запустив приложение и обновив страницу, мы уже можем видеть граф, который мы создали в коде. Он будет интерактивен, так что по нему можно перемещаться.
Далее, мы хотим видеть изменение некой скалярной величины во времени, например значение функции потерь (loss function or cost function) и accuracy нашей нейронной сети. Для этого добавляем выходы наших операций в summary:
try summary.scalar(output: accuracy, key: "scalar-accuracy")
try summary.scalar(output: cross_entropy, key: "scalar-loss")
Таким образом, после каждого шага вычислений нашей сессии, TensorFlow автоматически вычитает значения наших операций и передаст их на вход результирующего Summary, который мы сохраним в FileWriter (как это сделать я опишу ниже).
Также у нас в нейросети есть большее количество весов и предубеждений (bias). Как правило это различные матрицы достаточно большой размерности и анализировать их значения распечатывая крайне сложно. Будет лучше, если мы построим график распределений (distribution). Добавим в наш Summary еще и информацию о величине изменений весов, которую проделывает наша сеть после каждого шага обучения.
try summary.histogram(output: bias.output, key: "bias")
try summary.histogram(output: weights.output, key: "weights")
try summary.histogram(output: gradientsOutputs[0], key: "GradientDescentW")
try summary.histogram(output: gradientsOutputs[1], key: "GradientDescentB")
Теперь в нашем распоряжении визуализация того, как менялись веса и какими были изменения во время обучения.
Но это еще не все. Давайте действительно заглянем в устройство нашей нейронной сети.
Каждая картинка рукописного текста, полученная на вход, находит некое отражение в соответствующих ей весах. То есть, поданная на вход картинка умеет активировать определенные нейроны, тем самым имеет некий отпечаток внутри нашей сети. Напомню, что мы имеем 784 веса на каждый нейрон из 10. Таким образом, у нас 7840 весов. Все они представлены в виде матрицы 784×10. Попробуем развернуть всю матрицу в вектор и после этого «вытащить» веса, которые относятся к каждому отдельному классу:
let flattenConst = try scope.addConst(values: [Int64(7840)], dimensions: [1], as: "flattenShapeConst")
let imagesFlattenTensor = try scope.reshape(operationName: "FlattenReshape",
tensor: weights.variable,
shape: flattenConst.defaultOutput,
tshape: Int64.self)
try extractImage(from: imagesFlattenTensor, scope: scope, summary: summary, atIndex: 0)
try extractImage(from: imagesFlattenTensor, scope: scope, summary: summary, atIndex: 1)
…
try extractImage(from: imagesFlattenTensor, scope: scope, summary: summary, atIndex: 8)
try extractImage(from: imagesFlattenTensor, scope: scope, summary: summary, atIndex: 9)
Для этого добавим в граф еще несколько операций stridedSlice и reshape. Теперь, каждый полученный вектор добавим в Summary как картинку:
try summary.images(name: "Image-\(String(index))", output: imagesTensor, maxImages: 255, badColor: Summary.BadColor.default)
В разделе Images в TensorBoard мы теперь видим «отпечатки» весов, такими какими они были во время процесса обучения.
Осталось только обработать наш Summary. Для этого нам надо соединить все созданные Summary в один и обработать его во время обучения сети.
let _ = try summary.merged(identifier: "simple")
В момент работы нашей сети:
let resultOutput = try session.run(inputs: [x, y],
values: [xTensorInput, yTensorInput],
outputs: [loss, applyGradW, applyGradB, mergedSummary, accuracy],
targetOperations: [])
let summary = resultOutput[3]
try fileWriter?.addSummary(tensor: summary, step: Int64(index))
Прошу обратить внимание, что в этом примере я не рассматриваю вопрос вычисления accuracy, он вычисляется на данных обучения. Вычислять его на данных для обучения неверно.
В следующей статье я постараюсь рассказать, как собрать одну нейросеть и запустить ее на Ubuntu, MacOS, iOS из одного репозитория.