Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Большие данные - лекция-13 - обучение нейросети...

Большие данные - лекция-13 - обучение нейросети с библиотекой автоматического дифференцирования TensorFlow

Обучение нейронной сети: обучение нейросети с библиотекой автоматического дифференцирования TensorFlow, нейросеть 3 нейрона, распознавание цифр MNIST

- Матчасть: производная и теория вероятностей - раньше; линейная алгебра, операции над матрицами - сейчас
- Библиотеки автоматического дифференцирования: Theano, TensorFlow, PyTorch и т.п.
- TensorFlow 1.4 vs 2.x
- Реализуем рассмотренные ранее модели на TensorFlow: линейный нейрон, нейрон с активацией-сигмоидой, нейросеть из 3-х элементов
- Реализация линейного нейрона: двоичная классификация, объекты с единственным признаком
-- Объекты-плейсхолдеры (заглушки) - обучающая выборка и истинные метки
-- Инициализация весовых коэффициентов
-- Шаблон для функции активации - матричные операции с тензорами
-- Шаблон для функции потерь
-- Градиент функции потерь (волшебство автоматического дифференцирования любой сложной функции в одной строчке)
-- Инициализация и запуск сессии
-- Спуск по градиенту (обучение) по эпохам: передача данных в сессию, выполнение шаблона функции спуска внутри сессии, получение данных из сессии (смотрим значение ошибки на каждом шаге)
-- Результат обучения: ошибка уменьшается на каждой эпохе
-- Сверяем результат с ручным спуском из прошлой лекции (ошибки по эпохам совпадают до знака, но нужно скорректировать функцию ошибки)
-- Проверка обученного нейрона: предсказание класса для новых объектов
- Реализация линейного нейрона для классификации объектов с 2-мя признаками
- Реализация нейрона с активацией-сигмоидой (логистическая регрессия) для классификации объектов с одним признаком: поменяли в коде одну строчку с шаблоном функции активации
- Реализация нейрона с активацией-сигмоидой (логистическая регрессия): объект с 2-мя признаками
- Реализация нейросети из 3-х нейронов: меняем размерности входных матриц и матриц весовых коэффициентов, код активации и обучения остаётся как есть
- Самостоятельно: попробовать другие функции активации
- Классификатор рукописных цифр MNIST
-- о датасете MNIST
-- структура файла с данными MNIST: изображения представлены текстом
-- загружаем данные
-- кодирование метки символа в набор двоичных признаков при помощи кодировщика OneHotEncoder
-- структура сети: один слой, по одному нейрону на каждую цифру (по сути это, не сеть, а несколько отдельных нейронов, каждый из которых делит всё множество на два класса: "цифра X"-"НЕ цифра X")
-- создание сети
-- обучение (немного схитрим: возьмем только первые 100 изображений)
-- проверка обученной сети
-- визуализация внутренней кухни процесса обучения: как нейрон делит множество символов на "цифра X"-"НЕ цифра X" в 784-мерном пространстве (схлопнутом во взвешенную сумму)
- Самостоятельно
- Бонус: код PyTorch (не помню уже, рабочий ли он)

первый вариант лекции: 29.05.2020
https://vk.com/video53223390_456239477
https://www.youtube.com/watch?v=Rl9CZX3R-38
https://www.youtube.com/playlist?list=PLSu-UfrQJjQky3LrVLb3hnJ7cnPxjZUQP
обновлено: 27.05.2021
https://1i7.livejournal.com/155603.html

Avatar for Anton

Anton

May 29, 2020
Tweet

More Decks by Anton

Other Decks in Education

Transcript

  1. Использованы материалы • Глубокое обучение. Погружение в мир нейронных сетей,

    С. Николенко, А. Кадурин, Е. Архангельская • Python и машинное обучение, Себастьян Рашка • Википедия, Хабр, Гитхаб, СтэкОверфлоу, Интернет
  2. Матчасть • В предыдущих лекциях: - Производная, геометрический смысл -

    Теория вероятностей • Здесь: линейная алгебра - Операции над матрицами: сложение, умножение и т. п. - Многомерные матрицы (тензоры)
  3. Библиотеки для автоматического дифференцирования • (они же — платформы для

    обучения нейронных сетей и других моделей) • Оперируют многомерными матрицами — тензорами • Математические операции над матрицами (умножение, сложение, логарифмы и т. п.) • В том числе символьное дифференцирование (автоматическое нахождение градиента — благодаря представлению формулы в виде графа вычислений) • Аппаратное ускорение операций над матрицами (распараллеливание, видеокарты, специализированное железо)
  4. Платформы • Theano • TensorFlow • PyTorch • Apache MXNet

    • Apache CNTK • … en.wikipedia.org/wiki/Comparison_of_deep- learning_software
  5. Theano deeplearning.net/software/theano/ github.com/Theano/Theano/ en.wikipedia.org/wiki/Theano_(software) ru.wikipedia.org/wiki/Феано_(жена_Пифагора) • Монреальский университет (Канада) •

    Один из первых фреймворков глубокого обучения • Концепт графа вычислений, который обеспечил возможность автоматического дифференцирования • Первый выпуск: 2007 • Не разрабатывается (сообщение в рассылке: 28.09.17) • Вялотекущая поддержка: исправление ошибок, новые возможности не добавляются
  6. PyTorch pytorch.org/ ru.wikipedia.org/wiki/PyTorch • Группа ИИ в Фейсбуке • Первый

    выпуск: 2016 • В активной разработке • Один из наиболее популярных фреймворков (наравне с TensorFlow) • По некоторым данным: лидирует (или движется к лидерству) в среде исследователей ИИ
  7. Google TensorFlow www.tensorflow.org github.com/tensorflow/tensorflow ru.wikipedia.org/wiki/TensorFlow • Первый выпуск: 2015 •

    Основан на внутренних разработках ИИ Google • Один из наиболее популярных фреймворков (в связке с Keras — доминирует)
  8. Ландшафт стремительно меняется • Проекты популярные 2-3 года назад (сейчас

    — весна 2020) забрасываются или закрываются • Живые проекты в новых версиях ломают обратную совместимость, меняя API • Не слишком старые книги, статьи, обсуждения с примерами кода могут быть не актуальны • Монополизация с уклоном в сторону нескольких крупнейший фреймворков • Среди крупнейших лидеров — Google TensorFlow (в области аппаратных ускорителей — Nvidia)
  9. Библиотеки конструирования нейронных сетей • Реализуют «визуальную» метафору: нейронная сеть

    — это шарики и стрелочки, сгруппированные слоями • Основной строительный блок — слой • Но математика всё равно просвечивает и вылезает наружу • По существу — обертки над библиотеками автоматического дифференцирования
  10. Некоторые примеры • NoLearn (обертка над оберткой): DBN (устарел), Lasagne

    • Lasagne (Лазанья): обертка над Theano, не разрабатывается (судя по гиту) • Keras: обертка над Theano, CNTK, TensorFlow (в новых версиях только над TensorFlow) • Scikit-learn: нет полновесного модуля для конструирования нейронных сетей, но можно использовать некоторые модели (например, достаточно для обучения MNIST) • ...
  11. На самом деле всё уже не так однозначно • TensorFlow

    внедрил в собственное ядро Keras так, • что они хотят его использовать для работы с данными (и, похоже, для конструирования моделей в «интуитивных» терминах нейронов и слоёв) • Раньше модуль совместимости с TensorFlow был внутри Keras • А теперь Keras — это модуль внутри TensorFlow
  12. TensorFlow 1.4 vs 2.x • В версии 2.0 серьезно изменили

    API, нарушив обратную совместимость • В частности, убрали объекты session и placeholder, которые будут дальше у нас • Старые объекты доступны через tf.compat.v1.* • Интегрируют keras, примеры на сайте опираются на этот модуль (пытаются спрятать за него математику) • Код в книгах и статьях (не слишком давнишних), использующий 1.4, на 2.0 просто так не заработает
  13. Здесь будем использовать TF 1.4 pip3 install --user tensorflow==1.4.0 •

    (если уже был установлен 2.x) pip3 uninstall tensorflow pip3 install --user tensorflow==1.4.0 • или на TF 2.x import tensorflow.compat.v1 as tf вместо import tensorflow as tf
  14. Повторим то, что уже делали • Нейрон с линейной активацией

    (ADALINE) • Нейрон с логистической регрессией (сигмоидой) в качестве активации • Нейронная сеть из 3-х элементов (делим линейно неразделимое множество, ищем градиент сложной функции — «обратное распространение ошибки»)
  15. Линейный нейрон Φ(x(i) ,w)=Φ(∑ j=0 m w j x j

    (i))=∑ j=0 m w j x j (i) ,x 0 (i)=1∀ i • Активация — взвешенная сумма плюс свободный коэффициент: • Порог: • Ошибка: ŷ(i)= {1,Φ(x(i),w)≥0 −1,Φ(x(i) ,w)<0 J (w)= 1 2 SSE= 1 2 ∑ i=1 n (Φ(∑ j=0 m w j x j (i))− y(i)) 2 = 1 2 ∑ i=1 n (∑ j=0 m w j x j (i)− y(i)) 2
  16. import numpy as np # объекты обучающей выборки - #

    таблица с одной колонкой X_train = np.array([[1], [2], [6], [8], [10]]) # метки классов - правильные ответы y_train = np.array([[-1], [-1], [1], [1], [1]])
  17. import tensorflow as tf # объекты — обучающая выборка x

    = tf.placeholder(tf.float32, [None, 1]) # истинные метки y = tf.placeholder(tf.float32, [None, 1])
  18. Здесь • x — переменная-заглушка, 2-д матрица ??x1, в которую

    будут помещены объекты обучающей выборки: - None — количество строк — количество объектов в обучающей выборке (заранее неизвестно) - 1 (один) — количество столбцов (количество признаков у объектов: у нас 1) • y — переменная-заглушка, 2-д матрица ??x1, в которую будут помещены истинные метки классов для объектов обучающей выборки: - None — количество строк — количество объектов в обучающей выборке (заранее неизвестно) - 1 (один) — количество столбцов с правильными ответами (1 для единичного нейрона или сетки с одним выходом)
  19. # весовые коэффициенты #W = tf.Variable(tf.zeros([1, 1])) #w0 = tf.Variable(tf.zeros([1]))

    #W = tf.Variable(tf.random( [1, 1], mean=0.0, stddev=1.0)) #w0 = tf.Variable(tf.random( [1], mean=0.0, stddev=1.0)) W = tf.Variable([[-0.9]]) w0 = tf.Variable([-0.9])
  20. Здесь • W — весовые коэффициенты (w 1 ,… ,

    w m )=(w 1 ) для признаков объекта (1xm = 1x1) • w0 — свободный коэффициент (w 0 ) (вектор из одного элемента)
  21. Замечание • Признаки x лучше приводить в диапазон от 0

    до 1, но мы повторим наш прошлый эксперимент • Значения W и w0 можно взять нулями или небольшими случайными значениями • Но мы возьмем те же начальные значения, которые использовали в лекции про линейный нейрон: w 0 =-0.9, w 1 =-0.9
  22. Здесь • tf.matmul — умножение матриц • tf.add или оператор

    «+» — сложение матриц • Реальная операция здесь не выполняется (при всём желании не получится, т. к. x еще не имеет значения) • Здесь y_ не получает значение, а сохраняет шаблон операции, к ней можно будет обращаться через эту переменную • Сама операция будет выполнена потом, причем не в контексте интерпретатора Python, а в контексте TF (возможно, на видео-карте или на кластере с аппаратным ускорением) • (это symbolic programming [по определению Apache MXNet] — одна из важных особенностей TensorFlow и других аналогичных библиотек)
  23. # потери (сумма квадратичных ошибок) # J(w)=SSE j_err = tf.reduce_sum((y

    - y_)*(y - y_)) # градиент функции потерь eta=0.001 train_step = tf.train.GradientDescentOptimizer(eta).minimize(j_err)
  24. Здесь • j_err — функция потерь, она же — сумма

    квадратичных ошибок (каждая ошибка — истинная метка минус активация) - (сравните с формулой) - (для минимизации делить на 2 не обязательно) • tf.reduce_sum — сумма элементов матрицы (схлопнуть матрицу квадратичных ошибок суммой в число) • train_step — операция спуска по градиенту (один шаг), будет вызвана потом • tf.train.GradientDescentOptimizer — здесь всё волшебство автоматического дифференцирования функции j_err, которую мы только что сами написали • eta — коэффициент обучения
  25. Здесь • Инициализация глобальных переменных (объявленных выше) • Создание и

    запуск сессии sess (tf.Session) • Все дальнейшие операции будут выполняться через нее • (концепт сессии удален в TF 2.x, смотрите в документации, что теперь вместо нее)
  26. # хватит 50 попыток epochs=50 for i in range(epochs): sess.run(train_step,

    feed_dict={x: X_train, y: y_train}) print("epoch: %s, J(w)=SSE: %s" % ((i+1), sess.run(j_err, feed_dict={x: X_train, y: y_train})))
  27. Здесь • Запускаем процесс обучение — на каждую итерацию цикла

    один шаг по градиенту вниз • Шагаем при помощи сохраненной выше процедуры train_step, которую вызываем через sess.run • В зарезервированные ранее заглушки x и y передаем объекты обучающей выборки X_train и истинные метки y_train через параметр feed_dict • Если поддерживается аппаратное ускорение, то здесь sess.run передаст управление на видео-карту или кластер или куда-то еще
  28. Здесь • Объявленные выше переменные считаются глобальными, внутри формулы для

    одной переменной можно ссылаться на другие • Таким образом, у нас переплетается контекст выполнения скрипта Python и внутренних вычислений — нужно внимательно смотреть, что где (поначалу непривычно, но потом нормально) • Коэффициенты W и w0 обновляются на каждом шаге и сохраняют значения между всем вызовами sess.run • С sess.run можно вызывать любые процедуры, объявленные выше, любое количество раз (например, вычислить текущее значение sse при обновленных коэффициентах W и w0)
  29. Посмотрим, что получается epoch: 0, J(w)=SSE: 258.43997 epoch: 1, J(w)=SSE:

    88.4552 epoch: 2, J(w)=SSE: 30.710127 epoch: 3, J(w)=SSE: 11.09293 … epoch: 50, J(w)=SSE: 0.92827904
  30. Очевидно, сходимся • Отлично, сравним результат с тем, что делали

    вручную на лекции про линейный нейрон (градиентный спуск): epoch: 0, SSE=258.440000, J(w)=SSE/2: 129.220000 epoch: 1, SSE=162.247723, J(w)=SSE/2: 81.123861 epoch: 50, SSE=0.964818, J(w)=SSE/2: 0.482409 • Везде сошлись, на нулевой эпохе ошибка одинаковая (уже хорошо — значит работает формула), на дальше расхождения, в чем дело? • Очевидно в том, что раньше мы искали ошибку как SSE/2, а здесь решили не делить на 2
  31. Немного поправим код • Добавили деление на 2 • И

    еще поправили print ниже • Остальное всё так же, запускаем заново скрипт # потери (сумма квадратичных ошибок * 1/2) # J(w)=SSE/2 j_err = tf.reduce_sum((y - y_)*(y - y_)/2)
  32. Посмотрим, что получается Скрипт с TF: epoch: 0, J(w)=SSE/2: 129.21999

    epoch: 1, J(w)=SSE/2: 81.12386 epoch: 2, J(w)=SSE/2: 50.998955 epoch: 3, J(w)=SSE/2: 32.130222 … epoch: 50, J(w)=SSE/2: 0.48240894 Лекция — градиентный спуск: epoch: 0, J(w)=SSE/2: 129.220000 epoch: 1, J(w)=SSE/2: 81.123861 epoch: 2, J(w)=SSE/2: 50.998956 epoch: 3, J(w)=SSE/2: 32.130229 … epoch: 50, J(w)=SSE/2: 0.482409
  33. • Слева ошибки TF, справа — старая лекция про градиентный

    спуск (линейный нейрон) • С точностью до миллионных долей (дальше округление) • Полное соответствие, аж дух захватывает • (обратите внимание, как легко оказалось поправить функцию ошибки — новый градиент был вычислен автоматом, с аналитической работой такой вариант не пройдет)
  34. Посмотрим, какие коэффициенты или print("W=%s, w0=%s" % (W.eval(session=sess), w0.eval(session=sess))) with

    sess.as_default(): print("W=%s, w0=%s" % (W.eval(), w0.eval())) W = [[0.2055623]], w0 = [-0.7860891]
  35. • w 0 =-0.7860891, w 1 =0.2055623 • Ровно такие

    же, как в лекции про градиентный спуск
  36. И посмотрим, как работает классификация • Возьмем два объекта: -

    x=(x 1 =7): должен попасть в класс «1» - x=(x 1 =1.4): должен попасть в класс «-1»
  37. Смотрим значение активации для новых объектов # здесь первый объект

    должен быть 1, второй -1 print("f: %s" % sess.run(y_, feed_dict={x: [[7], [0]]})) f: [[0.6528469] [-0.49830192]] • Ф(x=(7)) = 0.6528469 > 0 => класс «1» (ок) • Ф(x=(1.4)) = -0.49830192 < 0 => класс «-1» (ок)
  38. Замечание про порог • Здесь порог не вычисляем автоматически, смотрим

    на глаз • В некоторых случаях не пропускать активацию через порог полезно — например, когда хотим оценить «вероятность» или «уверенность» назначения класса (но не здесь) • Кому интересно, сделайте самостоятельно
  39. import numpy as np # объекты обучающей выборки - таблица

    с двумя колонками X_train = np.array([ [2, 1], [3, 1], [1, 2], [5, 2], [10, 3], [1, 5], [6, 6], [7, 6], [10, 7],[6, 8], [7, 8] ]) # метки классов - правильные ответы y_train = np.array([ [-1], [-1], [-1], [-1], [1], [-1], [1], [1], [1], [1], [1] ])
  40. import tensorflow as tf # объекты — обучающая выборка x

    = tf.placeholder(tf.float32, [None, 2]) # истинные метки y = tf.placeholder(tf.float32, [None, 1])
  41. Здесь (изменились размерности) • x — переменная-заглушка, 2-д матрица ??x2,

    в которую будут помещены объекты обучающей выборки: - None — количество строк — количество объектов в обучающей выборке (заранее неизвестно) - 2 (один) — количество столбцов (количество признаков у объектов: у нас теперь 2) • y — переменная-заглушка, 2-д матрица ??x1, в которую будут помещены истинные метки классов для объектов обучающей выборки: - None — количество строк — количество объектов в обучающей выборке (заранее неизвестно) - 1 (один) — количество столбцов с правильными ответами (1 для единичного нейрона или сетки с одним выходом)
  42. # весовые коэффициенты #W = tf.Variable(tf.zeros([2, 1])) #w0 = tf.Variable(tf.zeros([1]))

    #W = tf.Variable(tf.random( [2, 1], mean=0.0, stddev=1.0)) #w0 = tf.Variable(tf.random( [1], mean=0.0, stddev=1.0)) W = tf.Variable([[-0.9], [-0.9]) w0 = tf.Variable([-0.9])
  43. Здесь (изменились размерности) • W — весовые коэффициенты (w 1

    ,… , w m )=(w 1, w 1 ) для признаков объекта (1xm = 1x2) • w0 — свободный коэффициент (w 0 ) (вектор из одного элемента)
  44. Дальше ничего не поменялось • Активация • Ошибка • Градиент

    • Запуск сессии • Спускаемся в цикле по шагам • Смотрим ошибку и коэффициенты на последнем слое • Проверяем классификацию на новых объектах
  45. # активация #y_ = tf.add(tf.matmul(x, W), w0) y_ = tf.matmul(x,

    W) + w0 Активация (формула та же — у матриц другие размерности)
  46. # потери (сумма квадратичных ошибок) # J(w)=SSE/2 j_err = tf.reduce_sum((y

    - y_)*(y - y_)/2) # градиент функции потерь eta=0.001 train_step = tf.train.GradientDescentOptimizer(eta).minimize(j_err) Потери и градиент (здесь тоже возьмем SSE/2)
  47. # хватит 50 попыток epochs=50 for i in range(epochs): sess.run(train_step,

    feed_dict={x: X_train, y: y_train}) print("epoch: %s, J(w)=SSE/2: %s" % ((i+1), sess.run(j_err, feed_dict={x: X_train, y: y_train}))) Спуск
  48. Результат epoch: 0, J(w)=SSE/2: 683.3799 epoch: 200, J(w)=SSE/2: 1.0680617 •

    В лекции про градиентный спуск: epoch: 200, J(w)=SSE/2: 1.068061 • (совпадают до знака)
  49. Посмотрим коэффициенты или print("W=%s, w0=%s" % (W.eval(session=sess), w0.eval(session=sess))) with sess.as_default():

    print("W=%s, w0=%s" % (W.eval(), w0.eval())) W = [[0.1469777] [0.12344587]], w0 = [-1.1016785]
  50. • w 0 =-1.1016785, w 1 =0.1469777, w 2 =0.12344587

    • Ровно такие же, как в лекции про градиентный спуск
  51. И посмотрим, как работает классификация • Возьмем два объекта: -

    x=(x 1 =1, x 2 =3): должен попасть в класс «-1» - x=(x 1 =10, x 2 =8): должен попасть в класс «1»
  52. Смотрим значение активации для новых объектов # здесь первый объект

    должен быть -1, второй 1 print("f: %s" % sess.run(y_, feed_dict={x: [[1, 3], [10, 8]]})) f: [[-0.5843632] [1.3556653]] • Ф(x=(1, 3)) = -0.5843632 < 0 => класс «-1» (ок) • Ф(x=(10, 8)) = 1.3556653 > 0 => класс «1» (ок)
  53. Нейрон с сигмоидой • Активация — логистическая регрессия (она же

    — сигмоида): • Порог: • Ошибка (на основе функции правдоподобия): ŷ(i)= {1,Φ(x(i) ,w)≥0.5 0,Φ(x(i), w)<0.5 Φ(x(i) ,w)=Φ(∑ j=0 m w j x j (i))=σ(∑ j=0 m w j x j (i)),σ(s(i))= 1 1+e−s(i) J (w)=−ln L(w)=−∑ i=1 n [ y(i) ln(Φ(s(i)))+(1− y(i))ln(1−Φ(s(i)))]
  54. import numpy as np # объекты обучающей выборки - #

    таблица с одной колонкой X_train = np.array([[1], [2], [6], [8], [10]]) # метки классов - правильные ответы y_train = np.array([[0], [0], [1], [1], [1]]) Возьмем те же самые объекты-точки (только классы теперь «0» и «1»)
  55. import tensorflow as tf # объекты — обучающая выборка x

    = tf.placeholder(tf.float64, [None, 1]) # истинные метки y = tf.placeholder(tf.float64, [None, 1]) Заготовки для входных данных (отправляем через параметр feed_dict)
  56. По сравнению с прошлым разом • Будем использовать тип данных

    tf.float64 • Можно было бы обойтись и tf.float32 (по умолчанию) • Но для таких начальных значений коэффициентов w, которые мы использовали в лекции про сигмоиду, при вычислении ошибки здесь получаем nan'ы (на каком-то шаге не хватает точности) • Поэтому, чтобы воспроизвести точно результат лекции с сигмоидой, возьмем здесь tf.float64 (и заодно увидим, что так тоже можно)
  57. # весовые коэффициенты #W = tf.Variable(tf.zeros([1, 1], dtype=tf.float64), dtype=tf.float64) #w0

    = tf.Variable(tf.zeros([1], dtype=tf.float64), dtype=tf.float64) #W = tf.Variable(tf.random( [1, 1], mean=0.0, stddev=1.0, dtype=tf.float64), dtype=tf.float64) #w0 = tf.Variable(tf.random( [1], mean=0.0, stddev=1.0, dtype=tf.float64), dtype=tf.float64) W = tf.Variable([[2.9]], dtype=tf.float64) w0 = tf.Variable([4.9], dtype=tf.float64) Весовые коэффициенты
  58. Здесь • W — весовые коэффициенты (w 1 ,… ,

    w m )=(w 1 ) для признаков объекта (1xm = 1x1) • w0 — свободный коэффициент (w 0 ) (вектор из одного элемента) • Везде указываем тип данных dtype=tf.float64
  59. # активация — сигмоида от суммы sum_ = tf.matmul(x, W)

    + w0 y_ = 1 / (1 + tf.exp(-sum_)) Активация y_ = tf.sigmoid(tf.matmul(x, W) + w0) • можно короче:
  60. Здесь • tf.sigmoid — встроенная функция сигмоиды, которую можно использовать

    в качестве активации • Есть множество других встроенных для популярных функций, которые подойдут в качестве активации, • например, tf.nn.softmax, tf.nn.relu и т. п. • Как видим, также нет никакой проблемы подставить свою формулу
  61. # ошибка (потери) на основе функции правдоподобия j_err = -tf.reduce_sum(y*tf.log(y_)

    + (1-y)*tf.log(1-y_)) # градиент функции потерь # здесь волшебство: оно само автоматом посчитает # нам производную даже от такой функции eta=0.1 train_step = tf.train.GradientDescentOptimizer(eta).minimize(j_err) Потери и градиент (на основе функции правдоподобия)
  62. Еще раз восхитимся в этом месте • Мы только написали

    какую-то произвольную формулу (с множителями, суммированием ряда, логарифмами, а могло быть и всего больше), • а tf.train.GradientDescentOptimizer сразу считает нам производную по всем измерениям (градиент) и организует спуск • Всё, что мы делали до этого не имело бы никакого смысла, если бы не этот GradientDescentOptimizer • Можно сказать, что нейронные сети (они же — ИИ) — это не шарики со стрелочками, а вот этот вот GradientDescentOptimizer, • без него бы не было ничего
  63. # 97 эпох — как в лекции про сигмоиду epochs=97

    for i in range(epochs): sess.run(train_step, feed_dict={x: X_train, y: y_train}) print("epoch: %s, J(w): %s" % ((i+1), sess.run(j_err, feed_dict={x: X_train, y: y_train}))) Спуск
  64. Результат epoch: 0, J(w): 18.50043219595177 epoch: 97, J(w): 0.4791097661275506 •

    В лекции про логистическую регрессию: epoch: 97, J(w): 0.0479110 • (совпадают до знака)
  65. • w 0 =-3.01450184, w 1 =0.9063273 • Ровно такие

    же, как в лекции про логистическую регрессию
  66. И посмотрим, как работает классификация • Возьмем два объекта (те

    же, что раньше): - x=(x 1 =7): должен попасть в класс «1» - x=(x 1 =1.4): должен попасть в класс «0»
  67. Смотрим значение активации для новых объектов # здесь первый объект

    должен быть 1, второй -1 print("f: %s" % sess.run(y_, feed_dict={x: [[7], [0]]})) f: [[0.96543674] [0.14859751]] • Ф(x=(7)) = 0.96543674 > 0.5 => класс «1» (ок) • Ф(x=(1.4)) = 0.14859751 < 0.5 => класс «0» (ок)
  68. Замечание про порог • Здесь порог опять не вычисляем автоматически,

    смотрим на глаз • Но здесь его уже можно интерпретировать как «вероятность» или «уверенность» назначения класса • Чем ближе значение активации к «1», тем увереннее этот объект относим к классу «1» (тем он «синее») • Чем ближе значение активации к «0», тем увереннее этот объект относим к классу «0» (тем он «краснее»)
  69. import numpy as np # объекты обучающей выборки - таблица

    с двумя колонками X_train = np.array([ [2, 1], [3, 1], [1, 2], [5, 2], [10, 3], [1, 5], [6, 6], [7, 6], [10, 7],[6, 8], [7, 8] ]) # метки классов - правильные ответы y_train = np.array([ [0], [0], [0], [0], [1], [0], [1], [1], [1], [1], [1] ])
  70. 0

  71. import tensorflow as tf # объекты — обучающая выборка x

    = tf.placeholder(tf.float32, [None, 2]) # истинные метки y = tf.placeholder(tf.float32, [None, 1]) Заготовки для входных данных (заполняем в sess.run через параметр feed_dict)
  72. # весовые коэффициенты W = tf.Variable(tf.zeros([2, 1])) w0 = tf.Variable(tf.zeros([1]))

    Весовые коэффициенты (для разнообразия возьмем нули)
  73. # активация — сигмоида от суммы sum_ = tf.matmul(x, W)

    + w0 y_ = 1 / (1 + tf.exp(-sum_)) Активация (формула та же, сработает и для новых матриц) y_ = tf.sigmoid(tf.matmul(x, W) + w0) • равнозначно:
  74. # ошибка (потери) на основе функции правдоподобия j_err = -tf.reduce_sum(y*tf.log(y_)

    + (1-y)*tf.log(1-y_)) # градиент функции потерь eta=0.01 train_step = tf.train.GradientDescentOptimizer(eta).minimize(j_err) Потери и градиент (без изменений)
  75. # 200 эпох epochs=200 for i in range(epochs): sess.run(train_step, feed_dict={x:

    X_train, y: y_train}) print("epoch: %s, J(w): %s" % ((i+1), sess.run(j_err, feed_dict={x: X_train, y: y_train}))) Спуск (без изменений)
  76. Классификация • Возьмем два объекта: - x=(x 1 =1, x

    2 =3): должен попасть в класс «0» - x=(x 1 =10, x 2 =8): должен попасть в класс «1»
  77. Активация для новых объектов # здесь первый объект должен быть

    -1, второй 1 print("f: %s" % sess.run(y_, feed_dict={x: [[1, 3], [10, 8]]})) f: [[0.21663123] [0.9780246 ]] • Ф(x=(1, 3)) = 0.21663123 < 0.5 => класс «0» (ок) • Ф(x=(10, 8)) = 0.9780246 > 0.5 => класс «1» (ок)
  78. Нейронная сеть • 2 слоя, 3 нейрона • На входе:

    элементы с единственным признаком (m=1) • Скрытый слой: два нейрона с нелинейностью (сигмоидой) • На выходе: 1 нейрон с сигмоидой и порогом — назначает объекту класс «0» или «1»
  79. Активации на 1-м слое a 1 (i)(1)=Φ(x(i) , w 1

    (1))=Φ(∑ j=0 m(1) w 1 j (1) x j (i))=σ(∑ j=0 m(1) w 1 j (1) x j (i))=σ(s 1 (i)(1))= 1 1+e−s 1 (i)(1) , s 1 (i)(1)=s(x(i) ,w 1 (1))=∑ j=0 m(1) w 1 j (1) x j (i)=w 10 (1)+w 11 (1) x 1 (i) a 2 (i)(1)=Φ(x(i) , w 2 (1))=Φ(∑ j=0 m(1) w 2 j (1) x j (i))=σ(∑ j=0 m(1) w 2 j (1) x j (i))=σ(s 2 (i)(1))= 1 1+e−s 2 (i)(1) , s 2 (i)(1)=s(x(i) ,w 2 (1))=∑ j=0 m(1) w 2 j (1) x j (i)=w 20 (1)+w 21 (1) x 1 (i)
  80. Активация на 2-м слое a 1 (i)(2)=Φ(x(i) ,W )=Φ(∑ j=0

    m(2) w 1 j (2) a j (i)(1))=σ(∑ j=0 m(2) w 1 j (2)a j (i)(1))=σ(s 1 (i)(2)), s 1 (i)(2)=s(x(i),W )=∑ j=0 m(2) w 1 j (2) a j (i)(1)=w 10 (2)+w 11 (2) a 1 (i)(1)+w 12 (2) a 2 (i)(1), a 0 (i)(1)=1
  81. Функция стоимости (ошибка на последнем слое) J (W )=−ln L(W

    )=∑ i=1 n [− y(i) ln(Φ(s(i)))−(1− y(i))ln(1−Φ(s(i)))] s(i)=s 1 (i)(2)=w 10 (2)+∑ k=1 m(2) (w 1k (2)a k (i)(1))=w 10 (2)+∑ k=1 m(2) (w 1k (2)Φ(∑ j=0 m(1) w kj (1) x j (i))) =w 10 (2)+w 11 (2)Φ(w 10 (1)+w 11 (1) x 1 (i))+w 12 (2)Φ(w 20 (1)+w 21 (1) x 1 (i)) • Развернем для нашей сети из 2-х слоёв с одним нейроном на выходном слое и 2-мя нейронами на одном скрытом слое:
  82. import numpy as np # объекты обучающей выборки - #

    таблица с одной колонкой X_train = np.array([[0.1], [0.2], [0.5], [0.6], [0.8], [1.0]]) # метки классов - правильные ответы y_train = np.array([[0], [0], [1], [1], [0], [0]]) Обучающая выборка (здесь сразу нормализуем)
  83. import tensorflow as tf # объекты — обучающая выборка x

    = tf.placeholder(tf.float32, [None, 1]) # истинные метки y = tf.placeholder(tf.float32, [None, 1]) Заготовки для входных данных (отправляем через параметр feed_dict)
  84. Весовые коэффициенты (уже интереснее) # слой-1 #W_1 = tf.Variable(tf.zeros([1, 2]))

    #w0_1 = tf.Variable(tf.zeros([2])) #W_1 = tf.Variable(tf.random([1, 2], mean=0.0, stddev=1.0)) #w0_1 = tf.Variable(tf.random([2], mean=0.0, stddev=1.0)) # это значения из лекции про сетку из 3-х нейронов W_1 = tf.Variable([[2., -2.]]) # w1_11, #w1_21 w0_1 = tf.Variable([0.1, 0.1]) #w1_10, #w1_20 # слой-2 #W_2 = tf.Variable(tf.zeros([2, 1])) #w0_2 = tf.Variable(tf.zeros([1])) #W_2 = tf.Variable(tf.random([2, 1], mean=0.0, stddev=1.0)) #w0_2 = tf.Variable(tf.random([1], mean=0.0, stddev=1.0)) # это значения из лекции про сетку из 3-х нейронов W_2 = tf.Variable([[0.1], [0.1]]) # w2_11, #w2_12 w0_2 = tf.Variable([3.]) #w2_10
  85. Здесь • W_1, w0_1 — весовые коэффициенты для нейронов из

    слоя-1 (здесь всего 2 нейрона) • W_2, w0_2 — весовые коэффициенты для нейронов на слое-2 (здесь — единственный нейрон) • Обратите внимание на размерности матриц
  86. Размерности матриц • W_1 — 2-д матрица (таблица): - количество

    столбцов — количество признаков у исходного объекта - количество строк — количество нейронов на 1-м слое • W_2 — 2-д матрица (таблица): - количество столбцов — количество входящих связей == количество нейронов на 1-м слое == количество строк в матрице W_1 - количество строк — количество нейронов на 2-м слое • w0_1, w0_2 — векторы: количество элементов — количество нейронов в текущем слое
  87. Размерности матриц <=> «габариты» сетки • Меняем количество строк в

    матрицах — меняем «количество нейронов» в сетке • Только следим за тем, чтобы согласовывались размерности между слоями
  88. Активации (плюс одна строка на слой) # активации на 1-м

    слое # a_1_ = tf.sigmoid(tf.matmul(x, W_1) + w0_1) sum_1_ = tf.matmul(x, W_1) + w0_1 a_1_ = 1 / (1 + tf.exp(-sum_1_)) # активация на 2-м слое # y_ = tf.sigmoid(tf.matmul(a_1_, W_2) + w0_2) sum_2_ = tf.matmul(a_1_, W_2) + w0_2 y_ = 1 / (1 + tf.exp(-sum2_))
  89. # ошибка (потери) на основе функции правдоподобия j_err = -tf.reduce_sum(y*tf.log(y_)

    + (1-y)*tf.log(1-y_)) # градиент функции потерь: # здесь функция j_err зависит уже от 7-ми параметров! eta=0.5 train_step = tf.train.GradientDescentOptimizer(eta).minimize(j_err) Потери на последнем слое и градиент по всей сетке сразу
  90. Здесь еще сильнее восхитимся • С последнего раза (обучали единичный

    нейрон) код даже не поменялся • Здесь формула функции потерь j_err уже включает как параметры нейрона выходного слоя, так и параметры нейронов скрытого слоя (см. формулу y_) • Сейчас там всего 7 параметров, но их может быть существенно больше (десятки, сотни тысяч): - добавим размерности объектам, - добавим нейронов на скрытый слой - добавим еще слоёв • И формула здесь от этого никак не изменится (всё те же 2 строчки кода) — tf.train.GradientDescentOptimizer всё прожуёт.
  91. # 300 эпох epochs=300 for i in range(epochs): sess.run(train_step, feed_dict={x:

    X_train, y: y_train}) print("epoch: %s, J(w): %s" % ((i+1), sess.run(j_err, feed_dict={x: X_train, y: y_train}))) Спуск (без изменений)
  92. Результат epoch: 0, J(w): 12.678076 epoch: 300, J(w): 0.41892102 •

    В лекции про нейронную сеть: epoch: 300, J(w): 0.418921 • (совпадают до знака)
  93. Коэффициенты with sess.as_default(): print("W_1, w0_1: %s %s" % (W_1.eval(), w0_1.eval()))

    print("W_2, w0_2: %s %s" % (W_2.eval(), w0_2.eval())) W_1=[[9.041237 -11.766912]], w0_1=[-6.618652 3.4365644] W_2=[[-9.78924 ] [-9.784952]], w0_2=[4.38049]
  94. Нашли коэффициенты • Слой-1 w1_10=-6.618652, w1_11=9.041237 w1_20=3.4365644, w1_21=-11.766912 • Слой-2

    w2_10=4.38049, w2_11=-9.78924, w2_12=-9.784952 • Всё ровно то же, что в лекции про нейросеть
  95. Классификация • Возьмем три объекта: - x=(x 1 =-0.3): должен

    попасть в класс «0» (снаружи) - x=(x 1 =0.55): должен попасть в класс «1» (внутри) - x=(x 1 =0.7): должен попасть в класс «0» (снаружи)
  96. Активация для новых объектов # здесь первый объект должен быть

    0, второй 1, третий 0 print("f: %s" % sess.run(y_, feed_dict={x: [[-0.3], [0.55], [0.9]]})) f: [[0.0045137] [0.9128777] [0.02515101]] • Ф(x=(-0.3)) = 0.0045137 < 0.5 => класс «0» (ок) • Ф(x=(0.55)) = 0.9128777 > 0.5 => класс «1» (ок) • Ф(x=(0.9)) = 0.02515101 < 0.5 => класс «0» (ок)
  97. Самостоятельно • Другие активации: relu, softmax (обобщенная sigma для многоклассовой

    классификации, в этот раз обошлись без нее) и т.п. • Ссылка на страницу документации TensorFlow — встроенные математические операции, функции активации и т. п. • Посмотрите, как эта математика ложится на абстракции «слоёв» и «шариков со стрелочками» в новом TensorFlow 2.x с интерфейсами Keras • Посмотрите, как аналогичные вычисления реализованы в других фреймворках — PyTorch, MXNet и т.п.
  98. MNIST — база данных рукописных цифр • Modified National Institute

    of Standards and Technology • Национальный институт стандартов и технологий США • 1998 год • Эталонная задача для тестирования и сравнения алгоритмов машинного обучения • yann.lecun.com/exdb/mnist/ • ru.wikipedia.org/wiki/MNIST_(база_данных)
  99. MNIST — база данных рукописных цифр • 60000 изображений для

    обучения • 10000 изображений для тестирования • 28x28 пикселей, оттенки серого • (разбивки на обучающую/тестовую выборку могут отличаться)
  100. Скачать • С домашней странички (первоисточник): yann.lecun.com/exdb/mnist/ • Kaggle: www.kaggle.com/c/digit-recognizer

    • И множества других мест • например: github.com/tensorflow/datasets import tensorflow_datasets as tfds mnist_train = tfds.load('mnist', split='train')
  101. Kaggle: Digit Recognizer • www.kaggle.com/c/digit-recognizer • Зарегистрироваться • => Data:

    Скачать датасет: • train.csv — обучающая выборка: 77Мб, 42000 образов • test.csv — выборка тестирования: 50Мб, 28000 образов (переименуем скачанные файлы в mnist-train.csv и mnist-test.csv)
  102. Этапы • Сбор данных, выбор параметров • Унификация данных: формат

    CSV • Структурирование и нормализация данных: значения параметров для обучения в диапазон [0,1] • Подбор параметров нейронной сети, обучение на обучающей выборке • Проверка обученной сети на контрольной тестовой выборке • Внедрение => продукт
  103. Посмотрим данные mnist-train.csv • cat mnist-train.csv | more label,pixel0,pixel1,pixel2...pixel783 1,0,0,0,...0,191,250,253,93,0,...0,0,0,0

    … 3,0,0,0,...21,130,190,254,254,...0,0,0,0 • 28*28=784 пикселей на изображение • Каждый пиксель: значение от 0 до 255 (256 оттенков серого)
  104. Загрузить данные в таблицу # coding=utf-8 import csv import numpy

    as np # загрузка данных - cтроки в список with open('mnist-train.csv', 'r') as f: data = list(csv.reader(f))
  105. Загрузить данные в таблицу # список строк - в двумерный

    массив значений, # срежем заголовок train_data = np.array(data[1:]) # если хотим сократить выборку # ([индекс_начала:кол-во_элементов]): #train_data = np.array(data[1:1000])
  106. Данные — пиксели картинок • Срежем первую колонку слева (это

    метки) • Остальные колонки — значения пикселей • Одно изображение цифры — одна строка • Здесь же нормализуем — переведем все X в диапазон [0, 1] • X_train — таблица из 784 колонок # данные - все столбцы, кроме 1-го + # нормализация данных - перенос значений в диапазон [0,1] X_train = train_data[:, 1:].astype(np.float32) / 255.0
  107. Метки классов (цифры «0-9») # метки - столбец 1 train_labels

    = train_data[:, 0].astype(np.int32) • Берем первую колонку слева — это метки • Переводим в целочисленные значения (astype) • Каждая метка теперь — целое число от 0 до 9 • train_labels — таблица из 1-й колонки
  108. Провернем с метками один фокус # метки - цифры от

    0 до 9, # нам удобнее провести для них дамми-кодирование # (превратить в 10 столбцов, в которых будут только 0 и 1) from sklearn.preprocessing import OneHotEncoder enc = OneHotEncoder() # перечислить все категории в правильном порядке enc.fit([[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]]) # сгенерировать 10 столбцов дамми-кодом (One-Hot) для всей выборки y_train = enc.transform(train_labels.reshape(-1, 1)).toarray()
  109. print("labels:") print(train_labels[:5]) labels: [1 0 1 4 0] print("one-hot labels:")

    print(y_train[:5]) one-hot labels: [[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.] [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
  110. Теперь у нас • 10 столбцов с истинными метками классов

    вместо 1-го • Каждый столбец относится к одной цифре • и делит выборку на 2 части: «изображения выбранной цифры» и «все остальные» • Например, для цифры «3»: - «1»: объект является изображением цифры «3» - «0»: объект НЕ является изображением цифры «3» (изображения «0», «1», «2», «4», «5», «6», «7», «8», «9»)
  111. Теперь у нас • Очевидно, что каждая колонка делит исходную

    выборку по-разному • Т.е. у нас на каждую строку получается по 10 вариантов истинных ответов • (один «1» и девять «0», но у нуля будут трактовки: «НЕ 1», «НЕ 2», «НЕ 4» и т.п.)
  112. На картинке видим • Входы: 784 (пиксели от 0 до

    783) • Выходов: 10 — по одному на цифру, каждый выход — сигмоида с порогом 0 (эта картинка НЕ эта цифра) или 1 (эта картинка — эта цифра) • Скрытых слоёв нет
  113. Наша надежда • Что каждый из выходных нейронов сможет отделить

    выбранную цифру от всех остальных • Мы уже знаем, что для единичного нейрона это задача выполнима • Только требуется, чтобы исходное множество было линейно разделимо • (но даже если оно линейно неразделимо, мы всё равное получим результат, только ошибка останется достаточно большой)
  114. Наша надежда • Здесь мы имеем дело с точками в

    784-мерном пространстве, их нужно разделить 784-мерной гиперплоскостью (пересечение с пространством точек 785- мерной поверхности активации) • Сложно сказать, получится ли это сделать, глядя на исходные данные (даже если перевести их в изображения) • Но у нас есть метод градиентного спуска и его реализация в TensorFlow!
  115. import tensorflow as tf # объекты — обучающая выборка x

    = tf.placeholder(tf.float32, [None, 784]) # истинные метки y = tf.placeholder(tf.float32, [None, 10]) Заготовки для входных данных (всего изменений: поправить размерности)
  116. # 10 нейронов - на каждый подаём # входные данные

    (784 пикселя), # эти же нейроны - выходы W = tf.Variable(tf.zeros([784, 10], dtype=tf.float64), dtype=tf.float64) w0 = tf.Variable(tf.zeros([10], dtype=tf.float64), dtype=tf.float64) Весовые коэффициенты (почти так же, как для единичных нейронов, т. к. нет скрытых слоёв)
  117. #y_ = tf.nn.softmax(tf.matmul(x, W) + w0) #y_ = tf.sigmoid(tf.matmul(x, W)

    + w0) sum_ = tf.matmul(x, W) + w0 y_ = 1 / (1 + tf.exp(-sum_)) Активация (без изменений) (собственную реализацию сигмоиды таскаем для сохранения наследственности повествования — чтобы показать, что здесь так тоже можно)
  118. # ошибка (потери) на основе функции правдоподобия j_err = -tf.reduce_sum(y*tf.log(y_)

    + (1-y)*tf.log(1-y_)) # градиент функции потерь eta=0.05 train_step = tf.train.GradientDescentOptimizer(eta).minimize(j_err) Потери и градиент (без изменений)
  119. # 100 эпох epochs=100 for i in range(epochs): sess.run(train_step, feed_dict={x:

    X_train, y: y_train}) print("epoch: %s, J(w): %s" % ((i+1), sess.run(j_err, feed_dict={x: X_train, y: y_train}))) Спуск (код здесь без изменений...)
  120. Но есть нюанс: побатчевый спуск • С полной выборкой 42000

    экземпляров алгоритм не находит у ошибки глубокий минимум (ошибка падает с 2-х миллионов на 0-й эпохе, потом гуляет 50-100 тыс) • Решение этой проблемы — модицикация алгоритма градиентного спуска: побатчевый градиентный спуск • На каждый шаг мы считаем ошибку для обновления значений коэффициентов не для всей выборки обучающих данных, а для некоторой её части - батча (например, 100 элементов) • (если взять батч из одного элемента, то это будет стохастический градиентный спуск)
  121. Побатчевый спуск • Вообще говоря, получается, что на каждом шаге

    функция ошибки будет разная, поэтому в общем случае такой спуск не обязан вести нас в минимум ошибки для всей выборки, как это было с обычным полным градиентом • Но если обучающие данные хороше перемешаны и разные объекты из одного класса действительно друг на друга «похожи», то поверхность ошибки для разных батчей не должна отличаться совсем уж радикально • На практике этот спуск хорошо работает • И помогает спуститься там, где не работает обычный градинт
  122. Побатчевый спуск • Мы здесь поступим еще проще (схитрим немного)

    • Возьмем для обучения X_train первые 100 элементов
  123. Проверим работу • Подготовим тестовый файл из 2-х строк mnist-test-simple-digits-1and3.csv

    • (цифры «1» и «3») • Файл с заголовком, но без первой колонки с метками
  124. Тестовые данные # загрузить #with open('test.csv', 'r') as f: with

    open('mnist-test-simple-digits-1and3.csv', 'r') as f: data = list(csv.reader(f)) # срезать заголовок и нормализовать X_test = np.array(data[1:]).astype(np.float32) / 255.0
  125. Для цифр «1» и «3» (на картинках выше) y_: [[0.

    1. 0. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]] • Здесь элементы — значения активаций на каждом из входных нейронов • Через порог не пропущены ни «1»-цы, ни «0»-ли, просто сеть настолько «уверена» в назначении классов (распознанных образах)
  126. • Здесь у нас получается несколько датасетов в одном: основные

    данные (признаки X) одни и те же, а истинных ответов несколько колонок • Каждую колонку нужно рассматривать в контексте рассматриваемой цифры: она делить весь датасет на 2 класса: этот объект является выбранной цифрой (класс 1) или этот объект не является выбранной цифрой (класс 0) • По сути, мы просто обучаем несколько параллельных единичных нейронов с сигмоидной активацией, они никак не связаны между собой, результат обучения одного нейрона не влияет на ответ на соседнем нейроне (как у нас было в многослойной сетке), у них разное множество правильных ответов, • но одинаковые входные данные, мы построили для них общую функцию ошибки (простая сумма) и обучаем и получаем результат классификации в один присест
  127. Лабораторная работа • Нарисовать картинку 28x28 в любом графическом редакторе

    • Сохранить как png (или любой другой формат со сжатием без потерь) в отдельный файл • Конвертировать картинку в файл CSV в формате MNIST (test.csv) при помощи Python (найти библиотеку для загрузки и манипулирования пикселями изображения) • Проверить, как обученная на MNIST сетка распознаёт ваши закорючки
  128. Ссылки • Kaggle: State of Data Science and Machine Learning

    2019 [www.kaggle.com/kaggle-survey-2019] • The State of Machine Learning Frameworks in 2019 [thegradient.pub/state-of-ml-frameworks-2019-pytorch- dominates-research-tensorflow-dominates-industry/] • THE MNIST DATABASE of handwritten digits. [yann.lecun.com/exdb/mnist/] • Kaggle. Digit Recognizer. Classify handwritten digits using the famous MNIST data. [www.kaggle.com/c/digit-recognizer] • Википедия, Хабр, Гитхаб, СтэкОверфлоу, Интернет
  129. import math import csv import numpy as np with open('mnist-train-100.csv',

    'r') as f: data = list(csv.reader(f)) train_data = np.array(data[1:]) X_train = train_data[:, 1:].astype(np.float32) / 255.0 # метки - столбец 1 train_labels = train_data[:, 0].astype(np.int32) # метки - цифры от 0 до 9, нам удобнее провести для них дамми-кодирование # (превратить в 10 столбцов, в которых будут только 0 и 1) from sklearn.preprocessing import OneHotEncoder enc = OneHotEncoder() # перечислить все категории в любом порядке - он их сам отсортирует enc.fit([[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]]) y_train = enc.transform(train_labels.reshape(-1, 1)).toarray()
  130. eta=0.05 epochs=100 import torch # здесь важно везде тип данных

    float64, иначе сразу в ответа nan # (с float64 - ок есть результат) x = torch.tensor(X_train, dtype=torch.float64, requires_grad=False) # истинные метки (внутри модели TF) y = torch.tensor(y_train, dtype=torch.float64, requires_grad=False) # 10 нейронов - на каждый подаем входные данные (784 пикселя), # эти же нейроны - выходы W = torch.autograd.Variable(torch.zeros([784, 10], dtype=torch.float64), requires_grad=True) w0 = torch.autograd.Variable(torch.zeros([10], dtype=torch.float64), requires_grad=True) # градиент функции потерь optimizer = torch.optim.SGD([W, w0], lr=eta)
  131. for i in range(epochs): #y_ = tf.nn.softmax(tf.matmul(x, W) + b)

    #y_ = tf.sigmoid(tf.matmul(x, W) + b) #sum_ = tf.matmul(x, W) + w0 sum_ = torch.matmul(x, W) + w0 #sum_ = W * x + w0 y_ = 1 / (1 + torch.exp(-sum_)) j_err = -torch.sum(y*torch.log(y_) + (1-y)*torch.log(1-y_)) j_err.backward() print( "epoch: %s, J(w)=SSE: %s" %(i, j_err.item()) ) optimizer.step() optimizer.zero_grad()
  132. # проверка with open('mnist-test-simple-digits-1and3.csv', 'r') as f: data = list(csv.reader(f))

    test_data = np.array(data[1:]) # здесь без последней колонки #X_test = test_data[:, 1:].astype(np.float32) / 255.0 # метки - столбец 1 #test_labels = test_data[:, 0].astype(np.int32) X_test = test_data sum_ = torch.matmul( torch.tensor(X_test[:2].astype(np.float64) / 255.0, dtype=torch.float64), W) + w0 y_ = 1 / (1 + torch.exp(-sum_)) # TODO: округлить до int print( "y_: %s" % y_ )