Python и машинное обучение [Себастьян Рашка] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

МНЕНИЕ ЭКСПЕРТОВ

Python
и машинное

обучение
Машинное и глубокое обучение с использованием

Python, sciki~-learn и TensorFlow 2

3-е издание

-

охватывает

Tensor·flow 2,

поро>1
BIRMINGHAM + MUMBAI

и машинное
обучение

Python

3-е издание
Машинное и глубокое обучение
с использованием Python, scikit-learn
и TensorFlow 2

Себастьян Рашка
Вахид Мирджалили

Москва

2020

• Санкт-Петербург

ББК

32.973.26-018.2.75
Р28

УДК

681.3.07
ООО "Диалектика"
Зав. редакцией С.Н. Тригуб
Перевод с английского и редакция Ю.Н. Артеменко
По общим вопросам обращайтесь в издательство "Диалектика" по адресу:

info.dialektika@gmail.com. http://www.dialektika.com
Рашка, Себастьян, Мирджалили, Вахид.

Р28

Python

и машинное обучение: машинное и глубокое обучение с ис­

пользованием
СПб.

: ООО

Python, scikit-learn и TensorFlow 2,
2020. - 848 с.: ил. -

"Диалектика",

ISBN 978-5-907203-57-0

3-е изд.: Пер. с англ.

-

Парал. тит. англ.

(рус.)
ББК

32.973.26-018.2.75

Все названия программных продуктов являются заре1·истрированными торговыми марками соответ­

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

ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая
фотокопирование и запись на магнитный носитель, если на зто нет письменного разрешения издаrельства

Packt

PuЫishiпg

Ltd.

Authorized Russian translation of the English editioп ol' Python Machine Learning: Machine Learning and
Deep Learning tvith Python, scikit-/earn. and TensorF/mv 2, 3rd Edition (ISBN 978-1-78995-575-0) © 2019
Packt PuЫishiпg. All rights reserved.
This translatioп is puЬlished and sold Ьу permission of Pвckt PuЫishing Ltd" which owns or controls all
rights to puЫish апd sell the same.
All rights reserved. No part of this work mву Ье reproduced or transmitted in any form or Ьу any means,
electronic or mechanical, iпcludiпg photocopying, recording, or Ьу апу information storage or retrieval system,
without the prior writteп permission of the copyright owner апd the PuЫisher.
Научно-популярное издание

Себастьян Рашка, Вахид Мирджалили

и машинное обучение:
машинное и глубокое обучение с использованием
Python, scikit-learn и TensorFlow 2
3-е издание

Python

Подписано в печаrь

10.09.2020. Формаr 70х100/\6

Гврнmурв Times
Усл. печ. л.
Тираж

68,37. Уч.-изд. л. 38,9
500 экз. Заказ № 6013

Оmечаrано в АО "Первая Образ11овu типография"
Филиал "Чеховский Печаrный Двор"

142300, Московская область, г. Чехов, ул. Полиграфистов, д. 1
Сайт: www.chpd.ru, E-mail: sales@chpd.ru. тел. 8 (499) 270-73-59
ООО "Диалектика'',

195027,

ISBN 978-5-907203-57-0 (рус.)
ISBN 978-1-78995-575-0 (англ.)

Санкт-Петербург, Магнитогорская ул" д.

©

30, лит.

А, пом.

848

ООО "Диалектика",

© Packt

PuЬlishing,

2020
2019

Оrпавпение
Предисловие

20

Глава

1. Наделение компьютеров способностью обучения на данных

29

Глава

2. Обучение простых алгоритмов МО для классификации

49

Глава 3. Обзор классификаторов на основе машинного обучения
с использованием

Глава

scikit-learn

85

4. Построение хороших обучающих наборов предварительная обработка данных

145

Глава

5. Сжатие данных с помощью понижения размерности

Глава

6. Освоение практического опыта оценки моделей
и настройки гиперпараметров

185

233

Глава 7. Объединение разных моделей для ансамблевого обучения

273

Глава 8. Применение машинного обучения для смыслового анализа

313

Глава 9. Встраивание модели машинного обучения в веб-прило:жение

343

Глава

10. Прогнозирование значений непрерывных целевых
переменных с помощью регрессионного анализа

кластерный анализ

377

Глава

11. Работа с непомеченными данными -

Глава

12. Реализация многослойной искусственной нейронной сети с нуля

455

Глава

13. Распараллеливание процесса обучения нейронных сетей
с помощью TensorFlow

501

Глава

14. Погружаемся глубже -

555

Глава

15. Классификация изображений с помощью глубоких

механика TensorFlow

сверточных нейронных сетей

Глава

419

609

16. Моделирование последовательных данных с использованием
рекуррентных нейронных сетей

Глава

17. Порождающие состязательные сети для синтеза новых данных

Глава

18. Обучение с подкреплением для принятия решений
в сложных средах

Предметный указатель

665
723

781
835

Содержание
Об авторах

17

Предисnовие

20

Начало работы с машинным обучением

20
20
21
21
22
22

Практика и теория

Почему был выбран язык

Python?

Исследование области машинного обучения
Для кого предназначена эта книга?
Что рассматривается в этой книге?

Что необходимо при работе с этой книгой?

Ждем ваших отзывов!

26
26
28
28

Гnава

29

Соглашения
Загрузка кода примеров

1. Надеnение компьютеров способностью обучения на данных

Построение интеллекrуальных машин для трансформирования данных в знания

Резюме

30
30
31
34
36
38
39
41
42
43
44
45
45
46
46
47
48

Гnава

49

Три типа машинного обучения
Выработка прогнозов о будущем с помощью обучения с учителем
Решение интерактивных задач с помощью обучения с подкреплением
Обнаружение скрытых струкrур с помощью обучения без учителя
Введение в основную терминологию и обозначения

Обозначения и соr:лашения, используемые в книге
Терминология, связанная с машинным обучением

Дорожная карта для построения систем машинного обучения
Предварительная обработка -

приведение данных в приемлемую форму

Обучение и выбор прогнозирующей модели
Оценка моделей и прогнозирование на не встречавшихся ранее образцах данных

Использование

Python для машинного обучения
Установка Python и необходимых пакетов
Использование дистрибутива Anaconda и диспетчера пакетов
Пакеты для научных расчетов, науки о данных и машинного обучения

2. Обучение простых аnгоритмов МО дnя кnассификации

Искусственные нейроны

-

беглое знакомство с ранней историей

машинного обучения
Формальное определение искусственного нейрона

Правило обучения персептрона

[6]

50
51
53

Содержание
Реализация алгоритма обучения персептрона на

Python

Объектно-ориентированный АРl-интерфейс персептрона

Обучение модели персептрона на наборе данных

lris

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

Adaline на Python

Улучшение градиентного спуска посредством масштабирования признаков

Крупномасштабное машинное обучение и стохастический градиентный спуск
Резюме

56
56
60
67
68
71
75
78
84

Глава З. Обзор классификаторов на основе машинного обучения
с использованием scikit-learп

85

Выбор алгоритма классификации
Первые шаги в освоении

scikit-leam -

обучение персептрона

Моделирование вероятностей классов посредством логистической регрессии
Понятие логистической регрессии и условные вероятности

Выяснение весов функции издержек для логистической регрессии

Преобразование реализации

Adaline

в алгоритм для логистической регрессии

Обучение логистической регрессионной модели с помощью

scikit-leam

Решение проблемы переобучения с помощью регуляризации
Классификация с максимальным зазором с помощью методов опорных векторов
Понятие максимального зазора

86
87
93
94
98
101
106
109
113
113

Обработка нелинейно сепарабельного случая с использованием

фиктивных переменных
Альтернативные реализации в

scikit-leam

Решение нелинейных задач с применением ядерного метода опорных векторов
Ядерные методы для линейно сепарабельных данных

115
117
118
118

Использование ядерного трюка для нахождения разделяющих
гиперплоскостей в пространстве высокой размерности

Обучение моделей на основе деревьев принятия решений
Доведение до максимума прироста информации

121
124

-

получение наибольшей отдачи

126
131

Построение дерева принятия решений

Объединение множества деревьев принятия решений
с помощью случайных лесов

Метод

k ближайших соседей -

алгоритм ленивого обучения

Резюме

Глава

4.

Построение хороших обучающих наборов

-

предварительная обработка данных
Решение проблемы с недостающими данными
Идентификация недостающих значений в табличных данных

[7]

135
139
143
145
146
146

Содержание
Исключение обучающих образцов или признаков

Резюме

148
149
150
151
152
153
154
155
159
162
165
166
166
169
173
180
183

Глава

185

с недостающими значениями

Условный расчет недостающих значений

Понятие АРI-интерфейса оценщиков

scikit-leam

Обработка категориальных данных
Кодирование категориальных данных с помощью

pandas

Отображение порядковых признаков
Кодирование меток классов
Выполнение унитарного кодирования на именных признаках

Разбиение набора данных на отдельные обучающий и испытательный наборы
Приведение признаков к тому же самому масштабу
Выбор значимых признаков

Регуляризация

L 1 и L2

как штрафы за сложность модели

Геометрическая интерпретация регуляризации
Разреженные решения с регуляризацией

L2

Ll

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

5. Сжатие данных с помощью понижения размерности

Понижение размерности без учителя с помощью анализа главных компонентов
Основные шаги при анализе главных компонентов
Выделение главных компонентов шаг за шагом
Полная и объясненная дисперсия

Трансформация признаков
Анализ главных компонентов в

scikit-Ieam

Сжатие данных с учителем посредством линейного дискриминантного анализа

186
186
188
191
193
197
200

Анализ главных компонентов в сравнении с линейным дискриминантным
анализом

Внутреннее устройство линейного дискриминантного анализа
Вычисление матриц рассеяния

Выбор линейных дискриминантов для нового подпространства признаков
Проецирование образцов в новое подпространство признаков
Реализация

LDA в scikit-Ieam

200
202
203
205
208
209

Использование ядерного анализа главных компонентов для нелинейных
отображающих функций

Ядерные функции и ядерный трюк
Реализация ядерного анализа главных компонентов на языке
Проецирование новых точек данных

Ядерный анализ главных компонентов в

scikit-leam

Резюме

[8]

Python

211
212
217
225
229
231

Содержание

Глава

6. Освоение практического опыта оценки

моделей

и настройки гиперпараметров

233

Модернизация рабочих потоков с помощью конвейеров

Загрузка набора данных

234
234
236

Breast Cancer Wisconsin

Объединение преобразователей и оценщиков в конвейер
Использование перекрестной проверки по

k блокам для оценки

эффективности модели

238
239
240
245

Метод перекрестной проверки с удержанием

Перекрестная проверка по

k блокам

Отладка алгоритмов с помощью кривых обучения и проверки

Диагностирование проблем со смещением и дисперсией
с помощью кривых обучения

Решение проблем недообучения и переобучения с помощью кривых проверки
Точная настройка моделей машинного обучения с помощью решетчатого поиска
Настройка гиперпараметров с помощью решетчатого поиска

Отбор алгоритма с помощью вложенной перекрестной проверки

Использование других метрик оценки эффективности
Чтение матрицы неточностей

Оптимизация точности и полноты классификационной модели
Построение кривой рабочей характеристики приемника
Метрики подсчета для многоклассовой классификации

Решение проблемы с дисбалансом классов
Резюме

Глава

7.

Объединение разных моделей для ансамблевого обучения

246
250
252
253
255
257
258
260
263
266
267
271
273

Обучение с помощью ансамблей

Объединение классификаторов с помощью мажоритарного голосования
Реализация простого классификатора с мажоритарным голосованием

273
278
279

Использование принципа мажоритарного голосования для выработки
прогнозов

Оценка и настройка ансамблевого классификатора

Бэггинг -

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

Коротко о бэггинге

Применение бэггинга для классификации образцов в наборе данных

Wine

285
289
296
297
298

Использование в своих интересах слабых учеников посредством
адаптивного бустинга
Как работает бустинг
Применение алгоритма AdaВoost с помощью
Резюме

[9]

scikit-leam

302
303
308
312

Содержание

Глава 8. Применение машинного обучения дnя смыслового анализа

313

Подготовка данных с рецензиями на фильмы

314
314

IMDb для обработки

текста

Получение набора данных с рецензиями на фильмы
Предварительная обработка набора данных с целью приведения
в более удобный формат
Модель суммирования слов

Трансформирование слов в векторы признаков

Оценка важности слов с помощью приема

tf-idf

Очистка текстовых данных
Переработка документов в лексемы

Обучение логистической регрессионной модели для классификации документов
Работа с более крупными данными

-

315
317
318
320
323
325
328

динамические алгоритмы

и внешнее обучение

Резюме

331
335
336
337
341

Глава

343

Тематическое моделирование с помощью латентного размещения Дирихле

Разбиение текстовых документов с помощью
Реализация

LDA

LDA в библиотеке scikit-learn

9. Встраивание модели машинного обучения в веб-приложение

Сериализация подогнанных оценщиков

scikit-learn
SQLite для хранилища данных
Разработка веб-приложения с помощью Flask
Первое веб-приложение Flask
Настройка базы данных

Проверка достоверности и визуализация форм
Превращение классификатора рецензий на фильмы в веб-приложение
Файлы и подкаталоги

-

дерево каталогов

Реализация главного приложения как арр. ру

Настройка формы для рецензии
Создание шаблона страницы результатов

Развертывание веб-приложения на публичном сервере
Создание учетной записи PythoпAnywhere
Загрузка файлов для приложения классификации рецензий на фильмы

Обновление классифик~пора рецензий на фильмы
Резюме

Глава

1О.

344
348
350
351
353
360
362
363
366
367
370
370
370
372
375

Прогнозирование значений непрерывных целевых
переменных с помощью регрессионного анализа

Ведение в линейную регрессию

377

Исследование набора данных

378
378
379
381

Загрузка набора данных

381

Простая линейная регрессия

Множественная линейная регрессия

Housing
Housing в объект DataFrame

- - - - - - - - - - (10] - - - - - - - - - -

Содержание

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

383
385

Реализация линейной регрессионной модели с использованием обычного
метода наименьших квадратов

Использование градиентного спуска для выяснения параметров регрессии

Оценка коэффициентов регрессионной модели с помощью
Подгонка надежной регрессионной модели с использованием

scikit-learn
RANSAC

Оценка эффективности линейных регрессионных моделей
Использование регуляризированных методов для регрессии
Превращение линейной регрессионной модели в криволинейную

-

полиномиальная регрессия

Добавление полиномиальных членов с использованием
Моделирование нелинейных связей в

scikit-learn
наборе данных Housing

Обработка нелинейных связей с использованием случайных лесов
Регрессия на основе дерева принятия решений
Регрессия на основе случайного леса
Резюме

Глава

11. Работа с непомеченными данными -

кластерный анализ

Группирование объектов по подобию с применением алгоритма

Кластеризация

K-Means

с использованием

388
389
393
396
399
403

K-Means

scikit-learn

405
405
407
410
411
413
417
419
420
420

Более интеллектуальный способ размещения начальных центроидов
кластеров с использованием алгоритма

K-Means++

Жесткая или мягкая кластеризация

426
427

Использование метода локтя для нахождения оптимального количества
кластеров

Количественная оценка качества кластеризации через графики силуэтов

Организация кластеров в виде иерархического дерева
Группирование кластеров в восходящей манере
Выполнение иерархической кластеризации на матрице расстояний

Прикрепление дендрограмм к тепловой карте

430
431
436
437
439
443

Применение агломеративной иерархической кластеризации

с помощью

scikit-learn

Нахождение областей высокой плотности с помощью

DBSCAN

Р~юме

Глава

12. Реализация

445
446
452

многослойной искусственной нейронной сети с нуля

455

Моделирование сложных функций с помощью искусственных нейронных сетей

456
458
461
464
467

Кршкое повторение однослойных нейронных сетей
Введение в архитектуру многослойных нейронных сетей

Активация нейронной сети посредством прямого распространения
Классификация рукописных цифр

[11)------

Содержание

Получение и подготовка набора данных

MNIST

Реализация многослойного персептрона

Обучение искусственной нейронной сети
Вычисление логистической функции издержек

Выработка общего понимания обратного распространения
Обучение нейронных сетей с помощью обратного распространения
О сходимости в нейронных сетях
Несколько слов о реализации нейронных сетей
Резюме

Глава

13.

468
476
488
488
49]
493
498
499
500

Распараллеливание процесса обучения нейронных сетей
с помощью

TensorFlow и

TensorFlow

501

производительность обучения

502
502
504
506
506
506
507
508
509
511

Проблемы, связанные с производительностью

Что такое

TensorFlow?

Как мы будем изучать

TensorFlow

Первые шаги при работе с библиотекой

Установка

TensorF\ow

TensorF\ow
TensorF\ow

Создание тензоров в

Манипулирование типом данных и формой тензора
Применение математических операций к тензорам

Расщепление, укладывание стопкой и объединение тензоров
Построение входных конвейеров с использованием

tf. data Dataset библиотеки TensorF\ow
объекта Dataset из существующих тензоров

АРI-интерфейса

513
514
Объединение двух тензоров в общий набор данных
515
Тасование, создание пакетов и повторение
516
Создание набора данных из файлов на локальном диске
520
Извлечение досrупных наборов данных из библиотеки tensorflow_ datasets 524
Построение нейросетевой модели в TensorF\ow
530
АРI-интерфейс Keras в TensorFlow {tf. keras)
530
Построение линейной регрессионной модели
531
Обучение модели с помощью методов . cornpile () и . fit ()
536
Создание

Построение многослойного персептрона для классификации цветков

в наборе данных

Iris

Оценка обученной модели на испытательном наборе данных
Сохранение и повторная загрузка обученной модели
Выбор функций активации для многослойных нейронных сетей

Краткое повторение логистической функции

538
542
543
544
545

Оценка вероятностей классов в многоклассовой классификации через
многопеременную логистическую функцию
Расширение выходного спектра с использованием гиперболического тангенса

54 7
548

- - - - - - - - - - - - - [12) - - - - - - - - - - - - -

Содержание
Активация на основе выпрямленного линейного элемента

551
553

Резюме

Глава

14. Погружаемся глубже -

механика

TensorFlow

555

Ключевые средства
Вычислительные

TensorFlow
графы TensorFlow:

переход на

TensorFlow v2

Понятие вычислительных графов
Создание графа в
Перенос графа в

TensorFlow v 1.х
TensorFlow v2

Загрузка входных данных в модель: стиль
Загрузка входных данных в модель: стиль

TensorFlow v 1.х
TensorFlow v2

Увеличение вычислительной мощности с помощью декораторов функций
Обьекты VariaЫe библиотеки

TensorFlow для

556
558
558
559
560
561
561
562

хранения и обновления

565

параметров модели

Расчет градиентов посредством автоматического дифференцирования
и

GradientTape
Расчет градиентов потери по отношению к обучаемым переменным
Расчет градиентов по отношению к необучаемым тензорам
Сохранение ресурсов для множества вычислений градиентов

569
570
571
572

Упрощение реализаций распространенных архитеюур
посредством АРI-интерфейса

Keras

Решение задачи классификации

573
577

XOR

Увеличение гибкости построения моделей с помощью

функционального АРI-интерфейса

Keras
Model
Keras

Реализация моделей на основе класса
Реализация специальных слоев
Оценщики

библиотеки

Keras

TensorFlow

Работа со столбцами признаков
Машинное обучение с использованием готовых оценщиков

Использование оценщиков для классификации рукописных цифр МNIST
Создание специального оценщика из существующей модели

Keras

Резюме

Глава

583
584
586
590
591
596
601
604
606

15. Классификация изображений с помощью глубоких
сверточных нейронных сетей

609

Строительные блоки сверточных нейронных сетей

610
611
613
624
626
627
631

Понятие сетей

CNN

и иерархий признаков

Выполнение дискретных сверток
Слои подвыборки
Группирование всего вместе

-

реализация сверточной нейронной сети

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

- - - - - - - - - - [13) - - - - - - - - - -

Содержание

Функции потерь для классификации
Реализация глубокой сверточной нейронной сети с использованием

TensorF\ow

Архитектура многослойной сверточной нейронной сети

Загрузка и предварительная обработка данных

635
638
638
639

Реализация сверточной нейронной сети с использованием

АРI-интерфейса

Keras

библиотеки

641

TensorFlow

Классификация полов по изображениям лиц с использованием сверточной
нейронной сети

Загрузка набора данных Се\еЬА
Трансформация изображений и дополнение данных
Обучение классификатора полов, основанного на сверточной нейронной сети
Резюме

Глава

648
648
649
656
662

16. Моделирование последовательных данных с использованием
рекуррентных нейронных сетей

665
666
666
667
668
670
670
673
676
680
681

Понятие последовательных данных

Моделирование последовательных данных

-

вопросы порядка

Представление последовательностей
Категории моделирования последовательностей
Рекуррентные нейронные сети для моделирования последовательностей
Механизм организации циклов рекуррентной нейронной сети

Вычисление активаций в сети

RNN

Рекуррентность скрытого слоя или рекуррентность выходного слоя
Сложности изучения долгосрочных взаимодействий
Ячейки долгой краткосрочной памяти

Реализация многослойных рекуррентных нейронных сетей для моделирования

последовательностей в
Проект номер один

-

TensorFlow

прогнозирование отношения в рецензиях на фильмы

IMDb

Подготовка данных с рецензиями на фильмы
Слои вложений для кодирования предложений

Построение модели на основе рекуррентной нейронной сети

Проект номер два -

моделирование языка на уровне символов в

TensorF\ow

Понимание языка с помощью модели "Преобразователь"
Механизм самовнимания
Многоголовое внимание и блок "Преобразователь"
Резюме

Глава

17. Порождающие состязательные сети для синтеза новых данных

Понятие порождающих состязательных сетей

Начало работы с автокодировщиками
Порождающие модели для синтеза новых данных

Генерирование новых образцов с помощью порождающих состязательных сетей

684
685
685
691
694
702
716
717
720
722
723
724
725
727
729

- - - - - - - - - - - [14] - - - - - - - - - - - -

Содержание

Функции потерь сетей генератора и дискриминатора в модели

GAN

Реализация порождающей состязательной сети с нуля

Обучение моделей

GAN

в среде

Google Colab

Реализация сетей генератора и дискриминатора
Определение обучающего набора данных
Обучение модели

GAN

731
733
734
737
742
744

Повышение качества синтезированных изображений с использованием
сверточной сети

GAN

и сети

GAN

Вассерштейна

753
753
756
759
766

Транспонированная свертка
Пакетная нормализация

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

состязательных сетей

770
771

Штраф градиента
Реализация порождающей состязательной сети Вассерштейна со штрафом
градиента для обучения модели

DCGAN

Коллапс мод
Другие приложения порождающих состязательных сетей
Резюме

Глава

18. Обучение с подкреплением для принятия

решений

в сложных средах

Введение

-

772
777
778
780
781

обучение на опыте

Резюме по главе и по книге

782
782
785
786
787
787
791
796
797
798
801
804
808
808
818
823
832

Предметный указатель

835

Понятие обучения с подкреплением
Определение интерфейса "агент-среда" системы обучения с подкреплением
Теоретические основы обучения с подкреплением
Марковские процессы принятия решений
Математическая формулировка марковских процессов принятия решений

Терминология обучения с подкреплением: агдача, политика и функция ценности
Динамическое программирование с использованием уравнения Беллмана

Алгоритмы обучения с подкреплением
Динамическое программирование

Обучение с подкреплением с помощью метода Монте-Карло
Обучение методом временных разностей

Реализация первого алгоритма обучения с подкреплением
Введение в комплект инструментов

OpenAI Gym

Решение задачи с миром сетки с помощью Q-обучения
Обзор глубокого Q-обучения

- - - - - - - - - - - [15] - - - - - - - - - - -

Об авторах
Себастьян Рашка получил докторскую степень в Университете штата

Мичиган, где занимался разработкой методов на стыке вычислительной био­
логии и машинного обучения. Летом

2018

года он получил должность стар­

шего преподавателя статистики в Висконсинском университете в Мэдисоне.

Его исследовательская деятельность включает разработку новых архитектур

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

Python

и проводил многочисленные семинары по практическому применению на­

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

Python.
его книга "Python Machine Leaming",
Среди достижений Себастьяна которая стала бестселлером в Packt и Amazon.com. Книга получила награду
АСМ Computing Reviews' Best of 2016 и была переведена на многие языки,
включая немецкий, корейский, китайский, японский, русский, польский и
итальянский.

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

состязаниях по машинному обучению, таких как

в

Kaggle.

Я хотел бы воспользоваться этой возможностью, чтобы поблагода­
рить замечательное сообщество

Python

и разработчиков пакетов с

открытым кодом, которые помогли мне создать идеальную среду для

научных исследований и науки о данных. Также я хочу поблагода­
рить своих родителей, всегда поощряющих и поддерживающих меня

в выборе пути и занятия, в которое я страстно влюблен.

Выражаю особую благодарность основным разработчикам

learn

и

TensorFlow.

scikit-

Как участник этого проекта и пользователь, я по­

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

обучения, но также являются великолепными программистами.

- - - - - - - - - - - - - [16] - - - - - - - - - - - - -

Вахид Мирджалили получил степень

PhD

по машиностроению, рабо­

тая над новаторскими методами для крупномасштабных вычислительных
эмуляций молекулярных структур в Университете штата Мичиган. Будучи

увлеченным областью машинного обучения, он был принят в лабораторию

iPRoBe

Университета штата Мичиган, где занимался применением машин­

ного обучения в областях компьютерного зрения и биометрии. После не­
скольких продуктивных лет, проведенных в лаборатории

iPRoBe,

и многих

лет нахождения в академических кругах Вахид недавно стал научным со­

трудником в ЗМ

Company,

где может использовать свой опыт и применять

современные методики машинного и глубокого обучения для решения ре­
альных задач в разнообразных приложениях, направленных на улучшение
нашей жизни.
Я хотел бы поблагодарить свою жену Табан Эслами, оказавшую мне
большую поддержку и поощрявшую мой карьерный путь. Кроме
того, выражаю особую благодарность моим руководителям Николаю
Приезжеву, Майклу Фейrу и Аруну Россу за поддержку меня во вре­

мя обучения в аспирантуре, а также моим профессорам Вишну Бод­
дети, Лесли Куну и Сяомину Лю, которые научили меня настолько
многому и вдохновили двигаться своим путем.

- - - - - - - - - - [17) - - - - - - - - - ·

О технических рецензентах
Рагхав Бали

-

старший научный сотрудник в одной из крупнейших в

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

должности в компании

lntel

он принимал участие в реализации упреждаю­

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

American Express,

решая задачи цифрового привлечения и

удержания клиентов.

Рагхав также является автором нескольких книг, опубликованных ведущи­
ми издательствами, самая недавняя из которых посвящена последним дости­

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

Мотаз Саад получил степень

PhD

по компьютерным наукам в Универ­

ситете Лотарингии. Он любит данные и ему нравится с ними играть.

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

IUG.

----------(18] -----------

ПРЕДИСЛОВИЕ

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

как

Google, Facebook, Apple, Amazon

и IВМ, вполне обоснованно делают

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

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

помощником в наших смартфонах, рекомендации правильного товара на­
шим заказчикам, предотвращении мошенничества с кредитными картами,

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

-

список можно продолжать и продолжать.

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

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

Практика и теория
Ознакомление с практическими примерами кода и проработка образцов
приложений

-

замечательный способ погрузиться в эту сферу. Вдобавок

конкретные примеры помогают проиллюстрировать более широкие концеп-

- - - - - - - - - - - [19)

Предисловие

ции, применяя изложенный материал сразу на практике. Тем не менее, пом­

ните о том, что большая мощь предполагает и большую ответственность!

Кроме предложения практического опыта работы с машинным обучени­
ем, используя язык программирования

и библиотеки на

Python

Python

для

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

данная книга не является чисто практической; это книга, в которой обсуж­
даются необходимые детали, касающиеся концепций машинного обучения,
а также предлагаются интуитивно понятные и вместе с тем информативные
объяснения того, как работают алгоритмы машинного обучения, как их ис­

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

Почему быn выбран язык

Python?

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

он мощный, но очень доступный.

Python

Python?

Ответ прост:

стал наиболее популярным языком

программирования для науки о данных, потому что позволяет нам забыть о

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

Исследование обnасти маwинноrо обучения
Если вы введете поисковый термин

"machine leaming" ("машинное обу­
Google Scholar, то она возвратит ошеломляюще огромное
публикаций 4 890 ООО (по состоянию на июнь 2020 года -

чение") в системе
количество

Примеч.пер.). Конечно, обсудить особенности и детали всех алгоритмов
и приложений, появившихся за прошедшие

60

лет, попросту нереально.

Однако в книге мы совершим захватывающее путешествие, которое охватит

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

выдающимися достижениями в этой сфере.
Мы как авторы искренне заявляем, что исследование машинного обу­
чения сделало нас лучшими учеными,

мыслителями

- - - - - - - - - - (20) --

и решателями задач.

Предисловие

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

терство владения навыками может быть достигнуто только практикой.

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

возможностью и сконцентрируетесь на вознаграждении. Не забывайте, что

мы путешествуем вместе, и повсюду в книге мы будем добавлять в ваш ар­
сенал многие мощные приемы, которые помогут решить даже самые труд­

ные задачи в управляемой данными манере.

Дnя коrо предназначена эта книrа?
Если вы уже хорошо знаете теорию машинного обучения, тогда книга
покажет, как применить имеющиеся знания на практике. Если вы использо­
вали приемы машинного обучения ранее и хотите лучше понять, как факти­

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

-

полный новичок в области машинного обу­

чения; у вас есть даже еще больше поводов для заинтересованности! Можно
надеяться, что машинное обучение изменит ваше представление о задачах,

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

Python,

чтобы начать

отвечать на критически важные вопросы об имеющихся данных, тогда при­
ступайте к чтению этой книги. Начинаете вы с нуля или расширяете свои
познания в области науки о данных, настоящая книга будет неоLЬемлемым
и незаменимым ресурсом.

Что рассматривается в этой книrе?
В главе

1, "Наделение компьютеров способностью обучения на данных",

мы предложим введение в основные подобласти машинного обучения для

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

который будет направлять нас через последующие главы.
В главе

2,

"Обучение простых алгоритмов МО для классификации",

мы обратимся к истокам машинного обучения, представив классификаторы
на основе двоичного персептрона и адаптивных линейных нейронов. Глава

является кратким введением в фундаментальные основы классификации об-

" [21]

Предисловие

разцов и сосредоточена на взаимодействии алгоритмов оптимизации и ма­

шинного обучения.
В главе

3,

"Обзор классификаторов на основе машинного обучения

с использованием

scikit-learn",

описаны важные алгоритмы машинного

обучения, предназначенные для классификации, и приведены практические
примеры применения одной из самых популярных и всеобъемлющих библи­

отек машинного обучения с открытым кодом
В главе

4,

-

scikit-leam.

"Построение хороших обучающих наборов

-

предвари­

тельная обработка данных", показано, как иметь дело с распространен­
ными проблемами в необработанных наборах данных, такими как недо­

стающие данные. В ней обсуждаются подходы к идентификации наиболее
информативных признаков в наборах данных, а также объясняется, каким

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

В главе

5,

"Сжатие данных с помощью понижения размерности",

описаны важные приемы сокращения количества признаков в наборе дан­

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

линейной трансформации с учителем.
В главе

6,

"Освоение практического опыта оценки моделей и на­

стройки гиперпараметров", обсуждаются правила для оценки эффектив­
ности прогнозирующих моделей. Кроме того, в ней описаны различные

метрики для измерения эффективности моделей и методики для точной на­
стройки алгоритмов машинного обучения.
В главе

7, "Объединение

разных моделей для ансамблевого обучения",

представлены концепции рационального объединения нескольких алгорит­

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

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

----------(22) -----------

Предисловие

В главе 9, "Встраивание модели машинного обучения в веб-приложение",
продолжается работа с прогнозирующей моделью из предыдущей главы, а
также демонстрируются важные шаги разработки веб-приложений со встро­

енными моделями машинного обучения.
В главе

10,

"Прогнозирование значений непрерывных целевых пере­

менных с помощью регрессионного анализа", описаны приемы модели­

рования линейных взаимосвязей между целевыми и объясняющими пере­
менными для прогнозирования значений с непрерывным масштабом. После

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

внимание

11, "Работа с непомеченными данными- кластерный анализ",
переключается на другую подобласть машинного обучения -

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

12,

"Реализация многослойной искусственной нейронной

сети с нуля", расширяется представленная в главе

2

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

концепция оптимиза­

Python

строить мощ­

ные многослойные нейронные сети на базе популярного алгоритма обратно­
го распространения.

В главе

13, "Распараллеливание процесса обучения нейронных сетей
TensorFlow", опираясь на материал предыдущей главы, предо­

с помощью

ставляется практическое руководство по более эффективному обучению ней­
ронных сетей. Основное внимание в главе сосредоточено на

библиотеке

Python

TensorFlow 2.0 -

с открытым кодом, которая позволяет задействовать мно­

жество ядер современных графических процессоров и конструировать глу­

бокие нейронные сети из распространенных строительных блоков посредс­
твом дружественного к пользователю АРI-интерфейса

В главе

14,

"Погружаемся глубже

-

механика

Keras.
TensorFlow",

изложение

продолжается с места, где оно было оставлено впредыдущей главе, и пред­

ставляются более сложные концепции и функциональность

Библиотека

TensorFlow

TensorFlow 2.0.

чрезвычайно обширна и развита, и в главе рассмат­

риваются такие концепции, как компилирование кода в статический граф для

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

Предисловие

чения глубоких нейронных сетей с использованием АРI-интерфейса
библиотеки
В главе

TensorFlow, а также готовые оценщики TensorFlow.
15, "Классификация изображений с помощью

Keras

глубоких

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

глубоких нейронных сетей, которая особенно хорошо подходит для наборов

данных с изображениями. Благодаря превосходной эффективности в сравне­
нии с традиционными подходами сверточные нейронные сети теперь широ­

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

16,

"Моделирование последовательных данных с использо­

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

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

В главе

17,

"Порождающие состязательные сети для синтеза новых

данных", представлен популярный режим состязательного обучения для

нейронных сетей, который можно применять, чтобы генерировать новые
реалистично выглядящие изображения. Глава начинается с краткого введе­
ния в автокодировщики

-

отдельный тип архитектуры нейронных сетей,

которую можно использовать для сжатия данных. Затем в главе будет по­

казано, как объединить часть декодировщика со второй нейронной сетью,
которая способна проводить различие между настоящими и синтезирован­
ными изображениями. Позволив двум нейронным сетям соревноваться друг
с другом при состязательном обучении, вы реализуете порождающую со­
стязательную сеть, которая генерирует новые изображения с рукописными

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

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

- - - - - - - - - - (24] - - ·

Предисловие

В главе

18,

"Обучение с подкреплением дли принятии решений в

сложных средах", раскрывается подкатегория машинного обучения, кото­

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

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

-

на

основе модели и без модели. После того, как вы узнаете о базовых алгорит­
мических подходах, таких как метод Монте-Карло и метод временных раз­

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

является разновидностью Q-обучения, использующей глубокие нейронные
сети.

Что необходимо при работе с этой книrой?
Выполнение примеров кода, приводимых в книге, требует установ­

ки

Python 3.7.0 или более новой версии на машине с macOS, Linux или
Microsoft Windows. В книге будут повсеместно применяться важные биб­
лиотеки Python для научных расчетов, такие как SciPy, NumPy, scikit-learn,
Matplotlib и pandas.
В главе 1 будут представлены инструкции и полезные советы по настрой­
ке среды Python и указанных основных библиотек. К имеющейся совокуп­
ности мы добавим дополнительные библиотеки, предоставляя инструкции
по установке в соответствующих главах, например, библиотеку

обработки естественного языка (глава
библиотеку

TensorFlow для

8),

веб-фреймворк

Flask

NLTK

(глава

для

9)

и

эффективного обучения нейронных сетей на гра­

фических процессорах (главы

13-18).

Соrnаwения
Для представления различных видов информации в книге используется
несколько стилей текста. Ниже приведен ряд примеров таких стилей с объ­
яснениями, что они означают.

· - - - - - - - - - - [25]

Предисловие

Фрагменты кода в тексте представляются в следующем виде: "Уже уста­
новленные пакеты можно обновить, указав флаг

--upgrade".

Вот как представляется блок кода:

>>> import matplotlib.pyplot as plt
>>> import nU111py as np
>>>у= df.iloc[O:lOO, 4].values
>>>y=np.where(y== 'Iris-setosa', -1, 1)
>>> Х = df.iloc[O:lOO, [О, 2]] .values
>>> plt.scatter(X[:SO, О], X[:SO, 1],
color='red', marker='x', label='setosa')
>>> plt.scatter(X[50:100, 0], Х[50:100, 1],
color='Ьlue', marker='o', label='versicolor')
>>> pl t. xlaЬel ( 'sepal length' )
>>> pl t. ylabel ( 'petal length' )
>>> plt.legend(loc='upper left')
»> plt. show ()
Любой ввод или вывод в командной строке записывается так:

> dot

-Тpng

tree. dot



tree. png

Новые термины и важн1'1е слова выделяются курсивом. Слова, которые

отображаются на экране, например, в меню или диалоговых окнах, выде­
ляются следующим образом: "Щелкните на пункте

Change runtime type
(Изменить тип исполняющей среды) в меню Runtime (Исполняющая сре­
да) тетради и выберите в раскрывающемся списке Hardware accelerator
(Аппаратный ускоритель) вариант GPU (ГП)".

нf;l. Здесь приводятся предостережения и важные примечания.
заметку!

-~-

Здесь даются советы и трюки.

Совет

- - - - - - - - - - [26) - - - - - - - - - -

Предисловие

Заrрузка кода примеров
Исходный код всех примеров, рассмотренных в книге, досrупен для за­
грузки по ссылке

h t tps: / / g i thub. com/ rasЬt /python-machinelearning-book-Зrd-edi tion. После загрузки распакуйте файл с помо­

щью последней версии архиваторов:

• WinRAR/7-Zip

для

• Zipeg / iZip / UnRarX
• 7-Zip / PeaZip

для

Windows;
для Мае;

Linux.

Ждем ваших отзывов!
Вы, читатель этой книги, и есть главный ее критик. Мы ценим ваше мне­

ние и хотим знать, что было сделано нами правильно, что можно было сде­

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

нам электронное письмо либо просто посетить наш веб-сайт и оставить
свои замечания там. Одним словом, любым удобным для вас способом дай­
те нам знать, нравится ли вам эта книга, а также выскажите свое мнение о

том, как сделать наши книги более интересными для вас.

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

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

Наши электронные адреса:

E-mail:

info. dialektika@gmail.com

WWW:

http://www. dialektika. com
Все иллюстрации к книге в цветном варианте доступны по адресу

http://go.dialektika.com/pythorunl

---[27]

1
НАДЕЛЕНИЕ КОМПЬЮТЕРОВ
СПОСОБНОСТЬЮ

ОБУЧЕНИЯ НА ДАННЫХ

нашему мнению, машинное обучение (МО) как практическое при­
п- оменение
и наука об алгоритмах, которым понятен смысл данных, яв­
ляется самой захватывающей областью компьютерных наук! Мы живем в
эпоху изобилия данных; используя самообучающиеся алгоритмы из области
МО, мы можем превратить эти данные в знания. Благодаря многочислен­

ным мощным библиотекам с открытым кодом, которые были разработаны в
последние годы, пожалуй, никогда еще не было лучшего времени, чтобы за­

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

фундамент для успешного применения приемов МО при решении практи­
ческих задач.

В главе будут раскрыты следующие темы:



общие понятия МО;



три типа обучения и основная терминология;



строительные блоки для успешного проектирования систем МО;



установка и настройка

Python

для анализа данных и МО.

Глава

1.

Наделение компьютеров способностью обучения на данных

Построение интеллектуальных машин
для трансформирования данных в знания
При нынешней зрелости современных технологий есть один ресурс, ко­
торого у нас вдоволь: значительный объем структурированных и неструкту­

рированных данных. Во второй половине двадцатого века МО развивалось
как подобласть искусственного интеллекта (ИИ), вовлекающая самообуча­
ющие алгоритмы, которые выводили знания из данных с целью вырабаты­
вания прогнозов. Вместо того чтобы требовать от людей выводить правила
вручную и строить модели путем анализа крупных объемов данных, МО
предлагает более эффективную альтернативу для сбора знаний в данных,
которая постепенно улучшает эффективность прогнозирующих моделей и
принимает решения, управляемые данными.

Важность МО возрастает не только в исследованиях, имеющих отношение
к компьютерным наукам; его роль в нашей повседневной жизни становится

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

и перспективными программами игры в шахма­

ты. Надо надеяться, что скоро мы добавим к этому перечню безопасные
и эффективные беспилотные автомобили. Кроме того, заметный прогресс

был достигнут в медицинских приложениях; например, исследователи про­
демонстрировали, что модели глубокого обучения способны обнаруживать
рак кожи с почти человеческой точностью

(https: / /www. nature.

articles/nature21056) . Еще одна веха была недавно
дователями из DeepMind, которые использовали глубокое

сот/

достигнута иссле­

обучение для про­

гнозирования третичных структур белков, впервые превзойдя физические
подходы

(https: / / deepmind. com/Ьlog / alphafold/).

Три типа машинного обучения
В этом разделе мы взглянем на три типа МО: обучение с учителем

(supen·isetl learning),

обучение без учителя

с подкреплением (reiн{orceme11t

leaming).

(1msuperi•ise(/ [eaming)

и обучение

Мы объясним фундаментальные

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

областей, где они могут быть применены (рис.

(30]

1.1 ).

[лава 1. Наделение компьютеров способностью обучения на данных

Обучение с учителем

Обучение без учителя

>

Помеченные данные

)

Непосредственная обратная связь

>

Прогнозирование исхода/будущего

>

Метки отсутствуют

> Обратная связь отсутствует
> Поиск скрытой структуры в данных

> Процесс принятия решений

Обучение с подкреплением

) Система вознаграждения
> Изучение последовательности действий

Рис.

1.1.

Типы обу'lенuя и предметные области, в которых они могут
при.wеняться

Выработка проrнозов о будущем с помощью обучения с учителем
Главная цель обучения с учителем

-

обучить модель на помеченных обу­

чающих данных, что позволит вырабатывать прогнозы на не встречавшихся

ранее или будущих данных. Здесь понятие "с учителем" относится к набо­
ру обучающих образцов (входных данных), где желаемые выходные сигналы
(метки) уже известны. На рис.

1.2

представлена типичная последовательность

действий при обучении с учителем, где помеченные обучающие данные пере­
даются алгоритму МО для подгонки к прогнозирующей модели, которая мо­

жет вырабатывать прогнозы на новых непомеченных входных данных.
Рассматривая в качестве примера фильтрацию почтового спама, мы мо­

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

корректно маркированы

как спам

или не спам, и

прогнозировать, к

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

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

fлава 1. Наделение компьютеров способностью обучения на данных

Метки

Обучающие данные

'

Алгоритм МО

1

Прогнозирующая

Новые данные

Рис.

1.2.

Прогноз

модель

Типичная последовательность действий при обучении с учителел1

Кnассификация дnя проrнозирования меток кnассов
Классификация

-

это подкатегория обучения с учителем, где целью бу­

дет прогнозирование категориальных меток классов, к которым принадле­

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

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

не­

спамными почтовыми сообщениями.
На рис.

1.3

иллюстрируется концепция задачи двоичной классификации

с имеющимися

30

обучающими образцами;

15

обучающих образцов поме­

чены как отрицательный класс (знаком "минус") и

15

обучающих образцов

помечены как положительный класс (знаком "плюс"). При таком сценарии

наш набор данных оказывается двумерным, т.е. с каждым образцом ассоци­
ированы два значения: х 1 и х2 • Далее мы можем воспользоваться алгоритмом

МО с учителем, чтобы узнать правило (границу решений, представленную в
виде пунктирной линии), которое способно отделять эти два класса и отно­
сить новые данные к каждой из двух категорий, имея их значения х 1 и х 2 •

- - - - - - - - - - - - - - [32] -

fпава 1. Наделение компьютеров способностью обучения на данных

е
е

х2

е
е

е
е

е

е

е

е

ее
е
Х1

Рис.

1.3.

Концепция задачи двоичной классификации

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

Характерным примером задачи многоклассовой классификации считается
распознавание рукописных символов. Мы можем подготовить обучающий
набор данных, содержащий множество рукописных примеров для каждой
буквы алфавита. Буквы (А, В, С и т.д.) будут представлять различные неупо­
рядоченные категории или метки классов, которые мы хотим прогнозировать.

Если теперь пользователь с помощью устройства ввода предоставит новый

рукописный символ, тогда наша прогнозирующая модель будет в состоянии
с определенной точностью предсказать корректную букву алфавита для вве­

денного рукописного символа. Тем не менее, наша система МО не будет
способна правильно распознавать, например, цифры между О и

9,

если они

не входили в состав обучающего набора данных.

Реrрессия для проrнозирования непрерывных результатов
В предыдущем разделе мы выяснили, что задача классификации сводит­
ся к назначению образцам категориальных неупорядоченных меток. Вторым

типом обучения с учителем является прогнозирование непрерывных резуль­
татов, которое также называется регрессионным анализом. При регрессион­
ном анализе мы имеем несколько (поясняющих) переменных прогнозаторов

- - - - - - - - - - [33] - - - - - -

[лава

1.

Наделение компьютеров способностью обучения на данных

и переменную непрерывного отклика (исход или результат) и пытаемся
отыскать между указанными переменными взаимосвязь, которая позволила

бы прогнозировать результат.
Обратите внимание, что в области МО переменные прогнозаторов обыч­
но называют "признаками"

(.f'eat111"e),

а на переменные откликов в боль­

шинстве случаев ссылаются как на "целевые переменные" (tщ-get

i•1niable).

Мы будем придерживаться таких соглашений на протяжении всей книги.
нас

пусть

Скажем,

интересует

прогнозирование

мых студентами в результате прохождения теста

ru.wikipedia.org/wik i/SAT).

оценок,

получае­

SAT Math (https: / /

Если существует какая-то взаимосвязь

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

прогнозирования будущих оценок студентов, планирующих пройти тест.

\\:~ Регрессия к среднему

н~ Термин "регрессия" был введен Фрэнсисом Гальтоном в написанной
заметку!

им статье

"Regression towards Mediocrity in Hereditary Stature"

(Ре-

грессия к заурядности при наследовании роста), опубликованной в

1886

году. Гальтон описал биологическое явление, которое заключа­

лось в том, что изменчивость роста среди популяции совершенно не
увеличивается с течением времени.

Он обнаружил, что рост родителей не передается их детям, а взамен
рост детей движется назад к среднему по популяции.

На рис.

1.4

демонстрируется концепция линейной регрессии. Для пере­

менной признака х и целевой переменной у мы подгоняем прямую линию

к этим данным, чтобы свести к минимуму расстояние
квадратическое

-

-

обычно средне­

между точками данных и подогнанной линией. Теперь

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

Реwение интерактивных задач с помощью обучения с подкрепnением
Третьим типом МО, который мы рассмотрим здесь, будет обучение с

подкреплением (рис.

1.5).

Целью такого обучения является разработка сис­

темы (агента), которая улучшает свои характеристики на основе взаимо­
действий со средой.

----------(34] - - - - - - - - - -

[лава 1. Наделение компьютеров способностью обучения на данных

у

х

Рис.

1.4.

Концепция линейной

pe,"peccuu

Поскольку информация о текущем состоянии среды обычно включает так
называемый сигнал награды, мы можем трактовать обучение с подкрепле­

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

дят до максимума награду, применяя исследовательский метод проб и оши­

бок или совещательное планирование.

Среда

Награда

Состояние
Действие

Агент

Рис.

1.5.

Концепция обучения с подкрепл ение.м

- - [35)

[лава

1.

Наделение компьютеров способностью обучения на данных

Популярный пример обучения с подкреплением

-

шахматный движок.

Агент выбирает последовательность ходов в зависимости от состояния до­
ски (среды), а награда может быть определена как "выигрыш" или "проиг­
рыш" в конце игры.

Различают много подтипов обучения с подкреплением. Тем не менее, об­
щепринятая схема обучения с подкреплением заключается в том, что агент
пытается

довести до

максимума награду

через последовательность

взаи­

модействий со средой. С каждым состоянием можно ассоциировать поло­

жительную или отрицательную награду, причем награда может быть опре­

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

Чтобы продолжить пример с шахматами, давайте будем считать, что по­
сещение определенных позиций шахматной доски связано с состояниями,

которые более вероятно приведут к выигрышу

-

скажем, взятие шахматной

фигуры противника или угроза ферзю. Однако посещение других позиций
связано с состояниями, которые более вероятно приведут к проигрышу, та­
кими как сдача фигуры противнику на следующем ходе. Награда в шахмат­
ной игре (положительная при выигрыше и отрицательная при проигрыше)
не будет предоставляться вплоть до конца партии. Вдобавок финальная на­
града также будет зависеть от того, как играет противник. Например, про­
тивник может принести в жертву ферзя, но все равно выиграть партию.

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

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

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

при обучении без учителя мы имеем дело с непомеченными данными или

данными с неизвестной структурой. Использование приемов обучения без
учителя дает нам возможность исследовать структуру данных для извлече­

ния значимой информации без управления со стороны известной целевой
переменной или функции награды.

- - - - - - - - - - - - - [36] - - - - - - - - - - - - - - - -

Глава

1.

Наделение· компьютеров способностью обучения на данных

Нахождение подrрупп с помощью кпастеризации
Кластеризация

-

это исследовательская методика анализа данных, ко­

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

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

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

их интересов для разработки индивидуальных маркетинговых программ.
На рис.

1.6

показано, как можно применить кластеризацию с целью ор­

ганизации непомеченных данных в три отдельных группы, основываясь на
сходстве их признаков х 1 и х2 •

-

" "оо о',


/

о

0

о \

О О Оо 1

\ Оооо
'

О

О/

/

'--/

Х1

Рис.

1.6.

Пример испш1ьзова1шя кластеризации

Понижение размерности дпя сжатия данных
Понижение размерности (tfi11н•11 ..;ionalily ге1/ш:!iо11 ) -

еще одна подоб­

ласть обучения без учителя. Зачастую мы работаем с данными высокой раз­
мерности (каждое наблюдение сопровождается большим количеством измере­
ний), которые могут представлять проблему для ограниченного пространства
хранения и вычислительной мощности алгоритмов МО. Понижение размер-

---- [ 371 ---·

Глава

1.

Наделение компьютеров способностью обучения на даннЬ1х

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

приводить к ухудшению прогнозирующей эффективности определенных ал­
горитмов, и сжатия данных в подпространство меньшей размерности с со­

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

зации данных; скажем, набор признаков высокой размерности может быть
спроецирован в одно-, двух- или трехмерные пространства признаков с це­

лью их визуализации через двумерные или трехмерные графики рассеяния
либо гистограммы. На рис.

1.7

приведен пример применения нелинейного

понижения размерности для сжатия трехмерного швейцарского рулета (ЗD

Swiss Roll)

Рис.

в новое двумерное подпространство признаков.

/. 7.

При.:wер при.ме11ения 11ели11ей1ю,,о по11иж·е11ия paз.uep11ocmu

Введение в основную терминологию и обозначения
После обсуждения трех обширных категорий МО
теля и с подкреплением

-

-

с учителем, без учи­

давайте бегло взглянем на основную термино­

логию, которая будет использоваться повсеместно в книге. В следующем

подразделе раскрываются распространенные термины, которые мы будем
использовать при ссылке на различные аспекты набора данных, а также ма­

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

[38)

Глава

1.

Наделение компьютеров способностью обучения на данных

минами, которые относятся к тем же самым концепциям, так что пусть это

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

послужить справочником при чтении вами более несходной литературы по
теме МО.

Обозначения и соrnаwения, испоnьзуемые в книrе
На рис. 1.8 изображена таблица с выборками из набора данных об ирисах
(lris), который является классическим примером в области МО. Набор дан­
ных Iris содержит результаты измерений 150 цветков ириса трех видов ирис щетинистый (iris setosa), ирис разноцветный (iris versico\or) и ирис
виргинский (iris virginica). Каждый образец цветка представляет одну строку
в нашем наборе данных, а измерения цветка в сантиметрах хранятся в виде
столбцов, которые также называются признакшwи (lcatш·e) набора данных.

li

Выборки

(обра~цы, "'бл~дения:

1

5.1

3.5

1.4

0.2

2

4.9

3.0

1.4

0.2

50

6.4

3.5

4.5

1.2

Setosa

Versicolor
( ирис
разн о цвет

3.0

5.0

1.8

Чашелистик

/

Метки классов
(цели )

Признаки

(атрибуты , измерения , размерности)

Рис.

- - -- --

-

1.8.

Выборки из набора данных

-

-

-

(39]

Iris

fлава

1. Наделение компьютеров способностью обучения на данных

Чтобы сохранить систему обозначений и реализацию простыми, мы вос­

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

обособленном столбце.

Iris, состоящий из 150 образцов
ков, можно записать в виде матрицы 150 х 4, Хе т.,15 ох 4 :
Тогда набор данных

(150)
Х4

(150)
Х3

(150)
Х2

(150)
Х1

и четырех призна­

\\:~ Соглашение об обозначениях

н~ В оставшейся части книги, если только не указано иначе, мы будем
заметку!

использовать надстрочный индекс

образец и подстрочный индекс

j

i

для ссылки на i-тый обучающий

для ссылки на j-тое измерение обу­

чающего набора данных.

Мы будем применять строчные буквы с курсивным полужирным на­

чертанием для ссылки на векторы (х Е m.nxl) и прописные буквы с
полужирным начертанием для ссылки на матрицы (ХЕ m.nxm). Чтобы
ссылаться на одиночные элементы в векторе или матрице, мы будем

использовать буквы с курсивным начертанием (х(п) или х~) соответ­
ственно).

Например, х~ 50 ссылается на первое измерение образца цветка 150,
т.е. на длину чашелистика. Таким образом, каждая строка в этой
матрице

признаков

представляет один экземпляр

цветка

и

быть записана в виде четырехмерного вектора-строки, x=О.О,

1, -1)

Используя такую реализацию персептрона, мы можем инициализиро­

Perceptron с заданной скоростью обучения eta и
количеством эпох n _ i ter (проходов по обучающему набору).
Посредством метода fi t мы инициализируем веса в self. w вектором

вать новые объекты

~m+I, где т обозначает количество измерений (признаков) в наборе данных,
[58)

Глава

к которому добавляется

l

2.

Обучение простых алгоритмов МО для классификации

для первого элемента в этом векторе. Не забывай­

те, что первый элемент вектора,

self. w_

[О], представляет так называемый

член смещения, который упоминался ранее.

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

0.01 с помощью метода rgen. norrnal ( loc=O. О, scale=O. 01, size=l +
Х. shape [ 1] ) , где rgen генератор случайных чисел NumPy, который
снабжается указанным пользователем начальным значением, позволяя при
желании воспроизводить предыдущие результаты.

Важно помнить о том, что мы не инициализируем веса нулями, потому

что скорость обучения Т/

( eta)

влияет на результат классификации, только

если веса инициализированы ненулевыми значениями. Когда все веса ини­
циализированы нулями, параметр скорости обучения

eta

влияет только на

масштаб весового вектора, но не на его направление. Если вы знакомы с
тригонометрией, тогда подумайте о векторе
углом между ним и вектором

v2 = 0.5

х

v],

vl = [l 2 3]

с точно нулевым

как демонстрируется в следую­

щем фрагменте кода:

>>> vl = np.array( (1, 2, 3])
>>> v2 = 0.5 * vl
>>> np.arccos(vl.dot(v2) / (np.linalg.norm(vl) *
np.linalg.norm(v2)))
о.о

Здесь

norrn -

np. arccos -

тригонометрический арккосинус, а

np. linalg.

функция, вычисляющая длину вектора (наше решение извлекать

случайные числа из нормального распределения, а не, например, равномер­

ного, и выбор стандартного отклонения О.

01

было чисто произвольным; не

забывайте, что нас всего лишь интересуют небольшие случайные значения,

чтобы не иметь дела с особенностями векторов, содержащих все нули, как
обсуждалось ранее.)

r,
~ сации списков

Индексация одномерных массивов в

NumPy

работает подобно индек­

Python, использующих систему обозначений в виде
~:метку! квадратных скобок ( [ ] ). Для двумерных массивов первый индексатор
ссьтается на номер строки, а второй - на номер столбца. Например,
для выбора элемента, находящегося в третьей строке и четвертом
столбце двумерного массива Х, мы будем применять Х [ 2 , 3] .

· - - - - - - - - (59) - - - - - - - - - -

Глава

2.

Обучение прост111х алгоритмов МО для классификации

После инициализации весов метод

fi t

проходит в цикле по всем инди­

видуальным образцам в обучающем наборе и обновляет веса согласно пра­

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

методе f

predict,

который вызывается в

классов

i t на стадии обучения, чтобы получить метку класса для обновления
predict может также использоваться для прогнозирования меток
новых данных после подгонки модели. Кроме того, в списке self.

errors

мы накапливаем число неправильных классификаций в течение каж­

весов, но

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

np. dot, применяемая в
методе net input, просто вычисляет скалярное произведение векторов wтх.
\\/

Вместо использования

NumPy

для вычисления скалярного произ-

1~ ведения векторов с двумя массивами а и Ь через а. dot (Ь) или

~:метку! np.dot (а, Ь) мы могли бы выполнить вычисление на чистом
Python посредством sum ( [ i * j for i, j in zip (а, Ь) ] . Однако
преимущество библиотеки NumPy перед классическими структурами
циклов for языка Python заключается в том, что ее арифметические
операции векторизованы. Векторизация означает, что элементарная

арифметическая операция автоматически применяется ко всем эле­

ментам в массиве. За счет формулирования наших арифметических
операций как последовательности инструкций на массиве вместо

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

S/MD

(Siнgle lt1strщ·tim1,

iHultiple [)ata -

оди­

ночный поток команд, множественный поток данных). Более того, в

NumPy

применяются высокооптимизированные библиотеки линей­

ной алгебры, такие как

BLAS (Basic U11ear

Лlge/1ra

S11'1f'ro.l(rams LAPACK (Uщ•1Jr ЛlgеЬт
написанные на С или Fortran.

базовые подпрограммы линейной алгебры) и

Package Наконец,

пакет линейной алгебры),

NumPy

также делает возможным написание кода в более

компактной и понятной манере с использованием основ линейной

алгебры, таких как скалярные произведения векторов и матриц.

Обучение модеnи персептрона на наборе данных

lris

При тестировании нашей реализации персептрона мы сузим последую­

щий анализ и примеры в главе до двух переменных признаков (измерений).
Хотя правило персептрона не ограничивается двумя измерениями, учет

Глава

2.

Обучение простых алгоритмов МО для классификации

только двух признаков, длины чашелистика

(petal length),

(sepal length)

и длины лепестка

позволит визуализировать области решений обученной модели

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

lris -

Setosa

и

Versicolor,

из набора данных

вспомните о том, что персептрон является двоичным классификато­

ром. Тем не менее, алгоритм персептрона можно расширить для выполнения
многоклассовой классификации, скажем, по методике "один против всех"
(1111e - 1·ei-.щs - all - - О1·Л).

Г.~ Методика

OvA для

мноrоклассовой классификации

н~ Методика "один против всех" (OvA), также иногда называемая "один
заметку!

против остальиых" (>> import os
>>> :i.mport pandas as pd
>>> s = os.path.join(' https : //archive . ics . uci . edu ', ' ml ',

' machine - learning- databases ',
' i r i s ' , ' i r i s . da t а ' )
>>> print ( ' URL : ', s)
URL: https://archive.ics.uci.edu/ml/machine-learning-databases/
iris/iris.data

Глава

2.

Обучение простых алгоритмов МО для классификации

>>> d f = pd.read_csv (s ,
h eader=None ,
e n coding=' ut f - 8 ' )

»> d f . t a il ()
о

1

2

3

4

145 6.7 3.0 5.2 2.3 lris-virginica
146 6.3 2.5 5.0 1.9 lris-virginica
147 6.5 3.0 5.2 2.0 lris-virginica
148 6.2 3.4 5.4 2.3 lris-virginica
149 5.9 3.0 5.1 1.8 lris-virginica
г;~ Загрузка набора данных lris

н~ Копия набора данных Iris (и всех других наборов данных, используе­
заметку!

мых в книге) включена в состав загружаемого архива с кодом приме-

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

ht t рs : / /

archive . ics.uci.edu/rnl/rnachine-learning-databases/
iris/ iris . data на сервере UCI. Скажем, чтобы загрузить набор
данных lris из какого-то локального каталога, следующий оператор :
df = pd.read_csv (
' https : //archive . ics . uci . edu/ml/ '
' machi п e - 1ea r·niпg-databases / iris / iг.i s
header= '
, encoding=' utf - 8 ' )

. с!а t: a

',

понадобится заменить таким оператором:

df = pd. read_ csv (
' ваш/л о кальный/путь/к/ iris

header= ·,

. cJata ',
·., encoding=' utf - 8 ')

Затем мы извлекаем первые

меток классов, которые соответству­

100

ют 50 цветкам Iris-setosa (ирис разноцветный) и 50 цветкам Irisversicolor (ирис щетинистый) . Далее мы преобразуем метки классов в
две целочисленные метки классов

1

(разноцветный) и

торые присваиваем вектору у; метод
выдает надлежащее представление

- - - - -- - --

·-

-

-

values
NumPy.

объекта

-1

(щетинистый), ко­

DataFrarne

[62) - -- - - -

из

pandas

[лава

2.

Обучение простых алгоритмов МО для классификации

Аналогичным образом мы извлекаем из

100

обучающих образцов первый

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

>>> import ma t pl o tlib. p yplot as pl t
>>> import n ump y as np
>>> #

выбрать ирис щетинистый и ирис разноцветный

>>> у=
>>> у=

>>>
>>>

df.iloc[O : lOO , 4 ] . value s
np . where(y == ' Iris - setosa ', - 1, 1)

#извлечь длину чашелистика и длину лепестка
Х

[О,

= df. iloc[O : lOO ,

2 ] ] .value s

>>> # вычертить трафик для данных
>>> plt . scatter(X[ : SO, О ], X [: SO, 1 ] ,
color= ' red ', marker=' о ', lab el= ' щетини с тый ' )
>>> plt.scatter(X[50 :1 00 , О ], X[SO :l OO , 1] ,
co l or = ' Ьlu e ' , ma r ker=' x ', lаЬеl= ' разноцветный '
>>> рlt.хlаЬеl( ' длина чашелистика [см] ' )
>>> р l t . уlаЬеl( ' длина леп е стка [см] ' )
>>> plt . l egend ( loc= ' upper left ' )
>» p l t . s how ()

)

В результате выполнения приведенного примера кода должен отобразить­
ся график рассеяния (рис.

5

2.5).

Г. щетинистый!



х

раэноцветны йj

х
ХхХХ

)()(

х

ХХХ~



~

х

х

)(х

ХХ

х
х
ХхХ
х
х

хх

х
х
хх
х

хх

ХХ

х
хх
х

• •
1

• ···==·1•··=·

4.5

5.0

5.5

••


6.0

длина чашелистика [см]

Рис.

- - --

2.5.

Графш: рассеяния

- - -- - [63)

6.5

7.0

[лава

2.

Обучение простых алгоритмов МО для классификации

График рассеяния на рис.
ков в наборе данных

Iris

2.5

показывает распределение образцов цвет­

вдоль двух осей признаков

-

с длиной лепестка

и с длиной чашелистика (измеренных в сантиметрах). В этом двумерном
подпространстве признаков мы можем видеть, что линейной границы реше­

ний должно быть достаточно для отделения цветков ириса щетинистого от

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

ченном поднаборе данных

Iris.

Мы также построим график ошибки непра­

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

>>> ppn = Perceptron(eta=O.l, n_iter=lO)
>>> ppn.fit( X, у)
>>> plt.plot( range (l, len(ppn.errors ) + 1),

ppn.errors_, marker=' o ')
>>> plt. x label(' Эпoxи ')
>>> pl t. ylabel ( ' Количество
>>> plt. show ()

обновле ний '

)

После выполнения предыдущего кода мы должны получить график ошиб­
ки неправильной классификации относительно количества эпох (рис.

/-\

3.0

•:S:

:s:

2.5

z:

Q)

i::::
ID

2.0

о

/

z:


о

1.5

о

....ID
(.)
ф

1.0



:s:

\

i::::
о

:i.i:

2.6).

0.5
о.о

2

\----~-б

4

8

10

Эпохи

Рис.

2.6.

График ошибки неправuлыюй классификации

- --

- - - (64) - - -- - - - - - - ---

- - --- -

Глава

Как видно на рис.

2.6,

2.

Обучение простых алгоритмов МО для классификации

наш персептрон сходится после шестой эпохи и

теперь должен обладать возможностью в полной мере классифицировать

обучающие образцы. Давайте реализуем небольшую удобную функцию, ви­
зуализирующую границы решений для двумерных наборов данных:

from matplotlib.colors import ListedColormap
def plot_decision_regions(X, у, classifier, resolution=0.02):
# на строить генерат ор маркер ов и кар ту цветов
markers = ( ' s '' ' х '' , о '' '4'' 4 ')
colors = (' red ', ' Ьlue ', ' lightgreen ', ' gray ', ' c yan ')
стар= ListedColormap(colors[:len(np.unique(y))])
# вывести поверхность решения
xl_min, xl_max = Х[:, 0] .min() - 1, Х[ :, 0] .max() + 1
x2_min, x2_max = Х[:, 1] .min() - 1, Х[:, 1] .max() + 1
xxl, хх2 = np.meshgrid(np.arange(xl_min, xl_max, resolution),
np.arange(x2_min, x2_max, resolution))
Z = classifier.predict(np.array([xxl.ravel(), xx2.ravel()]) .Т)
Z = Z.reshape(xxl.shape)
plt.contourf(xxl, хх2, Z, alpha=0.3, cmap=cmap)
plt.xlim(xxl.min(), xxl.max())
plt.ylim(xx2.min(), xx2.max())
# вывести образцы по классам
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, О],
у=Х[у == cl, 1),
alpha=0.8,
c=colors [idx],
marker=markers[idx],
label=cl,
edgecolor='Ьlack')

Сначала мы определяем цвета
посредством

ListedColormap

минимальные и

(colors)

и маркеры

(markers),

после чего

создаем карту цветов . Затем мы определяем

максимальные значения для двух признаков и

применяем

эти векторы признаков для создания пары сеточных массивов хх 1 и хх2

через функцию

meshgrid

из

NumPy.

Поскольку мы обучали классификатор

на основе персептрона на двух размерностях признаков, нам необходимо
выпрямить сеточные

массивы

и создать матрицу,

количество столбцов, как у обучающего поднабора
применять метод

predict

которая

lris,

имеет такое же

чтобы можно было

для прогнозирования меток классов

z соответ­

ствующих позиций сеток.

-

-

- - -- - - - --

(65)

- - - -- - -·--- - - - - - -

Глава

2. Обучение простых алгоритмов МО для классификации

После изменения формы прогнозируемых меток классов

xxl и
contourf

Z

на сеточный

массив с такими же размерностями , как у

хх2, мы можем вывести

контурный график с помощью функции

из библиотеки

Matplotlib,

которая сопоставляет разные области решений с разными цветами для каж­
дого прогнозируемого класса в сеточном массиве:

>>>
>>>
>>>
>>>
>>>

plot_decis i on_regi ons(X , у , clas s ifi er=ppn)
pl t . xlabe l ( ' длина чашелистика [см] ' )
p l t . ylabel ( ' длина лепестка [см] ' )
plt . legend (l oc=' upper left ')
p lt . show ()

В результате выполнения предыдущего примера кодадолжен отобразить­
ся график с областями решений (рис.
На рис.

2.7

2.7).

видно, что персептрон узнал границу решений, которая спо­

собна идеально классифицировать все образцы цветков в обучающем под­

наборе

lris.

(66)--

-

Глава

2.

Обучение простых алгоритмов МО для классификации

Г.~ Сходимость nерсеnтронов

н~ Хотя персеnтрон прекрасно классифицирует два класса цветков ири­
заметку!

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

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

перестанут обновляться, если только мы не установим максималь­
ное количество эпох.

Адаптивные nинейные нейроны

и сходимость обучения
В этом разделе мы рассмотрим еще один тип однослойной нейронной
сети:

Adaline (AПЛptii·1.: Uucar NI.:1mm - адаптивный линейный нейрон).
Статья об Adaline была опубликована Бернардом Видроу и его докторан­
том Теддом Хоффом через несколько лет после публикации Фрэнком
Розенблаттом статьи об алгоритме персептрона и может считаться усовер­
шенствованием последнего

("An Adaptive 'Adaline' Neuron Using Chemical
'Memistors'" (Адаптивный нейрон 'Adaline', использующий химические
'мемисторы'), Technical Report Number 1553-2, Б. Видроу и др., Stanford
Electron Labs, Stanford, СА (октябрь 1960 года)).
Алгоритм Adaline особенно интересен тем, что он иллюстрирует клю­

чевые концепции определения и минимизации непрерывных функций из­

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

Ключевое отличие правила Adaline (также известного под названием правило

Видроу-Хоффа) от правила персептрона Розенблатта связано с тем, что веса
обновляются на основе линейной функции активации, а не единичной ступен­

чатой функции, как в персептроне. В

Adaline линейная

функция активации ф(z)

является просто тождественным отображением общего входа, так что:

ф(wтх)= wтх
Наряду с тем, что для выяснения весов применяется линейная функция
активации, для финального прогноза мы по-прежнему используем порого-

- - · - - - - · - - [67) - - - -

Глава

2.

Обучение простых алгоритмов МО для классификации

вую функцию, которая похожа на раскрытую ранее единичную ступенчатую

функцию.
Главные отличия между персептроном и алгоритмом
на рис.

Adaline

выделены

2.8.

Выход

Пороговая

функция

активации
входа

Адаптивный линейный нейрон

Рис.

2.8.

На рис.

(Adaline)

Главные отличия .нежду персептртюм и алюритмол1

2.8

видно, что алгоритм

Adaline

Adaline

сравнивает настоящие метки

классов с непрерывным выходом значений линейной функции активации,
вычисляя ошибку модели и обновляя веса. Напротив, персептрон сравнива­
ет настоящие и спрогнозированные метки классов.

Минимизация функций издержек с помощью rрадиентноrо спуска
Одним из ключевых составных частей алгоритмов МО с учителем явля­

ется определенная целевая функция

(ol>jcclii·eJ1111clio11),

которая должна быть

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

Adaline

J, для изучения
епоr:< --- SSI:) между

мы можем определить функцию издержек,

весов как сум.ну квадратuч11ых оиtuбок

(s11111 of .щua1"c1i

вычисленным результатом и настоящей меткой класса:

·----- (68) -----

Глава

2.

Обучение простых алгоритмов МО для классификации

J( w) =~ L;(/;) _Ф( z(;))) 2
Член ~ добавлен просто ради удобства и, как будет показано ниже, он
облегчит выведение градиента функции издержек или потерь относительно
весовых параметров. Основное преимущество непрерывной линейной фун­
кции активации по сравнению с единичной ступенчатой функцией заклю­

чается в том, что функция издержек становится дифференцируемой. Еще
одна приятная отличительная черта этой функции издержек

она выпук­

-

лая; таким образом, мы можем применять простой, но мощный алгоритм
оптимизации под названием градuе11тный спуск (~1·mlie111

clesco11)

для на­

хождения весов, которые минимизируют функцию издержек при классифи­

кации образцов в наборе данных

lris.

На рис .

2.9

продемонстрировано, что

мы можем описать главную идею , лежащую в основе градиентного спуска,

как скатывание с возвышенности до тех пор, пока не будет достигнут ло­

кальный или глобальный минимум издержек.

Начальный вес

/----------- Градиент

~/

~

JI

/,~
1

Глобальный минимум издержек

::_---

Jm;n(w)

w
Рис.

2.9.

Г.~ав11ая идея ,~радuетпиою спуска

На каждой итерации мы делаем шаг в направлении, противоположном

градиенту, где размер шага определяется величиной скорости обучения, а
также наклоном градиента.

С использованием градиентного спуска мы можем обновлять веса путем

совершения шага в направлении, противоположном градиенту
функции издержек

J(w):

w := w + Лw
- --

-

- · - - - ---·--· (69) -------- --- - -

V7 J(w)

нашей

Глава

2.

Обучение простых алгоритмов МО для классификации

Изменение весов Лw определяется как отрицательный градиент, умно­

женный на скорость обучения 1}:

Лw

= -l}VJ(w)

Чтобы рассчитать градиент функции издержек, нам понадобится вычис­

лить частную производную функции издержек по каждому весу

Следовательно, теперь мы можем записать обновление веса

wi

W/

Так как мы обновляем все веса одновременно, наше правило обучения

Adaline

приобретает вид:

w := w +Лw
Г'.~ Производная квадратичной ошибки

н~ Для тех, кто знаком с дифференциальным исчислением, вот как можно
заметку!

получить частную производную функции издержек

SSE

по j-тому весу:

=_!_~ L(Y(;) _Ф(z(i))) 2 =
2 tМ;

j

=
=_!_2 I2(/)-Ф(z(i)))~(/)-Ф(z(i)))
дw
1

./

= L(Y(i) -Ф( z(i)) )~(y(i) - L( ИJ4))J =
дwj

,

,

=I(i)-Ф(z(i)))(-xY')=
j

= - L(Y(i) -Ф( z(i)) )х);)
1

-- (70] - - - - - - - - - - - - · - - -

Глава

2.

Обучение простых алгоритмов МО для классификации

Несмотря на то что правило обучения

Adaline выглядит идентичным пра­
вилу персептрона, нужно отметить, что ф(z(i)) с z(i 1 = wтx(il - это веществен­
ное число, а не целочисленная метка класса. Кроме того, обновление весов

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

называют пакетны.и градиентным спуском (Ьаtс/1 gнulic11t

Реаnизация

на

Adaline

(/csce11t).

Python

Поскольку правило персептрона и

Adaline

очень похожи, мы возьмем ре­

ализацию персептрона, которую определили ранее , и модифицируем метод

fit,

чтобы веса обновлялись за счет минимизации функции издержек пос­

редством градиентного спуска :

class Adaline GD (obJect ) :
"""Классификатор на основе адаптивного линейного нейрона .
Параметры

eta : float
Скорость обучения
п

(между О . О и

1 . 0)

iter : int
Проходы по обуча101Цему набору данных .

random state : int
Начальное значение генератора случайных чисел
для инициализации случайными весами .
Атрибуты

w

:

одномерный массив

Веса после подгонки .

cost

:

список

Значение функции издержек на основе суммы квадратов

в каждой эпохе .

"",,
def

i n it

(se t , eta =0 ._ 01, n_i ter=5 0 , random_ s tate=l) :

s.,, i_ L. e ta = eta
S(.lt: .n iter = n iter
~i:; l t' . random state = random state

def f i t( :..,el·' ,

Х,

у) :

"""Подгоняет к обучающим данным .

- --

- --

(71)

Глава

2.

Обучение простых алгоритмов МО для классификации
Параметры

Х

:

{подобен массиву}

Обучающие векторы ,

у

1

форма =

где

[n_examples , n_features]
n_examples - количество образцов ,

n_features - количество признаков .
: подобен массиву , форма = [n_examples]
Целевые значения .

Возвращает

self : object

"""
rgen = np.random.RandomState( (·11 .random_state)
"":::.1 ,· .w_ = rgen.normal(loc=O.O, scale=0.01, size=l + X.shape[l])
- Р ~ t . cost
= []
for i in range ( -, l .n_iter):
net_input = ~' 1 : .net_input(X)
output = _c11 .activation(net_input)
errors = (у - output)
'. L 1· . w_ [ 1: ] +=
t . eta * Х. Т. dot ( errors)
.s..:lt . w_[OJ += . -, J_f . eta * errors. sum()
cost = (errors**2) . sum () / 2.0
~:Jt .cost .append(cost)
return

"?

l

Г

def net_input (

•:!

Х):

1 _,

""" Вычисляет общий вход """

return np.dot(X, -., 11 .w_[l:]) + . , J .w_[O]
def activation ( ,-

J

t,

Х):

""" Вычисляет линейную активацию """

return

Х

def predict

( :о>>

def cost_l (z):

>>>

def cost_O (z):

retпrn

- np. log (sigmoid ( z))

- np. log ( 1 - sigmoid ( z) )
z = np.arange(-10, 10, 0.1)
phi_z = sigmoid(z)
cl = [cost 1 (х) fox- х in z]
plt.plot(phi z, cl, label='J(w), если y=l')
сО = [cost_O (х) for:: х in z]
plt.plot(phi_z, сО, linestyle='--', label='J(w),
plt.ylim(O.O, 5.1)
pl t . xlim ( [О, 1] )
plt. xlabel ( '$\phi$ ( z) ')
plt. ylabel ( 'J (w) ')
plt.legend(loc='best')
plt.tight_layout()
plt. show ()
retнrn

>>>
>>>
>>>
>>>
>>>
>>>

»>
» >


>>>
>>>
>>>

На результирующем графике (рис.
ная активация в диапазоне от О до

значения

z

находятся в диапазоне от

l

3.4)

если у=О')

по оси х изображена сигмоидаль­

(входы сигмоидальной функции, где

-1 О

до

l О),

а по оси у

-

ассоциирован­

ные логистические издержки.

Легко заметить, что издержки приближаются к О (сплошная линия), если
мы корректно прогнозируем, что образец относится к классу

1.

Аналогично

по оси у видно, что издержки также приближаются к О, когда мы корректно
прогнозируем у= О (пунктирная линия). Однако если прогноз неправильный,

тогда издержки стремятся к бесконечности. Суть в том, что мы штрафуем за

неправильные прогнозы все более и более высокими издержками.

--------------------- [100] -- -

fлаваЗ. Обзор классификаторов на основе машинного обучения с использованием

5

,

1
1
1

- J(w), ecлиy=l

-

scikit-learn

J(w), если у=О 1

1

'

4

1
1
1
1

1
1
1
1

3

,
,,

1

2

~.,,..,,.""'

__ ... ,-

1

0.2

,

-- --

0.6

0.4

", "

1

0.8

1.0

ф(z)

Рис.

3.4. Издерж·кu, связанные с классификацией однократно
выбранного образца для раз11ьL'< шаче11ий ф(z)

Преобразование реаnизации
дnя nоrистической реrрессии

Adaline

в аnrоритм

Если бы мы реализовывали логистическую регрессию самостоятельно,

то могли бы заменить функцию издержек

J

в реализации

Adaline

из главы

2

новой функцией издержек:

J ( w) = -

I

y(i)

log ( ф ( z(i))) + ( 1- у(;)) log ( 1- ф ( z(i)))

1

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

ходимо поменять линейную функцию активации на сигмоидальную функ­
цию активации и изменить пороговую функцию с целью возвращения меток
классов О и

Adaline,

1

вместо

-1

и

1.

Внеся указанные изменения в код реализации

мы получим работающую реализацию логистической регрессии:

c l.a.ss LoqisticRegressionGD (obji=c t ):
"""Классификатор на основе логистиче ской реrрес сии,

ИСПОЛЬЭ}'IО!Щ1Й rрадиентный спуск.
Параметры

eta : float
Скорость обучения

(между О. О и

- - - -·- · ( 1011

1. 0 ).

~------- ------------·---------

Глава 3. Обзор классификаторов на основе машинного обучения с использованием
п

scikit-learn

iter : int
Проходы по обучающему набору данных.

random state : int
Начальное значение генератора случайных чисел
для инициализации случайными весами.
Атрибуты

одномерный массив

:

w

Веса после подгонки.

: list

cost

Значение логистической функции издержек в каждой эпохе.

"""
def

, eta=0.05, n_iter=lOO, random_state=l):
eta = eta
: .n iter = n- iter
random state
~.:.,• U. random state
init

,,._, 1 f.

def fit(

,

х,

у)

:

"""Подгоняет к обучающим данным.
Параметры

Х

:

{подобен массиву},

форма =

[n_examples, n_features]
- количество образцов,

Обучающие векторы, где п_ examples
п_
у

:

fea tures -

количество признаков.

подобен массиву,

форма =

[n_examples]

Целевые значения.
Возвращает

self : object

"""
rgen = np.random.RandomState(:.: i:.random_state)
>· : • w_ = rgen. normal ( loc=O. О, scale=O. О 1,
size=l + Х. shape (1))
"': '· ~' .cost = [)
'.n_iter):
for i in range (
net_input = '···' ·: .net_input (Х)
1. activation (net_input)
output
(у - output)
error-s
· . ' . w [ 1: ] += '>: ~ : • eta * Х. т. dot (errors)
• r .w_[OJ += ·е: .eta * errors.sum()
1

[102] ~-----

Глава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-learn

# обратите внимание, что теперь мы вычисляем
# логистические 'издержки', а не издержки в виде
# суммы квадратичных ошибок
cost = (-y.dot(np.log(output)) ( (1 - у) .dot(np.log(l - output))))
зJ;.cost_.append(cost)

return
def

-~•':

,_ !'

net_input(э,,l.~:,

Х):

"""Вычисляет общий вход"""

return np.dot(X,

~"'Jf.w_[l:])

+ sel.f.w [0]

def activation (.'i·.:l!, z):
"""Вычисляет логистическую сигмоидальную активацию"""

return 1. / (1. + np.exp(-np.clip(z, -250, 250)))
def predict (

i.

т,

Х)

:

"""Возвращает метку класса после единичного шага"""

ret.u.rn np.where(
t1:.net_input(X) >=О.О, 1, 0)
# эквивалентно:
# return np.where(self.activation(self.net_input(X))
#
>= 0.5, 1, 0)
При подгонке логистической регрессионной модели мы должны иметь в

виду, что она работает только для задач двоичной классификации.
Итак, давайте примем во внимание только цветки

Iris-versicolor

(классы О и

1)

I ris-setosa

и

и проверим, работает ли наша реализа­

ция логистической регрессии:

>>> X_train_Ol_subset = X_train[ (y_train == 0)
(y_train == 1)]
>>> y_train_Ol_subset = y_train[ (y_train == 0)
(y_train == 1)]
>>> lrgd = LogisticRegressionGD(eta=0.05,
n_iter=lOOO,
random_state=l)
>>> lrgd.fit(X_train_Ol_subset,
y_train_Ol_subset)
>>> plot_decision_regions(X=X_train_Ol_subset,
y=y_train_Ol_subset,
classifier=lrgd)
>>> plt .xlabel ('длина лепестка [стандартизированная]')
>>> plt.ylabel ('ширина лепестка [стандартизированная]')
>>> plt.legend(loc='upper left')
»> plt. tight_layout ()
>>> plt. show ()
Результирующий график области решений показан на рис.

[103]-----

3.5.

Глава 3. Обзор классификаторов на основе машинного обучения с использованием

~
1'О

:z:
:z:

о

2.5

111

а.

s
s~

1

х

1'О

о

scikit-learn

2.0

1'1
а.

1.5

1'О

ct

:z:

1'О
~

1.0

~
1'О

"

~

0.5

11



()
ф

с:

ф

с:

о.о

1'О

:z:

s -0.5
а.
s
3

о

1

з

2

б

5

4

длина лепестка [стандартизированная]

Рис.

3.5.

Результат подгонки ло,~истическ·ой регрессионной модели

Г'.~ Алгоритм обучен~я на основе градиентного спуска
~
На

заметку!

для логистическои регрессии

Исnользуя математику, мы можем nоказать, что обновление весов логистической регрессии через градиентный сnуск делается идентично

уравнению, которое мы nрименяли в главе

2.

Тем не менее, обратите

внимание, что демонстрируемое ниже выведение nравила обучения
на основе градиентного сnуска nредназначено для читателей, которые
интересуются

математическими

концеnциями, лежащими

в

основе

этого nравила для логистической регрессии . Оно не является сущест­
венно важным для nонимания оставшихся материалов главы.

Начнем с вычисления частной nроизводной функции логарифмичес­
кого nравдоnодобия относительно }-того веса:

1
( у--(1-у)
д
-l(w)=
Ф(z)

дw1

д

1 ) -Ф(z)
1-ф(z) дw1

Прежде чем nродолжить, давайте вычислим также и частную nроиз­
водную сигмоидальной функции:

_i_ф(z)=_i__l_=
дz

дz l+e-z

1

(l+e-z)

2

е-==-1-(1--1-)=
l+e-z

l+e-z

=Ф(z)(l-ф(z))
- - -----·---·--- -- - -

( 1 0 4 ) - - --

- - - - - - --

Глава 3. Обзор классификаторов на основе машинного обучения с использованием scikit-learn

д

Теперь мы можем подставить -ф(z)=Ф(z)(1-ф(z)) в наше пердz

вое уравнение, получив следующее:

( у ф (1z)

1

д

)

-( l - у) 1- ф ( z) дwj ф ( z) =

1
= ( у--(1-у)
Ф(z)

1
1-ф(z)

) Ф(z)(1-ф(z))-z
д =
дwj

=(y(l-ф(z))-(1-y)ф(z))xj =
=(y-ф(z))xj
Вспомним, что цель заключается в том, чтобы отыскать веса, которые

доводят до максимума логарифмическое правдоподобие, поэтому мы
выполняем обновление для каждого веса, как показано далее:

wj

:= wj + 71

I(i) -Ф( z(i)) )х~;)
1~1

Поскольку мы обновляем все веса одновременно, то можем записать
общее правило обновления такого вида:

w :=

w+Лw,

Мы определяем Лw следующим образом:

Лw

= 17\ll(w)

Так как доведение до максимума логарифмического правдоподобия
эквивалентно минимизации определенной ранее функции издержек

J,

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

w := w + Лw,

Лw

= -17\lJ(w)

Данное правило равносильно правилу градиентного спуска для

Adaline

в главе

2.

----------------(105)

Глава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-learn

Обучение nоrистической реrрессионной модеnи с помощью

scikit-learn

В предыдущем подразделе мы проделали полезные упражнения по коди­
рованию и математическим

выкладкам, которые помогли

вать концептуальные отличия между

Adaline

проиллюстриро­

и логистической регрессией.

А теперь давайте выясним, как использовать более оптимизированную реа­
лизацию логистической регрессии из библиотеки

scikit-\earn,

которая также

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

последних версиях

scikit-\earn

методика, применяемая для многоклассовой

классификации (полиномиальная логистическая регрессия или

OvR),

вы­

бирается автоматически. В следующем примере кода мы будем применять
класс

sklearn. linear_ model. LogisticRegression, а также знакомый
метод fi t для обучения модели на всех трех классах в стандартизирован­
ном обучающем наборе Iris. Кроме того, в целях иллюстрации мы устанав­
ливаем mul ti _ class=' ovr'. В качестве упражнения для самостоятельной
проработки можете сравнить результаты с теми, которые получаются при
установке

multi_class='multinomial'. Имейте в виду, что на практике
настройка mul tinomial обычно рекомендуется для взаимно исключающих
классов вроде находящихся в наборе данных Iris. Здесь "взаимно исключаю­
щие" означает, что каждый обучающий образец может принадлежать только

одному классу (в противоположность многозначиой (multi/aЬel) классифика­

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

>>> from sklearn.linear_model import LogisticRegression
>>> lr = LogisticRegression(C=lOO.O, random_state=l,

solver=' lЬfgs', multi_class=' ovr')
>>> lr.fit(X_train_std, y_train)
>>>

plot_decision_regions(X_comЬined_std,
y_comЬined,

classifier=lr,
test_idx=range(105, 150))
>>> plt .xlabel ('длина лепестка [стандартизированная] ')
>>> рlt.уlаЬеl('ширина лепестка [стандартизированная]')
>>> plt.legend(loc='upper left')
>>> plt.tight_layout()

»> plt.show() После подгонки модели к обучающим данным мы вычерчиваем области
решений, обучающие образцы и испытательные образцы (рис.

[106)-------

3.6).

[лава 3. Обзор классификаторов на основе машинного обучения с использованием

';'

"'
"'ID
х

х

2

о

о

х

1

о

2

о

Q.

:s:
м
:s:
....



~~о

ислытательный набор

1

Q.

"'

~

)(,

х

"'....

о

ф



-1

с

ф
с;

"'
:s:
х

Q.

-2

:s:
3

о

@@<

~

"'u:..:....

scikit-learn

-2

-1

о

2

1

длина лепестка [стандартизированная]

Рис.

3.6.

Результат подгонки модели посредствтw

scikit-/earn

\\:~ Обратите внимание, что существует мноrо различных алrоритмов оп1~ тимизации для решения оптимизационных задач. Чтобы свести к минина

1аметку!

муму выпуклую

ф

v

ункцию потерь, такую как потери логистическои рег-

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

деле в

scikit-leam

(SGD).

На самом

реализован целый ряд таких алrоритмов оптимиза­

ции, которые можно указывать посредством параметра

ности,

sol ver, в част­
'newton-cg ', 'lЬfgs ', 'liЫinear ', 'sag' и 'saga'.

Наряду с тем, что функция потерь логистической регрессии выпук­

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

Например, в текущей версии

(0.21)

библиотеки

scikit-leam

по умол­

чанию используется решатель 'liЫinear', который не способен
обрабатывать полиномиальные потери и ограничен схемой

OvR

для

многоклассовой классификации. Тем не менее, в будущих версиях

scikit-leam

(т.е.

0.22)

стандартный решатель изменится на 'lЬfgs

',

который означает алгоритм Бройдена-Флетчера-Гольдфарба­
Шанио с ограииченной памятыо (limit eti - m e mмy Bп1ytie11-- f'/etc/1ei-·­
C olt~(aт/J - Sfщmн1

--- нн;s; ht tps: / / ru. wi kipedia. org /wi ki /

Алгоритм_Бройдена_- _Флетчера_- _Гольдфарба_- _Шанно) и в

этом отношении он более гибкий. Чтобы принять такой новый стан­
дартный вариант, повсюду в книге в случае применения логистичес­
кой регрессии мы будем явно указывать

[107) -

-

sol ver=' lЬfgs '.

-

-

- -- --- -

fлава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-/earn

Глядя на приведенный выше код, который мы использовали для обучения мо­
дели

LogisticRegression,

вас может заинтересовать, что это за таинствен­

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

predict _proba.

Например, вот как спрогнозировать вероятности для первых трех образцов

в испытательном наборе:

>>> lr.predict_proba(X_test

std[:З,

:] )

В результате возвращается следующий массив:

array([[3.81527885e-09,

8.55207131е-01],

1.44792866е-01,

[8.34020679е-01,

1.65979321е-01,

3.25737138е-13],

[8.48831425е-01,

1.51168575е-01,

2.62277619е-14]])

Первая строка соответствует вероятностям членства в классах первого
цветка, вторая строка

-

вероятностям членства в классах второго цветка и т.д.

Обратите внимание, что значения столбцов в строке в сумме дают едини­
цу, как и ожидалось. (Проверить сказанное можно, выполнив

lr. predict

: ] ) .swn(axis=l).)

proba(X_test_std[:З,

Самое высокое значение в первой строке составляет приблизительно

0.85,

т.е. первый образец принадлежит третьему классу

со спрогнозированной вероятностью

85%.

(Iris-virginica)

Следовательно, как вы уже навер­

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

тифицируя в каждой строке столбец с наибольшим значением, например, с
использованием функции

argmax

>>> lr.predict_proba(X_test

из

NumPy:

std[:З,

:]) .argmax(axis=l)

Ниже показаны возвращенные индексы классов (они соответствуют

Iris-virginica, Iris-setosa
array([2,

О,

и

Iris-setosa):

0])

В предыдущем примере кода мы вычисляли условные вероятности и

вручную преобразовывали их в метки классов с помощью функции

из

NumPy.

argmax

Более удобный способ получения меток классов при работе с

библиотекой

scikit-leam

предусматривает прямой вызов метода

>>> lr.predict(X_test
array([2, О, О])

std[:З,

predict:

:] )

----------(108]

-------·-------·

Глава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-learn

И в заключение одно предостережение на случай, если желательно про­

гнозировать метку класса для одиночного образца цветка: библиотека

leam

scikit-

ожидает получить в качестве входных данных двумерный массив; таким

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

reshape

из

NumPy,

добавив новое измерение:

>>> lr.predict(X_test std[O, :] .reshape(l, -1))
array ( [2])

Решение пробnемы переобучения с помощью реrуnяризации
Переобучение

распространенная проблема в МО, когда модель хорошо

-

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

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

ных. Подобным же образом модель может также страдать от недообучения

(111ufc1:{itti11g), или высокого смещения, которое означает, что модель недо­
статочно сложна для того, чтобы хорошо выявлять структуру в обучающих
данных, из-за чего она обладает низкой эффективностью на не встречав­
шихся ранее данных.

Хотя до сих пор мы встречались только с линейными моделями для клас­

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

Xz

Xz

\

\

d,

+

Xz
01
.--+

( ++
(+ +

+
о 'i+ +
+ ', +...

а

о

Недообучение
(высокое
смещение)

3. 7.

о.

о:

о+~,+

Рис.

~

о ,ч-

о\++

о

~

О/



Х1

3.7.


о,,,'+

------о--

' ...

Хороший
компромисс

Х1

Переобучение
(высокая
дисперсия)

Х1

Иллюстрация проблем переобучения и недообу•1ения

--------------(109)-------------

[лава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-learn

Га~ Компромисс между смещением и дисперсией

н~ При описании эффективности модели исследователи часто исполь­
заметку! зуют термины "смещение" и "дисперсия" или "компромисс между
смещением и дисперсией"

-

т.е. в беседах, книгах или статьях вы

можете встретить заявления о том, что модель имеет "высокую дис­

персию" или "высокое смещение". Что же это означает? В целом мы
можем говорить, что "высокая дисперсия" пропорциональна пере­
обучению, а "высокое смещение"

-

недообучению.

В контексте моделей МО дисперсия измеряет постоянство (либо из­
менчивость) прогноза модели для классификации отдельного образца

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

в целом при многократном обучении модели на разных обучающих

наборах данных; смещение представляет собой меру систематической
ошибки, которая не является результатом случайности.
Если вас интересует техническое описание и происхождение терми­

нов "смещение" и "дисперсия", тогда обратитесь к конспекту лек­

https: / / sebastianraschka. com/pdf/ lecture-notes /
stat479fsl8/08_eval-intro_notes.pdf.

ций:

Один из способов отыскания хорошего компромисса между смещением
и дисперсией предусматривает настройку сложности модели посредством

регуляризации. Регуляризация

-

очень полезный метод для обработки кол­

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

Концепция, положенная в основу регуляризации, заключается в том, что­
бы вводить дополнительную информацию (смещение) для штрафования экс­
тремальных значений параметров (весов). Самой распространенной формой
регуляризации является так называемая регуляризация
зываемая сокращением

L2

= Aiw~
Allwll
2 J~I
2
2

-

(иногда также на­

или ослаблением весов), которую можно записать

следующим образом:

Здесь А

L2

параметр регуляризации.

-----------(110].

fлава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-learn

\\:~ Реrуnяризация и нормаnизация признаков

н~ Регуляризация заметку!

еще одна причина важности масштабирования

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

Функцию издержек для логистической регрессии можно регуляризиро­

вать, добавив простой член регуляризации, который будет сокращать веса
во время обучения модели:

J ( w) = ~ [-у(;) log ( ф { z(i)) )-( 1- y(i)) log ( 1- ф { z(i))) + ~

J llwll

2

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

веса низкими. Увеличивая значение А., мы увеличиваем силу регуляризации.
Параметр С, предусмотренный для класса

лиотеке

scikit-learn,

LogisticRegression

в биб­

происходит из соглашения, принятого в методах опор­

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

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

L2

для двух весовых коэффициентов:

>>> weights, params = [], []
>>> for с in np.arange (-5, 5):
lr = LogisticRegression(C=lO.**c, random_state=l,
solver='lbfgs', multi_class='ovr')
lr.fit(X_train_std, y_train)
weights.append(lr.coef_[l])
params.append(lO.**c)
>>> weights = np.array(weights)
>>> plt.plot(params, weights[:, О],
lаЬеl='длина лепестка')

>>> plt.plot(params, weights[:, 1], linestyle='--',
lаЬеl='ширина лепестка')

>>>
>>>
>>>
>>>

pl t. ylabel ( 'весовой коэффициент' )
plt.xlabel ('С')
plt. legend ( loc=' upper left' )
plt .xscale ( 'log')
»> pl t. show ()
----[111)

Глава 3. Обзор классификаторов на основе машинного обучения с использованием scikit-learn

В результате выполнения показанного выше кода мы выполнили под­
гонку



логистических регрессионных моделей с разными инверсными

параметрами регуляризации С. В демонстрационных целях мы собрали

только весовые коэффициенты класса

Iris-versicolor)

(второго класса в наборе данных,

1

по отношению ко всем классификаторам

те, что мы применяем методику

OvR для

не забывай­

-

многоклассовой классификации.

Как видно на результирующем графике (рис .

3.8),

в случае уменьшения

параметра С весовые коэффициенты сокращаются, т.е. сила регуляризации
увеличивается.

2
1-

:z:
ф

s:
s:

::r

1

-&
-&
(')
о

о

:..:

-----~~,

>S:

о

ID

о
(,)
ф

ID

''

-1

-2

''

''

'

''

''

' """ ________ _
....

102

104

с

Рис.

3.8.

Путь регуляризации

L2

для двух весовых коэффициеитов

\\.~ Допоnнитеnьные ресурсы по nоrистической регрессии

н~ Поскольку исчерпывающее раскрытие индивидуальных алгоритмов
заметку!

классификации выходит за рамки настоящей книги, тем читателям,
которые заинтересованы в дальнейшем изучении логистической рег­

рессии, настоятельно рекомендуется обратиться к книге Скотта Ме­
нарда

"Logistic Regression: From lntroductory to Advanced Concepts
and Applications" (Логистическая регрессия: от введения до расши­
ренных концепций и приложений), Sage PuЫications (2009 г. ).

[112)

Глава 3. Обзор классификаторов на основе машинного обучения с использованием scikit-learn

Классификация с максимальным зазором
с помощью методов опорных векторов
Еще одним мощным и широко используемым алгоритмом обучения яв­
ляется метод опорных векторов (Suppмt

М~и-Ыпс

\!ector

---- S \'М),

который

можно считать расширением персептрона. С применением алгоритма пер­

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

Тем не менее, в методах

SVM

цель оптимизации

довести до максимума

-

зазор. Зазор определяется как расстояние между разделяющей гиперплос­
костью (границей решений) и ближайшими к этой гиперплоскости обуча­
ющими образцами, которые называются опорными вектора.ми. Сказанное
проиллюстрировано на рис.

3.9.
Зазор
\

Опорные векторы

\,

Xz

1
1

\

'

Xz

1
'

1
1

+ ++ Гран:~::~ений о ' :\ +
о о,~ '
+
•', \,

',,

', ', ',\~.,
'

/

о

отрицательная

1

о о:

'

гиперплоскость

'

',~,

wтх

"

=-1

6Х\\,
W

-'
\

-7~c:±J'----//.(

О0

\iQ:' '\\.&,;-./+' + положительная

. ', '\--·-- -- -- гиперплоскость
о Оо\\,'\·.,,
wтx=l

Какая из

SVM:

гиперплоскостей?

Доведение до максимума зазора

Рис.

3.9.

Метод опорных векторов

Понятие максимаnыоrо зазора
Логическое обоснование наличия границ решений с широкими зазорами
заключается в том, что такие модели обычно имеют меньшую ошибку обоб­
щения, тогда как модели с маленькими зазорами больше предрасположены

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

гиперплоскости, которые параллельны границе решений и могут быть выра­

жены следующим образом:
т

+ W Хположительная = 1 (1)
Wo + WТХотрицательная = -1 (2)
Wo

------(113]

-------------

Глава 3. Обзор классификаторов на основе машинного обучения с использованием

Если мы вычтем два линейных уравнения

(1)

и

(2)

scikit-learn

друг из друга, то по­

лучим:

т

~ w (хполо.ж·ительная

-

хотрицателыtая)

=2

Мы можем нормализовать это уравнение по длине вектора

w,

которая оп­

ределяется так:

1 wll = JI;= w~
1

В итоге мы приходим к представленному ниже уравнению:

WT (

Х поло:ж:ительная

Хотрицательная)

-

2

R

llwll

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

Теперь целевой функцией

зора путем максимизации

SVM

становится доведение до максимума за-

2

llwll при условии корректной классификации об-

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

w0 +

wTx(i) 2 1, если y> from sklearn, svm i.щport SVC
>>> svm = SVC ( kernel=' linear', C=l. О, random_ state=l)
>>> svm.fit(X_train_std, y_train)
>>> plot_decision_regions(X_comЬined_std,
у_ comЬined,

classifier=svm,
test_idx=range(lOS, 150))

>>> pl t. xlabel ('длина лепестка [стандартизированная] ' )
>>> plt. ylabel ('ширина лепестка [стандартизированная] ')
>>> plt.legend(loc='upper left')

»> plt. tight_layout ()
>>> pl t. show ()
На рис.

3.11

показаны три области решений

SVM,

визуализированные

после обучения классификатора на наборе данных путем выполнения при­
веденного выше кода.

·----- [ 116] -. ---

Глава 3. Обзор классификаторов на основе машинного обучения с использованием
';"



са

:z:
:z:

са

111

х

2

о

о

а.

s
м
s

...

а.

scikit-learn

о

1
2

О испытатепьный набор

1

са

q

:z:

...
са

~

о

са

...:..:
о

~ -1
С11

с;

са

:z:
~ -2

s
3

-2

-1

о

1

2

длина лепестка [стандартизированная]

Рис.

3.11.

Области решеиий после обучеl/ИЯ классификатора
на наборе данных

Iris

r,.~ Лоrистическая реrрессия в сравнении
~ с методами опорных векторов
На

заметку! Дnя практических задач классификации линейная логистическая регрессия и линейные

SVM

часто выдают очень похожие результаты.

Логистическая регрессия пытается довести до максимума условные

правдоподобия обучающих данных, что делает их в большей степе­
ни подверженными выбросам, чем

SVM,

которые главным образом

заботятся о точках, ближайших к границе решений (опорных век­
торах). С другой стороны, преимущество логистической регрессии

связано с тем, что она является более простой моделью и ее легче
реализовать. Кроме того, логистические регрессионные модели мож­
но легко обновлять, что привлекательно при работе с потоковыми
данными.

Аnыернативные реаnизации в
Класс

LogisticRegression

scikit·learn
из библиотеки

scikit-learn,

которые мы ис­

пользовали в предшествующих разделах, задействуют LIВLINEAR

-

высо­

кооnтимизированную библиотеку на С/С++
университете

Тайваня

, разработанную в Национальном
(ht tp: / /www. csie . ntu. edu. tw / ~cj lin /

1 iЫ inear /).

. [117] - - - - - - - - --

-------

fлава 3. Обзор классификаторов на основе машинного обучения с использованием scikit-learn

Аналогично класс

SVC,

применяемый для обучения модели

SVM,

за­

действует LIВSVM, которая является эквивалентной библиотекой на С/С++,
специально предназначенной для

SVM (http://www.csie.ntu.edu.tw/

-cj lin/ libsvm/).
Преимущество использования библиотек LIВLINEAR и LIВSVM по
сравнению с собственными реализациями на

Python

заключается в том,

что они делают возможным чрезвычайно быстрое обучение больших коли­
честв линейных классификаторов. Однако временами наши наборы данных
слишком велики, чтобы уместиться в памяти компьютера. Соответственно

библиотека

scikit-learn

SGDClassifier,
редством метода

предлагает альтернативные реализации через класс

который также поддерживает динамическое обучение пос­

partial_fit.

Лежащая в основе класса

SGDClassifier

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

2

для

Adaline.

Мы могли бы ини­

циализировать основанную на стохастическом градиентном спуске версию

персептрона, логистической регрессии и метода опорных векторов со стан­

дартными параметрами следующим образом:

>>>
>>>
>>>
>>>

from sklearn. linear_ model import: SGDClassifier
ppn = SGDClassifier(loss='perceptron')
lr = SGDClassifier ( loss=' log' )
svm = SGDClassifier ( loss=' hinge' )

Решение неnинейных задач с применением
ядерноrо метода опорных векторов
Еще одна причина высокой популярности

SVM

у специалистов-прак­

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

(kanelizetl)

для решения задач нелинейной

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

SVM (keniel S \1 ЛJ),

давайте сначала созда­

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

Ядерные методы дnя nинейно сепарабеnьных данных
С помощью следующего кода мы создадим простой набор данных в фор­
ме логического вентиля

XOR

("исключающее ИЛИ"), используя для этого

----------(118)-------·---·---

fлава 3. Обзор классификаторов на основе машинного обучения с использо ванием

функцию
класса

>>>
>>>
>>>
>>>
>>>
>>>
>>>

>>>

»>
»>
>>>
>>>

»>

из

logical _ or

NumPy,

1, а 100 образцам -

где

образцам будет назначена метка

100

метка класса

scikit-leam

-1:

import matplotlib.pyplot as plt
irnport numpy as np

np.random.seed(l)
x_xor = np.random . randn(200, 2)
y_xor = np. logical_xor (X_xor [:, 0] > О,
X_xor[:, 1] > 0)
y_xor = np.where(y_xor, 1, -1)
plt.scatter(X_xor[y_xor == 1, 0),
X_xor[y_xor == 1, 1),
с='Ь' , marker='x',
label=' 1')
plt.scatter(X_xor[y_xor == -1, 0),
x_xor [y_xor == -1, 1),
c='r',
marker=' s',
label=' -1')
plt.xlim( [-3, 3])
plt.ylim( [-3, 3])
plt.legend(loc='best')
plt.tight_layout()
plt. show ()

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

3.12).
х

2

х

х

х

х

х

х

х



х

х

..
•8'•

••



~
"S\(lo

•х ~~

......

х

1



-1



х )сХ хх ~ -t·
. • 1• ••
х хх~ : х~ 8 "~ 88 ,
."."х~ * х
:· • ~ " •
~ >s< ххх х ~ хх х

1

XOR

88

о

88

-1

1

• •

-2





::ж х

• :х

х

х

х



х

Х хХ
х

х
х
х

-3+---~~--~---~---~--~~---i

-3
Рис.

-2

3.12.

о

-1

Набор данl/ых

XOR

[ 119]

1

2

со случайныw шумом

з

со слу­

Глава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-learn

Очевидно, что с применением линейной гиперплоскости в качестве гра­

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

линейной логистической регрессии или линейного

которая обсужда­

SVM,

лась в предшествующих разделах.

Базовая идея ядерных Jнетодов, предназначенных для работы с такими
линейно несепарабельными данными, заключается в создании нелинейных
комбинаций исходных признаков с целью их проецирования на простран­

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

странство признаков, внутри которого классы становятся сепарабельными:

В

(рис.

результате

3.13)

мы

получаем

разделения

возможность

двух

классов

через линейную гиперплоскость, которая превращается в нели­

нейную границу решений при ее проецировании обратно на исходное про­
странство признаков.

,

"~-------~

·.
" " . . . .-~
....



"

....
•10

ф

~.,



---=

2.0
1.5

J

0.5 Z3

о. о

.·~ .·· , ...
..·..:.·:.

- -- - - ~- -

"

---

l ф·l

... .
.,.

,.

.·,
."":: ;:" .;··

t :· •. ·.:

:.~·

1

.. ·:'(: . .....:>'~:•.· ..::·:
•'.
·.·
:: ".

,•'

...·.:..\,.-

· !О

3.13.

- 0.::i

00051.01.5
· ---- 1.s..1 .0..0500
.
о. 5 1.о 1 . 5-1 . тl а-о . 5 .
z .
Z1
2

!>~-------~

Рис.

1.0

При't1ер работы ядерного ,wетода

(120)------

fлава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-learn

Испоnьзование ядерноrо трюка дnя нахождения раздеnяющих
rиперпnоскостей впространстве высокой размерности
Чтобы решить нелинейную задачу с применением

SVM,

мы могли бы с

помощью отображающей функции ф трансформировать обучающие данные
в пространство признаков более высокой размерности и обучить линейную
модель

SVM

для классификации данных в новом пространстве признаков.

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

SVM.

Тем не менее, этому подходу с отображением присуща одна проблема:
конструирование новых признаков сопряжено с очень высокими вычисли­

тельными затратами, особенно если мы имеем дело с данными высокой
размерности. Именно здесь в игру вступает то, что называется ядериы.м

трюко.w

(kemel tt·ick).

Хотя мы не будем вдаваться в мельчайшие подроб­

ности о том, как решать задачу квадратичного программирования для обуче­

ния

SVM,

на практике необходимо лишь заменить скалярное произведение

х>> svm = SVC(kernel='rЬf', random_state=l, gamma=lOO.O, C=l.0)
>>> svm.fit(X_train_std, y_train)
>>> plot_decision_regions(X_comЬined_std,
y_comЬined, classifier=svm,
test_idx=range(105,150))

[123)

[лава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-/earn

>>> рlt.хlаЬеl('длина лепестка [стандартизированная]')
>>> рlt.уlаЬеl('ширина лепестка [стандартизированная]')
>>> plt.legend(loc='upper left')
>>> plt.tight_layout()
>>> plt. show ()
На результирующем графике мы можем заметить, что при относительно
большом значении у граница решений между классами О и
гораздо компактнее (рис.
';"
са
:z:
:z:
са

111

2

о

1')

...

а.

х

о

о
а.

:s;
:s;



1

оказывается

3.16).

о

1
2
испытательный набор

1

са

~

...

са

~

о

~

t

~ -1
С11

с;

са

:z:
~ -2
:s;

3
-2

-1

о

2

1

длина лепестка [стандартизированная]

Рис.

3.16.

Граница решений модели на основе
при высоко.w значении

r

SVM с ядром

RВF

Несмотря на очень хорошую подгонку модели к обучающему набору дан­
ных, такой классификатор, вероятно, будет иметь высокую ошибку обобще­
ния на не встречавшихся ранее данных. Это говорит о том, что параметр у

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

Обучение моделей на основе
деревьев принятия решений
Классификаторы на основе деревьев при11ятия реше11ий являются привле­
кательными моделями, когда нас заботит интерпретируемость . Как подска­
зывает само название, мы можем предположить, что такая модель разбивает
наши данные, задавая последовательность вопросов.

- - - -- -- -- -- - - ( 1 2 4 ] - - - - - -- - -- - -

[лава 3. Обзор классификаторов на основе машинного обучения с использованием

Рассмотрим пример, представленный на рис.

3.17,

scikit-learn

в котором дерево при­

нятия решений используется при определении вида деятельности в отдельно
взятый день.

Есть работа?

Остаться

Внутренний узел

Каков прогноз
погоды?

дома

Ветвь

... ··
.··

Пойти на пляж

Пойти
на пробежку

..··

...···

Друзья заняты?

Листовой узел

Рис.

3.17.

Пример дерева принятия реше11ий

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

3.17

иллюстрируется концепция

дерева принятия решений, основанного на категориальных переменных, та
же самая

концепция

применима и

в случае, когда признаки представляют

собой вещественные числа, как в наборе данных

lris.

Например, мы могли

бы просто определить отсекающее значение по оси признака "ширина ча­
шелистика" и задать двоичный вопрос: ширина чашелистика

2: 2.8

см?

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

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

(i11(ormation g11i11 ·-- [(;),

как будет более подробно

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

очень глубокое дерево с многочисленными узлами, что легко способно при­
вести к переобучению. Таким образом, обычно мы подрезаем дерево, уста­
навливая предел для его максимальной глубины.

-

- -- - -- -- (125)

Глава 3. Обзор классификаторов на основе машинного обучения с использованием

Доведение до максимума прироста информации
поnучение наибоnьшей отдачи

scikit-learn

-

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

целевая функция заключается в доведении до максимума прироста инфор­
мации при каждом разделении, что определяется следующим образом:
т

N

J='

NP

1G(Dp,f)=1(np)-L:-J !(DJ)
Здесь/- признак для выполнения разбиения,
родительского и }-того дочернего узла,

/-

DP

и

D1 -

набор данных

мера загрязиеююсти,

количество образцов в родительском узле и ~·

-

NP -

общее

количество образцов в }-том

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

-

чем ниже загрязненность дочерних узлов, тем

выше прирост информации. Однако для простоты и ради сокращения про­

странства комбинаторного поиска в большинстве библиотек (включая

leam)

scikit-

реализованы двоичные деревья принятия решений. Это значит, что каж­

дый родительский узел разбивается на два дочерних узла, Dлевый и Dправый:

.)
.)- N,,равый!(D
IG(DР ' !) = I(DР )- ~евый!(D
правыи
N
левыи
N
р

р

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

(IE). Давайте начнем с
О):
определения энтропии для всех иепустых классов (p(i 1 t)

Джини

эитропия Uн) и ошибка классификации

(10 ),

*

с

1н ( r) = -

L: р ( i r) 1og2 р ( i r)
1

1

i=1

Здесь p(i

1

t) -

дуального узла

t.

доля образцов, которые принадлежат классу

i для индиви­

Следовательно, энтропия равна О, если все образцы в узле

принадлежат тому же самому классу, и максимальна при равномерном рас­

пределении классов. Скажем, в окружении с двоичными классами энтропия

p(i = 1 1 t) = 1 или p(i = О 1 t) = О. Если классы распределены
равномерно с p(i = 1 1 t) = 0.5 и p(i = О 1 t) = 0.5, тогда энтропия составляет 1.

равна О, если

-------~-----

[126] · - - - - - -

fлава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-learn

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

с

Ic(t)= LP(ilt)(1-p(ilt))=1-Lp(ilt) 2
i=I

i=I

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

(с=

2):
с

/ G (

t) = 1- L 0.5 2 = 0.5
;~1

Тем не менее, на практике загрязненность Джини и энтропия обычно вьща­
ют очень похожие результаты, и часто не стоит тратить много времени на оцен­

ку деревьев с использованием различных критериев загрязненности вместо экс­
периментирования с разными значениями параметра отсечения при подрезке.

Еще одной мерой загрязненности является ошибка классификации:

IE= 1-max{p(i 1 t)}
Ошибка классификации

-

полезный критерий для подрезки, но не ре­

комендуется для выращивания дерева принятия решений, т.к. она менее

чувствительна к изменениям в вероятностях классов узлов. В качестве ил­

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

3.18.

Мы начинаем с набора данных
держит

40

образцов из класса

l

DP
и

в родительском узле

40

образцов из класса

DP,

который со­

2,

которые мы

разбиваем на два набора данных, Dлевый и Dправый· Прирост информации с
применением ошибки классификации в качестве критерия разбиения будет
одинаковым

(IGE

= 0.25)

Рис.

в обоих сценариях, А и В:

3.18.

Два возJ11ож·11ых сце11ария разбие11ия

------------(127)------------

fпава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-learn

IE (Dp) = 1-0.5 = 0.5

А: f Е (д~евыu)= 1-% = 0.25
А: f Е (д,11авыu)= 1-~ = 0.25
4

А:

4
8

4
8

IGE = 0.5--0.25--0.25 = 0.25
4 1
B:IE (девыи)=1-6=3

В : f Е ( Dпра11ый) = 1-1 = О
B:JGE

6 1
8 3

=0.5--х--0=0.25

Однако для загрязн~нности Джинн более привлекательно разбиение в

сценарии В (IG 0 = О.16) по сравнению со сценарием А (IG 0

= 0.125),

сценарий В на самом деле чище:

1G ( D р) = 1-( 0.5 2 + 0.5 2 ) = 0.5

А :IG (D,_)=1-((~)' +(~)}~=0.375
A:IG
А:

(D_.)=1-(Ш' +(~)}~=0.375
4

4

8

8

/GG = 0.5--0.375--0.375 = 0.125

в:1G (v_.)=1-((~)' +Ш}~=о.4
В: f G (Dпра11ыu) = 1-(12 + 02 )

=0

6 =
B:JGG =0.5--0.4-0=0.16

8

------------(128)-------

т.к.

Глава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-learn

Аналогично критерий энтропии также отдает предпочтение сценарию В

(IGн

= 0.31)

перед сценарием А (!Gн

= 0.19):

1н(Dp)=-(0.5 log 2 (0.5)+0.5 log 2 (0.5))=1

А• I" (D,,,;,) =-( ~log 2
A:IGн

(:

)+%log 2 (

4

~)) =0.81

4

=1--0.81--0.81=0.19
8
8

В• I" (D,>> import matplotlib. pyplot as pl t
>>> import numpy as np

»>

def gini (р) :
return (р) * (1 - (р)) + (1 - р) * (1 - (1-р))
>>> def entropy (р):
return - p*np.log2(p) - (1 - p)*np.log2((1 >>> def error (р) :
return 1 - np.max ( [р, 1 - р])

(129)

р))

[лава 3. Обзор классификаторов на основе машинного обучения с использованием

scikit-/earn

х = np.arange(O.O, 1.0, 0.01)
ent = [entropy(p) if р != О else None for р in х]
sc_ent = [е*О.5 if е else None for е in ent]
err = [error(i) for i in х]
fig = plt.figure()
>»ах= plt.subplot (111)
>>> for i, lab, ls, с, in zip ( [ent, sc_ent, gini (х), err],

>>>
>>>
>>>
>>>
>>>

['Энтропия',

'Энтропия

(масштабированная)',

'Загрязненность Джини',
'Ошибка неправильной классификации'],
[ ' -

' '

' -

' ,

1-

' -- ' ,

• ' ] '

'lightgray',
'red', 'green', 'cyan']):
line = ax.plot(x, i, label=lab,
linestyle=ls, lw=2, color=c)
ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15),
ncol=5, fancybox=True, shadow=False)
ax.axhline(y=0.5, linewidth=l, color='k', linestyle='--')
ax.axhline(y=l.O, linewidth=l, color='k', linestyle='--')
plt.ylirn( [О, 1.1))
plt.xlabel('p{i=l) ')
plt. ylabel ('Индекс загрязненности')
plt. show ()
['Ыасk',

>>>
>>>
>>>
»>

>>>
»>

На рис.

3.19

показан график, полученный в результате выполнения пре­

дыдущего кода.

-

Энтропия

Энтропия

1.0

(масштабированная)

Загрязненность

- - - Джини

_ . _ Ошибка неправильной
классификации

-----------------::.;-..---;;:.

0.8

0.6

0.4

0.2

0.2

о. о

0.6

0.4

0.8

1.0

p(i=l)

Рис.

-

-

3.19.

- - - - --

Сравнение критериев загряз//еютсти

----[130]

fпава 3. Обзор классификаторов на основе машинного обучен ин с использованием scikit-learn

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

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

scikit-leam мы обучим дерево приня­
4, используя загрязненность Джини

в качестве критерия загрязненности. Хотя масштабирование признаков мо­

жет быть желательным при визуализации, имейте в виду, что оно не являет­
ся требованием для алгоритмов на основе деревьев принятия решений. Вот
как выглядит код:

>>> from sklearn. tree .iпipoз:·t DecisionTreeClassifier
>>> tree_model = DecisionTreeClassifier(criterion='gini',
max_ depth=4,
random_state=l)

>>> tree_model.fit(X_train, y_train)
>>> X_comЬined = np. vstack ( (X_train, X_test))
>>>
>>>

y_comЬined

= np.hstack ( (y_train, y_test))

plot_decision_regions(X_comЬined,
у_ comЬined,

>>>
>>>
>>>
>>>
>>>

classifier=tree_model,
test_idx=range(lOS, 150))
лепестка [см] ' )

pl t. xlabel ( 'длина
plt.ylabel ('ширина лепестка
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()

[см]')

В результате выполнения примера кода мы получаем типовые параллель­
ные осям границы решений для дерева принятия решений (рис.

Удобная возможность библиотеки

scikit-leam

3.20).

заключается в том, что пос­

ле обучения она позволяет без труда визуализировать модель на основе де­
рева принятия решений посредством следующего кода:

>>> froщ sklearn iщpo:t·t. tree
>>> tree.plot_tree(tree_model)

»> plt. show ()
Итоговая визуализация показана на рис.

[131]

3.21.

Глава 3. Обзор классификаторов на основе машинного обучения с использованием

~

о

са

:i::
:i::

3.0

х

са

111
о

а..

2.5

...

2.0

s
м
s

scikit-learn

о

О

1
2
испытательный набор

а..
са

q

:i::

1.5

~

1.0

...

са

са

.....:
()

QI

0.5

с

QI
i:;

о.о

са

:i::

~

- 0.5

s
3

о

1

3

2

4

б

5

7

длина лепестка [стандартизированная]

Рис.

3.20.

Обучение дерева припятия реще11uй с .~iaкcu\ta7ыmй глубиной

4

X[l] >>
>>>
>>>
>>>
>>>

10.0,11.0,12.0,'''
# В случа~работы с Python 2. 7

понадобится

# преобразовать строку в формат Unicode:
# csv_data = unicode(csv_data)
df
df

= pd. read_csv (StringIO (csv_data))
[146)---------------------

Глава

о

1
2

4.

Построение хороших обучающих наборов

А

в

с

о

1.0
5.0
10.0

2.0
6.0
11.0

3.0
NaN

4.0
8.0

12.0

NaN

-

предварительная обработка данных

В показанном выше коде посредством функции
данные

read_ csv

мы читаем

в объект

DataFrame из pandas и замечаем, что две недостаю­
щие ячейки были заменены NaN. Функция StringIO применялась просто в
целях иллюстрации. Она позволяет читать строку, присвоенную csv_ data,
в объект DataFrame из pandas, как если бы она была обычным файлом CSV

CSV

на жестком диске.

В более крупном объекте

DataFrame

ручной поиск недостающих значе­

ний становится утомительным; в таком случае мы можем использовать ме­

тод

isnull

для возвращения объекта

DataFrame

с булевскими значениями,

которые указывают, содержит ли ячейка числовую величину

данные отсутствуют

(True).

Затем с помощью метода

sum

(False)

или же

мы можем полу­

чить количество недостающих значений по столбцам:

>» df. isnull () . sum ()
А

О

в

о

с

1
1
dtype: int64
о

Таким способом мы способны подсчитать количество недостающих зна­
чений на столбец; в последующих подразделах мы рассмотрим различные

стратегии решения проблемы с недостающими данными.

Г'...~ Удобная обработка данных с помощью класса
~ из библиотеки
На

заметку!

DataFrame

pandas

Несмотря на то что библиотека

scikit-leam изначально проектироваNumPy, иногда удобнее предва­
рительно обрабагывать данные с применением класса DataFrame из
библиотеки pandas. В настоящее время большинство функций scikitleam поддерживают на входе объекты DataFrarne, но поскольку в
АРl-интерфейсе scikit-leam более естественной является обработка
массивов NumPy, рекомендуется по возможности использовать мас­
сивы NumPy. Следует отметить, что с помощью атрибута values мы

лась для работы только с массивами

---(147)----------

[лава

4.

Построение хороших обучающих наборов

-

предварительная обработка данных

NumPy
scikit-learn:

всегда можем получить доступ к массиву

перед его передачей оценщику

>>> df.values
array([[ 1.,
[ 5.,
[ 10.,

2.,
6. ,
11.,

nan,

4.],
8. ] ,

12.,

nan]])

3.,

внутри

DataFrame

Исключение обучающих образцов или признаков
с недостающими значениями

Один из простейших способов решения проблемы с недостающими дан­
ными предусматривает полное удаление соответствующих признаков (столб­

цов) или обучающих образцов (строк) из набора данных; строки с недостаю­
щими значениями могут быть легко удалены посредством метода

dropna:

>>> df.dropna(axis=O)
С

В

А

1.0

о

3.0

2.0

D
4.0

Подобным образом мы можем отбрасывать столбцы, которые имеют, по
крайней мере, одно значение NaN в любой строке, устанавливая аргумент

axis

в

1:

>>> df.dropna(axis=l)
о

1
2

А

В

1.0
5.0
10.0

2.0
6.0
11.0

Метод

dropna

поддерживает несколько дополнительных параметров, ко­

торые могут оказаться полезными:

#
#
#

удалить только строки, где все столбцы содержат
(здесь возвращается полный массив,

строка со значениями

т.к.

NaN

отсутствует

NaN во всех столбцах)

>>> df.dropna(how='all')
о

А

В

С

D

1.0

2.0

4.0
8. О
NaN

1

5. О

6. О

3.0
NaN

2

10.0

11.О

-12.О

#

удалить строки, которые содержат менее 4 вещественных значений

>>> df.dropna(thresh=4)
о

А

В

С

D

1.0

2.0

3.0

4.0

------------(148)--···-·

Глава

4.

Построение хороших обучающих наборов

#удалить только строки, где

#в специфических столбцах

NaN

-

предварительная обработка данных

появляется

(эдесь:

'С')

>>> df.dropna(subset=['C'])
А
В
С
D
о
1.0
2.0
3.0
4.0
2 10.0
11.О
12.О
NaN
Хотя удаление недостающих данных кажется удобным подходом, ему так­
же присущи определенные недостатки; скажем, мы можем в итоге удалить

слишком много образцов, сделав надежный анализ невозможным. Либо же
в случае удаления чересчур большого количества столбцов признаков воз­

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

У(nовный расчет недо(тающих значений
Часто удаление обучающих образцов или отбрасывание целых столб­
цов признаков попросту неосуществимо из-за возможности утери слишком

многих ценных данных. В таком случае мы можем использовать различные
методики интерполяции для оценки недостающих значений на основе ос­

тальных обучающих образцов в наборе данных. Одним из наиболее рас­
пространенных приемов интерполяции является условный расчет на основе
среднего (тет1

imputation),

когда мы просто заменяем недостающее значе­

ние средним значением полного столбца признака. Достичь этого удобно с

применением класса

Simpleimputer

из

scikit-leam,

как демонстрируется в

следующем коде:

>>>
>>>
>>>
>>>
>>>
>>>

sklearn. impute iщpo1:·t. Simpleimputer
numpy as np
imr = Simpleimputer(missing_values=np.nan, strategy='mean')
imr = imr.fit(df.values)
imputed_data = imr.transform(df .values)
imputed_data
f':r·om

:i.1.npo:r·t

array ( [ [

1.,

[ 5.,
[ 10.,

2.,
6.,
11.,

7.5,

4.] ,
8.] ,

12.,

6.]])

3.,

------·-- [ 1491 ---------

Глава

4.

Построение хороших обучающих наборов

предварительная обработка данных

-

NaN соответствующим средним, ко­

Здесь мы заменяем каждое значение

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

strategy

-

доступны другие варианты

median

и

most frequent,

где

последний заменяет недостающие значения самыми часто встречающимися

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

признаков, например, столбца признака, который хранит коды названий цве­
тов, таких как красный, зеленый и синий; позже в главе будут приведены
примеры данных подобного вида.

В качестве альтернативы можно использовать еще более удобный способ
условного расчета недостающих значений

-

метод

fillna

из

pandas,

пере­

давая ему в аргументе метод условного расчета. Например, с применением

pandas

мы могли бы обеспечить условный расчет на основе среднего прямо

в объекте

DataFrame:

>>> df.fillna(df.mean())

D

с

А

в

1.0

2.0

3.0 4.0

5.0

6.0

7.5

2 10.0

11.0

о

Понятие АРl-интерфейса оценщиков

8.0

12.0 6.0

scikit-learn

В предыдущем разделе мы использовали класс

learn

Simpleimputer

из

scikit-

для условного расчета недостающих значений в нашем наборе данных.

В рамках библиотеки

scikit-leam

класс

зываемым классам преобразователей

Simpleimputer относится к так на­
(l 1a11.~(ormeг), которые применяются

для трансформации данных. Двумя неотьемлемыми методами оценщиков

являются

fit

и

transform.

Метод

fit

используется для того, чтобы узнать

параметры из обучающих данных, а метод

transform

применяет выявлен­

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

4.1

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

-- [ 150] --------------~--~-----

Глава

4.

Построение хороших обучающих наборов

4.1.

предварительная обработка данных

Обучающие

Испытательные

данные

данные

ф est. transform(X_train)

Рис.

-

Модель

est. transform(X_test)

Трансформированные

Трансформированные

обучающие

испытательные

данные

данные

®

Использование преобразователя для трансформации данных

Классификаторы, применяемые в главе

3,

внутри библиотеки

scikit-leam

принадлежат к числу так называемых оценщиков с АРI-интерфейсом, ко­
торый концептуально очень похож на АРI-интерфейс преобразователей.
Оценщики имеют метод

но, как мы увидим позже в главе, также

могут иметь метод

можете вспомнить, что мы использовали

метод

fi t,

predict,
transform. Вы

чтобы узнать параметры модели, когда обучали эти оценщики

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

применять для выработки прогнозов о новых непомеченных образцах дан­
ных через метод

predict,

как иллюстрируется на рис.

4.2.

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

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

--[151]

Глава

4.

Построение хороших обучающих наборов

(!)

-

предварительная обработка данных

Обучающие

Обучающие

данные

метки

est.fit(X_train, y_train)

Модель

Испытательные
данные

~ est.predict(X_test)

Спрогнозированные

1

метки

Рис.

4.2.

Дополнителыюе предоставление меток классов для подгонки модели

Когда речь идет о категориальных данных, мы должны дополнитель­
но проводить различие между именными и порядковы.\tи признаками.
Порядковые признаки можно понимать как категориальные значения, кото­

рые допускают сортировку или упорядочение. Например, размер футболки
был бы порядковым признаком, потому что мы можем определить порядок

XL > L >

М. Напротив, именные признаки не подразумевают какого-либо

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

Кодирование катеrориаnьных данных с помощью

pandas

Прежде чем заняться исследованием различных методик обработки та­
ких категориальных данных, давайте создадим новый объект DataFrame
для иллюстрации задачи:

>>> import pan(fas as pd
>>> df = pd. DataFrame ( [
['green', 'М', 10.1, 'class2'],
['red', 'L', 13.5, 'classl'],
['Ыuе', 'XL', 15.З, 'class2']])
----------~--(152)-------------

fлава

4. Построение хороших обучающих наборов - предварительная обработка данньtх

>>> df.columns
>>> df

1

color
green
red

2

Ыuе

о

size
м

L
XL

[ 'color', 'size', 'price', 'classlabel']
price
10.1
13.5
15.3

classlabel
class2
classl
class2

В предыдущем выводе видно, что вновь созданный объект

содержит столбцы с именным признаком
и числовым признаком

(size)

(price).

(color),

DataFrame

порядковым признаком

Метки классов (при условии, что

мы создали набор данных для задачи обучения с учителем) хранятся в пос­
леднем столбце

(classlabel).

Алгоритмы обучения для классификации,

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

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

size,

поэтому нам придется

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

скажем,

XL = L + 1 = М + 2:

>>> size_mapping

=

{'XL': 3,
'L' : 2,
'М':

1}

>>> df [ 'size'] = df [ 'size'] .map (size_mapping)
>>> df
О

1
2

color
green
red
Ыuе

size
1
2
3

price
10 .1
13. 5
15.3

classlabel
class2
classl
class2

Если на более поздней стадии целочисленные значения необходимо
трансформировать в первоначальное строковое представление, тогда мы
можем определить словарь обратного отображения

inv_ size _ mapping =

{v: k for k, v in size_ mapping. i tems () }. Затем он может применяться
map библиотеки pandas на трансформированном столбце
признака подобно использованному ранее словарю size _ mapping:
при вызове метода

[153)

Глава

4.

Построение хороших обучающих наборов

-

предварительная обработка данных

>>> inv_size_mapping = {v: k for k, v in size_mapping.items ()}
>>> df['size'] .map(inv_size_mapping)
о

м

1

L

2

XL

Name: size, dtype: object
Кодирование меток кnассов
Многие библиотеки МО требуют, чтобы метки классов были закодиро­
ваны как целочисленные значения. Несмотря на то что большинство оцен­
щиков в библиотеке

scikit-learn,

ориентированных на классификацию, внут­

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

во избежание технических затруднений. Для кодирования меток классов мы

можем применять подход, подобный обсуждавшемуся ранее подходу с отоб­
ражением порядковых признаков. Необходимо помнить, что метки классов
не являются порядковыми, поэтому не играет роли, какое целое число будет
присвоено определенной строковой метке. Таким образом, мы можем прос­
то перечислить метки классов, начиная с О:

>>> import numpy as np
>>> class_mapping = {label: idx for idx, label in

enuroerate(np.unique(df['classlabel']))}
>>> class_mapping

{'classl':

'class2': 1}

О,

Далее мы можем воспользоваться словарем отображения для трансфор­
мации меток классов в целые числа:

>>> df['classlabel']
>>> df

= df['classlabel'] .map(class_mapping)

color
green
1
red

size

price
10.1
13.5

classlabel

1
2

Ыuе

3

15.З

1

О

2

1
о

Чтобы вернуть преобразованные метки классов в первоначальное стро­
ковое представление, мы можем обратить пары "ключ-значение" в словаре

отображения, как показано ниже:

-----------(154)-------------

Глава

4. Построение хороших обучающих наборов - предварительная обработка данных

>>> inv_class_mapping = {v: k for k, v in class_mapping.items ()}
>>> df['classlabel'] = df['classlabel'] .map(inv_class_mapping)
>>> df
color size
price classlabel
green
1
10.1
о
class2
red
1
classl
13.5
2
2
Ыuе
3
15.3
class2
В качестве альтернативы для достижения той же цели в библиотеке

leam

реализован удобный класс

scikit-

LabelEncoder:

>>> f.r:om sklearn .preprocessing import LabelEncoder
>>> class_le = LabelEncoder ()
>>>у= class_le.fit_transform(df['classlabel'] .values)
>>>у

array( [1,

О,

1))

Обратите внимание, что метод

fi t _ transform- это лишь сокращение
transform по отдельности, а с помощью метода

для вызова методов

fi t и
inverse _ transform целочисленные

метки классов можно трансформиро­

вать обратно в первоначальное строковое представление:

>>> class_le.inverse_transform(y)
array(['class2', 'classl', 'class2'], dtype=object)

Выпоnнение унитарноrо кодирования на именных признаках
В предыдущем разделе для преобразования порядкового признака

size

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

scikit-learn,

предназначенные для классификации,

трактуют метки классов как категориальные данные, что не подразумевает

наличие любого порядка (именные данные), для кодирования строковых ме­
ток в целые числа мы использовали удобный класс

LabelEncoder.

Может

показаться, что похожий подход удалось бы применить для трансформации

именного столбца цвета

(color)

в нашем наборе данных:

>>> Х = df [ [ 'color', 'si ze', 'price'] ] . values
>>> color_le = LabelEncoder(}
>>> Х[:, О]= color_le.fit_transform(X[:, О])
>>> х
array([[l, 1, 10.1],
[2, 2, 13.5]'
[О, 3, 15.3]], dtype=object)

----

-[155)

f11ава

4.

Построение хороших обучающих наборов

-

предварительная обработка данных

После выполнения предыдущего кода первый столбец в NumРу-массиве Х
теперь хранит новые значения цвета, закодированные следующим образом:

• Ыuе =О
• green = 1
• red = 2
Если мы на этом остановимся и передадим массив нашему классифика­

тору, то допустим наиболее распространенную ошибку при работе с катего­
риальными данными. Заметили проблему? Хотя значения цвета не должны
поступать в каком-либо порядке, алгоритм обучения теперь предполагает,
что

green

больше Ыuе, а

red

больше

green.

Несмотря на некорректность

такого предположения, алгоритм по-прежнему мог бы выдавать пригодные
результаты. Однако результаты подобного рода не были бы оптимальными.
Общепринятый обход описанной проблемы предусматривает использова­

ние приема, называемого унитарным кодированием (оне-/10! e11cщii11,~) или
кодированием с одним активным состоянием. Идея подхода заключается в

том, чтобы создать новый фиктивный признак для каждого уникального зна­
чения в столбце именного признака. В рассматриваемом примере мы пре­
образовали бы признак

color

в три новых признака: Ыuе,

green

и

red.

Затем с помощью двоичных значений можно было бы указывать цвет

(color)

образца; скажем, образец синего (Ыuе) цвета кодировался бы как

green=O, red=O. Для выполнения такой трансформации мы мо­
жем применить класс OneHotEncoder, реализованный в модуле sci ki tlearn. preprocessing:
Ыue=l,

>>> frorn sklearn.preprocessing irnport OneHotEncoder
>>> Х = df[['color', 'size', 'price']] .values
>>> color_ohe = OneHotEncoder()
>>> color_ohe.fit_transform(X[:, О] .reshape(-1, 1)).toarray()
array ( [ [О. , 1. , О. ] ,
[О.,

О.,

1.],

[1.,

О.,

О.]])

OneHotEncoder только к единс­
. reshape (-1, 1) ) ) , чтобы избежать моди­

Обратите внимание, что мы применили
твенному столбцу,

(Х [ : ,

О]

фикации остальных двух столбцов в массиве. Если мы хотим выборочно

трансформировать столбцы в массиве с множеством признаков, то можем

ColumnTransformer, который
(name, transformer, column(s)) кортежей:

использовать класс

принимает список

------------(156)-------

fпава

4.

Построение хороших обучающих наборов

-

предварительная обработка даннЬIХ

>>> froш sklearn. compose import ColшnnTransformer
>>> Х = df[ [ 'color', 'size', 'price']] .values
>>> c_transf = ColшnnTransformer ( [
( ' onehot ' , OneHotEncoder ( ) , [О] ) ,
( 'nothing', 'passthrough', [1, 2])
] )

>>> c_transf.fit_transform(X) .astype(float)
array(

[[О.О,

1.0,

[О.О,

О.О,

[1.0,

О.О,

1, 10.1],
1.0, 2, 13.5],
О.О, 3, 15.3]])

О.О,

В предыдущем примере кода посредством аргумента

'passthrough'

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

Еще более удобный способ создания таких фиктивных признаков через

get _ dwnmies,
DataFrame, метод

унитарное кодирование предполагает использование метода

реализованного в

get_ dwnmies

pandas.

Будучи примененным к объекту

преобразует только строковые столбцы и оставит все осталь­

ные столбцы неизмененными:

>>> pd.get _dшnmies (df [ [ 'pri се~', 'col.or', 'size']])
size
1

1

price
10.1
13.5

2

15.З

о

color

col.or- green
1

color red

о

2

о

о

1

3

1

о

о

Ыuе

о

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

жет стать проблемой для определенных методов (например, методов, требу­

ющих обращения матриц). Если признаки сильно взаимосвязаны, тогда об­
ращать матрицы будет трудно в вычислительном плане, что может привести
к получению численно неустойчивых оценок. Чтобы сократить взаимосвязь
между переменными, мы можем просто удалить один столбец признака из
закодированного унитарным кодом массива. Обратите внимание, что в ре­

зультате удаления столбца признака мы не теряем какую-то важную инфор­
мацию. Скажем, после удаления столбца

color_Ыue информация признака
color_green=O и color_ red=O

все равно сохраняется, т.к. из наблюдения
вытекает, что цветом должен быть Ыuе.

-------(157)-----------

Глава

4.

Построение хороших обучающих наборов

Если мы применяем функцию
столбец, указав

True

предварительная обработка даннЬlх

-

get _ dummies, то
drop first,

для параметра

можем удалить первый
как демонстрируется в

следующем коде:

>>> pd.get_dummies(df(('price', 'color', 'size']],
drop_ first='Г.i::·ue)
price size color_green color red
о

1
2

1
2
3

10.1
13.5
15.3

1

о

о

1

о

о

Чтобы удалить избыточный столбец через
димо установить

drop=' first'

и

OneHotEncoder,
categories=' auto':

нам необхо­

>>> color_ohe = OneHotEncoder(categories='auto', drop='first')
>>> с_transf = ColшnnTransformer ( [
( 'onehot', color_ ohe, [О]),
('nothing', 'passthrough', (1, 2))
] )

>>> c_transf.fit_transform(X) .astype(float)
array([[ 1., О., 1., 10.1),

[
[

,

1. ,

о.,

о.,

о.

13. 5] ,
15.3)))

2. ,
3.,

\\°:~ Дополнительно: кодирование порядковых признаков

н~ Если мы не уверены в числовых различиях между категориями по­
заметку! рядковых признаков или разница между двумя порядковыми признаками не определена, тогда мы можем также кодировать их с ис­

пользованием пороговой кодировки со значениями
мы можем расщепить признак
два новых признака, "х

объект

>

0/1.

size со значениями М, L и XL на
> L". Давайте возьмем исходный

М" и "х

DataFrame:

>>> df = pd.DataFrame([('green', 'М', 10.1,
'class2'],
[ 'red' , 'L', 13. 5,
'classl'],
['Ыuе',

'XL', 15.3,

'class2' ] ] )
>>>

df.colшnns

Например,

( 'color', 'size', 'price',
'classlabel']

>>> df

(158)-------

Глава

4.

Построение хороших обучающих наборов

Мы можем применять метод

apply

-

предварительная обработка данных

объектов

DataFrame

из

pandas

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

>>> df['x >

= df['size'] .apply(

М']

lamЬda х:

1 if

х

in { 'L', 'XL'} else 0)

>>> df['x > L'] = df['size'] .apply(
lamЬda х: 1 if х == 'XL' else 0)
>>> d.el df [ 'size']
>>> df

Разбиение набора данных на отдельные
обучающий и испытательный наборы
В главах

1

и

3

мы кратко представили концепцию разбиения набора дан­

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

несмещенную (неискаженную) оценку эффективности модели перед ее вы­

пуском в реальный мир. В этом разделе мы подготовим новый набор данных
с информацией о винах

-

Wine.

После предварительной обработки набора

данных мы исследуем различные приемы выбора признаков для понижения
размерности набора данных.

Набор данных

Wine -

еще один набор данных с открытым кодом, кото­

рый доступен в Хранилище машинного обучения Калифорнийского универ­

ситета в Ирвайне

(UCI)

по ссылке

da tasets /Wine;

он содержит

https: / / archi ve. ics. uci. edu/ml/
178 образцов вин с 13 признаками, описыва­

ющими их химические свойства.

\\:~ Попучение набора данных Wine

н~ Копия набора данных Wine (и всех других наборов данных, исполь­
заметку!

зуемых в книге) включена в состав загружаемого архива с кодом
примеров для книги. Указанные копии можно задействовать при ав­

тономной работе или в случае временной недоступности

https: / /
archive.ics.uci.edu/ml/machine-learning-databases/
wine/wine. data на сервере UCI. Скажем, чтобы загрузить набор
данных Wine из какого-то локального каталога, следующий оператор:
df = pd.read_csv('https://archive.ics.uci.edu/ml/'
'machine-learning-databases/wine/wine.data',
header=None)

---[159)

[лава

4.

Построение хороших обучающих наборов

-

предварительная обработка данных

понадобится заменить таким оператором:

df = pd.read_csv(

'ваш/локальный/путь/к/wiпе.dаtа',

header=None)
С помощью библиотеки

pandas

мы будем читать набор данных

посредственно из Хранилища машинного обучения

Wine

не­

UCI:

>>> df wine

pd.read_csv('https://archive.ics.uci.edu/'
'rnl/rnachine-learning-databases/'
'wine/wine.data', header=None)
['Метка класса', 'Алкоголь',
>>> df wine. colurnns
# 'Class label', 'Alcohol'
'Яблочная кислота',

'Зола',

# 'Malic acid ', 'Ash'
'Щелочность золы',

'Магний',

# 'Alcalinityofash', 'Magnesium'
'Всего фенолов' , 'Флавоноиды' ,
# 'Total phenols ', 'Flavanoids'
'Нефлавоноидные фенолы' ,
# 'Nonflavanoid phenols'
'Проантоцианидины',

# 'Proanthocyanins'
'Интенсивность цвета',

'Оттенок',

# 'Color intensity', 'Ние'
'00280/00315 разбавленных вин',
# 'OD280/0D315 of diluted wines'
'Пролин']

# 'Proline'
>>> рп.пt ( 'Метки классов' , np. unique (df _ wine [ 'Метка
Метки классов [ 1 2 3]
>>> df_wine.head()
Ниже в таблице перечислены

13

признаков в наборе данных

рые описывают химические свойства

Метка
класса

~6rючАпкоrоль

ная
кислота

Щеrюч-

Зола

носrь

Магний

золы

Всего
фенолов

класса'

178

Wine,

ноидные

Интенсив·
ПроантоцианИд111ны

фенолы

ность

цвета

11

~

б

00280/0D315
разбаа.1енных

1

14.23

1.71

2.43 1s.e

127

2.60

3.06

0.28

2.29

S.64

1.04 3.92

13.20

1.78

2.14 11.2

100

2.85

2.76

0.26

1.28

4.38

1.05 3.40

-~

2 1

13.16

2.36

2.67 18.6

101

2.60

3.24

0.30

2.61

s.ee

1.03 3.17

3 1

14.37

1.95

2.50 16.8

113

З.85

З.49

0.24

2.18

7.60

0.86

4 1

13.24

2.59

2.87 21.0

118

2.60

2.89

0.39

1.82

4.32

1.04 2.93

----(160] - - - -

Пролин

вин

1 1

о

кото­

образцов вин.

Нефлаво-

Флавоноиды

] ))

Э.45

1065

-

1050
~--

1165
1480
·-~

735

Глава

4.

Построение хороших обучающих наборов

предварительная обработка данных

-

Образцы принадлежат одному из трех классов,

1, 2

и

которые отно­

3,

сятся к трем разным видам винограда, выращенного в одном и том же ре­

гионе Италии, но с отличающимися винными культурами, как описано в

сводке по набору данных

(https: / /archive. ics. uci. edu/ml/machinelearning-databases/wine/wine. names).
Удобный способ случайного разбиения этого набора данных на отде­

льные испытательный и обучающий поднаборы предусматривает примене­

ние функции
отеки

train_test_split
scikit-learn:

из подмодуля

model_selection

библи­

>>> from sklearn .model_selection impor:t train_test_split
>>> Х, у= df_wine.iloc[:, 1:] .values, df_wine.iloc[:, О] .values
>>> X_train, X_test, y_train, y_test =\
train_test_split(X, у,
test_size=0.3,
random_state=O,
stratify=y)
Сначала мы присваиваем переменной Х представление в виде масси­
ва

NumPy

столбцов признаков

1-13,

а переменной у

первого столбца. Затем мы используем функцию

- метки классов из
train_ test _ spli t для

случайного разделения Х и у на обучающий и испытательный наборы

данных. Установкой

test size=O. 3 мы присваиваем 30% образцов вин
Х_ test и у_ test, а оставшиеся 70% образцов вин - Х _ train и у_ train.
Предоставление массива меток классов у как аргумента для stratify га­

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

Г'..~ Выбор подходящей пропорции для разделения набора данных
~ на обучающий и испытательный наборы
На

заметку!

При разделении набора данных на обучающий и испытательный на-

боры следует иметь в виду, что мы удерживаем ценную информа­
цию, из которой алгоритм обучения мог бы извлечь выгоду. Таким

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

-

уравнове­

шивание такого компромисса. На практике самыми часто применяе­

мыми разделениями являются

60:40, 70:30

[ 1611

и

80:20

в зависимости от

-------~-------~----------

fлава 4. Построение хороших обучающих наборов

-

предварительная обработка данных

размера первоначального набора данных. Однако для крупных набо­
ров данных разделение

90: 1О

или

99: 1 на

обучающий и испытатель­

ный наборы также оказываются обычными и подходящими. Скажем,

если набор данных содержит свыше

100 ООО

обучающих образцов,

то может оказаться неплохим решение удержать только

10000

образ­

цов для испытаний, чтобы получить хорошую оценку эффективности
обобщения. Дополнительные сведения и иллюстрации ищите впер­

вом разделе статьи

"Model evaluation, model selection, and algorithm
se\ection in machine learning" ("Оценка моделей, подбор моделей и
выбор алгоритмов в машинном обучении"), которая свободно доступ­
на по ссылке

https: //arxiv .org/pdf/1811.12808 .pdf.

Кроме того, вместо отбрасывания выделенных испытательных дан­
ных после обучения и оценки модели классификатор обучают пов­

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

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

данных для оценки ее эффективности.

Приведение признаков к тому же самому масштабу
Масштабироваиие призиаков (fi·11tuп·

критически важный шаг

scali11g) -

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

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

признаки имеют один и тот же масштаб, как было показано в главе

2

при

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

масштабе от

1

до

1О,

а второй

-

в масштабе от

функцию суммы квадратичных ошибок в

Adaline

1

из

до

100 ООО. Вспомнив
главы 2, разумно гово­

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

--------[162)-------------------

Глава

4.

Построение хороших обучающих наборов

является алгоритм

k

ближ·айших соседей

предварительная обработка данных

-

(KNN)

с мерой в виде евклидова

расстояния; вычисленные расстояния между образцами будут преобладать
на оси второго признака.

Существуют два общих подхода к приведению признаков к тому же самому
масштабу: нормштзация

(11ommlizatio11)

и стандартизация (staщfal'lli::ation).

Указанные термины довольно свободно используются в разных областях, по­
этому их смысл приходится выводить из контекста. Чаще всего под норма­

лизацией понимается изменение масштаба признаков на диапазон [О,

1],

ко­

торое представляет собой частный случай масштабирова11ия по ми11uJ1юксу

(min--max scali11g).

Для нормализации данных мы можем просто применить

масштабирование по минимаксу к каждому столбцу признака, где новое зна­

чение x~2rm образца х< 1 > может быть вычислено как:
(i)

х,юпп

_

-

Х

(i)
-xmin

xmax -xmin

Здесь x>> f:roш sklearn .preprocessing .шrрс.н:t. MinMaxScaler
>>> mrns = MinMaxScaler ()
>>> X_train_norm = mms.fit_transform(X_train)
>>> X_test_norm = mrns.transform(X_test)
Несмотря на то что нормализация посредством масштабирования по
минимаксу является ходовым приемом, который полезен, когда необходи­
мы значения в ограниченном интервале, стандартизация может быть более
практичной для многих алгоритмов МО, особенно для алгоритмов оптими­
зации наподобие градиентного спуска. Причина в том, что многие линейные
модели, такие как рассмотренные в главе

3

логистическая регрессия и

SVM,

инициализируют веса нулями или небольшими случайными значениями,

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

1,

так что столбцы признаков имеют те же параметры, как нормальное рас­

пределение (нулевое среднее и единичная дисперсия), облегчая выяснение
весов. Кроме того, стандартизация сохраняет полезную информацию о вы-

- - - - [ 1 6 3 ] - - - - - - - ·- - - - - - - - -

Глава

Построение хороших обучающих наборов

4.

-

предварительная обработка данных

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

Процедура стандартизации может быть выражена с помощью такого
уравнения:

(i) _ Х

(i)

-µх

xstd -

Здесь µх

crx -

-

выборочное среднее по определенному столбцу признака, а

соответствующее стандартное отклонение.

В представленной ниже таблице демонстрируется разница между двумя
самыми распространенными приемами масштабирования признаков, стан­

дартизацией и нормализацией, на простом наборе данных, состоящем из чи­
сел от О до

5.

Входное значение

Стандартизированное значение

Нормапизованноезначение

о.о

-1.46385

о.о

1.0

-0.87831

0.2

2.0

-0.29277

0.4

3.0

0.29277

0.6

4.0

0.87831

0.8

5.0

1.46385

1.0

Продемонстрированную в таблице стандартизацию и нормализацию мож­
но выполнить вручную посредством следующего кода:

>>> ех = np.array( [О, 1, 2, 3, 4, 5))
>>> print ('стандартизированные значения:',
стандартизированные значения:

>>>

(ех

[-1.46385011
-о. 29277002
0.87831007

- ex.mean ()) /
-0.87831007
о. 29277002
1.46385011]

ех.

std ())

',
() ) / ( ех . max () - ех. min () ) )
О. 2 О. 4 О. 6 О. 8 1. ]
нормализованные-значения: [ О.
priвt ('нормализованные значения:

(ех -

Подобно классу

ех. min

MinMaxScaler

в

scikit-learn

стандартизации:

--(164)····

также реализован класс для

fлава

>>>
>>>
>>>
>>>

4. Построение хороших обучающих наборов - предварительная обработка данных

from sklearn. preprocessing iшport StandardScaler
stdsc = StandardScaler ()
X_train_std = stdsc.fit_transform(X_train)
X_test_std = stdsc.transform(X_test)

Также важно отметить, что мы подгоняем класс

ко один раз

на обучающих данных

-

-

StandardScaler

толь­

и применяем найденные парамет­

ры для трансформирования испытательного набора или любой новой точки
данных.

В библиотеке

scikit-Ieam

доступные и другие, более развитые методы

для масштабирования признаков, такие как класс

RobustScaler

RobustScaler.

Класс

особенно удобен и рекомендуется в случае работы с неболь­

шими наборами данных, которые содержат много выбросов. Аналогично,
если алгоритм МО, применяемый к этоиу набору данных, устойчив к
переобучению, то класс

RobustScaler

может оказаться хорошим вариан­

том. Работая с каждым столбцом признака независимо, класс

RobustScaler

удаляет медианное значение и масштабирует набор данных в соответствии

с 1-м и 3-м квартилем набора данных (т.е. 25-й и 75-й квантили), так что
более экстремальные значения и выбросы становятся менее резко выражен­
ными. Заинтересованные читатели могут найти дополнительную информа­

цию о классе

RobustScaler

в официальной документации по

scikit-learn:

https://scikit-learn.org/staЫe/modules./generated/sklearn.

preprocessing.RobustScaler.html.

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

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

3,

пере­

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

обобщается на новые данные; мы говорим, что модель имеет высокую дис­
персию. Причина переобучения состоит в том, что модель является слишком
сложной для имеющихся обучающих данных. Ниже перечислены общепри­
нятые решения, направленные на сокращение ошибки обобщения:



накопление большего объема обучающих данных;



введение штрафа за сложность через регуляризацию;

------- [ 165] ----

Глава

4.

Построение хороших обучающих наборов

-

предварительная обработка данньtх



выбор более простой модели с меньшим числом параметров;



понижение размерности данных.

Накопление большего объема обучающих данных часто неприменимо.
В главе

6

мы рассмотрим удобный прием, который позволяет проверить,

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

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

Реrуnяризация

L1 и L2

как штрафы за сnожность модеnи

Как известно из главы

3,

регуляризация

L2

представляет собой подход

к сокращению сложности модели путем штрафования крупных индивиду­
альных весов, при котором мы определяем норму

L2

весового вектора

w

следующим образом:
2

L2:

т

llwll2 = L w~
}=\

Другой

подход

регуляризация

к сокращению

сложности

модели

родственная

-

L1:

Ll:

l wl 1 = Ilw1I
/=1

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

L2

регуляризация

Ll

обычно

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

больше, чем обучающих образцов. В этом смысле регуляризацию

L 1 можно

понимать как прием для выбора признаков.

Геометрическая интерпретация реrуnяризации

L2

Как упоминалось в предыдущем разделе, регуляризация

L2

добавляет к

функции издержек член штрафа, который фактически приведет к менее экс-

(166) --------·--·-----··--------

Глава

4.

Построение хороших обучающих наборов

-

предварительная обработка данных

тремальным значениям весов по сравнению с моделью, обученной с нерегу­
ляризированной функцией издержек.

Чтобы лучше понять, каким образом регуляризация

L1

поддерживает

разреженность, давайте сделаем шаг назад и ознакомимся с геометрической

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

w 1 и w2•

Мы рассмотрим функцию издержек в виде суммы квадратичных ошибок

(SSE),

которая использовалась для

Adaline

в главе

2,

т.к. она сферическая

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

-

отыскать такое сочетание весовых коэффициентов, кото­

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

4.3

(точка в центре эллипсов).

Wz
Минимизировать издержки

Рис.

4.3.

Сведение к минимуму функции издержек для обучающих данных

Мы можем думать о регуляризации как о добавлении к функции изде­
ржек члена штрафа, чтобы поддерживать меньшие веса; другими словами,

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

эта концепция иллюстрируется для члена штрафа при регуляризации

-------[167)

L2.

4.4

fпава

4.

Построение хороших обучающих наборов

-

предварительная обработка данных

Минимизировать издержки

Минимизировать штраф

Рис.

4.4.

Увеличение сw1ы ре,~уляризации через параметр регуляризации Л

Квадратичный член штрафа регуляризации

L2

представлен на рис.

4.4

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

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

L2

-

выбрать точку, где круг регу­

пересекается с контурами функции издержек, не обложенной

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

L2.

Например, если увеличивать параметр регуляриза­

ции Л, в направлении бесконечности, то весовые коэффициенты станут фак­
тически нулевыми, что отмечается центром круга регуляризации
наша цель в рассмотренном

примере

-

L2.

Итак,

минимизировать сумму издержек,

не обложенных штрафом, плюс член штрафа. Это можно понимать как до­
бавление смещения и поддержку более простой модели, что сократит дис­
персию в условиях отсутствия достаточного объема обучающих данных для
подгонки модели.

------------------(168]----------

Глава

4.

Построение хороших обучающих наборов

-

Разреженные решения с реrуnяризацией

L1

Теперь давайте обсудим регуляризацию

L1

идея регуляризации

L 1 похожа

предварительная обработка данных

и разреженность. Основная

на ту, что мы раскрывали в предыдущем раз­

деле. Тем не менее, поскольку штраф регуляризации

L 1 выражается

суммой

абсолютных значений весовых коэффициентов (вспомните, что член регуля­
ризации

L2

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

ромбовидной формы (рис.

4.5).

Минимизировать издержки+ штраф
Минимизировать штраф

Рис.

4.5.

(w 1 =О)

Графическое представление регуляризации

Ll

На рис.

4.5 видно, что контур функции издержек касается ромба регуля­
ризации L 1 в точке w, = О. Так как контуры системы, подверженной регу­
ляризации L 1, оказываются остроконечными, более вероятно, что оптимум,
т.е. пересечение эллипсов функции издержек и границы ромба регуляриза­
ции

L 1,

расположится на осях, способствуя разреженности.

--------------(169)

Глава

4.

Построение хороших обучающих наборов

-

предварительная обработка данных

\\:~ Регуляризация L1 и разреженность

н~ Выяснение математических деталей того, почему регуляризация L 1
~аметку!

может приводить к разреженным решениям, выходит за рамки воп­
росов, рассматриваемых в книге. Если вам интересно, тогда можете
ознакомиться с великолепным сравнением регуляризации

разделе

3.4

книги

"The Elements of Statistical

L2

и

L1

в

Leaming"("Ocнoвы ста­

тистического обучения: интеллектуальный анализ данных, логичес­
кий вывод и прогнозирование", 2-е изд., пер. с англ., изд. "Диалек­
тика",

2020

г), написанной Тревором Хасти, Робертом Тибширани и

Джеромом Фридманом

(Springer Science+Business Media (2009

год)).

Для регуляризированных моделей, поддерживающих регуляризацию

в

scikit-learn

мы можем просто установить параметр

penal t

у в

' 11 '

L 1,

и по­

лучить разреженное решение:

>>> fr·om sklearn. linear_model шport LogisticRegression
>>> LogisticRegression(penalty='ll',
solver='liЫinear',

multi_class='ovr')
Обратите внимание, что нам также необходимо выбрать какой-то другой
алгоритм оптимизации (скажем,

sol ver='

liЫinear'

),

т.к. 'lЬfgs' в те­

кущий момент не поддерживает оптимизацию потерь с регуляризацией

Логистическая регрессия с регуляризацией

рованному набору данных

Wine,

L 1,

L 1.

примененная к стандартизи­

выдаст следующее разреженное решение:

>>> lr = LogisticRegression(penalty='ll',
C=l.O,
solver='liЫinear',

multi_class='ovr')
# Следует отметить, что C=l. О принимается по умолчанию.
# Вы можете увеличить или уменьшить значение С, чтобы сделать
# эффект регуляризации соответственно сильнее или слабее.
>>> lr.fit(X_train_std, y_train)
>>> print( 'Правильность при обучении:', lr.score (X_train_std, y_train))
Правильность при обучении: 1.0
>>> priнt ('Правильность при испытании:', lr. score (X_ test_std, y_test))
Правильность пр_и испытании: 1.0
Правильность при обучении и правильность при испытании (составляю­
щие

100%)

указывают на то, что наша модель идеально работает на обоих

наборах данных. В результате обращения к членам пересечения через атри-

[170] -------------·

Глава

бут

4.

Построение хороших обучающих наборов

lr. intercept

-

предварительная обработка данных

мы можем заметить, что возвращается массив с тремя

значениями:

>>> lr.intercept
array([-1.26346036, -1.21584018, -2.3697841 ])
Поскольку мы подгоняем объект

LogisticRegression

к многоклассо­

вому набору данных посредством подхода "один против остальных"
первое значение
против классов

2

это пересечение модели, которая подгоняется к
и

догнанной к классу

собой пересечение

(OvR),
классу 1

3, второе значение является пересечением модели, по­
2 против классов 1 и 3, а третье значение представляет
модели, подогнанной к классу 3 против классов 1 и 2:

>>> lr. coef
array ( [ [ 1. 24590762,

0.18070219,

о.

о.

о.

о.

2. 51028042],
[-1. 53680415, -о. 38795309,
-0.05981561, о.
о.
, -1.93426485,
-2.23137595]'
[ 0.13547047, 0.16873019,

0.74375939, -1.16141503,
1.16926815, о.
о.
0.54784923,
-о.

о.

99494046,
0.6681573'
1.23265994,

36508729,

о.
о.

0.35728003, о.
о.
, -2.43713947, о.
1.56351492, -0.81894749, -0.49308407,

о.

о.
о.

] ] )

Массив весов, к которому мы получаем доступ через атрибут

lr. coef_,

содержит три строки весовых коэффициентов, по одному весовому вектору
для каждого класса. Каждая строка состоит из

13

весов, и для вычисления

общего входа каждый вес умножается на соответствующий признак в
мерном наборе данных

13-

Wine:

z=wx
+"·+wт хт
О О

= L mj=O x.w.=w
J J

т

х

(!!. В scikit-learn атр~бут intercept _ соответствует w

н~ значениям w.1. для J > О.
3аметку!

В результате выполнения регуляризации

L 1,

0,

а

coef _ -

служащей методом для вы­

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

ально не относящимся к делу признакам в наборе данных
·---~(171]

Wine.

Но, строго

-------------·---

Глава

4.

Построение хороших обучающих наборов

-

предварительная обработка данных

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

В последнем примере главы, посвященном регуляризации, мы варьируем

силу регуляризации и строим график пути регуляризации

-

весовых коэф­

фициентов различных признаков для разных значений силы регуляризации:

>>> import matplotlib.pyplot as plt

»> fig = plt. figure ()
>>>

ах

= plt. subplot (111)

>>> colors = [ 'Ыuе', 'green', 'red', 'cyan',
'magenta', 'yellow', 'Ыасk',
'pink', 'lightgreen', 'lightЬlue',
'gray', 'indigo', 'orange']
>>> weights, params = [], []
>>> for с in np.arange (-4., 6.):
lr = LogisticRegression(penalty='ll', C=lO.**c,
solver='liЫinear',

multi_class='ovr', random_state=O)
lr.fit(X_train_std, y_train)
weights.append(lr.coef_[l])
params.append(lO**c)

>>> weights = np.array(weights)
>>> for column, color in zip(range(weights.shape[l]), colors):
pl t. plot (params, weights [:, column] ,
label=df_wine.columns[column + 1],
color=color)
>>> plt.axhline(O, color='Ыack', linestyle='--', linewidth=3)
»> plt.xlim( (10** (-5), 10**5])
>>> plt.ylabel('вecoвoй коэффициент')
>>> plt.xlabel ('С')
>>> plt .xscale ( 'log')
>>> plt.legend(loc='upper left')
>>> ax.legend(loc='upper center',
bbox_to_anchor=(l.38, 1.03),
ncol=l, fancybox=True)
>>> plt. show ()

-------------[172)--------

Глава

4.

Построение хороших обучающих наборов

предварительная обработка данных

-

Результирующий график, показанный на рис.
пониманию поведения регуляризации

L 1.

4.6,

содействует лучшему

Мы можем заметить, что веса

всех признаков будут нулевыми , если штрафовать модель с помощью силь­

ного параметра регуляризации (С<

0.01);

С

-

инверсия параметра регуля­

ризации Л..

Алкоголь
Яблочная кислота

Зола
Щелочность золы

Магний
Всего фенолов

Флавоноиды
Нефлавоноидные фенолы
Проантоцианидины
Интенсивность цвета

Оттенок

00280/00315 разбавленных вин
Пролин

-20
f--т-~--~~-~~~-~~~-~-~----j

10- 5

10 1

10 3

105

с

Рис.

4.6.

Графш.: пути регуляризации

Аnrоритмы посnедоватеnьноrо выбора признаков
Альтернативный способ сокращения сложности модели и избегания

переобучения предусматривает понижение размерности (1fimc11si01щ/iiy

1·«:i/11Uio11) посредством выбора признаков, что особенно полезно для не­
регуляризированных моделей. Существуют две главные категории приемов

понижения размерности: выбор признаков (/c11t1m'
признаков
множество

(lratim.'

sc/cctio11)

и выделение

extгacticm). При выборе признаков мы отбираем под­

исходных

признаков, тогда

как

при

выделении

признаков

мы

выводим информацию из набора признаков для построения нового подпро­
странства признаков.

В текущем разделе мы рассмотрим классическое семейство алгоритмов
выбора признаков. В следующей главе мы исследуем различные приемы вы­

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

Алгоритмы последовательного выбора признаков

-

это семейство пог­

лощающих ("жадных") алгоритмов поиска, которые применяются для по-

[ 1 7 3 ) - - - - --

- -- --

fлasa

Построение хороших обучающих наборов

4.

предварительная обработка данных

-

нижения первоначального d-мерного пространства признаков до k-мерного
подпространства признаков, где

k < d.

Мотивацией, лежащей в основе ал­

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

признаков, наиболее значимых для задачи. Цель такого отбора

-

повысить

вычислительную эффективность либо снизить ошибку обобщения модели
за счет удаления не имеющих отношения к делу признаков или шума, что

может быть полезно для алгоритмов, не поддерживающих регуляризацию.
Классический алгоритм последовательного выбора признаков называет­
ся последовательиым обратным выбором (set11н'11tiol l1щ·J.:н.·1ml scfc(fioн

SHS).

Он направлен на понижение размерности первоначального пространс­

тва признаков с минимальным спадом эффективности классификатора для
улучшения показателей вычислительной эффективности. В ряде случаев ал­
горитм

SBS

может даже усилить прогнозирующую мощь модели, если мо­

дель страдает от переобучения.

\\;~ Поrnощающие аnrоритмы поиска

н~ Поглощающие (".жадные") алгорит.wы делают локально оптималь­
эаметку!

ные отборы на каждой стадии задачи комбинаторного поиска и в целом дают субоптимальное решение задачи в отличие от а!/горuт.wов
исчерпывающего поиска, которые оценивают все возможные комби­
нации и гарантируют нахождение оптимального решения. Тем не
менее, на практике исчерпывающий поиск часто неосуществим в
вычислительном плане, тогда как поглощающие алгоритмы делают

возможным менее сложное и более эффективное с вычислительной
точки зрения решение.

Идея, лежащая в основе алгоритма

SBS,

довольно проста: алгоритм

SBS

последовательно удаляет признаки из полного набора признаков до тех
пор, пока новое подпространство признаков не станет содержать желатель­

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

J

и свести ее к минимуму. Вычисляемым этой функцией критерием может

быть просто разница в эффективности классификатора до и после удаления
индивидуального признака. Признаком, подлежащим удалению на каждой
стадии, будет тот, который доводит до максимума данный критерий; или,
выражаясь более понятными терминами, на каждой стадии мы исключаем

признак, удаление которого приводит к наименьшей потере эффективности.

------------(174)

Глава

4.

Построение хороших обучающих наборов

Опираясь на предыдущее определение

-

SBS,

предварительная обработка данных

мы можем представить алго­

ритм в виде четырех простых шагов.

1.

Инициализировать алгоритм с

k

= d,

где

d-

размерность полного про­

странства признаков Х, 1 •

2.

Определить признак х-, который доводит до максимума критерий

х-

= argmax J (Xk - х ),

где х Е

Xk.

3.

Удалить признак х- из набора признаков

4.

Закончить, если

k

xk 1 = xk -х-; k = k-1.

равно желательному количеству признаков; в про­

тивном случае перейти к шагу

2.

\\:~ Ресурс по алгоритмам последовательного выбора признаков

н~ Подробный анализ некоторых алгоритмов последовательного выбора
заметку!

признаков можно найти в работе Ф. Ферри, П. Пудила, М. Хатефа и

И. Киттлера

Selection"

"Comparative Study of Techniques for Large-Scale Feature

(Сравнительное изучение приемов крупномасштабного

выбора признаков), с.

К сожалению, алгоритм

403-413 (1994 г. ).

SBS

в библиотеке

scikit-learn

пока еще не реа­

лизован. Но поскольку он довольно прост, давайте реализуем его на

Python

самостоятельно:

f rom

sklearn. base impor·t clone

t rom i tertools import

cornЬinations

import numpy as np
from sklearn.metrics import accuracy_score
fгom sklearn.model_selection import train_test_split

class SBS () :
rief
ini t

( , , estimator, k _ features,
scoring=accuracy_score,
test_size=0.25, random_state=l):
· ·.scoring = scoring
'' .estimator = clone (estimator)
'1 .k features = k features
. · . test size = test size
. __ . random state = random state

def fit(

, Х, у):
X_train, X_test, y_train, y_test = \
train_test_split (Х, у, test_size= ..' ! .test size,
random state= '~:.random_state)

·[175]----------

fлава

4.

Построение хороших обучающих наборов

-

предварительная обработка данных

dim = X_train.shape[l]
~·' ~ Г .indices
= tuple (range (dim))
, _ :_ -" . suЬsets_ = [ '' --· ' : • indices _]
score = з

·:'F ~ -•~.

suЬsets =

for:

k features:

= []
[]

р in comЬinations (;;':J: .indices_, r=dim - 1):
score = ·,.·с: , ·- • _ calc_ score (Х_train, у_train,
X_test, y_test, р)
scores.append(score)
suЬsets.append(p)

best
"':

1_

::-••.1

г

= np.argmax(scores)
• indices

=

suЬsets

Г .suЬsets_.append(:'''

[best]
L.: . indices_)

dim -= 1
i • scores_.append(scores [best])
.k score = :"··•j: .scores_[-1]

.,,, 1

~"->>
>>>



= list(sbs.subsets_[lO])

print(df_wine.colшnns(l:] (kЗ])

Indех(['Алкоголь', 'Яблочная кислота',

'00280/00315

разбавленных вин'],

dtype='object')
В предыдущем коде мы получаем индексы столбцов поднабора, вклю­

чающего три признака, из 11-й позиции в атрибуте

sbs. subsets

и воз­

вращаем соответствующие имена признаков из столбцовоrо индекса раndаs­

объекта

DataFrame

для набора данных

(178]

Wine.

Глава

4.

Построение хороших обучающих наборов

-

Далее оценим эффективность классификатора

предварительная обработка данных

KNN

на исходном испыта­

тельном наборе:

>>> knn.fit(X_train std, у train)
>>> pri11t ('Правильность при обучении:',
knn.score(X_train_std, y_train))
Правильность при обучении: 0.967741935484
>>> print ('Правильность при испытании:',
knn.score(X_test_std, y_test))
Правильность при испытании: 0.962962962963
В коде мы применяем полный набор признаков и получаем правильность
почти

97%

на обучающем наборе и правильность около

96%

на испытатель­

ном наборе, что говорит о наличии у модели хорошей способности обоб­
щения на новые данные. А теперь возьмем выбранный поднабор из трех
признаков и посмотрим, насколько хорошо работает классификатор

KNN:

>>> knn.fit(X_train_std[:, k3], y_train)
>>> p.rint~ ('Правильность при обучении:',
knn.score(X_train_std[:, k3], y_train))
Правильность при обучении: 0.951612903226
>>> p:rint ('Правильность при испытании:',
knn.score(X_test_std[:, k3], y_test))
Правильность при испытании: 0.925925925926
При использовании менее четверти исходных признаков в наборе данных

Wine

правильность прогноза на испытательном наборе слегка ухудшилась.

Это может указывать на то, что выбранные три признака не предоставляют

менее отличительную информацию, чем исходный набор данных. Однако
мы также должны иметь в виду, что набор данных

Wine

имеет небольшой

размер, а потому он крайне чувствителен к случайности, т.е. к способу его
разделения на обучающий и испытательный поднаборы, и к тому, каким об­

разом обучающий набор данных дополнительно делится на обучающий и

проверочный поднаборы.
Хотя мы не увеличили эффективность модели

KNN,

уменьшив количест­

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

- - - - - - - - - - - - - - - - - - [ 1791 ----------·-·------·---

fлава

Построение хороших обучающих наборов

4.

предварительная обработка данн111х

-

\\:~ Алгоритмы выбора признаков в библиотеке scikit-learn

н~ В библиотеке scikit-learn доступно много других алгоритмов выбо­
эаметку!

ра признаков. В их число входят рекурсивное обратное исключение

(1·cu11·si1 c
1

lюckн·m·il

eli111i1111/i1111)

на основе весов признаков, мето­

ды выбора признаков по важности, основанные на деревьях, и од­

номерные статистические критерии. Всеобъемлющее обсуждение

разнообразных методов выбора признаков выходит за рамки этой
книги, но хорошую сводку с иллюстративными примерами

можно

найти по ссылке

http: //scikit-learn.org/staЬle/modules/
feature selection. html. Реализации нескольких вариантов пос­

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

ранее простому алгоритму

SBS, доступны в пакете Python по имe­
mlxtend: http://rasbt.github.io/mlxtend/user_guide/
feature_selection/SequentialFeatureSelector/.

ни

Оценка важности признаков

с помощью сnучайных nесов
В предшествующих разделах вы узнали, как применять регуляризацию

L1

для обнуления не имеющих отношения к делу признаков через логис­

тическую регрессию, а также как использовать алгоритм
признаков и применять его в классификаторе

KNN.

SBS

для выбора

Еще один подход к вы­

бору значимых признаков из набора данных предусматривает использова­
ние случайного леса

-

ансамблевого приема, который был введен в главе

3.

Применяя случайный лес, мы можем оценивать важность признаков как ус­
редненное уменьшение загрязненности, рассчитанное из всех деревьев при­

нятия решений в лесе, не выдвигая никаких предположений о том, являются

наши данные линейно сепарабельными или нет. Удобно то, что реализация

случайных лесов в

scikit-\eam

уже собирает показатели важности признаков,

к которым можно получать доступ через атрибут

feature _ importances _
RandomForestClassifier. Выполнив
обучим лес из 1О ООО деревьев на наборе дан­

после подгонки классификатора
приведенный ниже код, мы
ных
ти

-

Wine

и расположим

вспомните из главы

13 признаков в порядке их показателей важнос­
3, что в моделях на основе деревьев использовать

стандартизированные или нормализованные признаки не обязательно:

(180]--

f11ава

>>> from

4.

Построение хороших обучающих наборов

sklearn.ensemЫe

-

предварите11ьная обработка данных

import RandomForestClassifier

>>> feat_labels = df_wine.colurnns[l:]
>>> forest = RandomForestClassifier(n_estimators=500,
random_state=l)
>>> forest.fit(X_train, y_train)
>>> importances = forest.feature_importances_
>> > indi ces = np. argsort (importances ) [ : : -1]
>>> for f in range{X_train.shape[l]):
priг.t{"%2d) %-*s %f" % (f + 1, 30,
feat_labels[indices[f]],
importances[indices[f]]))
>>> pl t. ti tle ( 'Важность признаков' )
>>> plt.bar(range(X_train.shape[l]),
importances[indices],
align=' center' )
>>> plt.xticks{range{X_train.shape[l]),
feat_labels[indices] rotation=90)
»> plt.xlim{ [-1, X_train.shape[l]])
»> plt.tight_layout()
»> plt. show {)
1) Пролин
0.185453
2) Флавоноиды
0.174751
3) Интенсивность цвета
0.143920
4) OD280/0D315 разбавленных вин 0.136162
5) Алкоголь
0.118529
6) Оттенок
0.058739
7) Всего фенолов
0.050872
8) Магний
0.031357
9) Яблочная кислота
0.025648
10) Проантоцианидины
0.025570
11) Щелочность золы
0.022366
12) Нефлавоноидные фенолы
0.013354
13) Зола
0.013279
В результате выполнения кода отображается график, который располагает

признаки в наборе данных

Wine

по их относительной важности (рис.

4.8);

обратите внимание, что показатели важности признаков нормализованы, в
сумме давая

1.0.

---·~·

[ 181 1",______ _

[лава

4.

Построение хороших обучающих наборов

-

предварительная обработка данн111х

Важность признаков

0.15
0.10
0.05
0.00
:i:

JS

>> sfrn = SelectFromМodel (forest, threshold=O .1,
>>> X_selected = sfrn.transforrn(X_t-rain)
>>> рrint('Количество признаков, удовлетворяющих
критерию порога: ' ,
X_selected.shape[l])
Количество признаков,

prefit='Гrue)
данному

удовлетворяющих данному критерию порога:

5

>>> f'or f in range (X_selected. shape [1]):
print("%2d) %-*s %f" % (f + 1, 30,
feat_labels[indices[f]],
irnportances[indices[f]]))
1) Пролин
0.185453
2) Флавоноиды
0.174751
3) Интенсивность цвета
0.143920
4) 00280/00315 разбавленных вин
0.136162
5) Алкоголь
о .118529

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

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

Кроме того, мы кратко обсудили регуляризацию

L 1,

которая может по­

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

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

при выборе признаков.

-----------(183)--------·

5
СЖАТИЕ ДАННЫХ
С ПОМОЩЬЮ ПОНИЖЕНИЯ
РАЗМЕРНОСТИ

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

три фундаментальных приема, которые помогут подытожить информацион­

ное содержимое набора данных, трансформируя его в новое подпространс­
тво признаков с меньшей, чем у исходного набора данных размерностью.

Сжатие данных

-

важная тема в МО; сжатие содействует более эффектив­

ному хранению и анализу растущих объемов данных, которые вырабатыва­
ются и накапливаются в современную технологическую эпоху.

В главе будут раскрыты следующие темы:



анализ главных компонентов

(/nim·ipal сотропс11t a11alysis --- РСЛ) для

сжатия данных без учителя;



линейный дискриминантный анШ1uз (lim:aг tlisaimiщmt

tmalysis --- UM)

как прием понижения размерности без учителя для доведения до мак­
симума сепарабельности классов;



нелинейное понижение размерности посредством ядерного анализа

главных компонеитов

(kernel

1н·i11Cipal

comp011cnl analysis ----

КРСА).

Глава

5.

Сжатие данных с помощью понижения размерности

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

использовании алгоритмов выбора признаков, таких как последователь11ый
обратный выбор, первоначальные признаки сохраняются, то выделение

признаков применяется для трансформирования или проецирования данных
в новое пространство признаков.

В контексте понижения размерности выделение признаков можно пони­

мать как подход к сжатию данных с целью сохранения большей части значи­
мой информации. На практике выделение признаков используется не только
для улучшения характеристик пространства хранения или вычислительной

продуктивности алгоритма обучения. Оно способно также повысить эффек­
тивность прогнозирования за счет ослабления "проклятия размерности"

-

особенно при работе с нерегуляризированными моделями.

Основные шаrи при анаnизе rnавных компонентов
В текущем разделе мы обсудим РСА

-

прием линейной трансформации

без учителя, который широко применяется в разнообразных областях, на­
иболее значительными из которых являются выделение признаков и пони­
жение размерности. Другие популярные области использования РСА вклю­
чают исследовательский анализ данных и устранение шумов в сигналах при

торговле на фондовой бирже, а также анализ данных о геномах и уровнях
проявления генов в биоинформатике.
Анализ главных компонентов помогает распознавать шаблоны в данных,
основываясь на корреляции между признаками. Выражаясь кратко, анализ
РСА нацелен на обнаружение направлений максимальной дисперсии в дан­
ных высокой размерности и проецирование их в новое подпространство с

равным или меньшим числом измерений. Ортогональные оси (главные ком­
поненты) нового подпространства можно интерпретировать как направления
максимальной дисперсии при условии ограничения, что новые оси призна­

ков ортогональны друг к другу (рис.

5.1 ).

[ 186)

[лава

5. Сжатие данных с помощью понижения размерности

.········· ·····--·- ..........PCl
РС2 ~
....-....- о а\,

//О

. .ь· о

(

О\

о

о

о о

:

_/.!

Q.·/

\р о q~///

Рис.

Здесь х 1 и х 2 -

5.1.

Главные кшwпоненты

это исходные оси признаков, а

PCl

и РС2

-

главные

компоненты .

Когда мы применяем РСА для понижения размерности, то строим (dхk)­

мерную матрицу трансформации

W,

позволяющую отобразить вектор х с

признаками обучающего образца на новое k-мерное подпространство при­
знаков, которое имеет меньше измерений, чем исходное d-мерное про­
странство признаков. Ниже описан процесс. Предположим, у нас есть век­
тор признаков х:

х

= [х 1 , х2 ,

""

xd], х Е m_d

который затем трансформируется посредством матрицы трансформации
WElli.dxk:

xW=z
давая выходной вектор:

z = [z 1, z2,

""

zd],

z Elli.k

В результате трансформации исходных d-мерных данных в новое k-мер­
ное подпространство (обычно

k >> iшport pandas as pd
>>> df_wine = pd.read_csv('https://archive.ics.uc~.edu/ml/'

'machine-learning-databases/wine/wine.data',
header=None)
\\:~ Получение набора данных Wine

н~ Копия набора данных Wine (и всех других наборов данных, исполь­
заметку! зуемых в книге) включена в состав загружаемого архива с кодом
примеров для книги. Указанные копии можно задействовать при ав­

тономной работе или в случае временной недосrупности

https: / /
archive.ics.uci.edu/ml/machine-learning- databases/
wine/wine. data на сервере UCI. Скажем, чтобы загрузить набор
данных Wine из какого-то локального каталога, следующий оператор:
df = pd.read_csv('https://archive.ics.uci.edu/ml/'
'machine-learning-databases/wine/wine.data',
header=None)
понадобится заменить таким оператором:

df = pd. read_ csv ( 'ваш/локальный/путь/к/winе. data',
header=Noпe)

Затем мы разделим данные
(включающие

70%

и

30%

Wine

на обучающий и испытательный наборы

данных соответственно) и стандартизируем при­

знаки, приведя к единичной дисперсии:

>>> from sklearn.model selection import train_test_split
>>> Х, у= df_wine.iloc[:, 1:] .values, df_wine.iloc[:, 0] .values
>>> X_train, X_test, y_train, y_test = \

train_test_split(X, у, test_size=0.3,
stratify=y,
random_state=O)
>>> # стандартизировать признаки
>>> froщ sklearn.preprocessing import StandardScaler
>>> sc = StandardScaler ()
>>> X_train_std = sc.fit_transform(X_train)
>>> Х test std = sc.transform(X_test)

-----[189]------------

Глава

5.

Сжатие данных с помощью понижения размерности

После завершения обязательной предварительной обработки путем вы­
полнения приведенного выше кода мы переходим ко второму шагу: построе­

нию ковариационной матрицы. Симметричная (dхd)-мерная ковариационная
матрица, где

количество измерений в наборе данных, хранит попарные

d-

ковариации между разными признаками. Например, ковариация между дву­

мя признаками х1 и

xk на уровне генеральной совокупности может быть вы­

числена посредством следующего уравнения:

-

'7ik -

1 ~(

(i)

n -1 ~ Х; - µJ

)
)( (i)
Xk - µk

/=)

Здесь

µ1 и µk -

выборочные средние признаков j и

k.

Обратите внимание,

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

рицательная

-

что признаки изменяются в противоположных направлени­

ях. Скажем, ковариационную матрицу для трех признаков можно записать,

как показано ниже (не путайте прописную греческую букву

L

("сигма") с

символом, обозначающим сумму):

Собственные векторы ковариационной матрицы представляют главные
компоненты (направления максимальной дисперсии), а соответствующие

собственные значения будут определять их величину. В случае набора дан­
ных

Wine

мы получим

( l 3x 13)-мерной

13

собственных векторов и собственных значений из

ковариационной матрицы.

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

собственный вектор

v

удовлетворяет следующему условию:

LV = AV
Здесь Л представляет собой скаляр

-

собственное значение. Поскольку

ручное вычисление собственных векторов и собственных значений

-

от­

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

[ 190]

fпава

риационной матрицы

Wine

5.

Сжатие данн111х с помощью понижения размерности

мы будем применять функцию

linalg. eig

из

NumPy:
>>>
>>>
>>>
>>>

import numpy as np

cov_mat = np.cov(X_train_std.T)
eigen_vals,eigen_vecs = np.linalg.eig(cov_mat)
prin t ( '\nСобственные значения \n%s' % eigen_vals)

Собственные значения

[ 4.84274532 2.41602459
0.6620634
0.51828472
о. 21357215
о .15362835

1.54845825 0.96120438
0.34650377 0.3131368
о .1808613 ]

С использованием функции

numpy. cov

0.84166161
0.10754642

мы вычисляем ковариационную

матрицу стандартизированного обучающего набора данных. С применением
функции

linalg. eig мы выполняем разложение по собственным значени­
(eigen_vals), состоящий из 13 собственных значений,

ям, что дает вектор

и соответствующие собственные векторы, которые хранятся как столбцы в
(13х13)-мерной ковариационной матрице

(eigen_vecs).

Га~ Разложение собственных значений в NumPy

н~ Функция numpy. linalg. eig была спроектирована для работы с
заметку!

симметричными и несимметричными квадратными матрицами. Однако вы можете обнаружить, что в определенных случаях она воз­

вращает комплексные собственные значения.
Связанная с

numpy. linalg. eig

функция,

numpy. linalg. eigh,
(Hennitia11) матриц,

была реализована для разложения эрмитовых

которые обеспечивают численно более устойчивый подход к работе
с симметричными матрицами, такими как ковариационная матрица;

numpy. linalg. eigh

всегда возвращает вещественные собствен­

ные значения.

Поnная и объясненная дисперсия
Так как мы хотим понизить размерность набора данных, сжимая его в но­
вое подпространство признаков, мы выбираем только поднабор собственных
векторов (главных компонентов), которые содержат большую часть инфор­
мации (дисперсии). Собственные значения определяют величину собствен­
ных векторов, поэтому мы должны отсортировать собственные значения в
порядке убывания величины; нас интересуют верхние
------------·

~-----

[ 1911

k

собственных век-

[лава

5.

Сжатие данных с помощью понижения размерности

торов на основе величин соответствующих собственных значений. Но пре­
жде чем собирать

k

наиболее информативных собственных векторов, давай­

те построим график с коэффициентами объяснеююй дисперсии (1·m·im1cc

i!xplai11cil mtio) собственных значений. Коэффициент объясненной диспер­
сии собственного значения Л1 представляет собой просто долю собственного
значения ~в общей сумме собственных значений:

Коэффициент объясненной дисперсии

=

Л,j

-~"

d

L..Jj=I

Затем с использованием функции

cumsum

из

NumPy

А.J

мы можем подсчи­

тать кумулятивную сумму объясненных дисперсий и отобразить ее на гра­
фике посредством функции

step

из

Matplotlib:

>>> tot = sum(eigen_vals)
>>> var_exp = [ (i / tot) :f.or i in
sort:(,xI (eigen_vals, reverse=True)]
>>> cum_var_exp = np.cumsum(var_exp)
>>> import matplotlib.pyplot as plt
>>> plt.bar(range(l,14), var_exp, alpha=0.5, align='center',
lаЬеl='индивидуальная объясненная дисперсия')

>>> plt.step(range(l,14), cum_var_exp, where='mid',
lаЬеl='кумулятивная объясненная дисперсия')

>>>
>>>
>>>
>>>
>>>

рlt.уlаЬеl('Коэффициент объясненной дисперсии')

plt.xlabel ('Индекс главного
plt.legend(loc='best')
plt.tight_layout()
plt. show ()

Результирующий график (рис.

5.2)

компонента')

показывает, что на долю лишь одного

первого главного компонента приходится приблизительно

40%

дисперсии.

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

объясняют почти

60%

дисперсии в наборе данных.

Хотя график объясненной дисперсии в чем-то похож на показатели важ­
ности признаков, вычисляемые в главе

напомнить себе, что РСА

-

4

через случайные леса, мы должны

метод без учителя, т.е. информация о метках

классов игнорируется. В то время как случайный лес применяет информа­
цию членства в классах для подсчета показателей загрязненности узлов,

дисперсия измеряет разброс значений вдоль оси признака.

-----------------(192]---

Глава

5.

Сжатие данных с помощью пониж е ния размерности

1.0
:s:
:s:
(,)
а.

ф

0.8

с

(,)

:s:
q

>:S:
о

::r::
::r::
ф
::r::

0.6

-

(,)

кумулятивная объясненная дисперсия
индивидуальная объясненная дисперсия

°'



о

0.4

1-

::r::

ф

:s:
::r
:s:

-в-в-

0.2

(')
о

:..::
о. о

2

о

6

4

10

8

12

14

Индекс главного компонента

Рис.

5.2.

График объясненной дисперсии

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

Wine

в новые оси главных компонентов. Ниже пере­

числены оставшиеся шаги , которыми мы займемся в настоящем разделе:

1)

выбор

k

собственных векторов, которые соответствуют

шим собственным значениям, где
странства признаков

2)

k-

k

наиболь­

размерность нового подпро­

(k :s; d);

построение матрицы проекции

W

из "верхних"

k

собственных век­

торов;

3)

трансформация d-мерного входного набора данных Х с использова­
нием матрицы проекции

W

для получения нового k-мерного подпро­

странства признаков.

Или, выражаясь менее формально, мы отсортируем собственные пары в

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

-[193)--·- - - -

Глава

5.

Сжатие данных с помощью понижения размерности

Мы начнем с сортировки собственных пар в порядке убывания собствен­
ных значений:
>>>#создать список кортежей(собственное значение, собственный вектор)

>>> eigen_pairs = [(np.abs(eigen_vals[i]), eigen_vecs[:, i])
for i in

raпge

(len (eigen_vals))]

>>> # отсортировать кортежи (собственное значение,
>>> # собственный вектор) от высоких к низким
>>> eigen_pairs. sort ( key=lamЬda k: k [О], reverse='Гrue)
Далее мы соберем два собственных вектора, которые соответствуют двум

наибольшим собственным значениям, чтобы захватить около

60%

диспер­

сии в наборе данных. Обратите внимание, что мы выбрали два собственных
вектора только

в

целях

иллюстрации, т.к.

позже

в

подразделе

планируем

нарисовать двумерный график рассеяния данных. На практике количество
главных компонентов должно определяться компромиссом между вычисли­

тельной продуктивностью и эффективностью классификатора:

>>> w = np.hstack ( (eigen_pairs [О] [1] [:, np.newaxis],
eigen_pairs[l] [1] [:, np.newaxis]))

>>> print ('Матрица W: \n',
Матрица

w)

W:

[[-0.13724218
[ 0.24724326
[-0.02545159
[ 0.20694508
[-0.15436582
[-0.39376952
[-0.41735106
[ о. 30572896
[-0.30668347
[ о. 07554066
[-0.32613263
[-0.36861022
[-0.29669651

0.50303478]
о .16487119]
о. 24456476]
-0.11352904]
0.28974518]
0.05080104]
-0.02287338]
0.09048885]
0.00835233]
о. 54977581]
-0.20716433]
-0.24902536]
0.38022942]]

В результате выполнения предыдущего кода мы создаем

матрицу проекции

W

(1 Зх2)-мерную

из двух верхних собственных векторов.

---------(194]------·

Глава

5.

Сжатие данных с помощью понижения размерности

\.\:~ Зеркальные проекции

н~ В зависимости от используемых версий NumPy и LAPACK вы може­
заметку!

те получить матрицу

W

с противоположными знаками. Следует отме-

тить, что это не проблема; если

v-

собственный вектор матрицы

L,

то мы имеем:

LV =
Здесь

v-

Лv

собственный вектор и

-v -

тоже собственный вектор,

что мы можем доказать следующим образом. Вспомнив базовую ал­

гебру, мы можем умножить обе стороны уравнения на скаляр а:

aLv = аЛv
Поскольку перемножение матриц ассоциативно для умножения на
скаляр, мы можем затем сделать такую перестановку:

I:(av)
Теперь видно, что av -

= Л(аv)

собственный вектор с тем же самым соб­

ственным значением Л, для а

= 1и

а

= -1.

Отсюда

v

и

-v

являются

собственными векторами.

С применением матрицы проекции мы можем трансформировать образец
х (представленный как 13-мерный вектор-строка) в подпространство РСА

(первый и второй главные компоненты), получив х'

-

теперь двумерный

вектор образца, который состоит из двух новых признаков:

x'=xW
>>> X_train_std[O] .dot(w)
array( [ 2.38299011, 0.45458499])
Подобным образом мы можем трансформировать весь (124х 13)-мерный

обучающий набор данных в два главных компонента, вычислив скалярное
произведение матриц:

X =XW
1

>>>

Х

train _pca

=

Х

train std. dot (w)

Наконец, давайте визуализируем трансформированный обучающий набор
Wiпe, хранящийся в (124х2)-мерной матрице, в виде двумерного графика
рассеяния:

(195)

Глава

5.

Сжатие данных с помощью понижения размерности

>>> colors = [ 'r', 'Ь', 'g']
>>> markers = [ 's', 'х', 'о']
>>> for 1, с, т in zip(np.unique(y_train), colors, markers):
plt.scatter(X_train_pca[y_train==l, О],
X_train_pca[y_train==l, 1],
с=с, label=l, marker=m)
>>> plt.xlabel('PC 1')
»> plt. ylabel ( 'РС 2')
>>> plt.legend(loc='lower left')
>>> plt.tight_layout()
>>> plt. show ()
На результирующем графике (рис.

видно, что данные больше разбро­

5.3)

саны вдоль оси х (первого главного компонента), нежели вдоль оси у (второ­

го главного компонента) , что согласуется с графиком объясненной диспер­
сии (см. рис.

5.2).

Тем не менее, мы можем интуитивно представлять, что

линейный классификатор вероятнее всего будет способен хорошо разделять
классы.

з

2

..• ••
....
•••••••

• •)•• •,,.

1



N

u
(l_

о

х•

-1

-2




х х

х
1

2

хх

Х

х

х

х

х

х

х

l
х

х

х

5.3. Двумерный

Ххх

х»ЖХ
х
х

х

-.с

х


х

х

-2

о
РС

Рис.

• •••• ••
• •• • •
• • •
• :'\' •• •


х

з

-4

••

••• 1


х

-3



2

4

1

график рассеяния д. 11Я трансфор~tироватюю
обучающею набора

Jf'ine

Хотя ради демонстрации на предыдущем графике (см. рис.

5.3)

мы коди­

ровали сведения о метках классов, необходимо иметь в виду, что РСА явля­
ется приемом без учителя, который не использует какую-либо информацию
о метках классов.

-[196)- - ----

Глава

5.

Анаnиз rnавных компонентов в

Сжатие данных с помощью понижения размерности

scikit·learn

Несмотря на то что подробный подход в предыдущем подразделе помог в
исследовании внутреннего устройства РСА, далее мы обсудим применение

класса РСА, реализованного в

scikit-leam.

Класс РСА- еще один класс-преобразователь из

scikit-leam,

где мы сна­

чала подгоняем модель, используя обучающие данные, а затем трансфор­
мируем обучающий и испытательный наборы данных с указанием тех же

самых параметров модели. Давайте применим класс РСА из
обучающему набору данных

Wine,

scikit-learn

к

классифицируем трансформированные

образцы посредством логистической регрессии и визуализируем области ре­

шений с использованием функции
определили в главе

plot_decision_regions,

которую мы

2:

fx·om matplotlib. colors import, ListedColorrnap

def plot_decision_regions(X,

у,

classifier, resolution=0.02):

#настроить генератор маркеров и карту цветов

markers = ( 's', 'х', 'о', 'л', 'v')
colors = ('red', 'Ьlue', 'lightgreen', 'gray', 'cyan')
стар= ListedColormap(colors[:len(np.unique(y))])
# вывести поверхность решения
xl_min, xl_ma:x = Х[:, О] .min() - 1, Х[:, О] .max() + 1
x2_min, x2_max = Х[:, 1) .min() - 1, Х[:, 1] .max() + 1
xxl, хх2 = np.meshgrid(np.arange(xl_min, xl_max, resolution),
np.arange(x2_min, x2_max, resolution))
Z = classifier.predict(np.array([xxl.ravel(), xx2.ravel()]) .Т)
Z = Z. reshape (xxl. shape)
plt.contourf(xxl, хх2, Z, alpha=0.4, cmap=cmap)
plt.xlim(xxl.min(), xxl.max())
plt. ylim (хх2. min () , хх2. max () )
#вывести образцы по классам

for idx, cl in enumez.·ate (np. unique

(у)):

== cl, О],
== cl, 1),
alpha=O. 6,
color=cmap(idx),

plt.scatter(x=X[y
у=Х[у

edgecolor='Ьlack',

marker=markers[idx],
label=cl)

------------·-·---[197] ------------------------

Глава

5.

Сжатие данных с помощью понижения размерности

Для удобства код функции

plot _ decision _ regions можно поместить
plot _ decision _
импортировать его в текущем сеансе Python.

в отдельный файл внутри рабочего каталога, например,

regions _ script. ру,
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

>>>
»>
>>>

и

f r om sklearn.linear_model iшpor t LogisticRegression
f roш sklearn. decomposi tion i rnpo rt РСА
# инициализация преобразователя РСА и оценщика
#

на основе логистической регрессии:

= PCA(n_cornponents=2)
lr = LogisticRegression(rnulti_class='ovr',
randorn_ state=l,
sol ver=' lblgs')

рса

#

понижение размерности:

X_train_pca = pca.fit_transforrn(X_train_std)
X_test_pca = pca.transforrn(X_test_std)
# подгонка модели, основанной на логистической регрессии,
# к сокращенному набору данных:
lr.fit(X_train_pca, y_train)
plot_decision_regions(X_train_pca, y_train, classifier=lr)
plt.xlabel ( 'РС 1')
plt.ylabel (' РС 2')
plt.legend(loc='lower left')
plt. tight_layout ()
plt. show ()

В результате выполнения предыдущего кода должны отобразиться облас­

ти решений для обучающих данных, приведенные к двум осям главных ком­
понентов (рис.

5.4).
4
о

з

D
D

2



1

о

о

1 11

ааа 1
х С3


~

t:P

Хх

~)(

-1

х

-2
х

1
2

о

з

Е!

-3

-4

о о
оО О

0>> for eigen_val in eigen_pairs:

k: k [0],

reverse=Tл1e)

порядке убывания:

\n')

print(eigen_val[O])
Собственные значения в порядке убывания:

349.617808906
172.76152219
З.78531345125е-14
2.11739844822е-14
1.51646188942е-14
1.51646188942е-14
1.35795671405е-14
1.35795671405е-14
7.58776037165е-15
5.90603998447е-15
5.90603998447е-15
2.25644197857е-15
о.о

В алгоритме
где с

-

LDA

число линейных дискриминантов не превышает с-

количество меток классов, т.к. матрица рассеяния внутри классов

представляет собой сумму с матриц с рангом

1

1,
S8

или меньше. На самом деле

мы можем заметить, что имеем только два ненулевых собственных значения

(собственные значения

3-13

не точно равны нулю, но это обусловлено осо­

бенностями арифметики с плавающей точкой в

NumPy).

\\::~ Коnnинеарность

н~ Обратите внимание, что в редком случае идеальной коллинеарности
заметку! (все выровненные точки образцов попадают на прямую линию) кова­
риационная матрица имела бы ранг

1,

и в итоге получился бы только

один собственный вектор с ненулевым собственным значением.

Чтобы измерить, сколько информации, различающей классы, захватыва­
ется линейными дискриминантами (собственными векторами), мы постро-

- - - - - - - - - - - (206)

Глава

5.

Сжатие данных с помощью понижения размерности

им график линейных дискриминантов по убыванию собственных значений,

похожий на график объясненной дисперсии, который создавался в разделе ,
посвященном РСА . Для упрощения мы будем называть содержимое инфор­

мации, различающей классы, разл ичимостью (tlisпi111i11abllity) :

>>> tot = sum(eigen_vals.real)
>>> discr = [ (i / tot) for i in sorted(eigen_vals.real, reverse=True)]
>>> curn_discr = np.curnsurn(discr)
>>> plt . bar(range(l, 14), discr, alpha=0.5, align='cente r',
label=' индивидуал ьная "различимость"' )
>>> plt.step(range(l, 14), curn_discr, where='mid',
lаЬеl='кумуляти вная "различимо с ть"')

>>> pl t . ylabel ( 'Коэффициент "ра зличимо сти"' )
>>> pl t. xlabel ( 'Линейные дискриминанты' )
>» plt.ylim((-0.1, 1.1])
>>> plt.legend(loc='best')
>>> plt.tight_layout()
»> pl t. show ()
На результирующем графике (рис.

5.7)

видно, что первые два л инейных

дискриминанта без посторонней помощи захватывают

формации в обучающем наборе

100%

полезной ин­

Wine.

1.0

J

's

....
(,)
о

0.8

~

s
s

':/"
с;

С'1

0.6

-

111
:

кумулятивная ··различимость"

а.

....

:z:

ф

s
:r
s
-&
-&
(')

индивидуальная "различимость"

0.4

0.2

о

=w::

о.о

о

2

4

б

8

10

12

Линейные дискриминанты

Рис.

5. 7.

График коэффицuеюпа "разл ичимости "

(207]

14

fлава

5.

Сжатие данных с помощью понижения размерности

Теперь давайте уложим в стопку два столбца с самыми различающими

собственными векторами, чтобы создать матрицу трансформации

W:

>>> w = np.hstack( (eigen_pairs[O] [1] [:, np.newaxis] .real,
eigen_pairs[l] [1] [:, np.newaxis] .real))
>>> p:i:·int('Maтpицa W:\n', w)
Матрица W:
[[-0.1481 -0.4092]
[ 0.0908 -0.1577]
[-0.0168 -0.3537]
0.3223]
[ 0.1484
[-0.0163 -0.0817]
0.0842]
[ 0.1913
0.2823]
[-0.7338
-0.0102]
(-0.075
[ о. 0018 0.0907]
[ о. 294 -0.2152]
0.2747]
[-0.0328
(-0.3547 -0.0124]
(-0.3915 -0.5958]]

Проецирование образцов в новое подпространство признаков
С применением матрицы трансформации

W,

созданной в предыдущем

подразделе, мы можем трансформировать обучающий набор, умножая мат­
рицы:

X'=XW
>>> Х train lda = Х train_std.dot(w)
>>> colors = [ 'r', 'Ь', 'g']
>>> markers = ( 's •, • х •, ' 0 1 ]
>>> fo1· 1, с, m in z1p (np. unique (y_train), colors, markers):
plt.scatter(X_train_lda[y_train==l, О],
X_train_lda[y_train==l, 1] * (-1),
с=с, label=l, marker=m)
>>> plt .xlabel ( 'LD 1')
>>> plt. ylabel ( 'LD 2')
>>> plt.legend(loc='lower right')
>>> plt.tight_Iayout()
>>> plt. show ()
На результирующем графике (рис.

5.8)

можно заметить, что в новом подпро­

странстве признаков три класса вин в полной мере линейно сепарабельны.

- - - - - - - - - - (208]

Глава

Сжатие данных с помо щью понижения размерности

5.

• ••
••

2

1•

....""·.r-," ••

1

·~

•81

о


.• ··1

• ·'••• •

х

. ..

~

N

Q

~



-1

'>St.x
х

~~х

~ х

х

х~ ~ х х

х

-2

х

х

х

~

х

х

х



х

х



х

-3

-2

- 1

о

1

1
2
з

2

LD 1

Рис.

5.8.

Результат проецирования образцов в новое подпространство признаков

Реализация

LDA

в

scikit-learn

Пошаговая реализация была хорошим упражнением для понимания внутрен­
него устройства

LDA, а таюке отличий

время взглянуть на класс

LDA,

между алгоритмами

реализованный в

LDA и РСА. Пришло
библиотеке scikit-learn:

>>> # следуюший оператор импортирования располагается в одной
>>> f rorn sklearn.discriminant_analysis
import LinearDiscriminantAnalysis as LDA
>>> lda = LDA(n_components=2)
>>> X_train_lda = lda . fit_transform(X_train_std, y_train )

строке

Далее мы посмотрим, как классификатор на основе логистической рег­

рессии обрабатывает обучающий набор меньшей размерности по сле транс­
формации

>>> lr
>>>
>>>
>>>
>>>
>>>

»>
»>

LDA:

LogisticRegression(multi_class=' ovr' , random_state=l,
sol ver=' lЬf gs' )
lr = lr. fit (X_train_lda, y_train)
plot_decision_regions(X_train_lda, y_train, classifier=lr)
pl t. xlabel ( 'LD 1' )
plt.ylabel('LD 2 ')
pl t. legend (loc=' lower left' )
plt. tight_ layout ()
plt. show ()
~

(209)

Глава

5.

Сжатие данных с помощью понижения размерности

На результирующем графике (рис.

5.9)

видно, что модель на основе ло­

гистической регрессии неправильно классифицировала один образец из
класса

2.

Рис.

5.9.

Результат работы классификатора на обучающем наборе

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

тно классифицировала бы все образцы в обучающем наборе данных. Тем
не менее (и это важнее), давайте выясним, какие результаты получаются на

испытательном наборе:

X_test_lda = lda.transform(X_test std)
plot_decision_regions(X_test_lda, y_test, classifier=lr)
plt.xlabel('LD 1')
plt. ylabel ( 'LD 2')
plt.legend(loc='lower left')
plt.tight_layout()
»> plt. show ()

>>>
>>>
>>>
>>>
>>>
>>>

На результирующем графике (рис.

5.1 О)

можно заметить, что классифика­

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

13

признаков набора данных

Wine.

(210)---- -

-

-

[лава

4

о
С!

Е1

С1

2

1%!
о

о

о ~f;) о

dJD С1

о

-'

Сжатие данных с помощью понижения размерности

cD
о

li3

QliЗ
flCJ

о
N

5.

о о

00

х

)(

-2
х

-4

хХ

ж<

х
х

X.f

1

-6

х

о

2
3

х

-4

-2

о

б

4

2

LD 1

Рис.

5. 1О.

Результат работы классификатора на испытателыюм наборе

Использование ядерноrо анализа

rлавных компонентов для нелинейных

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

требует идеально линейно сепарабельных обучающих данных . Дру гие рас­
крытые до сих пор алгоритмы предполагают, что идеальная линейная се­

парабельность отсутствует из-за шума:
(стандартный) метод

SVM -

Adaline,

логистическая регрессия и

перечень далеко не полон.

Однако если мы имеем дело с нелинейными задачами , которые весьма
часто встречаются в реальных приложениях, то приемы линейной трансфор­
мации для понижения размерности , такие как РСА и

LDA,

могут оказаться

не лучшим выбором .
В текущем разделе мы рассмотрим ядерную
КРСА, которая относится к концепции

(keme/i:;el/) версию
ядерного SVM из главы 3.

РСА , или
С приме­

нением ядерного РСА мы научимся трансформировать данные, не являющи­
еся линейно сепарабельными, в новое подпространство меньшей размернос­

ти , которое подойдет для линейных классификаторов (рис .

[211 )---

5.11 ).

Глава

5.

Сжатие данных с помощью понижения размерности

Нелинейная задача

Линейная задача

о о
о о

,,"

о

"
""

+
о "
о о
++
,/
о о о
" ++
о о о "" + + ++
о ,"" + +++ + +
,/ + +
"" + + +
о

о

о

Рис.

5.11.

о

о

о

о

о

Пpu.wep 11елиней11ой задачи к.?ассификации

Ядерные функции и ядерный трюк
Из обсуждения ядерных

SVM

в главе

3

несложно вспомнить, что мы мо­

жем решать нелинейные задачи путем проецирования на новое пространс­

тво признаков более высокой размерности, где классы становятся линейно

сепарабельными. Чтобы трансформировать образцы хе m._d в такое более вы­
сокое k-мерное подпространство, мы определяли нелинейную отображаю­

щую функцию > d)

Мы можем считать ф функцией, которая создает нелинейные сочетания

исходных признаков для отображения первоначального d-мерного набора
данных на более крупное k-мерное пространство признаков. Например, при

наличии вектора признаков хе m._d (х знаков) с двумя измерениями

(d = 2)

вектор-столбец, состоящий из d при­

потенциальным отображением на трех­

мерное пространство могло бы быть:

--------·---------·------------ (212) -· ··---·----·---------·-·-

Глава

5.

Сжатие данных с помощью понижения размерности

Другими словами, мы выполняем нелинейное отображение с помощью

ядерного РСА, трансформирующего данные в пространство более высо­
кой размерности. Затем в новом пространстве более высокой размерности

мы используем стандартный РСА, чтобы спроецировать данные обратно на
пространство меньшей размерности, где образцы могут быть разделены ли­
нейным классификатором (при условии, что образцы во входном пространс­
тве поддаются разделению по плотности). Тем не менее, недостатком та­
кого подхода являются большие вычислительные затраты и именно здесь
мы применяем ядерный трюк (k.т11el

trick).

Используя ядерный трюк, мы

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

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

дартным РСА, который был реализован в начале главы. Мы вычисляли ко­

вариацию между двумя признаками

~k

kиj

следующим образом:

- -1 ~
~( xJ(i) -µJ )( xk(i) -µk )
n

i=I

Поскольку стандартизация признаков центрирует их в нулевом среднем,
например,

µ1 и µk,

мы можем упростить уравнение, как показано ниже:

Обратите внимание, что предыдущее уравнение ссылается на ковариацию

между двумя признаками. А теперь запишем общее уравнения для вычисле­
ния ковариационной матрицы

I:

Учитывая то, что подход был обобщен Бернхардом Шолькопфом

principal component analysis"

("Kernel

("Ядерный анализ главных компонентов"),

Б. Шолькопф, А. Смола и К.Р. Мюллер, с.

583-588 (1997

г.)), мы можем за­

менить скалярные произведения образцов в исходном пространстве призна­

ков нелинейными комбинациями признаков посредством ф:

-------(213)

-----·---~-----------

[лава

5. Сжатие данных с помощью понижения размерности

Для получения собственных векторов

-

главных компонентов

-

из ко­

вариационной матрицы мы должны решить следующее уравнение:

LV=AV

Здесь .А и

v-

онной матрицы

собственные значения и собственные векторы ковариаци­

}.:;

а можно получить путем извлечения собственных векто­

ров из матрицы ядра (матрицы подобия) К, как вы увидите далее.
\\;.~ Ниже показано, как можно вывести матрицу ядра. Сначала давайте

1 ~ запишем ковариационную матрицу
~:метку! ф(Х) - пхk-мерная матрица:

в матричном представлении, где

Теперь мы можем записать уравнение собственного вектора следу­
ющим образом:

Так как

L:v

= Л.v, мы получаем:

Умножение уравнения с обеих сторон на ф(Х) дает следующий ре­
зультат:

- - - - - - (214] -----------------------

Глава

5.

Сжатие данных с помощью понижения размерности

_!_ф(Х)ф(Х)т Ф(Х)ф(Х( а=Лф(Х)ф(Х)т а
п

1

~-Ка=Ла
п

Здесь К- матрица подобия (матрица ядра):

К

= ф (Х) ф (Х) т

Как объяснялось в разделе "Решение нелинейных задач с применением

ядерного метода опорных векторов" главы

3,

мы используем ядерный трюк,

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

k,

так что явно вычис­

лять собственные векторы не понадобится:

Другими словами, после использования ядерного РСА мы получаем об­
разцы, уже спроецированные на соответствующие компоненты, а не строим

матрицу трансформации, как при стандартном РСА. В своей основе ядер­
ную функцию (или просто ядро) можно понимать как функцию, которая вы­

числяет скалярное произведение двух векторов

-

меру подобия.

Ниже перечислены наиболее широко применяемые ядра.



Полиномиальное ядро:

К, (x>> Х, у= make_moons(n_samples=lOO, random_state=l23)
>>> plt.scatter(X[y==O, 0], Х[у==О, 1],

color='red',

marker=•л•,

alpha=0.5)

>>> plt.scatter(X[y==l, 0], X[y==l, 1],
color='Ыue',

marker='o', alpha=0.5)

»> plt. tight_layout ()
>>> plt. show ()
В целях иллюстрации полумесяц из символов треугольника будет пред­
ставлять образцы одного класса, а полумесяц из символов круга
другого класса (рис.

1.0

......

.......

о. в

0.6
0.4

0.2
О. О

.

образцы

.. ...

..........................................

..
...

.../-

...

......
...

-

5.12).

...

......

...
...

...

......
...
......

.

...

~

••••
•••


...
"...

••

•••

.........."..

-0.2
-0.4

- 1.0
Рис.

- 0 .5

5.12.

О.О

0.5

...
.:
.
••

1.0

1.5

2.0

Классы фигур в фор«е полу.месяца

Очевидно, такие две фигуры в форме полумесяца не являются линейно
сепарабельными и наша цель

-

посредством ядерного РСА развернуть по­

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

--[219)--

[лава

5. Сжатие данных с помощью понижения размерности

набор данных, если его спроецировать на главные компоненты через стан­
дартный РСА:

>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

»>
>>>
>>>

»>


from sklearn.decomposition import

РСА

scikit__pca = PCA(n_components=2)
X_spca = scikit__pca.fit_transform(X)
fig, ах= plt.subplots(nrows=l,ncols=2, figsize=(7,3))
ах[О] .scatter(X_spca[y==O, О], X_spca[y==O, 1],
color=' red' , mar ker=' л ' , alpha=O . 5)
ах[О] .scatter(X_spca[y==l, О], X_spca[y==l, 1],
color='Ьlue', marker='o', alpha=0.5)
ax[l] .scatter(X_spca[y==O, 0], np.zeros((50,1))+0.02,
color=' red' , mar ker=' л ' , alpha=O. 5)
ax[l] .scatter(X_spca[y==l, 0), np.zeros((50,1))-0.02,
color='Ьlue', marker='o', alpha=0.5)
ах[О] .set_xlabel('PCl')
ах[О] .set_ylabel('PC2')
ax[l].set_ylim((-1, 1))
ax[l] .set_yticks([])
ax[l] .set_xlabel('PCl')
pl t. tight _layout ()
pl t. show ()

Безусловно, линейный классификатор не способен хорошо работать
на наборе данных, трансформированном посредством стандартного РСА
(рис.

5.13).

0.75
0.50
0.25
N

u
а..

0.00
-0 .25
- 0.50
-0 .75
- 1

о

PCl
Рис.

5.13.

1

- 1

о

1

PCl

Результат работы лuпейною классификатора на наборе данных.
трапсформироватюм посредством стандартного РСА

- - -- - - -- - - - -- [220]---- - - -- -·

Глава

5.

Сжатие данных с помощью понижения размерности

Обратите внимание, что при построении графика только первого глав­
ного компонента (правая часть графика) мы сместили образцы из треуголь­
ников слегка вверх, а образцы из кругов

-

чуть вниз, чтобы лучше видеть

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

-

эта трансформация не оказала линейному

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

зано в правой части графика.

\\°:~ РСА или LDA

н~ Не забывайте, что в противоположность LDA алгоритм РСА пред­
заметку!

ставляет собой метод без учителя и не использует информацию о
метках классов с целью доведения до максимума дисперсии. Симво­

лы кругов и треугольников здесь были добавлены для целей визуа­
лизации, чтобы показать степень разделения.

Давайте испытаем нашу функцию rЬf _ kernel _рса ядерного РСА, кото­
рую мы реализовали в предыдущем подразделе:

>>> X_kpca = rbf_kernel_pca(X, gamrna=15, n_components=2)
>>> fig, ах= plt.subplots(nrows=l,ncols=2, figsize=(7,3))
>>> ax[OJ .scatter(X_kpca[y==O, О], X_kpca[y==O, 1],
>>>
>>>
>>>
>>>
>>>

»>
»>
>>>
>>>
>>>

color=' red', marker='"', alpha=O. 5)
.scatter(X_kpca[y==l, О], X_kpca[y==l, 1],
color='Ыue', marker='o', alpha=0.5)
ax[l] .scatter(X_kpca[y==O, О], np.zeros((50,1) )+0.02,
color=' red', marker='"', alpha=O. 5)
ax[l] .scatter(X_kpca[y==l, О], np.zeros((50,1) )-0.02,
color='Ьlue', marker='o', alpha=0.5)
ах[О] .set_xlabel('PCl')
ах [О]. set_ylabel ( 'РС2')
ax[l] .set_ylim( [-1, 1])
ax[l]. set_yticks ( [])
ax[l] .set_xlabel('PCl')
plt.tight_layout()
plt.show()
ах[О]

------[221)----------

fлава

5.

Сжатие данных с помощью понижения размерности

На рис.

5.14

мы видим, что два класса (круги и треугольники) хорошо

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

0.1 5

('~.

0.10

......

0.05
N

u

а..

••...

0.00

mмн+

••

~~

-0.05

..·/'\••

......

li.

- 0 .10
-0.15

-0.1

о.о

0.1

о.о

-0 .1

PCl
Рис.

5.14.

0.1

PCl

Резул ьтат работы л инейного классификатора на наборе данных,

трансформировштом посредством алгоритма РСА с ядром

RBF

К сожалению, для параметра настройки у нет какого-то универсально­

го значения , которое хорошо бы работало с различными наборами данных.
Отыскание значения у, подходящего для заданной задачи, требует проведе­
ния экспериментов. В главе

6

мы обсудим приемы, которые могут помочь

нам автоматизировать задачу оптимизации таких параметров настройки.

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

Пример

2-

раздеnение концентрических окружностей

В предыдущем подразделе мы показали, как разделить фигуры в форме
полумесяца посредством ядерного РСА. Поскольку мы решили приложить

немало сил , чтобы осмыслить концепции ядерного РСА, давайте взглянем
на еще один интересный пример нелинейной задачи

-

разделение концен­

трических окружностей:

>>> fr om sklearn.datasets i mpor t make_circles
>>> Х, у = make-_circles (n_samples=lOOO,
random_state=123, noise=0.1,
factor=0.2)
>>> plt. scatter (Х [у == О, О], Х [у == О, 1),
color='red', marker=• л •, alpha=0 . 5)

· ----[222]

Глава

>>> plt.scatter(X[y == 1,

5.

О],

color='Ыue',

Сжатие данных с помощью пониже н ия размерности

== 1, 1],
marker=' o ', alpha=0.5)

Х[у

>>> plt.tight_layout()
>>> plt.show()
Мы снова предполагаем, что имеем дело с задачей с двумя классами, в

которой фигуры из треугольников представляют один класс, а фигуры из
кругов

-

другой класс (рис .

5.15).

1.0

0.5

О. О

- 0.5

- 1.0

- 1.0
Ри с.

5.15.

- 0.5

О.О

0.5

1.0

Задача разделения концентри ч еских окружност ей

Мы начнем с использования стандартного РСА , чтобы сравнить его ре­
зультаты с результатами применения РСА с ядром

RBF:

>>> scikit_pca = PCA(n_components=2)
>>> X_spca = scikit_pca.fit_transform (X)
>>> fig, ах= plt.subplots(nrows=l,nc ols=2, figsize=(7,3))
>>> ах [О] .scatter(X_spca[y==O, О], X_spca[y==O, 1],
>>>
>>>
>>>
>>>
>>>



color='red', marker=• л •, alpha=0.5)
.scatter(X_spca[y==l, О], X_spca[y==l, 1],
color='Ьl u e', marker=' o ', alpha=0.5)
ax[l] .scatter(X_spca[y==O, О], np. zeros( (500,1))+0.0 2 ,
color=' red ', marker= ' л ', alpha=O. 5)
ax [l] .scatter(X_spca[y==l, О], np.zeros((500,l))-0.0 2,
color=' Ы u е', marker=' о ', alpha=O. 5)
ах[О] .set_xlabel('PCl')
ах [О]. set_ylabel ( 'РС 2 ' )
ax [l] .set_ylim( (-1, 1])
ах[О]

[223)

[лава

5.

Сжатие данных с помощью понижения размерности

>» ax[l] . set_yticks([])
>>> ах [1]. set_xlabel ( 'PCl')
>>> plt.tight_layout()
>>> pl t. show ()
Мы опять видим, что стандартный РСА не способен выдать результаты ,
подходящие для обучения линейного классификатора (рис.

5.16).

1.0
0.5
N

u
а..

о.о

- 0.5
-1 .0
-1.0

-0.5

0.5

о.о

1.0

PCl
Рис.

5.16.

-1 .0

-0 .5

о.о

0.5

1.0

PCl

Резул ьтаты использования стандартного РСА

Имея подходящее значение для параметра у, давайте посмотрим, прине­
сет ли нам удачу применение реализации РСА с ядром RВF:

>>> X_kpca = rbf_kernel_pca(X, ganuna=15, n_components=2)
>>> fig, ах= plt.suЬplots(nrows=l,ncols=2, figsize=(7,3))
>>> ax[O].scatter(X_kpca[y==O, О], X_kpca[y==O, 1],
color='red', marker=•л•, alpha=0.5)
>>> ax[OJ . scatter(X_kpca[y==l, 0), X_kpca[y==l, 1),
color='Ьlue', marker=' o ', alpha=0.5)
>>> ax[l] . scatter(X_kpca[y==O, 0], np . zeros((500,1))+0 . 02,
color='red', marker=•л•, alpha=0 . 5)
>>> ax[l] . scatter(X_kpca[y==l, 0], np.zeros((500,1))-0 . 02,
color='Ьlue', marker='o', alpha=0.5)
>>> ах [О]. set_xlabel ( 'PCl')
>>> ах [0]. set_ylabel ( 'РС2')
»> ax[l] .set_ylim( [-1, 1))
>>> ax[l] .set_yticks([])
>>> ax[l] .set_xlabel('PCl')
>>> plt.tight_layout()
»> plt . show ()

-

- --(224) -

Глава

5.

Сжатие данных с помощью понижения размерности

Несложно заметить, что реализация РСА с ядром

RBF

спроецировала

данные на новое подпространство, где два класса становятся линейно сепа­

рабельными (рис .

5.17).

0.075
0.050

-

0.025
N

u

а..

О.ООО

-0.025
-0.050
-0.075

-0.04 -0.02 0.00

0.02

0.04

О . Об

PCl
Рис.

5.17.

-0.04 -0.02 0.00

0.02

0.04

О . Об

PCl

Результаты использования РСА с ядром

RBF

Проецирование новых точек данных
В двух предшествующих примерах применения ядерного РСА (разделение
фигур в форме полумесяца и разделение концентрических окружностей) мы
проецировали одиночный набор данных на новое подпространство признаков.
Однако в реальных приложениях мы можем иметь сразу несколько наборов
данных, подлежащих трансформированию, скажем, обучающие и испытатель­
ные данные, и обычно также новые образцы, которые будут собираться после
построения и оценки моделей. В этом разделе мы покажем, как проецировать

точки данных, не являющиеся частью обучающего набора данных.
Как объяснялось при рассмотрении стандартного РСА в начале главы, мы
проецируем данные

путем

вычисления скалярного произведения матрицы

трансформации и входных образцов; столбцами матрицы проецирования бу­
дут верхние

k собственных

векторов

(v),

которые получаются из ковариаци­

онной матрицы .

Вопрос теперь в том, как можно перенести данную концепцию на ядер­

ный РСА. Вспоминая идею, лежащую в основе ядерного РСА, мы получали
собственный вектор (а) центрированной матрицы ядра (не ковариационной
матрицы), а это значит, что они являются образцами, которые уже спроеци­
рованы на ось главного компонента

- --

v.

Таким образом, если мы хотим спро-

- -- - - - -- --[225]

Глава

5.

Сжатие данных с помощью понижения размерности

ецировать новый образец х' на указанную ось главного компонента, тогда
должны рассчитать следующую проекцию:

ф (х')т v
К счастью, мы можем воспользоваться ядерным трюком и не вычислять

проекцию ф(х')т v явно. Тем не менее, полезно отметить, что в отличие от
стандартного РСА ядерный РСА представляет собой метод, основанный на
памяти, т.е. каждый раз при проецировании новых образцов мы должны

повторно задействовать первоначальный обучающий набор.
Нам нужно вычислить попарно ядро

RBF

(подобие) между каждым i-тым

образцом в обучающем наборе данных и новым образцом х':

Ф(х')т v=Ia(i)ф(x')т Ф(х(i))
i

=

Ia(i)к(x', x(i))
i

Собственные векторы а и собственные значения Л, матрицы ядра К в
уравнении удовлетворяют следующему условию:

Ка= Ла
После вычисления подобия между новыми образцами и образцами в обу­

чающем наборе мы должны нормализовать собственный вектор а по его
собственному значению. Итак, модифицируем реализованную ранее функ­
цию rЬf _ kernel _pca, чтобы она возвращала также и собственные значе­
ния матрицы ядра:

from scipy. spatial. distance import pdist, squareform
froш scipy iшport ехр
from scipy. linalg impor·t eigh
import numpy as np
def rbf_kernel_pca(X, gamma, n_components):

"""
Реализация алгоритма

РСА с ядром

RBF.

Параметры

Х:

{NumPyndarray},

форма

[n_examples, n_features]

gamma: float
Параметр настройки ядра

RBF
(226)----~

Глава

5.

Сжатие данных с помощью понижения размерности

n_components: int
Количество главных компонентов,

подлежащих возвращению

Возвращает

alphas: {NumPy ndarray},

форма

[n_examples, k features]

Спроецированный набор данных
lamЬdas:

список

Собственные значения

"" ,,
#

Рассчитать попарные квадратичные евклидовы расстояния

#в Мх.N-мерном наборе данных.

sq_dists = pdist

#

(Х,

'sqeuclidean' )

Преобразовать попарные расстояния в квадратную матрицу.

mat_sq_dists = squareform(sq_dists)
#

Вычислить симметричную матрицу ядра.

К

=

ехр

(-gamma * mat_sq_dists)

# Центрировать матрицу ядра.
N = К. shape [О]
one_n = np.ones( (N,N)) / N
К= К - one_n.dot (К) - K.dot (one_n) + one n.dot
#

(К)

.dot (one n)

Получить собственные пары из центрированной матрицы ядра;

# scipy. linalg. eigh

возвращает их в порядке по возрастанию.

eigvals, eigvecs = eigh(K)
eigvals, eigvecs = eigvals[::-1], eigvecs[:, ::-1]

#

Собрать верхние

k

собственных векторов

#(спроецированных образцов).

alphas = np.column_stack([eigve cs[:, i]
for i in ra.nge(n_components)])

#

Собрать соответствующие собственные значения.

lamЬdas

= [eigvals [i] for i in ra.nge (n_components)]

return alphas,

lamЬdas

А теперь создадим новый набор данных с фигурами в форме полумесяца
и спроецируем его в одномерное подпространство, применяя модифициро­
ванную реализацию РСА с ядром

RBF:

>>> Х, у= make_moons(n_samples=lO O, random_state=123)
>>> alphas, lamЬdas = rbf_kernel_pca(X, gamma=lS, n_components=l)

(227]

Глава

5.

Сжатие данных с помощью понижения размерности

Чтобы проверить, реализовали ли мы код для проецирования новых об­

разцов, предположим, что 26-я точка из набора данных с фигурами в форме
полумесяца является новой точкой данных х', и наша задача

спроециро­

-

вать ее в новое подпространство:

>>> x_new = Х[25]
>>> х new
array( [ 1.8713187 , 0.00928245])
# первоначальная проекция
>>> x__proj = alphas [25]
>>> x__proj
array( [ 0.07877284])
>>> def project_x (x_new, Х, gamma, alphas, larnЬdas):
pair_dist = np.array( [np.sum(
(x_new-row) **2) for row in
k = np. ехр (-gamma * pair_dist)
:r:et11rn k.dot (alphas / lamЬdas)

Х])

После выполнения приведенного ниже кода мы в состоянии воспроизвес­
ти первоначальную проекцию. С использованием функции

proj ect _ х

мы

также сможем проецировать любой новый образец данных. Вот как выгля­
дит код:

>>> x_reproj = project_x(x_new, Х,
gamma=15, alphas=alphas,
lamЬdas=lamЬdas)

>>> x_reproj
array( [ 0.07877284])
Наконец, давайте визуализируем проекцию на первом главном компоненте:

>>> plt.scatter(alphas[y==O, О], np.zeros((50)),
color='red', rnarker='л',alpha=0.5)
>>> plt.scatter(alphas[y==l, 0], np.zeros((50)),
color='Ыue', rnarker='o', alpha=0.5)
>>> plt.scatter(x_proj, О, color='Ьlack',
label=' первоначальная проекция точки
rnarker=•л•, s=100)
>>> plt.scatter(x_reproj, О, color='green',

Х

(25] ',

lаЬеl='повторно отображенная точка Х[25]

»>
>>>
>>>
>>>

- rnarker='x', s=500)
plt. yticks ( [], [] )
plt.legend(scatterpoints=l)
plt.tight_layout()
plt. show ()

------- [228) -·· -

',

Глава

5.

Сжатие данных с помощью понижен ия размер ности

На результирующем графике рассеяния (рис.

5.18)

видно, что образец х'

корректно отобразился на первый главный компонент.

0.015

~-----------------------~

первоначальная проекция точки Х[25]
повторно отображенная точка Х[25]

0.010

0.005

о.ооо

nr nам··мм"ММ+АМtt!:М.А д

••••·~~---•

-0.005

-0.010

-0.015

-.------,-----...----т-----,.------.------r

-0.15
Рис.

5.18.

-0.10

-0.05

0.00

0.15

Корректное отображе11ие образцах ' на первый главны й компонент

Ядерный анаnиз rnавных компонентов в
Ради нашего удобства в подмодуле
ки

0.10

0.05

scikit-learn

scikit·learn

sklearn. d e compo si tio n

библиоте­

реализован класс ядерного РСА . Он применяется аналогично

классу стандартного РСА, и с помощью параметра

kernel

можно указы­

вать ядро:

>>> fr·om sklearn. decomposi tion impor t KernelPCA
>>> Х, у= make_moons(n_sarnples=lOO, random_state=123)
>>> scikit_kpca = KernelPCA(n_components=2,

kernel='rbf', gamma=15)
>>> X_skernpca = scikit_kpca.fit_transform(X)
Чтобы проверить, согласуются ли полученные результаты с нашей реал иза­

цией ядерного РСА, мы построим график трансформированного набора дан­
ных с фигурами в форме полумесяца для первых двух главных компонентов:

-

- - -- - --- · [229] ---·

[лава

5.

Сжатие данных с помощью понижения размерности

>>> plt.scatter(X_skernpca[y==O, 0], X_skernpca[y==O, 1],
color='red', marker=•л•, alpha=0.5)
>>> plt.scatter(X_skernpca[y==l, О], X_skernpca[y==l, 1],
color='Ьlue', marker='o', alpha=0.5)
>>> plt.xlabel('PCl')
»> plt. ylabel ( ' РС2' )
>>> plt.tight_layout()
>>> plt. show ()
Легко заметить, что результатыиспользования класса

scikit-learn

..
.".. ."

0.4

......"

• Jtl.

0.3

"

""
"

О.О

-0.1

"

""
""•
".:..._."

-0 .2
-0 .3
-0.4
-0.4

-0.3

....
-0.2

........••

•••

"

""

•"

0.1
N



"

0.2

u
Q_

KernelPCA
5.19).

согласуются с результатами нашей реализации (рис .

" •

••



••
••
••
••

""
"
"
••
-0.1

о.о

0.1

••
••
••
••
•••
•••
•••

.....,,

0.2

0.3

0.4

PCl
Рис. 5.19. Результаты при-..1ененuя класса KernelPCA из

scikit-learn

\\:~ Обучение на основе мноrообразий (manifold learning)

н~ В библиотеке scikit-learn также реализованы развитые приемы для
заметку!

нелинейного понижения размерности, рассмотрение которых выходит за рамки настоящей книги. Заинтересованные читатели могут

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

http://scikit-learn.org/staЫe/modules/manifold.html.

[230]

из

Глава

5.

Сжатие данных с помощью понижения размерности

Резюме
В главе вы узнали три фундаментальных приема понижения размерности
для выделения признаков: стандартный РСА,

LDA

и ядерный РСА. С ис­

пользованием РСА мы проецируем данные в подпространство меньшей раз­
мерности, чтобы довести до максимума дисперсию по ортогональным осям

признаков, игнорируя метки классов. В отличие от РСА прием

LDA

обеспе­

чивает понижение размерности с учителем, что означает учет информации о
метках классов в обучающем наборе данных при попытке довести до макси­

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

ков

-

ядерным РСА. Применяя ядерный трюк и временную проекцию в

пространство признаков более высокой размерности, мы в итоге получили

возможность сжать наборы данных, состоящие из нелинейных признаков,
в подпространство меньшей размерности, где классы становятся линейно

сепарабельными.
Теперь, имея в своем распоряжении указанные важнейшие приемы пред­

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

и оценки эффективности различных моделей.

------------(231)------------

6
ОСВОЕНИЕ ПРАКТИЧЕСКОГО
ОПЫТА ОЦЕНКИ

МОДЕЛЕЙ И НАСТРОЙКИ
ГИПЕРПАРАМЕТРОВ

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

ритмов и оценки эффективности моделей. В главе будут раскрыты следую­
щие темы:



оценка эффективности моделей МО;



диагностика распространенных проблем с алгоритмами МО;



точная настройка моделей МО;



оценка прогнозирующих моделей с использованием различных метрик

эффективности.

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

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

4

или анализ главных компонентов для сжатия данных в главе

5,

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

Pipeline

из

scikit-learn.

-

классе

Он позволяет подгонять модель, включающую

произвольное количество шагов трансформации, и применять ее для выра­
батывания прогнозов относительно новых данных.

Заrрузка набора данных

Breast Cancer Wisconsin

В главе мы будем работать с набором данных
(диагнозы рака молочной железы в штате

Breast Cancer Wisconsin
Висконсин), содержащим 569 об­

разцов злокачестве1щых и доброкачественных опухолевых клеток. В первых
двух столбцах набора данных хранятся уникальные идентификационные но­

мера образцов и соответствующие диагнозы (М
ная), в

= benign

(доброкачественная)). Столбцы

= malignant
3-32

(злокачествен­

содержат

30

призна­

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

ющей доброкачественность или злокачественность опухоли. Набор данных

Breast Cancer Wisconsin

был передан в Хранилище машинного обучения

Калифорнийского университета в Ирвайне

(UCI), а с более детальной ин­
формацией о нем можно ознакомиться по ссылке https: / / archi ve. ics.
uci.edu/ml/datasets/Breast+Cancer+Wisconsin+(Diagnostic).
r~ Получение набора данных

~
н~

заметку!

Копия набора данных

Breast Cancer Wisconsin

Breast Cancer Wisconsin

(и всех других на-

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

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

https: / /archi ve. ics. uci. edu/ml/machinelearning-databases/breast-cancer-wisconsin/wd.Ьc. data
на сервере UCI. Скажем, чтобы загрузить набор данных Wine из ка­

недоступности

кого-то локального каталога, следующий оператор:

---------(234)

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

df = pd.read_csv(
'https://archive.ics.uci.edu/ml/'
'machine-learning-databases'
'/breast-cancer-wisconsin/wdЬc.data',

header=None)
понадобится заменить таким оператором:

df = pd.read_csv(
'ваш/локальный/путь/к/wdЬс.dаtа',

header=None)
Далее мы прочитаем набор данных

Breast Cancer Wisconsin

и разобьем

его на обучающий и испытательный наборы данных, выполнив три простых
шага.

1.

Мы начнем с чтения набора данных прямо из веб-сайта
нением

UCI

с приме­

pandas:

>>> import pandas as pd
>>> df = pd.read_csv ( 'https: //archive. ics. uci .edu/ml/'
'machine-learning-databases'
'/breast-cancer-wisconsin/wdЬc.data',

header=None)

2.

Затем мы присвоим
ем объекта
начального

30 признаков NumРу-массиву Х и с использовани­
LabelEncoder трансформируем метки классов из перво­
строкового представления ( 'М' и ' в ') в целые числа:

>>> from sklearn.preprocessing import LabelEncoder
>>> Х = df. loc [:, 2:] . values
>>> у = df. loc [:, 1] • values
>>> le = LabelEncoder ()
>>>у= le.fit_transform(y)
>>> le. classes
array(['B', 'М'], dtype=object)
После кодирования меток классов (диагнозов) в массиве у злокачествен­
ные опухоли представлены как класс

1, а доброкачественные-как класс О.

Мы можем еще раз проверить полученное отображение, вызвав метод

transform

подогнанного объекта

LabelEncoder

на двух фиктивных

метках классов:

>>> le.transform(['M',
array( [1, 0])

'В'])

------(235)------------

Глава

3.

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

До построения нашего первого конвейера модели в следующем под­

разделе мы разобьем набор данных на обучающий
пытательный

(20%

(80%

данных) и ис­

данных) наборы:

>>> f:r:·om sklearn.model selection

iшport

train_test_split

>>> X_train, X_test, y_train, y_test = \
train_test_split(X, у,
test_size=0.20,
stratify=y,
random_state=l)

Объединение преобразоватеnей и оценщиков в конвейер
В предыдущей главе вы узнали, что многие алгоритмы обучения для дости­

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

Breast Cancer Wisconsin

измерены с разными масштабами, нам необходимо стандартизировать столбцы
в нем, прежде чем мы сможем передать их линейному классификатору, напри­
мер, на основе логистической регрессии. Кроме того, пусть мы хотим сжать
данные от начальных

30

измерений до двумерного подпространства посредс­

твом аншrиза главных колтонентов ( РСА)

-

приема выделения признаков для

понижения размерности, который был представлен в главе

5.

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

объединить объекты

StandardScaler,

РСА и

LogisticRegression

конвейер:

from sklearn. preprocessing 1.rnpo.r:·t StandardScaler
frorn sklearn.decomposition import. РСА
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
pipe_lr = make_pipeline(StandardScaler(),
PCA(n_components=2),
LogisticRegression(random_state=l,
solver='lbfgs'))
>>> pipe_lr.f~t(X_train, y_train)
>>> y_pred = pipe_lr.predict(X_test)
>>> priпt ('Правильность при испытании: %• Зf'
% pipe_lr.score(X_test, y_test))
Правильность при испытании: 0.956

>>>
>>>
>>>
>>>
>>>

----------[236]

в

fлава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Функция

make _pipeline принимает произвольное количество пре­
scikit-learn (объектов, поддерживающих методы fi t и
transform), за которыми следует оценщик scikit-learn, реализующий мето­
ды fi t и predict. В предыдущем примере кода мы предоставили функции
make _pipeline в качестве входа два преобразователя, StandardScaler
и РСА, а также оценщик LogisticRegression, в результате чего
make _pipeline создаст из них объект Pipeline.
Мы можем считать объект Pipeline из scikit-learn метаоценщиком или
образователей

оболочкой вокруг индивидуальных преобразователей и оценщиков. Если
мы вызовем метод

объекта

fi t

Pipeline,

тогда данные будут передавать­

ся последовательности преобразователей посредством обращений к

transform

fi t

и

на промежуточных шагах, пока они не достигнут объекта оцен­

щика (финального элемента конвейера). Затем оценщик произведет подгон­
ку к трансформированным обучающим данным.
Когда в показанном выше коде мы выполняем метод

fi t

на конвейере

pipe _ lr, объект StandardScaler сначала осуществляет вызовы мето­
дов fi t и transform на обучающих данных. Затем трансформированные
обучающие данные передаются следующему объекту в конвейере, т.е. РСА.
Подобно предыдущему шагу объект РСА также выполняет методы

transform

fi t

и

на масштабированных входных данных и передает их финаль­

- оценщику.
LogisticRegression

ному элементу конвейера

Наконец, оценщик

обеспечивает подгонку к обуча­

ющим данным после того, как они были подвернуты трансформациям с помо­

щью

StandardScaler

и РСА. Мы снова обязаны отметить, что на количест­

во промежуточных шагов в конвейере никаких ограничений не накладывается;

тем не менее, последним элементом конвейера должен быть оценщик.
Аналогично методу

fi t

в конвейерах также реализован метод

Если мы передадим набор данных методу

predict

объекта

predict.
Pipeline, то

данные пройдут через все промежуточные шаги посредством обращений
к

transform.

На последнем шаге объект оценщика возвратит прогноз на

трансформированных данных.
Конвейеры из библиотеки

scikit-learn -

чрезвычайно полезные инстру­

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

работы объекта

Pipeline,

на рис.

6.1

приведена иллюстрация, подводящая

итог предшествующих обсуждений.

-----(237]

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

(Шаг2)

Испытательный набор

pipe line. predict( ... )

pipeline. fit( ... )

Pipeline
Масштабирование

. fit( ... ) &
. transform( ... )

. transform( ... )
Понижение размерности

. fit( ... ) &
. transform( ... )
Алгоритм обучения

. fit( ... )

. transform(".)

Прогнозирующая модель

Метки классов

Рис.

6.1.

Работа объе/\mа

Pipeline

Использование перекрестной проверки по
для оценки эффективности модели

k блокам

Одним из ключевых шагов при построении модели МО является оцен­

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

применяем те же данные для оценки, насколько хорошо она работает на но­
вых данных. Как мы помним из раздела "Решение проблемы переобучения с

помощью регуляризации" главы

3,

модель может либо страдать от недообу­

чения (высокого смещения), если она слишком проста, либо переобучать­
ся на обучающих данных (иметь высокую дисперсию), если она чересчур

сложна для лежащих в основе обучающих данных.
Чтобы отыскать приемлемый компромисс между смещением и диспер­

сией, нам необходимо тщательно оценивать модель. В этом разделе вы уз­
наете о распространенных приемах перекрестной проверки с удержанием

(238]

Глава

(lщ/1lo11t

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

aoss-1·alitlat im1) и перекрестной проверки по k блокам (k-.fbltl aoss-

l't1liilat io11 ),

которые могут помочь получить надежные оценки эффективнос­

ти обобщения модели, т.е. выяснить, насколько хорошо модель работает на
не встречавшихся ранее данных.

Метод перекрестной проверки с удержанием
Классическим и популярным подходом для оценки эффективности обоб­
щения моделей МО является перекрестная проверка с удержанием. При та­

кой проверке мы разбиваем начальный набор данных на отдельные обучаю­
щий и испытательный наборы; первый
а второй

-

используется для обучения модели,

для оценки ее эффективности обобщения. Однако в типовых

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

выработки прогнозов на не встречавшихся ранее данных. Такой процесс
называется отбором J1юдели, причем термин "отбор модели" относится к

заданной задаче классификации, для настраиваемых параметров (также на­
зываемых гиперпаршwетра.ии) которой мы хотим подобрать опти.мат1ь11ые
значения. Тем не менее, если во время отбора модели мы многократно при­
меняем тот же самый испытательный набор данных, то он становится час­

тью нашего обучающего набора и потому растет вероятность переобучения

модели. Несмотря на упомянутую проблему, многие продолжают использо­
вать испытательный набор для отбора модели, что не считается рекоменду­
емой практикой МО.
Более удачный способ применения метода перекрестной проверки с

удержанием для отбора модели предусматривает разбиение данных на три
части: обучающий набор, проверочный набор и испытательный набор.

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

6.2

иллюстрируется концеп­

ция перекрестной проверки с удержанием, где мы неоднократно используем

проверочный набор для оценки эффективности модели после ее обучения
с применением разных значений параметров. После получения удовлетво-

[239)-----------

[лава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

рительных результатов настройки значений гиперпараметров мы оцениваем

эффективность обобщения моделей на испытательном наборе.

Исходный набор данных

Испытательный набор

Обучающий набор

Проверочный

Обучающий набор

набор

Испытательный набор

Изменить
гиперпараметры
и повторить

Алгоритм
машинного

обучения
Оценка
• 1



Прогнозирующая
модель

Рис.

6.2.

Финальная оценка эффективности

Концепция перекрестной проверки с удержанием

Недостаток метода перекрестной проверки с удержанием в том, что оцен­

ка эффективности может быть очень чувствительной к способу разбиения
обучающего набора на обучающий и проверочный поднаборы; для разных
образцов данных оценка будет варьироваться. В следующем подразделе мы
рассмотрим более надежный прием оценки эффективности
проверку по

k

-

перекрестную

блокам, при которой мы повторяем метод перекрестной про­

верки с удержанием

k

раз на

Перекрестная проверка по

k

поднаборах обучающих данных .

k бnокам
k блокам мы случайным образом разбива­
k блоков без возвращения, где k-1 блоков

При перекрестной проверке по
ем обучающий набор данных на

используются для обучения моделей и один блок применяется для оценки
эффективности. Указанная процедура повторяется

ем

k

моделей и оценок эффективности.

- - - (240) -

k

раз, так что мы получа­

Глава

Освоение практического опыта оценки моделей и настройки гиперпараметров

6.

\\:~ Выборка с возвращением и без возвращения

н~ В главе 3 мы приводили пример, иллюстрирующий выборку с воз­
заметку!

вращением и без возвращения. Если вы еще не читали эту главу или

просто хотите освежить память, тогда обратитесь к врезке "Выборка
с возвращением и без возвращения" в разделе "Объединение мно­
жества деревьев принятия решений с помощью случайных лесов"
главы

3.

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

мы используем перекрестную проверку по

k

блокам для настройки моделей,

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

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

можем повторно обучить модель на полном обучающем наборе и получить
финальную оценку эффективности с применением независимого испыта­

тельного набора. Логическое обоснование подгонки модели к полному обу­
чающему набору после перекрестной проверки по

k

блокам заключается в

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

k

блокам является приемом повтор­

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

На рис.

с

k



= 1О.

6.3

подытожена концепция перекрестной проверки по

Обучающий набор данных разделяется на



k

блокам

блоков и в течение

итераций девять блоков применяются для обучения, а один блок будет

использоваться как испытательный набор для оценки модели. Затем оценки

эффективности Е; (например, правильность или ошибка классификации) для
каждого блока применяются при вычислении средней оценочной эффектив­
ности Е модели.

- - - - - - - - ( 2 4 1 ]----

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Обучающий набор

Испытательный блок

Обучающие блоки

1-я итерация

1

2-я итерация

1

3-я итерация

...
О-я итерация

1

Рис.

-'

~

11

с::::>

Е1

с::::>

Е2

с::::>

Ез

10

Е = 1~LE;
1=1

11'---'--'---'--..____,____.____.__-"-__,
6.3.

Концепция перекрестной проверки по

k

б.'юh·шн с

k = 10

Как показывают эмпирические данные, хорошим стандартным значением

для

k

в перекрестной проверке по

k

блокам является

1О.

Например, экспе­

рименты, проведенные Роном Кохави на разнообразных реальных наборах
данных, наводят на мысль, что перекрестная проверка по



блокам обеспе­

чивает наилучший компромисс между смещением и дисперсией ("А

Study
of Cross-Validation and Bootstrap for Accuracy Estimation and Model Selection"
("Исследование перекрестной проверки и бутстрэппинга для оценки правиль­
ности и отбора модели"), Рон Кохави, итоги международной конференции по
искусственному интеллекrу

(IJCAI), 14 (12):

стр.

1137-1143 (1995

год)).

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

значения

k

на каждой итерации будет использоваться больше обучающих

данных, что дает в результате менее пессимистическое смещение по отно­

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

k также

будут уве­

личивать время выполнения алгоритма перекрестной проверки, приводя к

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

k,

скажем,

k=5,

и по-прежнему получать точную оценку средней

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

· ---- 12421 ----------~------- -----·

Глава 6. Освоение практического опыта оценки моделей и настройки гиперпараметров

\'\:~ Перекрестная проверка по одному

н~ Специальным случаем перекрестной проверки по k блокам явля3аметку!

ется метод перекрестной проверки по одному (lc11н'-m1c-oul Lтoss-

1·11liiiatio11 ---

/,ООС\). В методе

LOOCV

мы устанавливаем коли­

чество блоков равным числу обучающих образцов (k=п), поэтому
на каждой итерации для испытания применяется только один обу­
чающий образец, что будет рекомендуемым подходом при работе с
очень маленькими наборами данных.
Незначительным усовершенствованием подхода перекрестной проверки

по

k

блокам считается стратифицированная перекрестная проверка по

k

бло­

кам, которая может давать оценки с лучшим смещением и дисперсией, осо­

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

каждый блок отображает доли классов в обучающем наборе данных, как
мы продемонстрируем с использованием итератора

StratifiedKFold

из

scikit-leam:
>>> :tшрсн:t numpy as np
>>> fr.'om sklearn .model_selection

нnport:

StratifiedKFold

>>> kfold = StratifiedKFold(n_splits=lO) .split(X_train, y_train)
>>> scores = []
>>> Гоr k, (train, test) in enumerate (kfold):

Блок:
Блок:
Блок:
Блок:
Блок:
Блок:

Блок:
Блок:
Блок:
Блок:

pipe_lr.fit(X_train[train], y_train[train])
score = pipe_lr.score(X_train[test], y_train[test])
scores.append(score)
p1~i11t:. ('Блок: %2d, Распределение классов: %s,Правильность:%.Зf'
% (k+l, np.bincount(y_train[train]), score))
1, Распределение классов: (256 153]' Правильность: 0.935
2, Распределение классов: (256 153]' Правильность: 0.935
3, Распределение классов: (256 153]' Правильность: 0.957
4, Распределение классов: (256 153]' Правильность: 0.957
5, Распределение классов: [256 153], Правильность: 0.935
6, Распределение классов: (257 153] ' Правильность: 0.956
7, Распределение классов: (257 153], Правильность: 0.978
8, Распределение классов: (257 153], Правильность: 0.933
9, Распределение классов: (257 153]' Правильность: 0.956
10, Распределение классов: [257 153], Правильность: 0.956

----~------~----(243)

Глава б. Освоение практического опыта оценки моделей и настройки гиперпараметров

>>>

%.Зf

рп. лt('\nТочность перекрестной проверки:

Точность

+/-

%.Зf'

%

(np.mean (scores), np. std (scores)))
перекрестной проверки: 0.950 +/- 0.014

StratifiedKFold из мо­
классов y_train в обучающем

Прежде всего, мы инициализируем итератор

sklearn.model selection метками
наборе и с помощью параметра n_splits указываем количество блоков.
Когда мы посредством итератора kfold проходим по k блокам, возвраща­
емые в train индексы применяются для подгонки конвейера с объектом

дуля

логистической регрессии, который был настроен в начале главы. Используя
конвейер

pipe_ lr,

мы гарантируем, что на каждой итерации образцы над­

лежащим образом масштабированы (например, стандартизированы). Затем

test для вычисления меры правильности модели,
список scores, чтобы вычислить среднюю точность и

мы применяем индексы

которую помещаем в

стандартное отклонение оценки.

Хотя приведенный выше пример кода был удобен в качестве иллюстра­
ции работы перекрестной проверки по

k

блокам, в библиотеке

также реализован счетчик перекрестной проверки по

k

scikit-learn

блокам, который поз­

воляет оценивать модель с использованием стратифицированной перекрест­

ной проверки по

k

блокам более кратко:

>>> frorn sklearn .model_selection

нaport

cross val score

>>> scores = cross_val_score(estimator=pipe_lr,
X=X_train,
y=y_train,
cv=lO,
n_jobs=l)
>>> pr·1nt('Mepы правильности перекрестной проверки: %s' % scores)
Меры правильности перекрестной проверки:

0.93478261 0.93478261 0.95652174
0.95652174 0.93478261 0.95555556
о. 95555556
о. 93333333
о. 97777778
о. 95555556 ]
>>> pr· i п t ( 'Точность перекрестной проверки: %• 3 f +/- %• 3 f '
% (np.mean(scores),
np. std ( scores) ) )
Точность перекрестной проверки: 0.950 +/- 0.014
Исключительно полезная особенность подхода с

cross val score

свя­

зана с тем, что мы можем распределить оценку разных блоков по множеству

---·----- [2441 -------

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

процессоров на машине. Если мы установим параметр

n _j obs

в

1,

тогда

для оценки эффективности будет применяться только один процессор, как
было ранее в примере с итератором

тановки

n _j obs=2

мы могли бы

StratifiedKFold. Однако за счет ус­
распределить 1О итераций перекрестной

проверки по двум процессорам (при их наличии на машине), а путем уста­
новки

n _j obs=-1

задействовать все доступные процессоры для выполне­

ния вычислений в параллельном режиме.

\\.~ Оценка эффективности обобщения

н~ Обратите внимание, что подробное обсуждение способа, которым
заметку!

оценивается дисперсия эффективности обобщения при перекрест­
ной проверке, выходит за рамки тематики настоящей книги. При же­

лании вы можете обратиться к исчерпывающей статье об оценке мо­
делей и перекрестной проверке

("Model evaluation, model selection,
and algorithm selection in machine learning" ("Оценка моделей, под­
бор моделей и выбор алгоритмов в машинном обучении"), С. Рашка,
препринт

arXiv: 1811.12808 (2018

г.)), где указанные темы раскрыва­

ются более глубоко. Статья свободно доступна по ссылке

https: / /

arxiv.org/pdf/1811.12808.pdf.
Вдобавок вы можете найти детальное обсуждение в великолепной

статье М. Маркату и других

"Analysis of Variance of Cross-validation
Estimators of the Generalization Error" ("Анализ дисперсии оценщиков
ошибки обобщения при перекрестной проверке"), Journal of Machine
Learning Research, 6: с. 1127-1168 (2005 г.).
Вы также можете почитать об альтернативных приемах перекрес­
тной проверки вроде метода перекрестной проверки с бутстрэп­

пингом

.632 ("lmprovements оп Cross-validation: The .632+ Bootstrap
Method" ("Усовершенствования перекрестной проверки: метод бутс­
трэппинга .632"), Б. Эфрон и Р. Тибширани, Joumal of the American
Statistical Association, 92(438): с. 548-560 (1997 г.)).

Отладка алгоритмов с помощью

кривых обучения и проверки
В этом разделе мы взглянем на два очень простых, но мощных диагнос­

тических инструмента, которые могут помочь повысить эффективность
алгоритма обучения: кривые обучения и кривые проверки. В последующих

---··-·--- [245] --------

fлава 6. Освоение практического оп111та оценки моделей и настройки гиперпараметров

подразделах мы обсудим, как можно использовать кривые обучения для ус­
тановления, есть ли у алгоритма обучения проблема с переобучением (вы­
сокая дисперсия) или недообучением (высокое смещение). К тому же мы
рассмотрим

кривые

проверки,

которые могут помочь с решением распро­

страненных проблем с алгоритмом обучения.

Диаrностирование пробnем со смещением
и дисперсией с помощью кривых обучения
Если модель чересчур сложная для имеющегося обучающего набора дан­
ных (в ней присутствует слишком много степеней свободы или параметров),

то она имеет склонность к переобучению обучающими данными и не обоб­
щается хорошо на не встречавшиеся ранее данные. Зачастую сократить сте­

пень переобучения может помочь сбор добавочных образцов.
Тем не менее, на практике сбор дополнительных данных часто оказыва­
ется крайне затратным или попросту неосуществимым. Построив графики

показателей правильности обучения и проверки как функций размера обу­
чающего набора, мы сможем легко обнаруживать, страдает ли модель от
высокой дисперсии или высокого смещения, а также выяснять, поможет ли

сбор добавочных данных решить имеющуюся проблему. Но прежде чем мы

посмотрим, каким образом строить кривые обучения в

scikit-learn,

давайте

обсудим две распространенные проблемы с моделями, проработав графики
на рис.

6.4.

На графике вверху слева показана модель с высоким смещением. Эта
модель характеризуется низкими показателями правильности обучения и

перекрестной проверки, которые говорят о том, что она недообучена на
обучающих данных. Общепринятые способы решения такой проблемы пре­
дусматривают увеличение количества параметров модели, например, путем

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

SVM

или ло­

гистической регрессии.

На графике вверху справа представлена модель, которая страдает от вы­
сокой дисперсии, на что указывает крупный разрыв между показателями

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

-----··------·--·---·------- [246] ··---·----··----------·----

fлава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Высокая дисперсия

Высокое смещение

1.0

--.а

§z

t

о

z





;!

с:

:s

111
111

-- -- --·------

111

111

с:а.

с

-..

- - - - ----

----

---------------------------------·

/,---------

с:а.

с

Количество обучающих образцов

Количество обучающих образцов

'

-· - -

Правильность при обучении

----

Правильность при проверке



....

--____________

:_:_:_:_:_:_~-~-=-~-~

1:;
о

z



с:

-----

ili111

Желательная правильность

с:а.
с

Количество обучающих образцов

Рис.

6.4.

Иллюстрация двух распростра11енных проблем с моделями

В случае нерегуляризированных моделей снижению степени переобуче­
ния

может также помочь уменьшение

выбора признаков (см. главу

4)

количества признаков посредством

или выделения признаков (см. главу

5).

Наряду с тем, что сбор добавочных обучающих данных обычно имеет тен­

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

В следующем подразделе мы покажем, каким образом решать такие про­
блемы с моделью, применяя кривые проверки, но давайте сначала посмот­

рим, как можно оценивать модель посредством функции кривой обучения

(learning_curve)

из

scikit-learn:

[247)

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

>>> iпtport matplotlib.pyplot as plt
>>> from sklearn.model_selection import learning_curve
>>> pipe_lr = make_pipeline(StandardScaler(),

>>>

>>>
>>>
>>>
>>>

LogisticRegression(penalty='l2',
random_ state=l,
sol ver=' lЬfgs',
max_iter=lOOOO))
train_sizes, train scores, test_scores =\
learning_curve(estimator=pipe_lr,
X=X_train,
y=y_train,
train_sizes=np.linspace(
0.1, 1.0, 10),
cv=lO,
n_jobs=l)
train rnean = np.mean(train_scores, axis=l)
train_std = np.std(train_scores, axis=l)
test_rnean = np.rnean(test_scores, axis=l)
test_std = np. std (test_scores, axis=l)

>>> plt.plot(train_sizes, train_rnean,
color='Ьlue',

marker='o',

markersize=5,

lаЬеl='правильность при обучении')

>>> plt.fill_between(train_sizes,

train mean + train_std,
train_mean - train_std,
alpha=0.15, color='Ыue')
>>> plt.plot(train_sizes, test_mean,

color='green', linestyle='--',
rnarker=' s', markersize=S,
lаЬеl='правильность при проверке')

>>> plt.fill_between(train_sizes,

test rnean + test_std,
test mean - test_std,
alpha=0.15, color•'green')

»> plt. grid ()
>>> pl t. xlabel ('Количество

обучающих образцов')

>>> рlt.уlаЬеlt'Правильность')
>>> pl t. legend ( loc=' lower right' )

»> plt.ylim( [О.В, 1.03])
>>> plt. show ()
(248]----------

[лава

6.

Освоение практического опыта оценки моделей и настройки г иперпараметров

Обратите внимание, что при создании объекта
(который

по

умолчанию

max iter=lOOO O в

использует

1ООО

LogisticRegression

итераций)

мы

передаем

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

проблем со сходимостью для меньших размеров наборов данных или экс­
тремальных значений параметра регуляризации (рассматривается в следу­
ющем разделе). В результате успешного выполнения предыдущего кода мы

получаем график кривой обучения, показанный на рис .



1.00



0.95

""

....





6.5.

....

...___.__....

"" _______ ___ ..... __ _
·---------"
_.. ___ ..,." .....

""

()
о

:z:


~

0.90

s

111

ltl
Q.

i:::::

0.85
-+-правильность при обучении

-• -

правильность при проверке

о.во-'--~--~--~--~--~--~--~--~--'

50

150

100

200

250

350

300

400

Количество обучающих образцов

Рис.

6.5.

График кривой обучения, построенный с по.wощью
функции

С помощью параметра

learn i ng_ curve

train_sizes

из

scikit-leam

в функции

learning_curve

мы

можем управлять абсолютным или относительным количеством обучающих

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



train_sizes=np . linspace(O.l, 1 . 0, 10 ) ,

чтобы приме­

равноотстоящих относительных интервал ов для размеров обучаю­

щих наборов данных. По умолчанию функция

l e arning_cur v e

для вы­

числения правильности перекрестной проверки классификатора использует
стратифицированную перекрестную проверку по
ваем

k =1О

через параметр

крестную проверку по



cv,

k

блокам, и мы устанавли­

чтобы применять стратифицированную пере­

блокам.

--- - - -- - (2491 - -- ·- --

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Далее мы просто рассчитываем средние показатели правильности на ос­

нове возвращенных показателей правильности обучения и перекрестной

проверки для разных размеров обучающего набора и посредством функ­
ции

plot

из

Matplotlib

строим график. Кроме того, мы добавляем к гра­

фику стандартное отклонение средней правильности, используя функцию

fill _between

для обозначения дисперсии оценки.

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

вольно хорошо, если во время обучения она встретила более

250

образцов.

Также видно, что правильность обучения увеличивается для обучающих на­
боров, содержащих менее

250

образцов, а разрыв между показателями пра­

вильности обучения и перекрестной проверки становится шире, указывая на

растущую степень переобучения.

Решение пробnем недообучения и переобучения
с помощью кривых проверки

Кривые проверки являются полезным инструментом для повышения эф­
фективности модели за счет решения таких проблем, как переобучение и
недообучение. Кривые проверки имеют отношение к кривым обучения, но

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

твом

scikit-learn:

>>> frorn sklearn .model_selection iшport validation_curve
>>> param_range = [О.001, 0.01, 0.1, 1.0, 10.О, 100.0]
>>> train_scores, test_scores = validation_curve(

estimator=pipe_lr,
X=X_train,
y=y_train,
param_name='logisticregression~C',

>>>
>>>
>>>
>>>

param_range=param_range,
cv=lO)
train_mean = np.mean(train_scores, axis=l)
train_std = np.std(train_scores, axis=l)
test_mean = np.mean(test_scores, axis=l)
test_std = np.std(test_scores, axis=l)

------------(250]

Глава

6.

Освоение практического оп1>1та оценки моделей и настройки гиперпараметров

>>> plt.plot(param_range, train_mean,
color='Ьlue', marker='o',
markersize=5, lаЬеl='правиль нос ть при обучении')
>>> plt.fill_between(param_ range, train_mean + tr_ain_std,
train_mean - train_std, alpha=0.15,
color='Ьlue')

>>> plt.plot(param_range, test_mean,

color='green', linestyle='--',
marker=' s', markersize=5,
lаЬеl='правильность при пр оверк е')

>>> plt.fill_between(param_ range,

test mean + test_ std,
test mean - test_std,
alpha=0.15, color='green')

>» pl t. grid ()
plt.xscale ( 'log')
plt.legend(loc='lower right')
plt.xlabel ('Параметр С')
plt. ylabel ('Правильность')
>» plt.ylim( [0.8, 1.0])
>» plt. show ()
>>>
>>>
>>>
>>>

В результате выполнения приведенного выше кода мы получаем график с
кривой проверки для параметра С (рис.

6.6).

1.000

-------~--- ....

0.975

------

0.950

~~

---- .....



.... 0.925

u

о

х



с;

:s:

0.900

ID

со

а.

t:

0.875
0.850
- - - правипьность при обучении

0.825

-• -

правильн ость при проверке

0.800
10- 3

10- 2

101

102

Параметр С

Рис.

6.6.

График кривой проверки для параметра С

[251) - - - - -- --

- --

-

-

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Подобно функции

learning_curve для оценки эффективности классифика­
validation_ curve по умолчанию применяет стратифицирован­
перекрестную проверку по k блокам. Внутри функции validation_ curve

тора функция

ную

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

ный параметр регуляризации классификатора

LogisticRegression, кото­
'logisticregression С', чтобы иметь доступ к
объекту LogisticRegression внутри конвейера scikit-learn для указанно­
го диапазона значений, установленного через параметр param_ range. Как
рый мы записали как

и в примере с кривой обучения в предыдущем разделе, мы строим график
со средними показателями правильности при обучении и при перекрестной
проверке и соответствующими стандартными отклонениями.

Хотя отличия в правильности для варьирующихся значений едва разли­

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

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

между О.

01

и О

.1.

Точная настройка моделей маwинноrо
обучения с помощью реwетчатоrо поиска
В области МО мы имеем дело с двумя типами параметров: те, что узна­

ются из обучающих данных, скажем, веса в логистической регрессии, и па­

раметры алгоритма обучения, которые оптимизируются отдельно. Последние
являются параметрами настройки модели (или гиперпаршиетра.ни); приме­
ром гиперпараметра может служить параметр регуляризации в логистичес­

кой регрессии или параметр глубины в дереве принятия решений.
В предыдущем разделе мы использовали кривые проверки для повыше­

ния эффективности модели, настраивая один из ее гиперпараметров. В те­
кущем разделе мы рассмотрим популярный прием оптимизации гиnерпа­

раметров, который называется решетчатьш поuско.н

(g1·itf

sеап:/1) и может

дополнительно содействовать повышению эффективности модели, отыски­
вая оптимальную комбинацию значений гиnерпараметров.

----------(252)-------

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Настройка rиперпараметров с помощью реwетчатоrо поиска
Подход, лежащий в основе решетчатого поиска, довольно прост. Это па­
радигма исчерпывающего поиска методом грубой силы, когда мы указываем
список значений для различных гиперпараметров, а компьютер оценивает

эффективность модели для каждого их сочетания, чтобы получить опти­
мальную комбинацию значений из списка:

>>> from sklearn.model_selection import GridSearchCV
>>> from sklearn. svm import SVC
>>> pipe_svc = rnake_pipeline(StandardScaler(),

SVC(randorn_state=l))
>>> param_range = [0.0001, 0.001, 0.01, 0.1,

1.0, 10.0, 100.0, 1000.0]
>>> param_grid = [ { 'svc_C' : param range,

'svc kernel': ['linear'] },
{ 'svc_C': param_range,
'svc_garnrna': pararn_range,
'svc kernel' : [ 'rЬf' ] } ]
>>> gs = GridSearchCV(estirnator=pipe_svc,

pararn_grid=pararn_grid,
scoring='accuracy',
cv=lO,
refit=True,
n_jobs=-1)
>>> gs = gs.fit(X_train, у train)
>>> pr1nt(gs.best_score_)
0.9846153846153847
>>> prin t ( gs. best _pararns _)
{'svc_C': 100.0, 'svc_garnrna': 0.001, 'svc kernel':

'rЬf'}

В показанном выше коде мы инициализируем объект

GridSearchCV
sklearn. model selection с целью обучения и настрой­
ки конвейера SVM. Мы устанавливаем параметр param_grid объекта
GridSearchCV в список словарей, чтобы указать параметры, которые хо­
тим настроить. Для линейного SVM мы оцениваем только обратный пара­
метр регуляризации с; для SVM с ядром RBF мы настраиваем параметры
svc С и svc gamma. Обратите внимание, что параметр svc gamma
специфичен для ядерных SVM.
из модуля

---[253)-----------

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

После применения обучающих данных для проведения решетчатого
поиска мы получаем меру лучше всех работающей модели через атрибут

best _ score _ и просматриваем ее параметры. доступные посредством ат­
рибута best _params _. В нашем конкретном случае модель SVM с ядром
RBF при svc_C = 1 О О. О дает наилучшую правильность при перекрестной
проверке по k блокам: 98.5%.
Наконец, мы будем использовать независимый испытательный набор

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

best estimator

объекта

Gr idSearchCV:
>>> clf = gs.best estimator
>>> clf.fit(X_train, y_train)
>>> p:r:·iпt. ('Правильность при испытании:
% clf.score(X_test, y_test))
Правильность при испытании:

%• Зf'

0.974

Следует отметить, что после завершения решетчатого поиска подгонять

(gs.best_estimator_) к обучающе­
clf. fit (X_train, у train)
GridSearchCV имеет параметр refi t, установка ко­

модель с наилучшими настройками

му набору вручную посредством метода

необязательно. Класс

True (стандартное значение) обеспечит автоматическую
gs.best_estimator_ к целому обучающему набору.

торого в

подгонку

Га~ Рандомиэированный поиск rиперпараметров

н~ Несмотря на то что решетчатый поиск -

мощный подход для на­

заметку! хождения оптимального набора параметров, оценка всех возможных
комбинаций параметров также сопряжена с очень большими вычис­
лительными затратами. Альтернативным подходом к выборке раз­
личных комбинаций параметров с применением
рандомизированный поиск (mml11111i;:дf

se1m·l1).

scikit-learn

служит

Рандомизированный

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

60

комбинаций параметров через рандомизиро­

ванный поиск, то уже имеем 95%-ную вероятность получения ре­
шений в пределах 5%-ной оптимальной эффективности

search for hyper-parameter optimization"

[254]

(Случайный

-~-------

("Random
поиск для on-

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

тимизации rиперпараметров ), Дж. Берrстра, Й. Бенджи, Journal of

Machine Learning Research.
С использованием класса

learn

с.

281-305 (2012 r.)).

RandomizedSearchCV из библиотеки scikit-

мы осуществляем выборку случайных комбинаций параметров

из выборочного распределения с указанным запасом. Дополнитель­
ные сведения и примеры применения класса

Randomi zedSearchCV

можно найти по ссылке http://scikit-learn.org/staЫe/

modules/grid search.html#randomized-parameteroptimi za tion.

Отбор аnrоритма с помощью вnоженной перекрестной проверки
Использование перекрестной проверки по
чатым поиском

-

k блокам

в сочетании с решет­

удобный подход для точной настройки эффективности мо­

дели МО путем варьирования значений ее rиперпараметров, как мы видели
в предыдущем подразделе. Тем не менее, если мы хотим производить отбор
среди разных алгоритмов МО, то еще одним рекомендуемым подходом яв­
ляется вложенная перекрестная проверка. Судхир Варма и Ричард Саймон в

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

("Bias in Error Estimation When Using Cross-validation for Model Selection"
(Смещение в ошибках оценки при использовании перекрестной провер­
ки для отбора модели), С. Варма и Р. Саймон, ВМС
с.

91 (2006

Bioinformatics, 7(1):

г.)).

При вложенной перекрестной проверке мы организуем внешний цикл пе­
рекрестной проверки по

k блокам

для разбиения данных на обучающие и ис­

пытательные блоки, а также применяем внутренний цикл для отбора модели
с использованием перекрестной проверки по

k

блокам на обучающем блоке.

После отбора модели оценивается ее эффективность с применением испыта­
тельного блока. На рис.

6. 7

объясняется концепция вложенной перекрестной

проверки с пятью внешними и двумя внутренними блоками, которая может
быть полезна для крупных наборов данных, где важна вычислительная эф­

фективность. Указанный конкретный тип вложенной перекрестной проверки
также известен как перекрестная проверка 5х2.

- - - - - - - - - - - - - [255).

[лава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Исходный набор

Обучающие блоки

Внешний цикл
Обучение
с оптимальными
параметрами

r----------------------------

----~1~~~~~~~~~11

'----------------------------·

~

}

Обучающий блок

Рис.

6. 7.

Внутренний цикл
Настройка
параметров

Концепция перекрест11ой проверки 5х2

С помощью библиотеки

scikit-learn

мы можем выполнять вложенную пе­

рекрестную проверку следующим образом:

>>> gs = GridSearchCV(estimator=pipe_svc,
param_grid=param_grid,
scoring='accuracy',
cv=2)
>>> scores = cross_val_score(gs, X_train, y_train,
scoring='accuracy', cv=S)
>>> рri.nt('Точность перекрестной проверки: %.Зf +/- %.Зf'
% (np.mean(scores),

np.std(scores)))
Точность перекрестной проверки:

- --- - - -- - ---

0.974 +/- 0.015

- - - (256)---- - -- - - - -- -- -

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

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

Например, мы можем применять подход с вложенной перекрестной про­

веркой для сравнения модели

SVM

с простым классификатором на основе

дерева принятия решений; в целях упрощения мы будем настраивать только

параметр глубины:

>>> frora sklearn. tree

impcн.·t

DecisionTreeClassifier

>>> gs = GridSearchCV(estimator=DecisionTreeClassifier (
random_state=O),
param_grid=[{ 'max_depth': (1, 2, 3,
4, 5, 6,
71

NOЛi,o')

}) /

scoring='accuracy',
cv=2)

>>> scores = cross_val_score(gs, X_train, y_train,
scoring='accuracy', cv=S)
>>> рr1.nt('Точность перекрестной проверки: %.3f +/- %.3f'
% (np.mean(scores),
np.std(scores)))
Точность перекрестной проверки:

0.934 +/- 0.016

Мы видим, что эффективность вложенной перекрестной проверки мо­
дели

SVM (97.4%)

принятия решений

SVM

заметно выше эффективности при варианте с деревом

(93.4%).

Вследствие этого мы ожидаем, что вариант с

может оказаться лучшим выбором для классификации новых данных,

которые поступают из той же генеральной совокупности, откуда поступил

наш задействованный выше набор данных.

Использование других метрик оценки эффективности
В предшествующих разделах и главах мы оценивали разные модели МО
с использованием правильности прогнозов

-

практичной метрики, с по­

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

точность

(pn:cisio11),

измерения степени соответствия модели, такие как

полнота

(1·ccall)

и .мера

Fl



scm·c).

[257]---·--

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Чтение матрицы неточностей
Прежде чем мы погрузимся в детали различных метрик подсчета, давай­

те посмотрим, что собой представляет матрица неточностей

111at1·ix),

(c01r(11sio11

которая раскладывает эффективность алгоритма обучения.

Матрица неточностей

это просто квадратная матрица, описываю­

-

щая численности истинно полож·итель11ых

(t/'llc positii·c -·-

ТР), иститю

- '/'N), ложжтоложитель11ых (li 1lse po..;iti1·e -ГР) и ложиоотрицательных (Ji1lse 11cк11ti1·c -- J .\) прогнозов классификато­

отрицательных

ра (рис.

(tntc

1н'~atii·e

6.8).

Рис.

6.8.

Матрица 11еточ11остей

Невзирая на то что такие метрики легко вычислить вручную, сравнивая на­

стоящие и спрогнозированные метки классов, библиотека
гает удобную функцию

confusion _ matrix,

scikit-learn

предла­

которой мы и воспользуемся:

>>> from sklearn.metrics import confusion_matrix
>>>
>>>
>>>
>>>

pipe_svc.fit(X_train, y_train)
y_pred = pipe_svc.predict(X_test)
confmat = confusion_matrix(y_true=y_test, y_pred=y_pred)
print(confmat)

[ (71

1)

[ 2 40)]
Возвращаемый в результате выполнения кода массив снабжает нас инфор­

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

matshow

из

Matplotlib

мы можем сопоставить эту информацию с иллюстрацией матрицы неточ­
ностей, приведенной на рис.

6.8:
[258)---

- --

- --

-

-- -

-

[лава

6. Освоение практического опыта оценки моделей и настройки г иперпараметров

>>> fig, ах= plt.suЬplots(figsize=(2.5, 2.5))
>>> ax.rnatshow(confmat, crnap=plt.crn.Blues, alpha=0.3)
>>> for i in raпge(confmat.shape[O]):
for j in ran.ge (confmat.shape[l]):
ax.text(x=j, y=i,
s=confrnat [i, j],
va=' center' , ha=' c enter' )
>>> рlt.хlаЬеl('Спрогнозированная метка')
>>> pl t. ylabel ('Настоящая метка')
>>> plt. show ()
Полученная матрица неточностей с добавленными метками (рис.

6.9)

должна немного облегчить интерпретацию результатов .

"'>.:

о

1

о

71

1

1

2

40

1

1

ID
::Е

"'"'3'
"'...
о

(,)

"'
ж

спроrнозированная метка

Рис.

6.9.

Матрица неточностей

с добавл енными мeтk·a.Jwu

Предполагая, что класс

1

(злокачественные опухоли)

-

положительный в

рассматриваемом примере, наша модель корректно классифицировала

71

разец как принадлежащие классу О (истинно отрицательные прогнозы) и
образцов как принадлежащие классу

1

об­

40

(истинно положительные прогнозы).

Тем не менее, модель также неправильно классифицировала два образца из
класса

1

как принадлежащие классу О (ложноотрицательные прогнозы) и

спрогнозировала, что один образец относится к злокачественным опухолям,

хотя он является доброкачественной опухолью (ложноположительный про­
гноз). В следующем разделе мы покажем, как использовать такую информа­

цию для вычисления разнообразных метрик ошибок.

(259)

Глава 6. Освоение практического опыта оценки моделей и настройки гиперпараметров

Оптимизация точности и поnноты кnассификационной модеnи
И оиtибка (ат1· --·

EUR},

и правилыюсть (т-с111·С1с) 1

--

ЛСС) прогноза пре­

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

ERR = __F_'P_+_F_N
__
FP+ FN + ТР+ TN
Тогда правильность прогноза можно вычислить прямо из ошибки:

АСС=

TP+TN
=1-ERR
FP+FN+TP+TN

Доля истиино положительиых классификаций (tп1е po.~iti1'e

и доля :южноположительиых к1шссификаций

mlc -- JPR)
(ti1lsc positi1'c mte - · H 1 U) яв­

ляются метриками эффективности, которые особенно полезны в задачах с
дисбалансом классов:

FPR=FP = FP
N FP+TN
TPR= ТР
Р

=

ТР
FN+TP

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

доброкачественные опухоли неправильно классифицируются как злокачест­
венные (прогнозы

от

FPR

величина

FP}, чтобы без нужды не волновать пациентов. В отличие
TPR предоставляет полезную информацию о части поло­

жительных (или значимых) образцов из общей совокупности положитель­

ных образцов, которые были идентифицированы корректно (Р).
Метрики эффективности точ11ость (РUГ) и потюта (Ю:С) связаны с

долями истинно положительных и отрицательных классификаций, причем

REC -

фактически синоним

TPR:

--[260)--·-·

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

ТР

PRE=

TP+FP
REC = TPR = ТР =
Р

ТР
FN+TP

В контексте примера выявления злокачественных опухолей оптимизация

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

FP).

С другой стороны, если мы оптимизируем точность, тогда придаем особое
значение правильности, когда прогнозируем, что у пациента есть злокачес­

твенная опухоль. Однако это происходит за счет более частого упущения
злокачественных опухолей (высокое число

FN).

Чтобы достичь баланса между достоинствами и недостатками оптимиза­
ции

PRE

и

емая мера

REC,
F 1:

часто применяется комбинация

PRE

и

REC -

так называ­

Fl = 2 PRExREC
PRE+REC
Г.~ Допоnнитеnьные сведения о точности и поnноте

н~ Если вас интересует более основательное обсуждение различ­
заметку!

ных метрик эффективности, таких как точность и полнота, почи­

тайте технический отчет Дэвида М. У. Пауэрса

"Evaluation: From
Precision, Recall and F-Factor to ROC, Informedness, Markedness
& Correlation" ("Оценка: от точности, полноты и F-меры до рабо­
чей характеристики приемника, информированности, степени по­

метки и корреляции"), который бесплатно доступен по ссылке
https://www.researchgate.net/puЫication/228529307

Evaluation- From- Precision - Recall - and - F-Factor to
ROC Informedness Markedness Correlation.
Все упомянутые метрики подсчета реализованы в
быть импортированы из модуля

sklearn.metrics,

scikit-learn

и могут

как демонстрируется в

следующем фрагменте кода:

- - - - - - - - - - - · - - - (261] ---·--------

fлава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

>>> from sklearn.m.etrics import precision_score
>>> from sklearn.metrics iroport recall_score, fl score
>>> print ('Точность: %. Зf' % precision_score (
y_true=y_test, y_pred=y_pred))
Точность: 0.976
>>> pririt ('Полнота: %. Зf' % recall_score (
y_true=y_test, y_pred=y_pred))
Полнота: 0.952
>>> print('Mepa Fl: %.Зf' % fl_score(
y_true=y_test, y_pred=y_pred))
Мера Fl: 0.964
Кроме того, посредством параметра
жем

scoring

в

GridSearchCV

мы мо­

использовать метрику подсчета, отличающуюся от метрики правиль­

ности. Полный список значений, принимаемых параметром

приводится по ссылке http:
model evaluation.html.

//scikit-learn.org/staЫe/modules/

Не забывайте, что положительный класс в
ченный как класс

1.

scor ing,

scikit-leam -

это класс, поме­

Если нужно указать другую положительную метку, тог­

да посредством функции

make _ scorer

мы можем построить собственный

счетчик, который затем предоставлять напрямую как аргумент параметру

scoring

в

GridSearchCV

(в текущем примере с применением

fl _ score

в качестве метрики):

>>> from sklearn.metrics import make_scorer, fl score
>>> c_gamma_range = [0.01, 0.1, 1.0, 10.0]
>>> param_grid = [ { 'svc_C' : c _gamma_ range,
'svc_kernel': ['linear']},
{ 'svc_C': c_gamma_range,
'svc_gamma': c_gaппna_range,
'svc_kernel': ['rbf']}]
>>> scorer = make_scorer(fl_score, pos_label=O)
>>> gs = GridSearchCV(estimator=pipe_svc,
param_grid=param_grid,
scoring=scorer,
cv=lO)
>>> gs = gs. fit_(X_train, y_train)
>>> print(gs.best_score_)
0.986202145696
>>> print(gs.best_params_)
{ 'svc_C': 10. О, 'svc_gamma': О. 01, 'svc kernel': 'rЬf'}

[262)-----

[лава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Построение кривой рабочей характеристики приемника
Кривая рабочей характеристики приемника (пceii 1 C1"

oper·ati11g

clшmcte-

I> frorн sklearn.metrics ~щро:гt roc_curve, auc
>>> f.rom scipy .tmport interp
>>> pipe_lr = make_pipeline(StandardScaler(),

PCA(n_components=2),
LogisticRegression(penalty='l2',
random_state=l,
sol ver=' lЬfgs' ,
C=l00.0))

- - - - - - - - - - - - - - - - - - - - [263]

fлава 6. Освоение практического опыта оценки моделей и настройки гиперпараметров

»> X_train2 = X_train[:, [4, 14]]
>>> cv = list(StratifiedKFold(n_splits-3,

>>>
>>>
»>

random_state=l) .split(X_train,
y_train))
fig = plt. figure ( figsize= ( 7, 5) )
mean_tpr = О. О
mean_fpr = np. linspace (0, 1, 100)
all_tpr = []

>>> for i, (train, test) in enumerate (cv):
probas = pipe_lr.fit(
X_train2[train],
y_train[train]) .predict_proba(X_train2[test])
fpr, tpr, thresholds = roc_curve(y_train[test],
probas [ : , 1] ,
pos_label=l)
mean_tpr += interp (mean_ fpr, fpr, tpr)
mean_tpr [0] = О. О
roc_auc = auc (fpr, tpr)
plt.plot(fpr,
tpr,
label='ROC блок %d (площадь= %0.2f)'
% (i+l, roc_auc))
»> plt .plot ([О, 1),
[0, 1]'
linestyle=' -- ',
color=(0.6, 0.6, 0.6),
lаЬеl='случайное угадывание')

>>>
>>>
>>>
>>>

mean_ tpr /= len (cv)
mean_tpr[-1] = 1.0
mean_auc = auc (mean_fpr, mean_tpr)
plt.plot (mean_fpr, mean_tpr, 'k--',
lаЬеl='средняя ROC (площадь = %0.2f)' % mean_auc, lw=2)
>>> plt.plot ([О, О, 1],
[0, 1, 1),
linestyle=': ',
color=' Ыасk' ,
lаЬеl='идеальная эффективность')

»>
»>
>>>
>>>
>>>
»>

plt .xlim( [-0. 05, 1. 05])
plt.ylim( [4).05, 1.05])
plt.xlabel('дoля ложноположительных классификаций')

pl t. ylabel ( 'доля истинно положительных
plt.legend(loc="lower right")
plt. show ()

классификаций'

)

----[264)-----------

Глава

В

6.

Освоение практического опыта оценки моделей и настройки г иперпараметров

предыдущем

примере кода мы используем уже знакомый

класс

StratifiedKFold из scikit-learn и вычисляем эффективность ROC клас­
сификатора LogisticRegression в конвейере pipe lr с применением
функции roc_curve из модуля sklearn.metrics отдельно для каждой
итерации. К тому же мы интерполируем кривую средней ROC трех блоков
посредством функции interp, которая импортируется из SciPy, и вычисля­
ем площадь под кривой с помощью функции auc. Результирующая кривая
ROC указывает на наличие определенной дисперсии между разными блока­
ми, а средняя площадь под кривой ROC (0.76) попадает между идеальным
показателем ( 1.0) и случайным угадыванием (0.5), как видно на рис. 6.1 О.

>S

s
:r
cu
~
s
-&
s
(J

1.0

0.8

(J

cu
с:

~

)(



0.6

:z:


с:
Q)

1-

s

!1Е

0.4

о

с:
о

с

-

о

:z:
:z:

-

0.2

s

RОСблок2(площадь=О .76)

ROC блок 3 (площадь= 0.79)

1(J

s

случайное угадывание

i:i:

с:
о

ROC блок 1 (площадь= О. 73)

ct

о.о
О .О

0.2

- - -

средняя

•••••

идеальная эффективность

0.6

0.4

ROC (площадь = 0.76)

0.8

1.0

доля ложноположительных классификаций
Рис.

6.10.

Кривые

ROC с разной

площадью под кривой

Обратите внимание, что если нас интересует только показатель

то мы могли бы также напрямую импортировать функцию
из подмодуля

sklearn.metrics,

ROC AUC,
roc_ auc_ s c ore

которую можно использовать аналогично

другим функциям подсчета (скажем,

precisio n _ score),

представленным

в предшествующих разделах.

-- - - - - (265) - - - - - -- -- - - - -

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Предоставление информации об эффективности классификатора в виде
показателя

ROC AUC

способствует дальнейшему погружению в суть работы

классификатора на несбалансированных образцах. Тем не менее, хотя мера
правильности

может

интерпретироваться

как

одиночная

точка

отсечения

на кривой

ROC, Э.П. Брэдли в своей работе продемонстрировал, что по­
ROC AUC и мера правильности обычно согласуются друг с дру­
гом ("The use of the area under the ROC curve in the evaluation of machine
learning algorithms" (Использование площади под кривой ROC при оценке
алгоритмов машинного обучения), Э.П. Брэдли, Pattern Recognition, 30(7):
с. 1145-1159 (1997 г.)).
казатель

Метрики подсчета для мноrоклассовой классификации
Метрики подсчета, которые мы обсуждали до сих пор, характерны для
систем двоичной классификации. Однако в

scikit-learn

реализованы также

методы макро- и микроусреднения для распространения этих метрик под­

счета на многоклассовые задачи с помощью классификации "один против
всех"
ТР,

(OvA). Микросреднее вычисляется из индивидуальных прогнозов
TN, FP и FN системы. Например, микросреднее показателя точности в

k-классовой системе может быть вычислено так:

=

PRE

+ ."
1

микро

ТР

TPi + ". +ТР,,
+ТР. +FP + ". +FP.
k
1
k

Макросреднее вычисляется просто как среднее из показателей точности
разных систем:

PRE
макро

= PREI + ... + PREk
k

Микроусреднение полезно, когда нужно назначить равные веса всем об­
разцам

или прогнозам, тогда как макроусреднение назначает равные веса

всем классам с тем, чтобы оценить общую эффективность классификатора
относительно самых распространенных меток классов.

Если мы применяем двоичные метрики эффективности из

scikit-learn

для оценки моделей многоклассовой классификации, то по умолчанию
используется нормализованный или взвешенный вариант макросреднего.
Взвешенное макросреднее подсчитывается путем назначения весов показа­

телям всех классов по числу настоящих образцов при вычислении средне-

----------(266)------------

fпава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

го. Взвешенное макросреднее удобно, когда мы имеем дело с дисбалансом
классов, т.е. разными количествами образцов для каждой метки.
Наряду с тем, что взвешенное макросреднее является стандартным для

многоклассовых задач в

scikit-learn, мы можем указывать метод усред­
нения через параметр average внутри различных функций подсчета из
модуля sklearn.metrics, например, функции precision_score или
make scorer:
>>> pre_scorer = make_scorer(score_func=precision_score,
pos_label=l,
greater_is_better=True,
average='micro')

Реwение проблемы с дисбалансом классов
В главе мы несколько раз упоминали о дисбалансе классов, но пока фак­

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

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

-

фильтрация спама, выявление подделок или ск­

рининг заболеваний.
Пусть набор данных
ранее в главе,

Breast Сапсеr Wisconsin, с которым мы работали
насчитывает 90% здоровых пациентов. В таком случае мы

могли бы достичь 90%-ной правильности на испытательном наборе данных,
просто прогнозируя для всех образцов мажоритарный класс (доброкачест­
венная опухоль) и не прибегая к помощи какого-то алгоритма МО с учите­

лем. Соответственно, обучение модели на наборе данных, которое достигает
приблизительно 90%-ной правильности при испытании, означало бы, что
наша модель не узнала ничего полезного из признаков,

предоставляемых

этим набором данных.
В настоящем разделе мы кратко рассмотрим ряд приемов, которые могли

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

те создадим несбалансированный набор данных из набора данных

Cancer Wisconsin,

который первоначально состоял из

·----------[267)

357

Breast

диагнозов добро-

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

качественных опухолей (класс О) и
лей (класс

»>


212

диагнозов злокачественных опухо­

1):

X_imЬ

= np.vstack((X[y

y_imЬ

= np.hstack(

==О],

Х[у

(у[у ==О],

у[у

== 1] [:40]))
== 1] [:40]))

В приведенном фрагменте кода мы берем все
венных опухолей и дополняем их первыми

40

357

образцов доброкачест­

образцами злокачественных

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

класс (доброкачественные опухоли, класс О), то достигли бы правильности

прогнозирования около

90%:

>>> y_pred = np.zeros(y_imЬ.shape[O])
>>> np.mean(y_pred == y_imЬ) * 100
89.92443324937027
Таким образом, когда мы подгоняем классификаторы на наборах данных
подобного рода, то при сравнении разных моделей имеет смысл сосредото­
читься не на правильности, а на других метриках вроде точности, полноты,

кривой

ROC -

т.е. на всем том, что нас заботит в строящемся приложении.

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

го скрининга; тогда предпочтительной метрикой служила бы полнота. В сис­
теме фильтрации спама, где мы не хотим помечать сообщения как спамные,
если в этом нет высокой уверенности, более подходящей метрикой могла бы

быть точность.
Помимо оценки моделей МО дисбаланс классов оказывает влияние на

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

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

--------------(268]----------

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Один из способов справиться с несбалансированными долями клас­
сов в течение процесса подгонки модели предусматривает назначение бо­
лее крупных штрафов неправильным прогнозам на миноритарном классе.

В

scikit-learn настройка такого
class _ weight в 'balanced',

штрафа сводится к установке параметра
что реализовано для большинства класси­

фикаторов.
Другие популярные стратегии решения проблемы с дисбалансом клас­
сов включают повышение дискретизации миноритарного класса, понижение

дискретизации мажоритарного класса и генерацию искусственных обучаю­

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

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

В библиотеке

scikit-learn

реализована простая функция

resample,

кото­

рая может помочь с повышением дискретизации миноритарного класса за

счет выборки с возвращением новых образцов из набора данных. В следу­
ющем коде мы берем миноритарный класс из нашего несбалансированно­

го набора данных

Breast Cancer Wisconsin

(здесь класс

1)

и многократно

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

>>> frorn sklearn.utils import resample
>>> pr·in t. ('Начальное

количество образцов класса

X_imЬ [y_imЬ

1: ',

== 1]. shape [О])

Начальное количество образцов класса

1: 40

>>> X_upsampled, y_upsampled = resample(
X_imЬ [y_imЬ == 1],
y_imЬ[y_imЬ == 1],
replace=True,
n_samples=X_imЬ[y_imЬ ==О] .shape[O],
random_state=123)
>>> pr ir1 t ( 'Конечное количество образцов класса 1: ' ,
X_upsampled.shape[O])
Конечное количество образцов класса

[269)

1: 357

Глава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

После повторной выборки мы можем скомпоновать образцы исходного
класса О с поднабором образцов, появившихся в результате повышения дис­

кретизации класса

1,

чтобы получить сбалансированный набор данных:

>>> X_bal = np.vstack(
>>> y_bal = np.hstack(

(Х[у

== 0], X_upsampled))
y_upsampled))

(у[у ==О],

Вследствие такого подхода правило мажоритарного прогнозирования бу­
дет достигать только 50%-ной правильности:

>>> y_pred = np.zeros(y_bal.shape[O])
>>> np.mean(y_pred == y_bal) * 100
50
Подобным же образом мы могли бы выполнить понижение дискретиза­

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

1

resample

мы прос­

и О в предыдущем коде.

Г'.~ Генерация новых обучающих данных для реwения
~ проблемы с дисбалансом классов
На

заметку!

Еще одним приемом решения проблемы с дисбалансом классов яв-

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

ных обучающих образцов следует считать прием

Minority Over-sampling Technique -

SMOTE (Synthetic

искусственное увеличение эк­

земпляров миноритарного класса); более подробные сведения о
нем доступны в исследовательской статье, которую опубликовал

Найтеш Чаула и другие:

"SMOTE: Synthetic Minority Over-sampling

Technique" (SMOTE:

искусственное увеличение экземпляров ми­

норитарного класса),

Journal of Artificial Intelligence Research, 16:

с.

321-357 (2002 г.). Крайне рекомендуем также ознакомиться с
irnbalanced-learn для Python, которая целиком

библиотекой

ориентирована на несбалансированные наборы данных и включает

реализацию

SMOTE.

Дополнительную информацию о библиотеке

irnЬalanced-learn можно найти по ссылке

https://github.

com/scikit-learn-contriЬ/irnЬalanced-learn.

- - - [270]---------

[лава

6.

Освоение практического опыта оценки моделей и настройки гиперпараметров

Резюме
В начале главы мы обсуждали, как связывать различные приемы транс­

формации и классификаторы в удобные конвейеры, которые помогают обу­
чать и оценивать модели МО более рационально. Мы применяли эти кон­

вейеры для выполнения перекрестной проверки по

k

блокам

-

одного из

важнейших приемов отбора и оценки модели. Используя перекрестную про­
верку по

k

блокам, мы строили кривые обучения и кривые проверки, чтобы

диагностировать распространенные проблемы алгоритмов обучения, такие

как переобучение и недообучение.
С помощью решетчатого поиска мы производили дальнейшую настройку
модели. Затем мы применяли матрицу неточностей и разнообразные метри­

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

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

-

методы, ко­

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

. (271]

7
ОБЪЕДИНЕНИЕ
РАЗНЫХ МОДЕЛЕЙ ДЛЯ
АНСАМБЛЕВОГО ОБУЧЕНИЯ

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

построения набора классификаторов, часто способного обладать лучшей эф­
фективностью прогнозирования, чем любой индивидуальный член набора.
В главе будут раскрыты следующие темы:



выработка прогнозов на основе большинства голосов;



использование бэггиню

(lmJ!,ging)

для сокращения степени переобуче­

ния путем выборки случайных комбинаций из обучающего набора с
повторением;



применение бустинга (lюoslinp:) для построения мощных моделей из

слабых учеников, которые учатся на своих ошибках.

Обучение с помощью ансамблей
Цель ансамблевых методов

-

объединить различные классификаторы в

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



экспертов, то ансамблевые

[лава

7.

Объединение разных моделей для ансамблевого обучения

методы позволили бы стратегически скомбинировать эти прогнозы для по­
лучения прогноза, более точного и надежного, нежели прогнозы каждого
эксперта по отдельности . Как будет показано позже в главе , существует не­

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

методах,

которые

принцип ма:жоритарно го голосования

используют

(majoгily )m fi 11)S), или голосования большинством голосов. Мажоритарное
голосование означает, что мы просто выбираем метку класса, которая была
спрогнозирована большинством классификаторов, т.е. получила свыше

50%

голосов. Строго говоря, термин больишнство голосов относится только к
конфигурациям с двоичными классами. Тем не менее, принцип мажоритар­

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

(pl1m1lity voti11к). В таком случае мы выбираем метку класса, которая полу­
чила наибольшее число голосов (моду (тщfе)). На рис.

7. 1

иллюстрируется

концепция голосования большинством и относительным большинством го­
лосов для ансамбля из



классификаторов, где каждый уникальный символ

(треугольник, квадрат и круг) представляет уникальную метку класса.

8 8 8 8 8 8 8 8 8 8

Единодушие

8 8 8 8 8 8

Большинство

8 8 8 8
Рис.

7.1.

,6. ,6. ,6. ,6.

,6. ,6. ,6.

О О О

Относительное большинство

Концепция голосования большинством и относительныАt

большинством голосов для а11са;wбля из



классифи11.аторов

Мы начинаем с применения обучающего набора для обучения т раз­
ных классификаторов (С 1 , "., Ст). В зависимости от приема ансамбль мо­
жет быть построен из разных алгоритмов классификации, скажем, деревьев
принятия решений, методов опорных векторов, классификаторов на основе
логистической регрессии и т.д. В качестве альтернативы мы также можем
использовать один и тот же базовый алгоритм классификации, выполняя

(274)

Глава

7.

Объединение разных моделей для ансам бле вого обучения

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

решений. На рис .

7.2

демонстрируется концепция общего ансамблевого под­

хода, применяющего мажоритарное голосование.

Обучающий набор

"



::i:
::i:

Классификационные

~

модели

!

"


"'

о

:i::

Прогнозы

Финальный прогноз
Рис.

7.2.

!


Ктщепция общего ансаwблевого подхода, использующе,~о
.нажоритарное голосование

Чтобы спрогнозировать метку класса посредством простого голосования
большинством или относительным большинством голосов, мы объединяем
метки классов, спрогнозированные каждым индивидуальным классификато­

ром С1, и выбираем метку класса у, которая получила наибольшее количес­
тво голосов:

у = мода {cl (х) ' с2 (х) ' ...' ст ( х)}
(В статистике мода представляет собой самое частое событие или результат
в наборе. Скажем , мода

{ 1, 2, 1, 1, 2, 4, 5, 4} = 1.)
[275] -

Глава

7.

Объединение разных моделей для ансамблевого обучения

Например, в задаче двоичной классификации, где класс

2

равен

+1,

1 равен -1,

а класс

мы можем записать мажоритарный прогноз следующим образом:

() [L

С х =знак

m.

С. х
(

)]

=

{

1

i

"L...i, С.1 х) ~ О
-1 в противном случае

1, если

(

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

мы делаем допущение о том, что все п базовых классификаторов для задачи
двоичной классификации имеют одинаковые частоты ошибок

s.

Кроме того,

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

разить вероятность ошибки ансамбля базовых классификаторов просто как
функцию вероятностной меры биномиального распределения:
- ~ (п)
k &k {1 - О. 25) n-k - &а11 са.11 б. 1я
- k )Р(у >
11

Здесь \:)-биномиальный коэффициент из п по k. Другими словами, мы
вычисляем вероятность того, что прогноз ансамбля неправильный. Теперь

взглянем на более конкретный пример с

11

базовыми классификаторами (п =

где каждый классификатор имеет частоту ошибок

Р(у ~ k) =

0.25 (s

11 ),

= 0.25):

L (11) 0.25* (1-в ) -* = 0.034
11

11

k~б k

Г'.~ Биномиальный коэффициент

н~ Биномиальный коэффициент имеет отношение к количеству спосо­
заметку!

бов, которыми мы можем выбирать поднаборы

k

неупорядоченных

элементов из набора с размером п; соответственно его часто назы­
вают "из п по /С'. Поскольку порядок не имеет значения, на бино­
миальный коэффициент также иногда ссылаются как на сочетаиuе

или кo;wбu1tamop11oe число, и в сокращенной форме он может быть
записан следующим образом:
п!

(n-k)!k!
Здесь символом

! обозначается

факториал, например,

------------·------------ (276) - - - - - - -

3 ! = 3 · 2 · 1 = 6.

Глава

7.

Объединение разных моделей для ансамблевого обучения

Несложно заметить, что частота ошибок ансамбля

(0.034)

гораздо мень­

ше частоты ошибок каждого индивидуального классификатора

(0.25),

если

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

ны времени. Для сравнения такого идеалистического ансамблевого класси­
фикатора и базового классификатора с некоторым диапазоном частот базо­
вых ошибок мы реализуем функцию вероятностной меры на

Python:

>>> fr·oщ scipy. special iшpo1:·t соmЬ
>>> import math
>>> c\e;f ensemЫe_error (n_classifier, error):

k_ start = in t: (math. ceil (n_ classifier / 2.) )
probs = [comЬ(n_classifier, k) *
error**k *
(1-error) ** (n_classifier - k)
fo1· k in r.ыt9fi;•(k_start, n_classifier + 1)]
rotд1:n sнm (probs)
>>> ensemЬle_error(n_classifier=ll, error=0.25)
0.03432750701904297
После реализации функции ensemЫe

error

мы можем подсчитать час­

тоты ошибок ансамбля для диапазона частот базовых ошибок от О.О до

1.0,

чтобы визуализировать связь между ансамблевыми и базовыми ошибками в
виде линейного графика:

>>> .нnport numpy as np
>>> iщport matplotlib.pyplot as plt
>>> error_range = np.arange(O.O, 1.01, 0.01)
>>> ens_errors = [ensemЬle_error(n_classifier=ll, error=error)

for error in error_range]
>>> plt.plot(error_range, ens_errors,
lаЬеl='Ансамблевая ошибка',

linewidth=2)
>>> plt.plot(error_range, error_range,
linestyle='--', lаЬеl='Базовая
linewidth=2)
>>> plt.xlabel ('Базовая ошибка')
>>> рlt.уlаЬеl('Базовая/ансамблевая
>>> plt.legend(loc='upper left')
>>> plt.grid(alpha=0.5)
>>> plt. show ()

- - ...... [ 2771

ошибка',

ошибка')

········--······-·-····----------------~--

[лава

7.

Объединение разнЬ1х моделей для ансамблевого обучения

Как видно на результирующем графике (рис.

7.3),

характеристика вероят­

ности ошибки, допускаемой ансамблем, всегда лучше такой характеристики
индивидуального базового классификатора до тех пор, пока базовые клас­

сификаторы работают лучше, чем случайное угадывание

(s < 0.5).

Обратите

внимание, что ось у изображает базовую ошибку (пунктирная линия), а так­
же ансамблевую ошибку (сплошная линия).

1.0

Ансамблевая ошибка

-

- - • Базовая ошибка
са

о.в

~
s
3

о
о:

0.6

:::
ф

~

:=::
са

~

0.4

са
........
о:

са

ID

:;
са

0.2

ш

О. О
О.О

0.2

0.4

0.6

о.в

1.0

Базовая ошибка

Рис.

7.3.

Связь .wеж·ду а11са.мблевы.wи и баювы.ми oшu61-;awu

Объединение классификаторов с помощью
мажоритарного голосования
После краткого введения в ансамблевое обучение, предложенного в пре­
дыдущем разделе, давайте приступим к упражнению и реализуем простой

ансамблевый классификатор с мажоритарным голосованием на

Python.

\\:~ Голосование относительным большинством rолосов

н~ Хотя обсуждаемый в этом разделе алгоритм мажоритарного голосо3аметку!

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

будем использовать термин "мажоритарное голосование'', как часто
поступают в литературе по МО.

-

- -- - - -- -- - --

-

(278)------- --

--·· · - -- - -

Глава

7. Объединение разных моделей для ансамблевого обучения

Реализация простоrо классификатора с мажоритарным rолосованием
Алгоритм, который мы собираемся реализовать в текущем разделе, позво­

лит объединять различные алгоритмы классификации, связанные индивиду­
альными коэффициентами доверия в форме весов. Наша цель

-

построить

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

у= argmax

т

L wJxA ( CJ (х) = i)

1

}=\

вес, ассоциированный с базовым классификатором С1 , у

-

метка класса, спрогнозированная ансамблем, Хл (греческая буква "хи")

-

Здесь

w1 -

характеристическая или индикаторная функция, которая возвращает
спрогнозированный класс }-того классификатора соответствует

1,

если

i (С/х) = i).

Для равных весов уравнение можно упростить, записав его так:

у= мода {с] (х)' с2 (х)' ". 'ст ( х)}
Для лучшего понимания концепции взвешивания мы рассмотрим более

конкретный пример. Пусть имеется ансамбль из трех базовых классификато­
ров, С/} Е {О, 1} ), и мы хотим спрогнозировать метку класса С/х) Е {О, 1} за­
данного образцах. Два из трех базовых классификаторов прогнозируют метку

класса О и один, С3 , вырабатывает прогноз, что образец принадлежит классу

1.

Если назначить прогнозам всех базовых классификаторов одинаковые веса,
тогда большинство голосов даст прогноз, что образец относится к классу О:

у =мода {О, О, 1} = О
Теперь давайте назначим С3 вес

у= argmax
1

0.6,

а С 1 и С2 взвесим с коэффициентом

т

L W;XA ( cj (х) =i)=
}=\

= arg max [ 0.2 х i0 + 0.2 х i0 + 0.6 х i 1 ] = 1
1

0.2:

fлава

7.

Объединение разных моделей для ансамблевого обучения

Поскольку

3

х

0.2 = 0.6,

мы можем сказать, что прогноз, сделанный С3 ,

имеет в три раза больший вес, чем прогнозы от С 1 или С2 , поэтому мы мо­
жем записать так:

у = мода {О, О,

1, 1, 1} = 1

Чтобы представить концепцию взвешенного большинства голосов в
коде

Python,

мы можем воспользоваться удобными функциями

Ьincount из библиотеки

argrnax

и

NumPy:

>>> iinport numpy as np
>>> np.argrnax(np.Ьincount ([О, О, 1],
weights=[0.2, 0.2, 0.6]))
1
Как мы помним из обсуждения логистической регрессии в главе

торые классификаторыв

scikit-leam

3,

неко­

также способны возвращать вероятность

спрогнозированной метки класса посредством метода

predict _proba.

Применение вероятностей спрогнозированных классов вместо меток клас­

сов для мажоритарного голосования может быть практичным, если наш ан­

самбль хорошо откалиброван. Модифицированную версию большинства го­
лосов для прогнозирования меток классов из вероятностей можно записать

следующим образом:


т

у= argmax
. L.J w.p
1 1)..
j=\

1

Здесь

спрогнозированная вероятность }-того классификатора для

piJ -

метки класса

i.

Чтобы продолжить предыдущий пример, давайте предположим, что мы

имеем задачу двоичной классификации с метками классов

блем из трех классификаторов С1 (} е

{ 1,2,3} ).

i

е {О, 1} и ансам­

Пусть классификаторы ~ воз­

вращают такие вероятности членства в классах для отдельного образца х:
С 1 (х) ~

[0.9,0.1], Cix)

~

[0.8,0.2],

Используя те же веса, что и ранее

(0.2, 0.2

С3 (х) ~
и

0.6),

[0.4,0.6]

мы можем рассчитать

вероятности индивидуальных классов следующим образом:

----·---[280)----------

Глава

7.

Объединение разных моделей для ансамблевого обучения

p(i0 1Х)=0.2 х 0.9 + О.2х 0.8+ О.бх 0.4 = 0.58
p(i

1

1х)=0.2 х 0.1+0.2 х 0.2 + 0.бх 0.6 = 0.42

у = arg max [ р ( i 0 1 х), р ( i 1 1 х)] = О
1

Для реализации взвешенного большинства голосов на основе вероятнос­
тей классов мы снова можем задействовать функции

np. argmax
>>>

библиотеки

numpy. average

и

NumPy:

ех

= np.array( [ (0.9, 0.1],
(0.8, 0.2],
(0.4, 0.6]])
= np.average(ex, axis=O, weights=[0.2, 0.2, 0.6])

>>> р
>>> р
array([ 0.58, 0.42])
>>> np. argmax (р)
о

Теперь соберем все вместе и реализуем класс

на

MajorityVoteClassifie r

Python:
froш sklearn.base import BaseEstimator
from sklearn.base import ClassifierMixin
froш sklearn.preprocessing iroport LabelEncoder
from sklearn. externals import six
from sklearn.base impor·t clone
t"r:om sklearn. pipeline import _ name_ estimators
impo1~t numpy as np
impo:r.t operator

class MajorityVoteClassifier( BaseEstimator,
ClassifierMixin) :
"""Ансамблевый классификатор с мажоритарным голосованием
Параметры

classifiers :

подобен массиву,

форма =

[n_classifiers]

Различные классификаторы для ансамбля.

vote : str, { 'classlabel ', 'probaЬili ty'}
По умолчанию: 'classlabel '

(281] - - - - - - - - - - - - - - - - - - - - -

Глава

7.

Объединение разных моделей для ансамблевого обучения

'classlabel' прогноз основывается
argmax меток классов. В случае

В случае

на результате

'probaЬility' для прогнозирования метки класса
применяется

argmax

суммы вероятностей

(рекомендуется для откалиброванных классификаторов)
подобен массиву,

weights :

Необязательно,

форма =

по умолчанию:

.

[n_classifiers]

None

Если предоставляется список значений

int

или

float,

тогда классификаторам назначаются веса по важности;

в случае

weights=None

используются равномерные веса.

"""
def

init

classifiers,
vote='classlabel', weights=None):

1~~:1,

~~l[.classifiers

= classifiers
{key: value for
key, value in
_name_estimators(classifiers)}

;;,, l' • named classifiers

vote = vote

•;с";

f.

,с~:

: . weights = weights

def fi t (

1~ ,

Х,

у)

:

"""Подгоняет классификаторы.
Параметры

Х

:

{подобен массиву, разреженная матрица},

форма =
Матрица

у

:

[n_examples, n_features]
обучающих образцов .

подобен массиву,

форма =

[n_examples]

Вектор целевых меток классов.

Возвращает

self : object

"""
if

•с:! t • vote not in ( 'probaЬili ty', 'classlabel') :
raise ValueError ( "vote должно быть 'probaЬili ty'"
"или 'classlabel'; получено (vote=%r)"
% ."'····J;.vote)

and

if '"'- :

t • weights

len (

1. f • weights)

! = len (ci•. ! ' • classifiers) :

------------[282]--- ----·-----------

fлава

raise

7.

Объединение разных моделей для ансамблевого обучения

ValueError("Koличecтвo классификаторов и

количество весов"
"должны быть равны;

"%d

получено

%d

весов,"

классификаторов"

% (len (

len(

. weights),
!.classifiers)))

#

Использовать

#

что метки классов начинаются с О;

LabelEncoder

#это важно при вызове

для гарантии того,

np.argmax в self.predict.

' '· . laЫenc_ = LabelEncoder ( )
:··.laЫenc_.fit(y)

".classes
.1 .• laЫenc .classes
:_.classifiers = []
for clf in ·'"'; r .classifiers:
fitted_clf = clone(clf) .fit(X,
•,

J :. •

laЫenc

.transform(y))

. · .classifiers .append(fitted_clf)
returп

В коде присутствует много комментариев, поясняющих его отдельные
части. Однако прежде чем реализовывать остальные методы, давайте нена­

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

и

ClassifierMixin,

BaseEstimator

чтобы даром получить определенную функциональ­

ность, включая методы

get _params

и

set _params

вращения параметров классификатора, а также метод

для установки и воз­

score

для подсчета

правильности прогнозирования.

Далее мы добавим метод

predict для

прогнозирования метки класса через

мажоритарное голосование, если новый объект
инициализируется с параметром

MajorityVoteClassifier
vote=' classlabel'. В качестве альтер­

нативы мы будем иметь возможность инициализации ансамблевого класси­
фикатора с параметром

vote=' probaЬili ty',

чтобы прогнозировать мет­

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

predict _proba

для возвращения усредненных вероятнос­

тей, которые полезны при вычислении показателя

def· predict ( :J ',
"""

ROC AUC:

Х):

Прогнозирует метки классов для Х.

Параметры

---[283)

Глава

7.

Объединение разных моделей для ансамблевого обучения
Х

{подобен массиву, разреженная матрица}

:

форма =

,

[n_examples, n_features}

Матрица обучающих образцов.

Возвращает

maj_vote :

подобен массиву,

форма=

[n_examples]

Спрогнозированные метки классов.

,,""
~,;[1.vote == 'probaЬility':
maj_vote = np.argmax(.;r_,; .predict_proba(X),
axis=l)
else:
# vote='classlabel'

if

# Получить результаты из вызовов clf.predict
predictions = np.asarray([clf.predict(X)
for clf in
~elt.classifiers ])
maj_vote



np.apply_along axis(
lamЬda х:
np.argmax(np.Ьincount(x,
weights=s~lt.weights)),

axis=l,
arr=predictions)
maj_vote = 3(tf.laЫenc .inverse_transform(maj_vote)
return maj_vote
def predict_proba ( "'" 1 з , Х) :
"""Прогнозирует вероятности классов для Х.
Параметры

Х

:

{подобен массиву, разреженная матрица},

форма =

[n_examples, n_features}
Обучающие векторы, где п_ examples образцов, а п features - количество

количество
признаков.

Возвращает

avg_proba :
форма_=

подобен массиву,

[n_examples, n_classes]

Взвешенная усредненная вероятность
для ка)l\Цого класса на

образец.

",,"
·----------(284]------- - -

Глава

7.

Объединение разных моделей для ансамблевого обучения

probas = np.asarray([clf.predict_proba(X)
for clf in sE·i_i .classifiers_])
avg_proba = np.average(probas,
axis=O, weights=:: · · : '---~ weights)
return avg_proba
def get_Params (. · , : , deep=True):
"'"'Получает имена параметров классификатора для

"""
not deep:
return super(MajorityVoteClassifier,
::.: :) .get_params(deep=False)

решетчатого поиска

if

else:

out = .: ·:: .named_classifiers.copy()
for name, step in ~;;-_1 :.• named_classifiers. items ():
for key, value in step.get_params(
deep=True) .items():
out [ '%s %s' % (name, key)] = value
return out
Мы также определили собственную модифицированную версию метода

get_params

с целью использования функции

_name_estimators

для до­

ступа к параметрам индивидуальных классификаторов в ансамбле. Сначала
подход может выглядеть несколько замысловатым, но он обретет смысл, ког­

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

Г'.~ Класс

Voti.nqClassifier в scikit-learn

н~ Хотя реализация MajorityVoteClassifier очень удобна для де­
заметку!

монстрационных целей, мы реализовали более сложную версию
данного классификатора с мажоритарным голосованием в библиоте­

ке

scikit-leam,

основываясь на реализации из первого издания книги.

Этот ансамблевый классификатор доступен в виде класса

sklearn.

ensemЫe.

и новее.

VotingClas s i f ier

в

scikit-learn

версии

0.17

Испоnьзование принципа мажоритарноrо
rоnосования дnя выработки проrнозов
Наступило время задействовать класс

MajorityVoteClassifier,

реа­

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

Maj ori tyVoteClassifier.

--------(285)------------

Глава

7.

Объединение разных моделей для ансамблевого обучения

Так как мы уже видели приемы для загрузки наборов данных из файлов

CSV, мы примем сокращенный путь и загрузим набор данных lris из модуля
da tasets библиотеки scikit-learn. К тому же вы выберем только два при­
знака, ширину чашелистика (sepal 11•i1ftl1) и длину лепестка (pclal fc11).;tf1),
чтобы сделать задачу классификации более подходящей для иллюстратив­
ных целей. Несмотря на то что

MajorityVoteClassifier

обобщается на

многоклассовые задачи, мы будем классифицировать образцы цветков толь­

Iris-versicolor и Iris-virginica, а позже вычислим
показатель ROC AUC. Ниже представлен соответствующий код:

ко из классов

для них

>>>
>>>
>>>
>>>
>>>
>>>
>>>

froш sklearn iПtport. datasets
from sklearn.model_selection import train_test_split
from sklearn .preprocessing 1mpo1"t StandardScaler
froПt sklearn. preprocessing import. LabelEncoder
iris = datasets.load_iris()
Х, у= iris.data[SO:, [1, 2]], iris.target[SO:]
le = LabelEncoder ()

Г:~ Вероятности nрин~дnежности кnассам в деревьях
~ принятия решении
На

заметку!

ROC AUC в
predict _proba (если

Обратите внимание, что для вычисления показателя
библиотеке

scikit-leam

он применим). В главе

используется метод

3

было показано, как вычисляются вероят­

ности классов в логистических регрессионных моделях. В деревьях

принятия решений вероятности вычисляются из частотного вектора,

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

из распределения меток классов в данном узле. Затем частоты нор­

мализуются, чтобы давать в сумме
пируются метки классов

k

1.

Подобным же образом груп­

ближайших соседей, чтобы возвратить

нормализованные частоты меток классов в алгоритме
соседей

(KNN).

k

ближайших

Хотя нормализованные вероятности, возвращаемые

классификаторами на основе дерева принятия решений и метода

k

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

навать, что в действительности они не получаются из функций ве­
роятностной меры.

-----,--------(286]--

Глава

7.

Затем мы разделяем образцы

Объединение разных моделей для ансамблевого обучения

Iris

на

50%

обучающих и

50%

испытатель­

ных данных:

>>> X_train, X_test, y_train, y_test =\

train_test_split(X, у,
test_size=0.5,
randorn_ state=l,
stratify=y)
Используя обучающий набор данных, мы обучим три разных классификатора:



классификатор на основе лоrистической регрессии;



классификатор на основе дерева принятия решений;



классификатор на основе метода

k ближайших

соседей.

Перед объединением базовых классификаторов в ансамблевый классифи­
катор мы оцениваем эффективность каждого классификатора посредством
перекрестной проверки по

>>>
>>>
>>>
>>>
>>>
>>>
>>>



блокам на обучающем наборе:

from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import КNeighЬorsClassifier
from sklearn.pipeline import Pipeline
import numpy as np
clfl = LogisticRegression(penalty='l2',
С=О.001,
solver='lЬfgs',

randorn_state=l)
>>> clf2 = DecisionTreeClassifier(rnax_depth=l,
criterion='entropy',
randorn_state=O)
>>> clfЗ = КNeighЬorsClassifier(n_neighЬors=l,
р=2,

rnetric='rninkowski')
>>> pipel = Pipeline ( [ [ 'sc' , StandardScaler () ] ,
[ 'clf', clfl]])
>>> рiреЗ = Pipeline([['sc', StandardScaler()],
[ 'clf', clfЗ]])
>>> clf labels
[ 'Логистическая регрессия' ,
'Дерево принятия решений' , 'КNN' ]

------ ---[287)-------·----

[лава

7.

Объединение разных моделей для ансамблевого обучения

>>> priпt ('Перекрестная проверка по 10 блокам: \n')
>>> 1'or clf, label in zip ( [pipel, clf2, рiреЗ], clf_labels):
scores = cross_val_score(estimator=clf,
X=X_train,
y=y_train,
cv=lO,
scoring='roc_auc')
pr i n t:. ( ROC AUC : %О • 2 f ( +/ - %О • 2 f) [ %s]
% (scores.mean(), scores.std(), label))
11

11

Получаемый вывод показывает, что эффективность прогнозирования ин­
дивидуальных классификаторов почти равна:
Перекрестная проверка по

ROC AUC:
ROC AUC:
ROC AUC :

0.92 (+/- 0.15)
0.87 (+/- 0.18)
О • 8 5 ( + / - О • 13 )

10

блокам:

[Логистическая регрессия]

[Дерево принятия решений]

[ КNN]

Вас может интересовать причина, по которой мы обучаем классифика­

k ближайших соседей в
главе 3, дело в том, что в от­

торы на основе логистической регрессии и метода

качестве части конвейера. Как обсуждалось в

личие от деревьев принятия решений алгоритмы логистической регрессии и

k

ближайших соседей (применяющие метрику евклидова расстояния) не яв­

ляются масштабно-инвариантными. Несмотря на то что все признаки в на­
боре данных измерены с тем же самым масштабом (сантиметры), работа со
стандартизированными признаками расценивается как хорошая привычка.

А теперь перейдем к более захватывающей части и объединим индиви­

дуальные классификаторы по правилу мажоритарного голосования в классе

MajorityVoteClassifier:
>>> mv_clf = MajorityVoteClassifier(
classifiers=[pipel, clf2, рiреЗ])
>>> clf_labels += ['Мажоритарное голосование']
>>> all_clf = [pipel, clf2, рiреЗ, mv_clf]
>>> .fo.r clf, label in zip (all_clf, clf_labels):
scores = cross_val_score(estimator=clf,
X=X_train,
y=y_train,
cv=lO,
scoring='roc_auc')

- - - (288]----

[лава

7.

Объединение разных моделей для ансамблевого обучения

pпnt("ROC

ROC
ROC
ROC
ROC

AUC:
AUC:
AUC:
AUC:

0.92
0.87
0.85
0.98

AUC: %0.2f (+/- %0.2f) [%s]"
% (scores.mean(), scores.std(), label))
(+/- 0.15) [Логистическая регрессия]
(+/- 0.18) [Дерево принятия решений]_
(+/- 0.13) [КNN]
(+/- 0.05) [Мажоритарное голосование]

Глядя на результаты перекрестной проверки по
метить, что эффективность

1О блокам, несложно
Maj ori tyVotingClassifier повысилась

за­
по

сравнению с эффективностью индивидуальных классификаторов.

Оценка и настройка ансамблевого классификатора
В этом разделе мы построим кривые

чтобы проверить, хорошо ли

ROC для испытательного набора,
Majori tyVoteClassifier обобщается на

не встречавшиеся ранее данные. Как вы помните, испытательный набор не
должен использоваться при отборе модели; он предназначен для выдачи не­
смещенной оценки эффективности обобщения, которую обеспечивает клас­
сификационная система:

>>>
>>>
>>>
>>>
>>>

from sklearn.metrics import roc_curve
from sklearn .metrics import auc
colors == ['Ыасk', 'orange', 'Ыuе', 'green']
linestyles = (' : ', • -- •, 1 - • 1 , • - • ]
for clf, label, clr, ls \
in zip(all_clf, clf_labels, colors, linestyles):
# предполагается, что меткой положительного класса является 1
y_pred = clf.fit(X_train,
y_train) .predict_proba(X_test) [:, 1]
fpr, tpr, thresholds = roc_curve(y_true=y_test ,
y_score=y_pred)
roc_auc = auc(x=fpr, y=tpr)
plt.plot(fpr, tpr,
color=clr,
linestyle=ls,
label='%s (auc = %0.2f)' % (label, roc_auc))
>>> plt.legend(loc='lower right')
»> plt.plot ( (0, 1], (0, 1],
linestyle='--',
color=' gray',
linewidth=2)
»> plt.xlim( (-0.1, 1.1])

(2891---------------- ------------

[лава

7.

Объединение разных моделей для а нсамблевого обу чения

>» plt.ylim((-0 . 1, 1.1))
>>>
>>>
>>>
>>>

plt.grid(alpha=0.5)
pl t. xlabel ('Доля ложноположительных кл а с сификаций ( FPR) ')
plt. ylabel ('Доля и с тинн о п оложительных кла с сифика ций (TPR ) ')
plt. show ()

На результирующих кривых

ROC

(рис .

7.4)

легко заметить, что ансам­

блевый классификатор также хорошо работает на испытательном наборе

(ROC AUC

= 0.95).

Тем не менее, мы видим, что классификатор на основе

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

i%'
а.

!::.
>:S:

1.0

:s:
:::r

"":s:

-в-

:s:

0.8




"5
><

0.6

:;;

...

:i:
с:

QI

"ilE

:s:

0.4

о

с:

о
с:

о

••• • · ·

0.2

:s:

":s:

Логистическая регрессия

(auc = 0.95)

Дерево принятия решений

:i:
:i:

(auc = 0.90)

- · - KNN(auc=0.86)
о. о

--

"'

Мажоритарное голосование

(auc = 0.95)

с:
о

ct

о. о

0.2

0.4

0.6

Рис.

7.4.

Кривые

ROC

1.0

0.8

Доля ложноположительных классификаций

(FPR)

для испытат елыю,00 11абора

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

да

k ближайших

соседей позаботятся об этом автоматически , мы стандарти-

(290)

Глава

7. Объединение разных моделей для ансамблевого обучения

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

>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

sc = StandardScaler ()
train std = sc.fit transform(X_train)
_from i tertools i.шport product
x_min = X_train_std[:, 0] .min() - 1
x_max = X_train_std[:, О] .max() + 1
y_min = X_train_std[:, 1] .min() - 1

Х

y_max = X_train_std[:, 1] .max() + 1
хх, уу = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, у max, 0.1))
>>> f, axarr = plt.subplots(nrows=2, ncols=2,
sharex='col',
sharey=' row' ,
figsize= (7, 5))
>>> for idx, clf, tt in zip (product ([О, 1], [0, 1]),
all_clf, clf_labels):
clf.fit(X_train_std, y_train)
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axarr[idx[O], idx[l]] .contourf(xx, уу, Z, alpha=0.3)
axarr[idx[O], idx[l]] .scatter(X_train_std[y_train==O,
X_train_std[y_train==O, 1],

О],

с='Ыuе',

marker=' л',
s=50)
axarr[idx[O], idx[l]] .scatter(X train_std[y_train==l, 0],
X_train_std[y_train==l, 1],
с=' green',
marker=' о',
s=50)
axarr[idx[O], idx[l]] .set_title(tt)
>>> plt.text(-3.5, -5.,
s='Ширина чашелистика

[стандартизированная]',

ha='center', va='center', fontsize=12)
>>> plt.text(-12.5, 4.5,
s='Длина лепестка

[стандартизированная]',

ha='center', va='center',
fontsize=12, rotation=90)
>>> plt.show()

----------------(291 ]----------------

Глава

Объединение разных моделей для ансамблевого обучения

7.

Интересно, но вполне ожидаемо области решений ансамблевого класси­
фикатора выглядят как гибрид областей решений индивидуальных класси­

фикаторов (рис.

7.5).

На первый взгляд граница решений классификатора с

мажоритарным голосованием очень похожа на пенек дерева принятия реше­

ний, который перпендикулярен оси у для ширины чашелистика ~

1.

Однако

мы также отмечаем примесь нелинейности со стороны классификатора на
основе метода

~

k

ближайших соседей.

Логистическая регрессия

Дерево принятия решений





2

со

:z:
:z:
со

ID

о

о

Q,

:s:
:s:

/?
1-

Q,

1








-2

С1:

:z:

со

KNN

1-

~
со

:.:

о



2

С11
с

С11

с;

со

:z:



о

Мажоритарное голосование



1



1



•••J"" "

:s:
с;

c::i:



•....J"" •

со

1-

1



-2

-2

о

-2

2

о

2

Ширина чашелистика [стандартизированная]

Рис.

7.5.

Области решений а11са.~16левого классификатора
и индивидуачьных классификаторов

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

get _pararns,

что­

бы получить базовое представление о том, как обращаться к отдельным па­
раметрам внутри объекта

>>>

GridSearchCV:

mv_clf.get~params()

{'decisiontreeclassifier':
DecisionTreeClassifier(class_weight=None, criterion='entropy',
max_depth=l, max_features=Noпe,
max_ leaf_ nodes=№me, min_ samples _ leaf=l,

---[292] ----------

-

-

fлава

7.

Объединение разных моделей для ансамблевого обучения

min_samples_split=2,
min_weight_fraction_leaf=O.O,
random_state=O, splitter='best'),
'decisiontreeclassifier_class_weight': None,
'decisiontreeclassifier_ criteriori': 'entropy',
[

... ]

'decisioritreeclassifier_random_state': О,
'decisiontreeclassifier_splitter': 'best',
'pipelirie-1':
Pipeline(steps=[('sc', StandardScaler(copy=True, with_mean=True,
wi th_ std='l'rue) ) ,
('clf', LogisticRegression(C=0.001,
class_weight=None,
dual=False,
fit_intercept=True,
intercept_scaling=l,
max_iter=lOO,
multi_class='ovr',
penalty=' 12',
random_state=O,
solver='liЫinear',

tol=0.0001,
verbose=O) ) ] ) ,
'pipeline-1 clf':
LogisticRegression(C=0.001, class_weight=None, dual=False,
fit_intercept=True, intercept_scaling=l,
max_iter=lOO, multi_class='ovr',
penalty='l2', random_state=O,
solver='liЫinear', tol=0.0001, verbose=O),
'pipeline-1 clf_C': 0.001,
'pipeline-l_clf_class_weight': None,
'pipeline-1 clf dual': False,
[

... ]

'pipeline-1 sc _ wi th_ std' : Тгuе,
'pipeline-2':
Pipeline (steps= [ ( 'sc', StandardScaler (copy=T:rue, with_ mean='Гrue,
with_std='I':гue)),

('clf',

КNeighborsClassifier(algorithm='auto',
leaf_size=ЗO,

metric='minkowski',
metric_params=None,
n_neighbors=l,

----- [2931

--------~--------------

fлава

7.

Объединение разных моделей для ансамблевого обучения
р=2,

weights='uniform'))]),
'pipeline-2

clf':

КNeighЬorsClassifier(algorithm='auto',

'pipeline-2_clf
[

... ]

'pipeline-2

leaf_size=ЗO,

metric='minkowski', metric_params=None,
n_neighЬors=l, р=2, weights='uniform'),
algorithm': 'auto',

sc_with_std': True}

Основываясь на значениях, которые возвратил метод

get _params,

мы

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

настроим обратный параметр регуляризации С для классификатора на базе
логистической регрессии и глубину дерева принятия решений:

>>> from sklearn. model_ selection i111pori:: GridSearchCV
>>> params = {'decisiontreeclassifier_max_depth': (1, 2],

'pipeline-1 clf_C': (0.001, 0.1, 100.0]}
>>> grid = GridSearchCV(estimator=mv_clf,
param_grid=params,
cv=lO,
iid=False,
scoring='roc_auc')
>>> grid.fit(X_train, y_train)
После завершения решетчатого поиска мы можем вывести различные
комбинации значений гиперпараметров и средние показатели
вычисленные посредством перекрестной проверки по

>>>

0.956
0.978
0.956

блокам:

in fэщныэrat~e(grid.cv_results_['mean_test score']):
prJ.пt ( "%0. Зf +/- %0. 2f %r"
% (grid.cv_results_['mean_test score'] [r],
grid.cv_results_['std_test_score'] (r] / 2.0,
grid.cv_results_['params'] [r]))
+/- 0.07 {'decisiontreeclassifier_max depth': 1,
'pipeline-1 clf С': 0.001}
+/- 0.07 {'decisiontreeclassifier_rnax depth': 1,
'pipeline-1 clf_C': 0.1}
+/- 0.03 {'decisiontreeclassifier_rnax depth': 1,
'pipeline-1 clf С': 100.0}
+/- 0.07 {'decisiontreeclassifier_max depth': 2,
'pipeline-1 clf_C': 0.001}

f'от

0.944



ROC AUC,

r,

------------------------ (294] ----------------------------------

fлава

7. Объединение разных моделей для ансамблевого обучения

0.956 +/- 0.07 {'decisiontreeclassifier_max_depth': 2,
'pipeline-l_clf_C': 0.1}
0.978 +/-о.аз {'decisiontreeclassifier_max_depth': 2,
'pipeline-l_clf_C': 100.0}

>>> print ('Наилучшие
Наилучшие параметры:

>>>

%s' % grid.best_params_)
{'decisiontreeclassifier_max_depth': 1,
'pipeline-1 clf_C': 0.001}

параметры:

рrint('Правильность:

Правильность:

%.2f' % grid.best_score_)

0.98

Как видите, мы получили лучшие результаты перекрестной проверки, ког­
да выбрали наименьшую силу регуляризации (С=О.

001),

тогда как глубина

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

что применение испытательного набора данных для оценки модели более

одного раза

-

скверная практика, мы не собираемся в этом разделе оцени­

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

-

бэггингу.

\\.~ Построение ансамбпей с исnопьзованием стекинrа

н~ Не следует путать подход мажоритарного голосования, реализован­
заметку!

ный в настоящем разделе, со стекингом (stщ·kiщд. Алгоритм стекинга можно понимать как двухслойный ансамбль, в котором первый

слой состоит из индивидуальных классификаторов, передающих
свои прогнозы второму слою, где еще один классификатор (обычно
основанный на логистической регрессии) подгоняется к прогнозам

классификаторов первого слоя, чтобы вырабатывать финальные про­
гнозы. Алгоритм стекинга подробно описан Дэвидом Вольпертом в

статье

"Stacked generalization" (Многослойное обобщение), Neural
Networks, 5(2): с. 241-259 (1992 г.). К сожалению, на момент написа­
ния книги описанный алгоритм не был реализован в scikit-learn, но
работа над ним велась. Тем временем вы можете найти совместимые
с библиотекой

scikit-learn реализации стекинга по ссылкам http: / /
rasbt.github.io/mlxtend/user_guide/classifier/
StackingClassifier / и http://rasbt.gi thub. io/mlxtend/
user_guide/classifier/StackingCVClassifier/.

[295]

fлава

7.

Объединение разных моделей для ансамблевого обучения

Бэггинг

-

построение ансамбля

классификаторов из бутстрэп-образцов
Бэггинг представляет собой прием ансамблевого обучения, тесно свя­
занный с классификатором

MajorityVoteClassifier,

который мы реа­

лизовали в предыдущем разделе. Тем не менее, вместо использования для

подгонки индивидуальных классификаторов в ансамбле того же самого
обучающего набора мы производим выборку бутстрэп-образцов (случай­
ных образцов с возвращением) из первоначального обучающего набора,

что и является причиной, по которой бэггинг называют также бутстрэп­
агрегирова11ием .

Концепция бэггинга подытожена на рис.

7.6.

В последующих подразделах мы проработаем простой пример бэггинга
вручную и применим библиотеку

scikit-learn

вин.

Бутстрэп­
образцы

Классификационные
модели

Финальный прогноз



(296)



для классификации образцов

Глава

7.

Объединение разных моделей для ансамблевого обучения

Коротко о бэrrинrе
Чтобы предоставить более конкретный пример, иллюстрирующий рабо­

ту бутстрэп-агрегирования классификатора на основе бэггинга, рассмотрим
таблицу на рис.
ченных

7.7. Здесь мы имеем семь обучающих образцов (обозна­
индексами 1-7), которые случайным образом выбираются с воз­

вращением в каждом раунде бэггинга. Каждый бутстрэп-образец затем ис­
пользуется для подгонки классификатора С1 , которым чаще всего является
неподрезанное дерево принятия решений.

Индексы

Раунд

образцов

бэггинга

бэггинга

...

1

2

7

2

2

3

3

1

2

...
...
...

4

3

1

...

5

7

1

6

2

7

7

4

7

...
...
...

Рис.

На рис.

7.7

Раунд

1

7. 7.

2

Пример бэггин2а

видно, что каждый классификатор получает случайный под­

набор образцов из обучающего набора. Мы обозначили такие случайные
образцы, полученные через бэггинг, как "Раунд

1

бэггинга", "Раунд

2

бэг­

гинга" и т.д. Каждый поднабор содержит определенную долю дубликатов, а
некоторые исходные образцы вообще не появляются в повторно выбранном
наборе данных из-за выборки с возвращением. После того, как индивиду­
альные классификаторы подогнаны к бутстрэп-образцам, прогнозы объеди­
няются с применением мажоритарного голосования.

Обратите внимание, что бэггинг находится в родстве с классификатором
на основе случайного леса, который был представлен в главе

случайные леса

-

-

3.

В сущности,

это частный случай бэггинга, где при подгонке индиви-

- - -- - - - - --- - --

(297) -

- - -- -

---------

[лава

7.

Объединение разных моделей для ансамблевого обучения

дуальных деревьев принятия решений также используются случайные под­

наборы признаков.

\\:~ Ансамбли моделей, использующие бэггинг

н~ Бэггинг был впервые предложен Лео Брейманом в техническом отче­
заметку! те по МО за

1994

год; он также показал, что бэггинг может улучшить

правильность нестабильных моделей и снизить степень переобуче­
ния. Настоятельно рекомендуем ознакомиться с его исследованием

"Bagging predictors" (Прогнозаторы на основе бэггинга),
Брейман, Machine Leaming, 24(2): с. 123-140 (1996 г. ), свободно

в статье
Л.

доступной в Интернете, которая позволит узнать больше подробнос­

тей о бэггинге.

Применение бэrrинrа дnя кnассификации
образцов в наборе данных

Wine

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

сов

2

и

3,

4.

Wine,

который был

Мы будем принимать во внимание только вина клас­

к тому же выберем два признака: "Алкоголь" и

"00280/00315

разбавленных вин":

>>> import pandas as pd
>>> df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/'
'machine-learning-databases/wine/wine.data',
header=None)
[ 'Метка класса' , 'Алкоголь' ,
>>> df wine.columns
'Яблочная кислота',

'Зола',

'Щелочность золы',
'Магний',

'Всего фенолов',

'Флавоноиды'

,

'Нефлавоноидные фенолы'

'Проантоцианидины'

,

'Интенсивность цвета',

'00280/00315

'Оттенок',

разбавленных вин',

'Пролин']

>>> # отбросить класс 1
>>> df_wine =-df_wine[df_wine['Meткa класса'] != 1]
>>> у = df_ wine ['Метка класса'] . values
>>> Х = df_ wine [ ['Алкоголь',
'00280/00315

разбавленных вин']]

[298]

. values

,

Глава

7.

Объединение разных моделей для ансамблевого обучения

Затем мы кодируем метки классов в двоичном формате и разбиваем на­
бор данных на 80%-ный обучающий и 20%-ный испытательный наборы:

>>> froro sklearn.preprocessing import LabelEncoder
>>> f:r.oxn sklearn .model selection i1nport train test_ spli t
>>> le = LabelEncoder ()
>>>у= le.fit_transforrn(y)
>>> X_train, X_test, y_train, y_test =\
train_test_split(X, у,
test_size=0.2,
randorn_state=l,
stratify=y)
r~ Получение набора данных Wine

н~ Коnия набора данных Wine (и всех других наборов данных, исnоль­
заметку!

зуемых в книге) включена в состав загружаемого архива с кодом
nримеров для книги. Указанные копии можно задействовать при ав­

тономной работе или в случае временной недостуnности

https: / /
archive.ics.uci.edu/ml/machine-learning-databases/
wine/wine. data на сервере UCI. Скажем, чтобы загрузить набор
данных Wine из какого-то локального каталога, следующий оnератор:
df = pd.read_csv('https://archive.ics.uci.edu/rnl/'
'rnachine-learning-databases/wine/wine.data',
header=None)
nонадобится заменить таким оnератором:

df = pd.read_csv(

'ваш/локальный/путь/к/winе.dаtа',

header=None)
Алгоритм

BaggingClassifier

реализован в библиотеке

scikit-leam

и

может импортироваться из подмодуля ensemЫe. Здесь мы будем приме­

нять в качестве базового классификатора неподрезанное дерево nринятия
решений и создадим ансамбль, включающий

500

таких деревьев, которые

подгоняются на разных бутстрэn-образцах из обучающего набора данных:

>>> froщ sklearn. ensemЬle impox:t BaggingClassifier
>>> tree = DecisionTreeClassifier(criterion='entropy',
randorn_ state=l,
rnax_depth=None)
>>> bag = BaggingClassifier(base_estimator=tree,
n_estirnators=SOO,
max_sarnples=l.O,

[299)

Глава

7.

Объединение разных моделей для ансамблевого обучения

max_features=l.O,
bootstrap=Trlle,
bootstrap_features=False,
n_jobs=l,
random_ state=l)
Далее мы вычислим меру правильности прогноза на обучающем и испы­

тательном наборах данных, чтобы сравнить эффективность классификатора
на основе бэrrинга с эффективностью одиночного неподрезанноrо дерева
принятия решений:

>>>
>>>
>>>
>>>
>>>
>>>
>>>

from sklearn.metrics 1mport accuracy score

tree = tree. fit (X_train, y_train)
y_train_pred = tree.predict (X_train)
y_test_pred = tree.predict(X_test)
tree_train = accuracy_score(y_train, у train pred)
tree_test = accuracy_score(y_test, y_test_pred)
print ('Меры

правильности дерева принятия решений при

обучении/испытании %• 3f/%.

3f'

% (tree_train, tree_test))
Меры правильности дерева принятия решений при обучении/испытании

1.000/0.833
Взглянув на выведенные значения мер правильности, можно сделать вы­
вод, что неподрезанное дерево принятия решений корректно прогнозирует

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

(переобучение) модели:

>>>
>>>
>>>
>>>
>>>
>>>

bag = bag. fi t (Х_ train, у train)
y_train_pred = bag.predict(X_train)
y_test_pred = bag.predict(X_test)
bag_train = accuracy_score(y_train, y_train_pred)
bag_test = accuracy_score(y_test, y_test_pred)
print ('Меры

правильности бэггинга при обучении/испытании

%.3f/%.3f'

% (bag_train, bag_test))
Меры правильности бэггинга при обучении/испытании

1.000/0.917

Хотя меры правильности при обучении классификаторов на основе де­
рева принятия решений и бэггинга подобны на обучающем наборе (обе со­

ставляют

100%),

мы можем заметить, что классификатор на базе бэгг~нга

----------(300]---

[лава

7.

Объединение разных моделей для ансамблевого обучения

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

>>>
>>>
>>>
>>>
>>>

x_min
x_max
y_min
y_max

= X_train[:, О] .min () - 1
= X_train[:, О] .max() + 1
= X_train[:, 1) .min() - 1
= X_train [:, 1) .max () + 1
хх, уу = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, у max, 0.1))
>>> f, axarr = plt.subplots(nrows=l, ncols=2,
sharex='col',
sharey=' row' ,
figsize=(8, 3))
>>> fo.r idx, clf, tt in zip ([О, 1),
[tree, bag] ,
[ 'Дерево принятия решений' , 'Бэггинг' ] ) :
clf.fit(X_train, y_train)
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axarr[idx] .contourf(xx, уу, Z, alpha=0.3)
axarr[idx] .scatter(X_train[y_train==O, 0],
X_train[y_train==O, 1],
с='Ыuе',

>>>
>>>
>>>

>>>

marker=•л•)

axarr[idx] .scatter(X_train[y_train==l, О],
X_train[y_train==l, 1],
с=' green', marker=' о')
axarr[idx] .set_title(tt)
axarr[O] .sеt_уlаЬеl('Алкоголь', fontsize=12)
plt.tight_layout()
plt.text(O, -0.2,
s=' 00280/00315 разбавленных вин',
ha='center',
va='center',
fontsize=12,
transform=axarr[l] .transAxes)
plt. show ()

На результирующем графике (рис.

7 .8)

видно, что кусочно-линейная гра­

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

-------[301]------------

Глава

7.

Объединение разных моделей для ансамблевого обучения

Дерево принятия решений

Бэггинг

4

~

3

~

2

...
о

~

11

Рис.

7.8.

12

"
е
u
и
OD260/0D315 разбавленных вин

п

п

14

15

Области решений классификаторов 1m основе дерева
принятия рещепий и бэг,;иша

В этом разделе мы рассмотрели очень простой пример бэггинга. На прак­
тике более сложные задачи классификации и высокая размерность набора
данных могут легко привести к переобучению в отдельных деревьях при­

нятия решений, и как раз здесь алгоритм бэггинга способен по-настоящему
проявить свои сильные стороны. Наконец, мы должны отметить, что алго­
ритм бэггинга может быть результативным подходом к снижению дисперсии
модели. Тем не менее, бэгrинг безрезультатен в плане сокращения смеще­

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

ансамбле классификаторов с низким смещением , например, основанных на
деревьях принятия решений.

Использование в своих интересах слабых

учеников посредством адаптивного бустинrа
В последнем разделе, посвященном ансамблевым методам , мы обсудим

бустинг с акцентированием особого внимания на его самой распространен­
ной реализации

-

адаптивном бустинге (Лtl11pti1 1 c П oosl i 11g

---- Aila Bu!1st).

\\:~ Идентификация AdaBoost

н~ Первоначальную идею, лежащую в основе AdaBoost, сформулировал
заметку!

Роберт Э . Шапир в

1990

году

("The Strength of Weak LearnaЬility"
Machine Leaming,

(Достоинство слабой обучаемости), Р.Э. Шапир,

5(2): с. 197-227 (1990 г.)). После того, как Роберт Шапир и Йоав
AdaBoost в трудах 13-й Междуна­
родной конференции по МО (ICML 1996), в последующие годы он
Фройнд представили алгоритм

[302] - - - - -

Глава

7.

Объединение разных моделей для ансамблевого обучения

стал одним из наиболее широко применяемых ансамблевых мето­
дов

("Experiments with

а

New Boosting Algorithm"

(Эксперименты с

новым алгоритмом бустинга), Й. Фройнд, Р.Э. Шапир и др., ICML,
том

96,

с.

148-156 (1996

г.)). В

2003

году за свою новаторскую ра­

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

области компьютерных наук.

В бустинге ансамбль состоит из очень простых базовых классификато­
ров, часто именуемых слабыми учениками, которые нередко имеют лишь

незначительное преимущество в эффективности над случайным угадывани­
ем

-

типичным примером слабого ученика является пенек дерева приня­

тия решения. Главная концепция, лежащая в основе бустинга, заключается

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

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

использовать библиотеку

scikit-leam

AdaBoost.

Наконец, мы будем

в практическом примере классификации.

Как работает бустинr
По контрасту с бэггингом алгоритм бустинга в своей первоначальной

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

1.

Произвести выборку случайного поднабора обучающих образцов

возвращения из обучающего набора

2.

D

d1

без

для обучения слабого ученика С 1 •

Произвести выборку второго случайного поднабора обучающих образ­

цов

d2

без возвращения из обучающего набора и добавить

50%

образ­

цов, которые ранее были неправильно классифицированы, для обуче­
ния слабого ученика С2 •

3.

Найти в обучающем наборе

D

обучающие образцы

d3,

по которым С 1

и С2 расходятся, для обучения третьего слабого ученика С3 •

4.

Объединить слабых учеников С 1 , С2 и С3 посредством мажоритарного
голосования.

---------

---[303]

[лава

7.

Объединение разнЬtх моделей для ансамблевого обучения

Как выяснил Лео Брейман

("Bias, variance, and arcing c\assifiers"
(1996 г.),

(Смещение, дисперсия и дуговые классификаторы), Л. Брейман

бустинг может привести к уменьшению смещения и дисперсии в сравне­
нии с моделями на основе бэггинга . Однако на практике алгоритмы бус­
тинга, подобные

AdaBoost,

также известны своей высокой дисперсией, т.е.

склонностью к переобучению обучающими данными

AdaBoost to avoid overfitting"

(Усовершенствование

("An improvement of
AdaBoost во избежание

переобучения), Г. Ретч, Т. Онода и К.Р. Мюллер, труды Международной кон­
ференции по нейронной обработке информации,

CiteSeer (1998

г.)).

В отличие от описанной здесь исходной процедуры бустинга алгоритм

AdaBoost

для обучения слабых учеников использует полный обучающий

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

вайте взглянем на рис.

7.9,

AdaBoost.

••

-------------.... ........
........ ....


1
1

D

1
1
1

Х2

8

1
1
1

А

А
А

А

Х1

••

. :•

1

• ••
1

7.9.

А



• : 11
- - ---- - - - - -·- - -

8

Рис.

• ••

е:·

Х1

••

да­

который содействует лучшему пониманию базо­

вой концепции, лежащей в основе

Х2

AdaBoost,

.

Три раунда алюрит.wа

[304)-~

............ ........

AdaBoost

Глава

Объединение разных моделей для ансамблевого обучения

7.

Пошаговый проход по иллюстрации

AdaBoost

мы начинаем с части

1 ри­

сунка, которая представляет обучающий набор для двоичной классифика­
ции, где всем обучающим образцам назначены равные веса. На основе этого

обучающего набора мы обучаем пенек решения (показанный пунктирной
линией), который пытается классифицировать образцы двух классов (тре­

угольники и круги), насколько возможно сводя к минимуму функцию изде­
ржек (или показатель загрязненности в специальном случае ансамблей из
деревьев принятия решений).

Во втором раунде (часть

2

рисунка) мы назначаем более высокий вес

двум ранее неправильно классифицированным образцам (кругам). Кроме
того, мы снижаем вес корректно классифицированных образцов. Следующий

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

-

обучающих образцах, которые предпо­

ложительно трудно классифицировать. Слабый ученик на части

2

рисунка

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

При условии, что ансамбль

AdaBoost

3

рисунка.

состоит только из трех раундов бус­

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

4

рисунка.

Получив лучшее представление о базовой концепции

AdaBoost,

можно

глубже исследовать алгоритм с применением псевдокода. Ради ясности мы

будем обозначать поэлементное умножение крестиком (х), а скалярное про­
изведение двух векторов точкой

(-).

1. Установить в весовом векторе w равномерные веса, где

2.

L; w; = 1.

Для }-того из т раундов бустинга выполнить следующие действия:
а) обучить взвешенного слабого ученика: С1

= train(X, у,

w);

б) спрогнозировать метки классов: у= predict(C1, Х);
в) вычислить взвешенную частоту ошибок: е

= w · (j -: ;:. у);

1-&

г) вычислить коэффициент: а1 =0.5log--;
&

д) обновить веса:

w := w х

ехр(-а1 х ух у);

е) нормализовать веса до суммы, равной 1: w := w 1

3.

L; w;.

Вычислить финальный прогноз: у = (L 7= 1{ а1 х predict ( С1 , Х)) > О).
[305]~-----

Глава

7.

Объединение разных моделей для ансамблевого обучения

Обратите внимание, что выражение

(j

-:/-у) на шаге 2в ссылается на дво­

ичный вектор, состоящий из единиц и нулей, где единица назначается, если
прогноз неправильный, и ноль

если правильный.

-

Несмотря на внешнюю прямолинейность алгоритма

AdaBoost,

давайте

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

Индексы

х

Веса

у

у (х

>> from sklearn. ensemЫe import AdaBoostClassifier
>>> tree = DecisionTreeClassifier(criterion='entropy',
random_state=l,
max_ depth=l)
>>> ada = AdaBoostClassifier(base_estimator=tree,
n_estimators=SOO,
learning_rate=0.1,
random_state=l)
>>> tree = tree.fit(X train,y_train)
>>> y_train_pred = tree.predict(X_train)
>>> y_test_pred = tree.predict(X_test)
>>> tree_train = accuracy_score(y_train, y_train_pred)
>>> tree_test = accuracy_score(y_test, y_test pred)
>>> pr1.11t. ('Меры правильности дерева принятия решений при
обучении/испытании %.Зf/%.Зf'

% (tree_train, tree_test))
Меры правильности дерева принятия решений при обучении/испытании

0.916/0.875
Легко заметить, что пеньки деревьев принятия решений кажутся недо­

обученными на обучающих данных по контрасту с неподрезанным деревом
принятия решений из предыдущего раздела:

>>> ada = ada.fit(X_train, y_train)
>>> y_train_pred = ada.predict(X_train)

- - - - · · - · - - - - - - [308] -·· ------------------------

fлава

7.

Объединение разных моделей для ансамблевого обучения

>>> y_test_pred = ada.predict(X_test)
>>> ada_train = accuracy_score(y_train, y_train_pred)
>>> ada_test = accuracy_score(y_test, y_test_pred)
>>> pr.·iнt. ('Меры правильности AdaBoost при обучении/испытании

%.3f/%.3f'
% (ada_train, ada_test))
Меры правильности AdaBoost при обучении/испытании 1.000/0.917
Как видим, модель

AdaBoost

корректно прогнозирует все метки классов

обучающего набора и показывает немного лучшую эффективность на ис­
пытательном наборе в сравнении с пеньком дерева принятия решений. Тем
не менее, мы также можем отметить внесение дополнительной дисперсии

за счет попытки снизить смещение модели

-

больший промежуток между

эффективностью при обучении и при испытании.
Хотя в демонстрационных целях мы использовали другой простой при­

мер, мы можем заметить, что эффективность классификатора

AdaBoost

слег­

ка улучшилась по сравнению с пеньком дерева принятия решений и достиг­

ла мер правильности, очень похожих на те, которые имеет классификатор на

базе бэггинга, обучаемый в предыдущем разделе. Однако мы обязаны отме­
тить, что выбор модели на основе многократного применения испытатель­

ного набора

-

плохая практика. Оценка эффективности обобщения может

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

6.

В заключение давайте посмотрим, на что похожи области решений:

= X_train [:, О] .min () - 1
= X_train[:, 0] .max() + 1
= X_train[:, 1] .min() - 1
= X_train[:, 1] .max() + 1
хх, уу = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, y_max, 0.1))
>>> f, axarr = plt.subplots (1, 2,
sharex='col',
sharey=' row' ,
figsize=(8, 3))
>>> for.· idx, clf, tt in zip ([О, 1],
[tree, ada],
['Дерево принятия решений', 'AdaBoost']):
clf.fit(X_train, y_train)
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()))
Z = Z.reshape(xx.shape)
axarr[idx] .contourf(xx, уу, Z, alpha=0.3)

>>>
>>>
>>>
>>>
>>>

x_min
x_max
y_min
y_max

- - - - - - - - - -----[309]--------

Глава

7.

Объединение разных моделей для ансамблевого обучения

axarr[idx) .scatter(X_train[y_train==O, 0),
X_train[y_train==O, 1),
с='Ыuе',

marker=' " ')
axarr[idx] .scatter(X_train[y_train==l, О],
X_train[y_train==l, 1),
с=' green',
marker='o')
axarr[idx) .set_title(tt)
axarr[O] .sеt_уlаЬеl('Алкоголь', fontsize=12)
>>> plt.tight_layout()
>>> plt.text(O, -0.2,
s='OD280 /0D3 15 разбавленных вин',
ha=' center',
va=' center',
fontsize=12,
transform=axarr[l) .transAxes)
>>> plt. show ()
На рис.

7.11

мы видим , что граница решений модели

AdaBoost

значитель­

но сложнее границы пенька решений. Кроме того, мы отмечаем, что модель

AdaBoost

разделяет пространство признаков очень похоже на то, как это де­

лает классификатор на основе бэггинга, который мы обучали в предыдущем
разделе.

Дерево принятия решени й

4


с:
о

3

....

о

:.i

~

:.:1t.
"

AdaBoost

.
.
• ·:·~~·

."..... .." .
~~'·
••
"1 4t •

~

~

2



11

12

"

". 4t

J.

"

13

14

"


*•·':~;,::
~."

..... ,·

••



15

11

12

13

14

15

OD280/0DЗ 15 разбавленных вин

Рис.

7. 11.

Области рещений классификаторов на основе дерева
принятия решений и ш1горитма

AdaBoos/

В качестве заключительных замечаний об ансамблевых приемах полезно

отметить, что ансамблевое обучение увеличивает вычислительную слож­
ность в сравнении с индивидуальными классификаторами. На практике мы

-----·------·- - - -- - - - - · (310] ------------·----··-----------

Глава

7.

Об'Ьединение разных моделей для ансамблевого обучения

должны хорошо обдумать, стоит ли расплачиваться повышенными вычисли­

тельными затратами за нередко относительно скромный рост эффективнос­
ти прогнозирования.

Часто приводимым примером этого компромисса является известный
приз

Netflix Prize

размером

1

миллион долларов, который был получен с ис­

пользованием ансамблевых приемов. Детали алгоритма были опубликованы
в статье А. Тошера, М. Ярера и Р. Белла

"The BigChaos Solution to the Netflix
Grand Prize" (Решение команды BigChaos, получившее гран-при Netflix), до­
кументация по призу Netflix (2009 г.), которая доступна по ссылке http: //
www.stat.osu.edu/-dmsl/GrandPrize2009_BPC_BigChaos.pdf.
Победившая команда получила гран-при размером 1 миллион долларов; тем
не менее, компании Netflix не удалось реализовать их модель из-за высокой
сложности, которая делала ее неосуществимой в реальном приложении:

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

равданием инженерных усилий, необходимых для их внедрения в про­
изводственную среду" (ht tp: / /techЫog. netf lix.
netflix-recommendations-beyond-5-stars. html).

сот/ 2 012

/04 /

Г:.~ Градиентный бустинг

н~ Еще одним популярным вариантом бустинга является градиентиый
заметку!

бустинг (~nuiie11t

lmostir1g).

Алгоритм

AdaBoost

и градиентный бус­

тинг разделяют основную общую концепцию: подъем слабых уче­
ников (таких как пеньки деревьев принятия решений) до уровня

сильных учеников. Два подхода, адаптивный и градиентный бустинг,
отличаются главным образом тем, как обновляются веса и как объ­
единяются (слабые) классификаторы. Если вы знакомы с оптимиза­

цией на базе градиентов и заинтересованы в градиентном бустинге,
тогда мы рекомендуем ознакомиться с работой Джерома Фридмана

gradient boosting machine"

(Жад­

ная аппроксимация функций: машина градиентного бустинга,

Annals

"Greedy function approximation:

а

of Statistics 2001, с. 1189-1232) и более поздней статьей по алгорит­
му XGBoost, который по существу является эффективной в плане
вычислений реализацией исходного алгоритма градиентного бус­
тинга

("XGBoost:

А sса\аЬ\е

tree boosting system" (XGBoost:

мас­

штабируемая система для бустинга на основе деревьев), Тяньцзи
Чен и Карлос Гестрин, материалы 22-й Международной конферен-

[311]

Глава

7.

Объединение разных моделей для ансамблевого обучения

ции АСМ

SIGKDD

по обнаружению знаний и интеллектуальному

анализу данных, АСМ
наряду с реализацией

тека

scikit-learn 0.21

2016, с. 785-794 ). Обратите внимание, что
GradientBoostingClassifier библио­

теперь также включает более быструю вер­

сию градиентного бустинга

HistGradientBoostingClassifier,
XGBoost. Дополнительные сведения
о классах GradientBoostingClassifier и HistGradient
BoostingClassifier в scikit-\eam ищите в документации по ссыл­
которая даже быстрее, чем

ке https://scikit-learn.org/staЫe/modules/ensem.Ьle.

html#gradient-tree-boosting.

Кроме того, краткое и общее

объяснение градиентного бустинга можно найти в конспекте лек­
ций по ссылке

https: / /sebastianraschka. com/pdf/lecturenotes/stat479fsl9/07-ensem.Ьles_notes .pdf.

Резюме
В главе мы взглянули на несколько наиболее популярных и широко при­
меняемых приемов для ансамблевого обучения. Ансамблевые методы объ­
единяют различные классификационные модели, чтобы уравновесить их
индивидуальные слабые стороны, часто давая в итоге стабильные и хорошо
работающие модели, которые очень привлекательны для индустриальных

приложений и состязаний по МО.

В начале главы мы реализовали на

VoteClassifier,

Python

классификатор

Maj ori ty

который позволяет комбинировать разные алгоритмы

классификации. Затем мы рассмотрели бэггинг

-

полезный прием сниже­

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

AdaBoost,

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

- - - - - - - - - - - - - - - - (312)

8
ПРИМЕНЕНИЕ МАШИННОГО
ОБУЧЕНИЯ ДЛЯ

СМЫСЛОВОГО АНАЛИЗА

нынешнюю эпоху Интернета и социальных сетей мнения, рецензии и
в
.
рекомендации людей стали ценным ресурсом в политологии и бизнесе.
Благодаря современным технологиям мы теперь в состоянии собирать и ана­
лизировать такие данные более рационально. В этой главе мы углубимся в

подобласть обработки естественного языка

NlJ>),

называемую смысловым анализом

(1111timil laи,i:uage processing -(scнtiment mmlj·sis), и выясним, как

использовать алгоритмы МО для классификации документов на основе их
направленности: отношения со стороны автора. В частности, мы собираем­

ся работать с набором данных, включающим

50 ООО

базы дштых фильмов в Интернете (Iнtemet

рецензий на фильмы из

!Ho11ie

ПаtаЬаsе

-···-

J,ИH!J), и

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

В главе будут раскрыты следующие темы:



очистка и подготовка текстовых данных;



построение векторов признаков из текстовых документов;



обучение модели МО для классификации положительных и отрица­
тельных рецензий на фильмы;

fлава



8.

Применение машинного обучения для смысловоzо анализа

работа с крупными наборами текстовых данных с применением
внешиего обучения (оиl-о(смс



lc11mi11g,);

выведение тем из совокупностей документов в целях категоризации.

Подготовка данных с рецензиями

на фиnьмы

IMDb

дnя обработки текста

Как упоминалось ранее, смысловой анализ, иногда также называемый
глубинны;w анализом м11еиий

(opi11im1 111ini11g),

ную дисциплину из более широкой области

представляет собой популяр­

NLP;

он занимается исследо­

ванием направленности документов. Популярной задачей при смысловом

анализе является классификация документов на основе выраженных мнений
или эмоций авторов в отношении определенной темы.

В главе мы будем иметь дело с крупным набором данных, содержащим ре­
цензии на фильмы, из базы данных

и другими

IMDb, который был собран Эндрю Маасом
("Leaming Word Vectors for Sentiment Analysis" (Изучение словар­

ных векторов для смыслового анализа), Э. Маас, Р. Дели, П. Фам, Д. Хуан,
Э. Ын и К. Поттс, труды 49-го ежегодного собрания Ассоциации компью­
терной лингвистики: технологии естественного языка, с.

Портленд,

142-150,

Орегон, США, Ассоциация компьютерной лингвистики (июнь

Набор данных с рецензиями на фильмы состоит из

50 ООО

2011

г.)).

диаметрально

противоположных обзоров, которые помечены как положительные или отри­

цательные; здесь положительный означает, что фильм имеет рейтинг
более шести звезд, а отрицательный

-

что рейтинг

IMDb

IMDb

фильма меньше

пяти звезд. В последующих разделах мы загрузим набор данных, обработа­
ем его с целью приведения в формат, пригодный для инструментов МО, и

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

ли фильм определенному рецензенту.

Поnучение набора данных с рецензиями на фиnьмы
Сжатый посредством

gzip

архив с набором данных с рецензиями на

фильмы (объемом

84, 1 Мбайт) можно загрузить
ai.stanford.edu/-amaas/data/sentiment/:

----------

по ссылке

(314)-~---------

h t tp: / /

-------

Глава



8.

если вы работаете в среде

Применение машинного обучения для смыслового анализа

Linux

или

macOS,

то для распаковки набора

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



если вы работаете в среде

Windows,

cd в
tar -zxf aclimdЬ_ vl. tar. gz;

то для распаковки набора данных

можете загрузить бесплатную программу архивации, такую как

7-Zip

{http://www. 7-zip. org);



в качестве альтернативы вы можете распаковать сжатый посредством

gzip архив tarball прямо в Python:
>>> import tarfile
>>> w.i t!1 tarfile. open ( 'aclimdb_ vl. tar. gz' , 'r: gz' ) as tar:
tar.extractall()

Предваритеnьная обработка набора данных с цеnью
приведения в боnее удобный формат
После успешного извлечения набора данных мы соберем индивидуаль­
ные текстовые документы из распакованного архива в одиночный файл

CSV.

В показанном ниже фрагменте кода мы будем читать рецензии на фильмы
в объект

DataFrame

из

pandas,

ре может потребовать вплоть до

что на стандартном настольном компьюте­



минут. Для визуализации хода работ и

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

Python (Pytlum P1·og1·ess

(PyPriml); https: / /pypi .python. org/pypi/PyPrind/),
ботали несколько лет в таких целях. Пакет
полнив команду

PyPrind

lmlicatм

который разра­

можно установить, вы­

pip install pyprind.

>>> iroport pyprind
>>> impot·t pandas as pd
>>> i.Itipo:t•t OS
>>> # укажите в basepath каталог, где находится
>>> #набор данных с рецензиями на фильмы
>>>
>>>
>>>
>>>
>>>
>>>

basepath =

'aclimdЬ'

labels = { 'pos': 1, 'neg': О}
pbar = pyprind. ProgBar (50000)
df = pd.DataFrarne ()
r·or s in ( 'test' , 'train' ) :
for 1 in ( 'pos', 'neg') :

[315] --------·

распакованный

Глава

8.

Применение машинного обучения для смыслового анализа

path = os.path.join(basepath, s, 1)
for file in sorted (os. listdir (path)) :
with open(os.path.join(path, file),
'r', encoding='utf-8') as infile:
txt = infile.read()
df = df.append([[txt, labels[l)]],
ignore_index=True)
pbar. update ()
>>> df. columns = [ 'review', 'sentiment']
0%
100%
[##############################] 1 ЕТА: 00:00:00
Total time elapsed: 00:02:05
Всего прошло времени: 00:02:05
В предыдущем коде мы сначала инициализируем новый объект индикато­
ра выполнения

pbar,

указывая

50000

итераций, что соответствует количест­

ву документов, подлежащих чтению. С помощью вложенных циклов
мы проходим по подкаталогам

train

и

test

в главном каталоге acllmd.Ь

и читаем отдельные текстовые файлы из подкаталогов
в итоге добавляем к раndаs-объекту
лочисленными метками классов

(1 -

for

DataFrame

pos

по имени

и

neg,

df

которые

вместе с це­

положительный класс и О

-

отрица­

тельный класс).
Поскольку метки классов в собранном наборе данных отсортированы, мы

тасуем объект
дуля

DataFrame с применением функции permutation из подмо­
np. random - это полезно для разбиения набора данных на обучающий

и испытательные наборы в будущем, когда мы организуем поток данных на­
прямую из локального диска. Ради удобства мы также сохраняем собранный

и перетасованный набор данных с рецензиями на фильмы в файле

CSV:

>>> import numpy as np
>>> np.random.seed(O)
>>> df = df.reindex(np.random.permutation(df.index))
>>> df.to_csv('movie_data.csv', index=False, encoding='utf-8')
Так как мы планируем работать с набором данных позже в главе, давайте

быстро удостоверимся в том, что данные были успешно сохранены в правиль­
ном формате, прочитав файл

CSV

и выведя выборку из первых трех образцов:

>>> df = pd.read_csv( 'movie_data.csv', encoding='utf-8')

»>

df.head(З)

-----------(316)-------

[лава

8.

Применение машинного обучения для смыслового анализа

В случае выполнения примеров кода в

Jupyter Notebook

должна отобра­

зиться таблица с первыми тремя образцами из набора данных, показанная
на рис.

8.1.
review sentiment
О

ln 1974, the teenager Martha Moxley (Maggie Gr".
ОК ...

so". 1really like Kris Кristofferson а".

о

***SPOILER*** Do not read this, if you think а."

о

1
2
Рис.

8.1.

1

Первые три образца из набора даттх с рецеизия.ми на фW1ьмы

В качестве проверки работоспособности до перехода к следующему раз­
делу давайте удостоверимся в том, что объект DataFrame содержит все

50 ООО

строк:

>>> df.shape
(50000, 2)

Модеnь суммирования сnов
В главе

4

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

ные данные, такие как текст или слова, в числовую форму, прежде чем их

можно будет передавать алгоритму МО. В настоящем разделе мы введем
.wодель сум.~1ирования, или .нодель Аtешка слов (lmg-(~Г-н'м1ls

m0tiel),

которая

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

1.

Мы создаем глоссарий уникальных лексем

-

например, слов

-

из

полного набора документов.

2.

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

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

[317)-----------

Глава

8.

Применение машинного обучения для смыслового анализа

страктно; в последующих подразделах мы пошаrово пройдем через процесс·

создания простой модели суммирования слов.

Трансформирование сnов в векторы признаков
Чтобы построить модель суммирования слов на основе счетчиков слов в со­
ответствующих документах, мы можем использовать класс

CountVectorizer,

scikit-leam. Как демонстрируется в следующем фрагменте
CountVectorizer принимает массив текстовых данных, которые мо­

реализованный в
кода,

гут быть документами или предложениями, и конструирует модель сумми­
рования слов:

import numpy as np
from sklearn. feature_extraction. text import CountVectorizer
count = CountVectorizer()
docs = np.array(['The sun is shining',
'The weather is sweet',
'The sun is shining, the weather is sweet, '
'and one and one is two'] )
>>> bag = count.fit_transform(docs)

>>>
>>>
>>>
>>>

Вызывая метод

fit_transform

на

CountVectorizer,

мы создаем глос­

сарий модели суммирования слов и трансформируем следующие три пред­
ложения в разреженные векторы признаков:

• 'The sun is shining'

(Солнце светит);

• 'The weather is sweet'

(Погода приятная);

• 'The sun is shining, the weather is sweet, and one and one is two'
(Солнце светит, погода приятная и один плюс один равно двум).
Теперь давайте выведем содержимое глоссария, чтобы лучше понять ле­
жащие в основе концепции:

>>> print(count.vocabulary_

{ 'and' : О,
'two': 7,
'shining': З,
'one': 2,
'sun': 4,
'weather': 8,
'the': 6,
'sweet': 5,
'is': 1}

-(318)

Глава

Применение машинного обучения для смыслового анализа

8.

После выполнения приведенной выше команды мы видим, что глоссарий

хранится в словаре

Python,

который отображает уникальные слова на целочис­

ленные индексы. Далее выведем только что созданные векторы признаков:

>>> print(bag.toarray())

1 О 1 1 О 1 О О]
1 О О О 1 1 О 1]
[2 3 2 1 1 1 2 1 1]]

[[О


Индексные позиции в показанных здесь векторах признаков соответству­
ют целочисленным значениям,

глоссарии

которые хранятся

зиции О отражает счетчик слова

'is'

словаря

в

Например, первый признак в индексной по­

CountVectorizer.

документе, а слово

как элементы

появляющегося только в последнем

'and',

в индексной позиции

1

(второй признак в векторах

документов) встречается во всех трех предложениях. Эти значения в век­
торах признаков также называются сырыми частотами термов (mw tеп11

_fi·eц11e11cy):

tf (t, d) -

сколько раз терм

t

встречается в документе

d.

Важно

отметить, что в модели суммирования слов порядок следования слов или

термов в предложении или документе не играет роли. Порядок, в котором
частоты термов расположены в векторе признаков, определяется индексами

в словаре, обычно назначаемыми по алфавиту.

r~ Модели n-rрамм

н~ Последовательность элементов в только что созданной модели
заметку!

суммирования слов также называется 1-гра.:wмной
у11игра.'l-в1ной моделью

-

( 1-gmm)

или

каждый элемент или лексема в глосса­

рии представляет одиночное слово. В более общем случае сопри­
касающаяся последовательность элементов в
или символов

-

называется п-> from sklearn. feature_extraction. text import TfidfTransformer
>>> tfidf = TfidfTransformer (use_idf=Tп1e,
norm=' 12',
smooth_idf=Trпe)

>>> np.set_printoptions(precision=2)
>>> p.ririt (tfidf. fi t_transform (count. fi t _ transform (docs))

[ [ о.
[ о.
[ о. 5

. toarray () )
0.43 о.
0.56
0.43 о.
о.
0.45 0.5
0.19

0.56

о.

о.

0.56
0.19

0.19

0.43 о.
0.43 о.
0.3
0.25

о.

0.56]
0.19]]

Как было показано в предыдущем подразделе, слово

'is'

имело на­

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

tf-idf

мы видим, что теперь со словом

относительно небольшая мера

tf-idf



. 4 5)

'is'

ассоциирована

в третьем документе, т.к. оно

также присутствует в первом и втором документах и соответственно вряд

ли содержит любую полезную различительную информацию.

Тем не менее, если бы мы вручную вычислили меры
ных термов в векторах признаков, то заметили бы, что

вычисляет меры

tf-idf несколько

tf-idf индивидуаль­
TfidfTransformer

иначе по сравнению со стандартными урав­

нениями из учебника, которые мы приводили выше. Уравнение для вычис­
ления обратной частоты документа, реализованное в

scikit-learn,

выглядит

следующим образом:

. ( )
1df t,d =log

1+ nd
( )
1+df d,t

Подобным же образом уравнение для вычисления меры

tf-idf

в

scikit-leam

слегка отклоняется от стандартного уравнения, представленного ранее в главе:

tf-idf (t, d)

= tf (t, d)

Обратите внимание, что добавление
зано с установкой

(idf (t, d) + 1)

х

"+ 1"

smooth _ idf=True

в предыдущем уравнении свя­

в предшествующем примере кода,

которая полезна для назначения нулевых весов (т.е.
термам, встречающимся во всех документах.

--(321)

idf(t,d)

= log(l)

=О)

Глава

8.

Применение машинного обучения для смыслового анализа

Наряду с тем, что более привычно проводить нормализацию сырых
частот термов перед вычислением мер

класс

TfidfTransformer
нормализует меры tf-idf напрямую. По умолчанию (norm=' 12 ') класс
TfidfTransformer из scikit-learn применяет нормализацию L2, которая
возвращает вектор длиной
признаков

v

на его норму

1

путем деления ненормализованного вектора

L2:

v

v

Vnorm

v

= 1ivll2 = ~V~ + v; + ·· · + V~

удостовериться

Чтобы

tf-idf,

в

понимании

правильном

работы

класса

TfidfTransformer, давайте рассмотрим пример и вычислим меру tf-idf
слова 'is' в третьем документе.
Слово 'is' имеет частоту терма 3 (tf = 3) в третьем документе, а частота
документа этого терма равна 3, потому что терм 'is' встречается во всех
трех документах (df = 3). Таким образом, мы можем вычислить обратную
частоту документа, как показано ниже:

idf (" is ", d3) = log ~: ~
Теперь для вычисления меры

tf-idf нам



просто нужно добавить



обрат­

ной частоте документа и умножить сумму на частоту терма:

tf-idf ("is'', d3) = 3

х (О

+ 1)

=

3

Если мы повторим такое вычисление для всех термов в третьем доку­

менте, то получим следующий вектор

1. 29, 1. 29, 2.

О,

1. 69, 1. 29].

tf-idf: [ 3 . 3 9, 3 . О, 3 . 3 9, 1 . 2 9,

Однако обратите внимание, что значения

в этом векторе признаков отличаются от значений, которые мы получаем от

ранее используемого класса

нии меры

tf-idf

TfidfTransformer. В приведенном вычисле­
- нормализации L2, которую

не хватает финального шага

можно применить следующим образом:

[3.39, 3.0, 3.39, 1.29, 1.29, 1.29, 2.0, 1.69, 1.29]

. ( )
tf -1df d3

=~========================
norm

~3.39 2 +3.0 2 +3.39 2 +1.29 2 +1.29 2 +1.29 2 +2.0 2 +1.69 2 +1.29 2

= [ 0.5, 0.45, 0.5, 0.19, 0.19, 0.19, 0.3, 0.25, 0.19]

tf-idf ("is ", d3)

--------(322]

= 0.45

f11ава

8.

Применение машинного обучения для смыслового анализа

Несложно заметить, что сейчас результаты совпадают с результатами,

возвращенными классом
перь понятно, как

TfidfTransformer из scikit-leam, и поскольку те­
вычисляются меры tf-idf, в следующем разделе мы приме­

ним рассмотренные концепции к набору данных с рецензиями на фильмы.

Очистка текстовых данных
В предшествующих подразделах мы узнали о модели суммирования слов,

частотах документов, частотах термов и мерах

tf-idf.

Тем не менее, первый

важный шаг (перед построением модели суммирования слов) предусматри­
вает очистку текстовых данных путем удаления всех нежелательных симво­

лов. Чтобы проиллюстрировать важность такого шага, мы отобразим пос­
ледние

50

символов из первого документа в перетасованном наборе данных

с рецензиями на фильмы:

>>> df.loc[O, 'review'] [-50:]
'is seven.Title (Brazil): Not

AvailaЬle'

Здесь видно, что текст содержит НТМL-разметку, а таюке знаки препина­
ния и другие небуквенные символы. Хотя НТМL-разметка не несет в себе
много полезной семантики, знаки препинания в определенных контекстах

NLP

могут представлять важную дополнительную информацию. Однако ради

простоты мы удалим все знаки препинания кроме эмотиконов вроде

:),

т.к.

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

использованием библиотеки для работы с регулярны.,1wи выраженuяJwu

(re):

>>> з.шроrt; re
>>> clef preprocessor (text):
text = re.suЬ( '] *>', '', text)
ernoticons = re. findall (' (?:: 1; 1=) (?: -) ? (?: \) 1\ ( 1D1 Р) ',
text)
text = (re.sub('[\W]+',' ', text.lower()) +
' '.join(ernoticons).replace('-', ''))
retнrn text
С помощью первого регулярного выражения,

< [ л>] *>,

мы пытаемся уда­

лить всю НТМL-разметку из рецензий на фильмы. Несмотря на то что мно­
гие программисты в целом не советуют применять регулярные выражения

при разборе НТМL-разметки, нашего регулярного выражения должно быть
достаточно для очистки этого конкретного набора данных. Поскольку мы за­
интересованы только в удалении НТМL-разметки и не планируем в дальней-

------(323]---

Глава

8.

Применение машинного обучения для смЬ1слового анализа

шем ее использовать, применение регулярного выражения для выполнения

работы должно быть приемлемым. Тем не менее, если для удаления НТМL­
разметки из текста вы предпочитаете использовать сложно устроенные инс­

html. parser в стандартной
библиотеке Python, описание которого доступно по ссылке https: / /docs.
python.org/3/library/html.parser.html. После удаления НТМL­

трументы, тогда можете взглянуть на модуль

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

emoticons.

Далее мы

удаляем из текста несловарные символы посредством регулярного выраже­

ния

[ \ W] +

и приводим все буквы к нижнему регистру.

\\:~ Работа со словами, написанными с заглавной буквы

н~ В контексте проводимого анализа мы предполагаем, что написание
заметку!

слова с заглавной буквы (например, в начале предложения) не несет

в себе семантически важной информации. Тем не менее, есть ис­
ключения

-

скажем, мы отказываемся от правильной записи имен.

Но в рассматриваемом контексте мы опять выдвигаем упрощающее

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

Наконец, мы дополняем обработанную строку документа эмотиконами,
временно хранящимися в

emoticons.

Кроме того, в целях согласованности

мы удаляем из эмотиконов символ "носа", т.е.

"-"

в

":-)''.

\\:~ Регулярные выражения

н~ Несмотря на то что регулярные выражения являются рациональным
заметку!

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

в данной книге. Однако вы можете обратиться к великолепному ру­

по ссылке

https: / /
developers.google.com/edu/python/regular- expressions
или почитать официальную документацию по модулю re в Python:
https://docs.python.org/3.7/library/re.htm l.
ководству на портале разработчиков

Google

Хотя добавление эмотиконов в конец очищенных строк документов мо­
жет не выглядеть особо элегантным приемом, мы должны отметить, что по-

---------------------------- (3241 - - - - - - - - - - · - - - - -

[лава 8. Применение машинного обучения для смыслового анализа

рядок слов в модели суммирования слов не имеет значения, если глоссарий

состоит только из однословных лексем. Но прежде чем дальше обсуждать

разбиение документов на отдельные термы, слова или лексемы, давайте вы­

ясним, корректно ли работает наша функция

preprocessor:

>>> preprocessor(df.loc[O, 'review'] [-50:])
'is seven title brazil not availaЫe'
>>> preprocessor("This :) is: (а test :-) !")
'this is а test : ) : ( : ) '
Наконец, поскольку мы снова и снова будем использовать очищенные
текстовые данные в последующих разделах, применим нашу функцию

preprocessor

ко всем рецензиям на фильмы в

DataFrame:

>>> df['review'] = df['review'] .apply(preprocessor)

Переработка документов в лексемы
После успешной подготовки набора данных с рецензиями на фильмы
нам теперь предстоит подумать о том, как разделить совокупность текстов

на индивидуальные элементы. Один из способов разбиения документов на
лексемы предусматривает разделение очищенных документов на отдельные

слова по пробельным символам:

>>> c1ef tokenizer (text):
return text.split()
>>> tokenizer ( 'runners like runнing and thus they run')
['runners', 'like', 'running', 'and', 'thus', 'they', 'run']
В контексте разбиения на лексемы еще одним удобным приемом явля­
ется стеммииг слов (н'ои!

stemminx),

или нахождение основы слова, ко­

торый представляет собой процесс трансформирования слова в его кор­
невую форму. Он позволяет отображать связанные слова на одну и ту же
основу. Первоначальный алгоритм стемминга был разработан Мартином
Ф. Портером в

1979

году и с тех пор известен как стеммер Портера (Рт·lсг

stc11111н'1) или алгоритм стемминrа Портера

("An algorithm for suffix stripping"
Program:
Electronic Library and Information Systems, 14(3): с. 130-137 (1980 г.)).
(Алгоритм для отбрасывания суффиксов), Мартин Ф. Портер,

Алгоритм стемминга Портера реализован в наборе инструментов обработки
естестве1111ого языка (Natrm1i

l 1111.~ии,>> Е:~:ош nltk.stem.porter i:r.iport: PorterStemmer
>>> porter = PorterStemmer ()
>>> def tokenizer_porter(text):
r€~tш:т1 [porter.stem(word) fог word in text.split ()]
>>> tokenizer__porter ( 'runners like running and thus they run')
[ 'runner', 'like', 'run', 'and', 'thu', 'they', 'run']

PorterStemmer из пакета nl tk, мы
tokenizer, чтобы сократить слова до их
как было показано в примере выше, где слово ' running'
лось в свою корневую форму ' run'.
Используя класс

нашу функцию

модифицировали

корневой формы,
трансформирова­

~\:~ Алгоритмы стемминга

н~ Алгоритм стемминга Портера, вероятно, представляет собой самый
заметку!

старый и простой алгоритм стемминга. Среди других алгоритмов

стемминга важно отметить более новый стеммер "С11е:ж11ый ком"

(.'\11mi·lmll stemma;

стеммер

Porter2

или стеммер английского язы­

ка) и стеммер Ла11кастерско?о у11иверситета

стеммер Пейса/Хаска

(Paice/Husk stemmer)).

(1 m1ci1sta

s/t'11шн·1;

Наряду с тем, что стем­

мер "Снежный ком" и стеммер Ланкастерского университета быс­
трее первоначального стеммера Портера, стеммер Ланкастерского
университета также печально известен своей большей агрессивнос­
тью по сравнению со стеммером Портера. Упомянутые альтернатив­
ные алгоритмы стемминга доступны в пакете

NLTK (http: //www.

nltk. org/api/nltk. stem. html).

[326] -----------------------

Глава

8.

Применение машинного обучения для смыслового анализа

В то время как стемминг может создавать несуществующие в ре­

альности слова вроде
лось

в

предыдущем

(lc11111111ti:.:;11/io11)

'thu'

примере,

(из

'thus '),

что демонстрирова­

прием под названием ле.wматизация

помогает получить канонические (грамматичес­

ки правильные) формы индивидуальных слов

-

так называемые

:1e.w.wы. Тем не менее, с вычислительной точки зрения лемматизация

является более сложной и затратной, нежели стемминг, к тому же
на практике выяснилось, что стемминг и лемматизация оказывают

лишь небольшое влияние на эффективность классификации текстов

("lnfluence of Word Normalization on Text Classification"

(Влияние

нормализации слов на классификацию текстов), Михал Томан, Ро­

ман Тесар и Карел Йежек, труды lnSciT, с. 354-358 (2006 г.)).
До того, как переходить к следующему разделу, где мы будем обучать
модель МО с применением модели суммирования слов, давайте кратко об­
судим еще одну полезную тему -удаление стоп-слов (:;tор-н'онi
Стоп-слова

1·c111m•11l).

это просто такие слова, которые крайне распространены во

-

всех видах текстов и вероятно не несут в себе (или несут, но мало) полезной

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

документов. Примерами стоп-слов в английском языке могут служить
и

"and", "has"
дится

"like".

иметь дело с

вместо мер

tf-idf,

"is",

Удаление стоп-слов может пригодиться, когда прихо­
сырыми

или

нормализированными

частотами

термов

в которых веса часто встречающихся слов уже понижены.

Для удаления стоп-слов из рецензий на фильмы мы будем использовать

набор из

NLTK

127

стоп-слов английского языка, который доступен в библиотеке

и может быть получен вызовом функции

nl tk. download:

>>> import. nl tk
>>> nl tk. download ( 'stopwords' )
После загрузки набора стоп-слов мы можем загрузить и применить набор
стоп-слов английского языка:

>>>

frorп

nltk.corpus import. stopwords

>>> stop = stopwords. words ( 'english')
>>> [w for w in tokenizer_porter ('а runner likes'
' running and runs

а

lot') [-10:]

if w not in stop]

['runner', 'like', 'run', 'run', 'lot']

(327)

[лава

8.

Применение машинного обучения для смыслового анализа

Обучение логистической регрессионной
модели для классификации документов
В этом разделе мы обучим логистическую регрессионную модель, ос­

нованную на модели суммирования слов, которая будет классифицировать
рецензии на позитивные и негативные. Первым делом мы разобьем объект

DataFrame

с очищенными текстовыми документами на

для обучения и

>>>
>>>
>>>
>>>

25

25 000

документов

ООО документов для испытания:

X_train = df.loc[:25000,
y_train = df.loc[:25000,
X_test = df.loc[25000:,
y_test = df.loc[25000:,

'review'] .values
'sentiment'] .values
'review'] .values
'sentiment'] .values

Затем мы воспользуемся объектом

GridSearchCV,

чтобы отыскать опти­

мальный набор параметров для нашей логистической регрессионной моде­

ли, применяя стратифицированную перекрестную проверку по

>>>
>>>
>>>
>>>

from
from
from
from

5 блокам:

sklearn.model_selection import GridSearchCV
sklearn. pipeline import Pipeline
sklearn.linear_model import LogisticRegression
sklearn.feature_extraction.text import TfidfVectorizer

>>> tfidf = TfidfVectorizer(strip_accents=None,

lowercase=False,
preprocessor=None)
>>> param_grid = [{'vect_ngram_range': [(1,1)),
'vect stop_words': [stop, None],
'vect tokenizer': (tokenizer,
tokenizer_porter],
'clf_penalty': ['ll', '12'],
'clf_C': [1.0, 10.О, 100.0) },
{'vect_ngram_range': [(1,1)),
'vect stop_words': [stop, None],
'vect __ tokenizer': [tokenizer,
tokenizer_porter],
'vect_use idf': [False],
'vect_norm' : (None],
'clf_penalty': ('11', '12'],
'clf_C': [1.0, 10.0, 100.0]}

-------(328]

Глава

8.

Применение машинного обучения для смыслового анализа

>>> lr tfidf = Pipeline ( [ ( 'vect', tfidf),

( 'clf',
LogisticRegression(random_state=O,
solver='liЫinear'))])

>>> gs lr_tfidf = GridSearchCV(lr_tfidf, param_grid,

scoring='accuracy',
cv=5, verbose=2,
n_jobs=l}
>>> gs lr tfidf.fit(X_train, y_train)
\.\:~ Многопроцессорная обработка посредством параметра

njobs

н~ Обратите внимание, что в предыдущем примере кода настоятельно
заметку!

рекомендуется устанавливать

n_j obs=-1

(вместо

n_j obs=l),

чтобы

задействовать все свободные процессорные ядра и ускорить решет­

чатый поиск. Однако при выполнении кода с установкой
ряд пользователей

Windows

n_j obs=-1

сообщали о проблемах, связанных со

сложностями, которые создают для многопроцессорной обработки

в среде

Windows

функции

tokenizer

и

tokenizer_porter.

Дру­

гой обходной путь предусматривает замену указанных двух функ­
ций,

[tokenizer, tokenizer_porter],

функцией

[str.split].

Тем не менее, важно иметь в виду, что замена на просrую функцию

str. spli t

не будет поддерживать стемминг.

Когда мы инициализируем объект
метров

(param_grid)

GridSearchCV

и его струкrуру пара­

с использованием показанного выше кода, то ограни­

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

40

компьютере выполнение

нашего решетчатого поиска может занять до

минут.
В приведенном ранее примере кода мы заменили объекты

Count

Vectorizer и TfidfTransformer из предыдущего подраздела объ­
ектом TfidfVectorizer, который комбинирует CountVectorizer с
TfidfTransformer. Наш param_grid состоит из двух словарей парамет­
ров. В первом словаре мы применяем объект TfidfVectorizer со стандар­
тными настройками (use _ idf=True, smooth_ idf=True и norm=' 12 ')для
вычисления мер tf-idf. Во втором словаре мы устанавливаем эти параметры

-----------[329]

Глава 8. Применение машинного обучения для смыслового анализа

в

use_idf=False, smooth_idf=False

и

norm=None,

чтобы обучить мо­

дель на сырых частотах термов. Кроме того, для самого классификатора на
основе логистической регрессии мы обучили модели с использованием регу­
ляризации

L2

и

L1

через параметр штрафа и сравнили силы регуляризации,

определив диапазон значений для обратного параметра регуляризации С.
После завершения решетчатого поиска мы можем вывести наилучший

набор параметров:

>>> print ('Наилучший набор параметров: %s '
% gs_lr_tfidf.best_params _)
Наилучший набор параметров: {'clf
С': 10.0, 'vect
stop_words':
None, 'clf penalty': '12', 'vect tokenizer': , 'vect~ngram_range': (1, 1)}
В выводе видно, что наилучшие результаты решетчатого поиска мы по­

лучили в случае применения обычного разбиения на лексемы без стеммера
Портера, в отсутствие библиотеки стоп-слов и с использованием мер

tf-idf в

сочетании с классификатором на основе логистической регрессии, который
применяет регуляризацию
ным

L2

с обратным параметром регуляризации С, рав­

10.0.

При наличии наилучшей модели, найденной решетчатым поиском, давай­

те выведем среднюю меру правильности при перекрестной проверке по

5

блокам на обучающем наборе и правильность классификации на испыта­
тельном наборе данных:

>>>

рrint('Правильность при перекрестной проверке:

%.Зf'

% gs_lr_tfidf.best_score_ )
Правильность при перекрестной проверке: 0.897
>>> clf = gs_lr_tfidf.best_estima tor_
>>> print ('Правильность при испытании: %. Зf'
% clf.score(X_test, y_test))
Правильность при испытании: 0.899

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

(330]--

90%-

[лава

8.

Применение машинного обучения для смыслового анализа

r~ Наивный байесовский классификатор

н~ Все еще очень популярным классификатором для классификации
заметку!

текстов остается наивный байесовский классификатор

(11ai've Hayes

class~Лn), который заработал популярность в приложениях фильтра­
ции спама из сообщений электронной почты. Наивные байесовские
классификаторы легко реализовывать, они рациональны с вычисли­
тельной точки зрения и имеют тенденцию работать особенно хорошо
на относительно небольших наборах данных в сравнении с другими
алгоритмами. Хотя обсуждать наивные байесовские классификато­

ры в книге мы не будем, заинтересованные читатели могут обра­
титься к статье

and Theory"

"Naive Bayes and Text Classification 1 -

Introduction

(Наивные байесовские классификаторы и классифика­

ция текстов

1 - введение и теория), С. Рашка, Computing Research
Repository (CoRR), abs/1410.5329 (2014 г.), свободно доступной по
ссылке http: / / arxi v. org/pdf/1410. 5329v3. pdf.

Работа с более крупными данными динамические алгоритмы и внеwнее обучение
Если вы выполняли примеры кода из предыдущего раздела, то наверняка

заметили, что конструирование векторов признаков для набора данных, со­
держащего

50

ООО рецензий на фильмы, во время решетчатого поиска может

оказаться весьма затратным в вычислительном плане. Во многих реальных

приложениях нередко приходится работать даже с более крупными набора­
ми данных, которые могут не уместиться в памяти компьютера. Поскольку
не каждый имеет доступ к суперкомпьютерам, мы теперь применим прием,

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

Г.~ Классификация текста с помощью рекуррентных нейронных сетей

н~ В главе 16 мы вернемся к этому набору данных и обучим классифи­
заметку!

катор, основанный на глубоком обучении (рекуррентной нейронной

сети), для классификации рецензий в наборе данных

IMDb.

Такой

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

зации на базе стохастического градиентного спуска, но не требует
конструирования модели суммирования слов.

- - - - - - - - - - - - - - · - - [331]

Глава

8.

Применение машинного обучения для смыслового анализа

В главе
спуска

-

2

была представлена концепция стохастического градиентиого

алгоритма оптимизации, который обновляет веса модели с исполь­

зованием одного образца за раз. В этом разделе мы задействуем функцию

partial_ fi t

класса

SGDClassifier

из

scikit-learn

для потоковой переда­

чи документов прямо из локального диска и обучим логистическую регрес­

сионную модель с применением небольших мини-пакетов документов.
Сначала мы определяем функцию

ботанные :екстовые данные из

tokenizer, которая очищает необра­
файла movie _ data. csv,созданного в нача­

ле главы, и разбивает его на лексемы, одновременно удаляя стоп-слова:

>>>
>>>
>>>
>>>
>>>

import numpy as np
import re
from nl tk. corpus import stopwords
stop = stopwords. words ( 'english' )
def tokenizer (text):
text = re.sub(']*>', ", text)
emoticons = re. findall (' (?:: 1; 1=) (?: -) ? (?: \) 1\ ( 1D1 Р) ',
text. lower ( ) )
text = re.sub(' [\WJ+', ' ', text.lower()) \
+ ' '. join (emoticons). replace ( ' - ', '')
tokenized = [w for w in text. split () if w not in stop]
return tokenized

Далее мы определяем генераторную функцию

stream_docs,

которая чи­

тает и возвращает один документ за раз:

>>> def stream_docs (path):
with open(path, 'r', encoding='utf-8') as csv:
next ( csv)
# пропустить заголовок
for line in csv:
text, label = line[:-3], int(line[-2))
yield text, label
Чтобы проверить, корректно ли работает наша функция
давайте прочитаем первый документ из файла

stream_ docs,
movie _ da ta. csv, что долж­

но привести к возвращению кортежа, содержащего текст рецензии и соот­

ветствующую метку класса:

>>> next(stream_docs(path='movie_data.csv'))
( '" In 197 4, the teenager Martha Moxley . . . ' , 1)

-----------(332)--------

Глава

8.

Применение машинного обучения для смыслового анализа

Затем мы определяем функцию

get _ miniba tch, которая будет прини­
stream_ docs и возвращать определен­
указанное в параметре size:

мать поток документов от функции
ное количество документов,

>>> def get_minibatch (doc_stream, size):

docs,

у

= [] , []

t.ry:

for

in range (size) :

text, label = next(doc_stream)
docs.append(text)
y.append(label)
except Stopiteration:
return None, None
return docs, у
К сожалению, мы не можем использовать для внешнего обучения класс
т.к. он требует удержания полного глоссария в памя­

CountVectorizer,
ти. К тому же классу

TfidfVectorizer

необходимо хранить в памяти все

векторы признаков обучающего набора, чтобы вычислять обратные частоты

документов. Однако в библиотеке
векторизатор

-

scikit-learn доступен еще один удобный
HashingVectorizer, который не зависит от данных и за­

действует трюк с хешированием через 32-битную функцию MurmurHashЗ,

разработанную Остином Эпплби

(https: / / si tes. google. com/si te/

murmurhash/):
>>> from sklearn. fea ture_ extraction. text irnport HashingVectorizer
>>> frorn sklearn.linear_model import SGDClassifier
>>> vect = HashingVectorizer(decode_error='ignore',

n_features=2**21,
preprocessor=None,
tokenizer=tokenizer)
>>> clf = SGDClassifier(loss='log', random_state=l)
>>> doc_stream = stream_docs(path='movie_data.csv')
В показанном выше коде мы инициализируем объект

с нашей функцией

2**21.

tokenizer

HashingVectorizer

и устанавливаем количество признаков в

Кроме того, мы повторно инициализируем классификатор на ос­

нове логистической регрессии, устанавливая

SGDClassifier

в

числа признаков в

параметр

loss

объекта

'log'. Обратите внимание, что за счет выбора большого
HashingVectorizer мы снижаем шанс возникновения

--------(333)-----------

Глава

8.

Применение машинного обучения для смыслового анализа

хеш-коллизий, но также увеличиваем количество коэффициентов в логисти­
ческой регрессионной модели.
А теперь мы подошли к действительно интересной части. Создав все до­

полнительные функции, мы можем начать внешнее обучение с применением
следующего кода:

>>>
>>>
>>>
>>>

import pyprind

pbar = pyprind.ProgBar(45)
classes = np.array( (0, 1])
for
in .range (45):
X_train, y_train = get_minibatch(doc_stream, size=lOOO)
if not Х train:
break

train = vect.transform(X_train)
clf.partial_fit(X_train, y_train, classes=classes)
pbar. update ()
0%
100%
[##############################] 1 ЕТА: 00:00:00
Total time elapsed: 00:00:21
Х

Всего прошло времени:

00:00:21

Мы снова используем пакет

PyPrind

для оценки продвижения нашего ал­

горитма обучения. Мы инициализируем объект индикатора выполнения

итерациями и в приведенном ниже цикле

for

проходим по

там документов, где каждый мини-пакет состоит из

1 ООО

45

45

мини-паке­

документов. По

завершении процесса постепенного обучения мы применим последние

5 ООО

документов для оценки эффективности модели:

>>> X_test, y_test = get_minibatch(doc_stream, size=SOOO)
>>> X_test = vect.transform(X_test)
>>> pririt ('Правильность: %• Зf' % clf. score (X_test, y_test))
Правипьность:

0.868

Как видим, правильность модели составляет приблизительно

87%,

что

чуть ниже правильности, которой мы достигали в предыдущем разделе, ис­

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

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

последние

5 ООО

документов для обновления модели:

>>> clf = clf.partial_fit(X_test, y_test)

----·--------(334)

Iлава

8.

Применение машинного обучения для см111слового анализа

Г.~ Модеnь word2vec

н~ Более современной альтернативой модели суммирования слов яв­
заметку! ляется

в

2013
Space"

word2vec - алгоритм, который компания Google выпустила
году ("Efficient Estimation of Word Representations in Vector
(Эффективная оценка представлений слов в векторном про­

странстве), Т. Миколов, К. Чен, Г. Коррадо и Дж. Дин, препринт

arXiv:1301.3781 (2013
Алгоритм

word2vec -

г.)).
это алгоритм обучения без учителя, основан­

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

word2vec,

за­

ключается в том, чтобы помещать слова с похожим смыслом в по­

добные кластеры, и посредством искусной организации пространс­
тва векторов модель способна воспроизводить определенные слова

с использованием простой векторной математики, например,
тап+ wотап

=

qиееп (король

-

king -

мужчина+ женщина= королева).

Исходную реализацию на языке С вместе с полезными ссылками на

связанные работы и альтернативные реализации можно найти по ад­
ресу

https: / /code. google. com/p/word2vec/.

Тематическое моделирование с помощью
латентноrо размещения Дирихле
Тематическое ."Уюделироваиие

(lopic m°'lelin,1{)

описывает обширную за­

дачу назначения тем непомеченным текстовым документам. Например,

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

моделирования мы затем стремимся назначить подобным статьям метки
категорий

-

скажем, спорт, финансы, международные новости, политика,

местные новости и т.д. Таким образом, в контексте широких категорий МО,

обсуждавшихся в главе

l,

мы можем считать тематическое моделирование

задачей кластеризации, т.е. подкатегорией обучения без учителя.
В этом разделе мы обсудим популярный прием для тематического моде­
лирования, который называется латентным раз.,1еще11ием Дирихле

(Latc11t

f)i;·iL'i1/ct Allocaticm --- /ЛЛ). Однако обратите внимание, что аббревиатуру
LDA (Latent Dirichlet Allocation) не следует путать с аббревиатурой LDA, обоз­
начающей линейный дискриминантный анализ (linear discriminant analysis)пpиeм понижения размерности с учителем, который был введен в главе 5.

[335)---

fлава

8.

Применение машинного обучения для см111слового анализа

Г'..~ Встраивание кnассификатора рецензий на фиnьмы
~ в веб-приnожение
На

заметку!

Прием

LDA

отличается от подхода обучения с учителем, который

мы приняли в этой главе для классификации рецензий на фильмы
как позитивные и негативные. Таким образом, если вас интересу­

ет встраивание моделей
фреймворка

Flask

scikit-learn

в веб-приложение посредством

с применением в качестве примера рецензента

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

Разбиение текстовых документов с помощью
Поскольку лежащая в основе

LDA

LDA

математика довольно сложна и требует

знания байесовского вывода, мы подойдем к этой теме с практической точки

зрения и будем интерпретировать

LDA,

используя терминологию для непрофес­

сионалов. Тем не менее, заинтересованные читатели могут почерпнуть допол­

нительную информацию о

LDA

из научной статьи

"Latent Dirichlet Allocation"

(Латентное размещение Дирихле), Дэвид Блей, Эндрю Ын и Майкл Джордан,

Journal of Machine Learning Research 3, с. 993-1022 (январь 2003 г.).
LDA - порождающая вероятностная модель, которая пытается отыскать
группы слов, часто появляющихся вместе в различных документах. Такие
часто появляющиеся вместе слова представляют наши темы

при допуще­

нии, что каждый документ является смесью разных слов. На входе

LDA

по­

лучает модель суммирования слов, которую мы обсуждали ранее в главе, и
разлагает ее на две новые матрицы:



матрица отображения документов на темы;



матрица отображения слов на темы.

LDA

разлагает матрицу суммирования слов на две матрицы таким обра­

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

ошибкой. На практике нас интересуют темы, которые прием

LDA

нашел в

матрице суммирования слов. Единственный недостаток может заключаться в

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

LDA,

-

количество тем

который должен указываться вручную.

(336)--

Глава

Реаnизация

LDA

8.

в бибnиотеке

Применение машинного обучения для смыслового анализа

scikit·learn

В настоящем подразделе мы будем применять класс

Allocation,

реализованный в библиотеке

scikit-leam,

LatentDirichlet

для разложения набо­

ра данных с рецензиями на различные темы. В приведенном ниже примере

мы ограничиваем анализ



темами, но заинтересованные читатели могут

поэкспериментировать с гиперпараметрами алгоритма, чтобы выяснить, ка­

кие еще темы могут быть найдены в этом наборе данных.
Первым делом мы загрузим набор данных в объект

используя локальный файл

movie _ da ta. csv

DataFrame

из

pandas,

с рецензиями на фильмы, ко­

торый мы создали в начале главы:

>>> import pandas as pd
>>> df = pd.read_csv('movie_data.csv', encoding='utf-8')
Далее мы применим уже знакомый класс

CountVectorizer,

здать матрицу суммирования слов, служащую входом для

тва мы будем использовать встроенную в
английского языка через

scikit-leam

LDA.

чтобы со­

Ради удобс­

библиотеку стоп-слов

stop_ words=' english':

>>> from sklearn.feature_extraction.text import CountVectorizer
>>> count = CountVectorizer(stop_words='english',

>>>

Х

max_df=.l,
max_features=SOOO)
= count.fit_transform(df['review'] .values)

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

10% (max _ df=. 1 ),

что­

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

встречающихся слов

(max features=5000)

для установления лимита на

размерность набора данных, чтобы улучшить выведение, выполняемое
Однако значения гиперпараметров

max_df=.1

и

LDA.

max_features=5000

вы­

браны произвольно, и читатели могут регулировать их, попутно сравнивая
результаты.

---------[337)-----

Глава

В

8.

Применение машинного обучения для смыслового анализа

следующем

примере кода демонстрируется

подгонка оценщика

к матрице суммирования слов и выведение

LatentDirichletAllocation
10 разных тем из документов (отметим,

что подгонка модели на портативном

или стандартном настольном компьютере может занять

5

и более минут):

>>> from sklearn.decomposition iшport LatentDirichletAllocation
>>> lda = LatentDirichletAllocation(n_components=lO,

random_state=l23,
learning_method='batch')
>>> X_topics = lda.fit_transform(X)
Устанавливая

lda

learning_method

в

'batch',

мы позволяем оценщику

производить оценку на основе всех доступных обучающих данных (мат­

рица суммирования слов) в одной итерации, что медленнее альтернативного

метода обучения

может привести к более точным результатам

(установка

в

'online', но
learning_method

'online'

аналогична динамическому или

мини-пакетному обучению, которое мы обсуждали в главе

2

и в текущей

главе).

\\:~ Максимизация ожиданий

н~ Реализация LDA в библиотеке scikit-learn применяет алгоритм
заметку! максимизации ожиданий (f:xpc(ft1lio11"\lt1ximi::alio11

-

1 М) для

многократного обновления оценок своих параметров. В главе алго­
ритм ЕМ не обсуждается, но если вам интересно, тогда ознакомь­

тесь с обзором в Википедии

(https: / /ru. wikipedia. org/wiki/

ЕМ-алгоритм) и подробным руководством по его использованию в

LDA,

свободно доступным по ссылке

http://obphio.us/pdfs/

lda_tutorial .pdf.
После подгонки
екта

lda,

(здесь

LDA

мы получаем доступ к атрибуту

components

объ­

который хранит матрицу, содержащую значения важности слов

5000)

для каждой из



тем в порядке возрастания:

>>> lda.components .shape
(10, 5000)
Чтобы проанализировать результаты, давайте выведем пять самых важных

слов для каждой из



тем. Обратите внимание, что значения важности слов

расположены в порядке возрастания. Таким образом, для вывода верхних пяти

слов нам необходимо отсортировать массив

topic

в обратном порядке:

----- [338]------

Глава

8.

Применение машинного обучения для смыслового анализа

>>> n_top_words = 5
>>> feature_names = count.get_feature_names()
>>> for topic_ idx, topic in enumerate ( lda. components _) :
print("Teмa %d:" % (topic_idx + 1))
p1'iпt("

Тема

".join( [feature_names[i]
Eor i in topic. argsort () \
[:-n_top_words - 1:-1]]))

1:

worst minutes awful script stupid
Тема

2:

family mother father children girl
Тема

3:

american war dvd music tv
Тема

4:

human audience cinema art sense
Тема

5:

police guy car dead murder
Тема

6:

horror house sex girl woman
Тема

7:

role performance comedy actor performances
Тема

8:

series episode war episodes tv
Тема

9:

book version original read novel
Тема

10:

action fight guy guys cool
Прочитав пять самых важных слов для каждой темы, мы можем выдви­

нуть предположение о том, что

LDA

идентифицировал перечисленные далее

темы.

l.

В целом плохие фильмы (не является по-настоящему тематической ка­
тегорией).

2.

Семейные фильмы.

3.

Военные фильмы.

4.

Фильмы об искусстве.

5.

Криминальные фильмы.

6.

Фильмы ужасов.

-------- --- [339]

Глава

8.

Применение машинного обучения для смыслового анализа

7.

Комедийные фильмы.

8.

Фильмы, как-то связанные с телевизионными шоу.

9.

Фильмы по мотивам книг.

10.

Фильмы-боевики.

Для подтверждения, что категории имеют смысл на основе рецензий, вы­

ведем три фильма из категории фильмов ужасов (категория
позиции

6

в индексной

5):

>>> horror = X_topics[:, 5] .argsort() [::-1]
>>> foI· iter_idx, movie_idx in enumerate (horror [ :3]):
pr:·iпt; (' \nФильм ужасов #%d:' % (iter_idx + 1))
print(df['review'] [movie_idx] [:300], ' ... ')
Фильм ужасов # 1:
House of Dracula works from the same basic premise as House of
Frankenstein from the year before; namely that Universal' s three
most famous monsters; Dracula, Frankenstein's Monster and The
Wolf Man are appearing in the movie together. Naturally, the film
is rather messy therefore, but the fact that ...
Дом Дракулы исходит из того же основного пocwra,

годом ранее Дом Франкенштейна,

чудовища от

Universal;

а именно

три самых знаменитых.

Дракула, монстр Франкенштейна и Человек­

волк появляются в фильме вместе.
местами запутан,

-

что вышедший

Конечно, по этой причине фильм

но сам факт того,

что

...

Фильм ужасов

#2:
Okay, what the hell kind of TRASH have I been watching now? "The
Witches' Mountain" has got to Ье one of the most incoherent and
insane Spanish exploitation flicks ever and yet, at the same
time, it's also strangely compelling. There's absolutely nothing
that makes sense here and I even douЬt there
Ладно,

что за невыносимую ЕРУНДУ я только что посмотрел?

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

но вместе с тем она удивительно

Здесь нет абсолютно никакого смысла, и я даже
что

...

Фильм ужасов

#3:
Horror movie time, Japanese style. Uzumaki/Spiral was
а total freakfest from start to finish. А fun freakfest at that,
but at times it was а tad too reliant on kitsch rather than the
horror. The story is difficult to summarize succinctly:
а carefree, normal teenage girl starts coming fac ...

-·------- [ 3401 -- ------

fлава

8.

Применение машинного обучения для смыслового анализа

Время фильмов ужасов, японский стиль. Узумаки/Спираль
от начала до конца был полным фестивалем фриков. При всей своей
забавности фестиваль фриков временами он слишком полагался на
китч,

чем на ужас.

Подытожить историю трудно: леI'_комысленная

нормальная молоденькая девушка

начинает

сталкиваться

В предыдущем примере кода мы вывели первые
них трех фильмов ужасов. Рецензии

какому фильму они относятся,

-

-

300

с

•..

символов из верх­

несмотря на то, что мы не знаем, к

выглядят как рецензии на фильмы ужасов

(хотя можно было бы утверждать, что Фильм ужасов

бы и под тематическую категорию

1:

#2

хорошо подошел

В целом плохие фw~ьмы).

Резюме
В главе было показано, как применять алгоритмы МО для классификации
текстовых документов на основе их направленности, которая является базо­
вой задачей при смысловом анализе в области

NLP.

Вы не только научились

кодировать документ в виде вектора признаков, используя модель суммиро­
вания слов, но также узнали, как взвешивать частоту терма по важности с

применением меры

tf-idf.

Работа с текстовыми данными может быть сопряжена с довольно высо­
кими вычислительными затратами из-за больших векторов признаков, кото­

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

Наконец, мы представили концепцию тематического моделирования с ис­

пользованием

LDA

для разделения рецензий на фильмы по разным катего­

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

------- [341]

-·~---------·-------

9
ВСТРАИВАНИЕ МОДЕЛИ
МАШИННОГО ОБУЧЕНИЯ
В ВЕБ-ПРИЛОЖЕНИЕ

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

низмами веб-служб. Например, популярные и удобные случаи употребления
моделей МО в веб-приложениях включают обнаружение спама в отправляе­
мых формах, поисковые механизмы, системы выдачи рекомендаций для ме­
дийных и продающих порталов, а также многие другие.

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

В главе будут раскрыты следующие темы:



сохранение текущего состояния обученной модели МО;



использование баз данных



разработка веб-приложения с применением популярного веб-фрейм­
ворка



SQLite

для хранилищ данных;

Flask;

развертывание приложения МО на публичном веб-сервере.

Глава

9.

Встраивание модели машинного обучения в веб-приложение

Сериаnизация подоrнанных оценщиков
Как было показано в главе

8,

scikit-learn

обучение модели МО может оказаться до­

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

модель заново каждый раз, когда закрыли интерпретатор

Python

и желаем

выработать прогноз или перезагрузить веб-приложение?
Одним из вариантов обеспечения постоянства моделей является модуль

pickle для Python (https: //docs.python.org/3. 7/library/pickle.
html). Он делает возможными сериалиацию и десериалиацию структур
объектов Python с целью сжатия байт-кода, чтобы мы могли сохранить клас­
сификатор в текущем состоянии и опять загрузить его, когда нужно класси­
фицировать новые непомеченные образцы, не заставляя модель учиться на
обучающих данных еще раз. Прежде чем выполнять следующий код, удос­

товерьтесь в том, что обучили внешнюю логистическую регрессионную мо­
дель из последнего раздела главы

сеансе

8

и обеспечили ее готовность в текущем

Python:

>>> irнpo:et pickle
>>> нr.port. os
>>> dest = os .path. join ( 'movieclassifier', 'pkl _ obj ects')
>>> i f not os.path.exists(dest):
os.makedirs(dest)
>>> pickle.dump(stop,
open(os.path.join(dest, 'stopwords.pkl'), 'wb'),
protocol=4)
»> pickle.dump(clf,
open(os.path.join(dest, 'classifier.pkl'), 'wb'),
protocol=4)
В приведенном выше коде мы создаем каталог

movieclassifier,

где

позже будем хранить файлы и данные для нашего веб-приложения. Внутри
каталога

movieclassifier

мы создаем подкаталог

pkl objects,

чтобы

сохранять на локальном жестком диске или твердотельном накопителе се­

риализированные объекты

Python.

С помощью метода

dump

модуля

pickle

мы затем сериализируем обученную логистическую регрессионную модель,

а также набор стоп-слов из библиотеки
мость в установке глоссария

Метод

dump

NLTK

NLTK,

чтобы устранить необходи­

на сервере.

принимает в своем первом аргументе объект, который мы

хотим законсервировать. Во втором аргументе предоставляется открытый

···-·· ·····-· ···- -------···--·- ··-··----------------·-·---- [ 3441 ---------·· ·-·-·· ---------------·-··

fлава

9.

Встраивание модели машинного обучения в веб-приложение

файловый объект, куда будет записываться объект
гумента

wb

внутри функции

open

Python.

Посредством ар­

мы открываем файл в двоичном режиме

для консервирования и устанавливаем

protocol=4,

чтобы выбрать самый

последний и эффективный протокол консервирования, который появился в

Python 3.4

и совместим с последующими версиями

Python. В случае воз­
protocol=4 проверьте, работаете
Python 3 - в этой книге рекомендуется

никновения проблем с использованием
ли вы с самой последней версией

Python 3.7.

Или же можете обдумать вопрос применения протокола с мень­

шим номером.

Также имейте в виду, что если вы имеете дело со специальным веб-сер­
вером, то должны также проверить, совместима ли установленная на нем

копия

Python

с версией протокола.

Г:.~ Сериаnизация массивов

NumPy с

помощью бибnиотеки joЫib

н~ Наша лоrистическая регрессионная модель содержит несколько мас3аметку!

сивов

NumPy,

таких как весовой вектор, а более рациональный спо­

соб сериализации массивов

альтернативной библиотеки

NumPy предусматривает использование
j оЫiЬ. Чтобы обеспечить совмести­

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

полнительные сведения о библиотеке joЬlib доступны по ссылке
https://joЬlib.readthedocs.io.

Консервировать объект

HashingVectorizer

нет никакой необходимос­

ти, потому что он не нуждается в подгонке. Взамен мы можем создать файл

сценария

Python.

Python,

Скопируем следующий код и сохраним его в файле

внутри каталога

f P".IШ

из которого импортировать векторизатор в текущий сеанс

vectorizer. ру

movieclassifier:

sklearn. feature extraction. text

i.mpoi:-t.

HashingVectorizer

import re

import os
.i.шpoi:t pickle
cur_dir = os.path.dirname(_file_)
stop = pickle.load(open(os.path.join(
cur_dir, 'pkl objects', 'stopwords.pkl'),
'rb'))

. ·- [345) .

Глава

9.

Встраивание модели машинного обуцения в веб-приложение

def tokenizer(text):
text)
emoticons = re. findall ( ' (?: : 1 ; 1=) (?: - ) ? (?: \) 1 \ ( 1D1 Р) ' ,
text. lower ())
text = re.suЬ(' [\WJ+', ' ', text.lower()) \
+' '.join(emoticons) .replace('-', '')
tokenized = [w for w in text. spli t () i f w not in stop]
return tokenized

text=re.suЬ('>>
>>>
>>>
>>>

movieclassifier,

vectorizer

и расконсервировать классификатор:

import pickle
import re
import os
from vectorizer irnport vect

-------------------------- ----------------- [346] ------------------------------ -----------------------------------------------------

fлава

>>> clf

=

9.

Встраивание модели машинного обучения в веб-приложение

pickle.load(open(os.path.join(
'pkl_objects', 'classifier.pkl'),
'rb'))

После успешной загрузки

vectorizer

и расконсервирования классифи­

катора мы можем использовать эти объекты для предварительной обработки
образцов документов и выработки прогнозов об их отношениях:

>>> :iщport numpy as np
>>> label = {О:' негативный', 1:

'позитивный'}

>>> example = [ 11 I love this movie. It' s amazing. 11 ]
>>> Х = vect.transform(example)
>>> рrint('Прогноз: %s\nВероятность: %.2f%%' %\
(label[clf.predict(X) [О]],
np.max(clf.predict_proba(X))*lOO))
Прогноз: позитивный
Вероятность:

95.55%

Поскольку наш классификатор возвращает спрогнозированные мет­

ки классов как целые числа, мы определили простой словарь

Python

для

отображения таких целых чисел на их отношения ('негативный' или

' позитивный').

Хотя в итоге получилось простое приложение только с

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

pickle.

Однако в реальных приложениях с более обширными

словарями отображения вы можете задействовать те же самые команды

pickle. dump

и

pickle. load,

которые использовались в предыдущем

примере кода.

Продолжая обсуждение предыдущего примера кода, мы затем применили

HashingVectorizer

для трансформации простого примера документа в

вектор слов Х. Наконец, мы использовали метод

predict

классификатора на

основе логистической регрессии для прогнозирования метки класса, а также
метод

predict_proba

для возвращения соответствующей вероятности про­

гноза. Обратите внимание, что в результате вызова метода

predict_proba

возвращается массив со значениями вероятностей для всех уникальных ме­

тод классов. Так как метка класса с наибольшей вероятностью соответствует

---- ·----·----·---------·--··---·----·--·- ··--··-----·--------·----- [347] ---·-----·-----·-·-----·----------·-------·----·------

fлава

9.

Встраивание модели машинного обучения в веб-приложение

метке класса, возвращаемой методом

predict,

для возвращения вероятнос­

ти спрогнозированного класса мы применили функцию

Настройка базы данных

np. max.

SQLite

для хранилища данных
В текущем разделе мы настроим простую базу данных

SQLite

для сбо­

ра дополнительных отзывов о прогнозах от пользователей веб-приложения.

Мы можем использовать такую обратную связь для обновления классифи­

кационной модели.

это механизм баз данных

SQLite -

SQL

с открытым

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

SQLite

можно считать одиночным

самодостаточным файлом базы данных, который позволяет напрямую обра­
щаться к хранилищу.

Кроме того,

SQLite

не требует конфигурирования, специфичного для

системы, и поддерживается

темах. Механизм

SQLite

во всех распространенных операционных сис­

известен своей высокой надежностью и применяет­

ся популярными компаниями, среди которых

Google, Mozilla, Adobe, Apple,
Microsoft и многие другие. Дополнительная информация о механизме SQLite
доступна на официальном веб-сайте ht tp: / /www. sqli te. org.
К счастью, согласно принятой в Python философии "батарейки в комп­
лекте" стандартная библиотека Python предлагает АРl-интерфейс sqli tеЗ,
который позволяет работать с базами данных SQLite. (Больше сведений о
модуле sqliteЗ можно получить по ссылке https: //docs .python. org/
3. 7 / library / sqli tеЗ. html.)
Выполнив следующий код, мы создадим внутри каталога movieclas
sifier новую базу данных SQLite и сохраним в ней два образца рецензий
на фильмы:

>>> import sqli tеЗ
>>> import: os
>>> conn =

sqliteЗ.connect('reviews.sqlite')

>>>с=

conn.-cursor()
>>> с. execute ( 'DROP TABLE IF EXISTS review_ db' )
>>> c.execute('CREATE TABLE review_dЬ'\
' (review ТЕХТ, sentiment INTEGER, date

------------------ - ---------

-- [348]----

ТЕХТ)

')

Глава

9.

Встраивание модели машинного обучения в веб-приложение

>>> examplel = ' I love this movie'
>>> c.execute( 11 INSERT INТO review_db 11 \
" (review, sentiment, date) VALUES 11 \
(?, ?, DATETIME('now'))", (examplel, 1))

11

>>> example2 = 'I disliked this movie'
>>> c.execute("INSERT INТO review_db 11 \
(review, sentiment, date) VALUES 11 \
(?, ?, DATETIME('now')) 11 , (example2, 0))
>>> conn.commit()
>>> conn. close ()
11

11

В коде мы создаем подключение

( conn) к файлу базы данных SQLite, вы­
зывая метод connect из библиотеки sqli tеЗ, который создает в каталоге
movieclassifier файл базы данных reviews. sqli te, если он пока не
существует.

Затем посредством метода

cursor

мы создаем курсор, который даст воз­

можность проходить по записям базы данных, используя универсальный

синтаксис

SQL.

Далее с применением первого вызова

новую таблицу базы данных

review_ db,

execute

сохранения и последующего доступа к записям. В таблице
создаем три столбца:

мы создаем

которую будем использовать для

review, sentiment

и

date.

review_ db

мы

Они будут применять­

ся для хранения примеров рецензий на фильмы и соответствующих меток
классов (отношений).

С использованием SQL-команды

DATETIME ( 'now' )

мы добавляем к за­

писям дату и отметку времени. Знаки вопроса(?) применяются для переда­
чи методу

execute

текстов рецензий на фильмы

и связанных с ними меток классов

(1

(examplel

и

example2)

и О) в виде позиционных аргументов

как членов кортежа. В заключение мы вызываем метод

commi t

для сохра­

нения изменений, внесенных в базу данных, и закрываем подключение пос­

close.

редством метода

Чтобы проверить, корректно ли сохранились записи в базе данных, мы
снова откроем подключение к базе данных и с помощью SQL-команды

SELECT

извлечем из таблицы все строки, зафиксированные в период между

началом

2017

года и текущей датой:

>>> conn = sqliteЗ.connect('reviews.sqlite')
>>> с = conn. cursor ()
>>> c.execute("SELECT * FROM review_dЬ WHERE date"\
11

BETWEEN '2017-01-01 00:00:00' AND DATETIME('now')")

.-·- - - -- ---- [349] -- ·- - ----------- - -- --------- ---·----·--- ·---···-

Глава

9.

Встраивание модели машинного обучения в веб-приложение

>>> results = c.fetchall()
>>> conn. close ()
>>> print(results)
[('I love this movie', 1, '2020-04-15 17:53:46'), ('I disliked
this movie', О, '2020-04-15 17:53:46')]
В качестве альтернативы мы могли бы также восnользоваться бесnлат­

DB Browser for SQLite (Программа nросмотра баз дан­
SQLite), достуnным no ссылке https://sqlitebrowser.org/dl/;

ным nриложением
ных

оно nредлагает удобный графический nользовательский интерфейс для ра­
боты с базами данных

SQLite

(рис.

9.1 ) .



~

0 New DataЬase

DB Browser for SQLite - /Users/sebasti

Open Datallue
v

;

Edit Pragmas

Database Structure
review_db

ТаЫе :

New Recorc(..
иntlmtnt

r~w

datt

2019-06-15 17:40:57

1 1 lovt! thls movit

2 1dlsliktd thls movit

Рис.

9.1.

Execute SQL

2019-06-15 17:40:57

о

Пршюжение

DB Browser.for SQLite

Разработка веб-приnожения с помощью

Flask

Теnерь, когда код для классификации рецензий на фильмы готов, давай­

те обсудим основы веб-фреймворка

Flask,

необходимого для разработки на­

шего веб-приложения. После вьшуска Армином Ронахером nервоначальной
версии

Flask

в

201 О

году этот фреймворк с годами обрел огромную поnу­

лярность и nрименяется в таких известных приложениях, как

Pinterest.

Поскольку фреймворк

Flask

наnисан на языке

Python,

Linkedln

он снабжает

Python удобным интерфейсом для встраивания существу­
Python, подобного классификатору рецензий на фильмы.

nрограммистов на
ющего кода

и

------ - --~-- - ----- - ---- ------ --- -·· --·-- .. ... [350) --- -- - -- - -·· ·· --- . .. -- - - --······-- -

fлава 9. Встраивание модели машинного обучения в веб-приложение

Г'.~ Микрофреймворк

Flask

н~

Flask

также называют микрофреймворком, имея в виду тот факт, что

заметку!

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

легковесного АРl-интерфейса

Flask

не настолько крута, как у дру­

гих популярных веб-фреймворков вроде

Django,

рекомендуется за­

глянуть в официальную документацию по ссылке

https: //flask.

palletsprojects. com/en/1. О .х/,

чтобы получить больше све­

дений о его функциональности.

Если библиотека

Python отсутствует, тогда ее мож­
но установить с помощью команды conda или pip в терминальном окне
(на момент написания главы последним стабильным выпуском был 1.0.2):
Flask

в текущей среде

conda install flask
# или pip install flask

Первое веб-приnожение

Flask

Перед тем, как заняться реализацией классификатора рецензий на филь­
мы, в настоящем подразделе мы разработаем очень простое веб-приложение
с целью ознакомления с АРI-интерфейсом

Flask.

Первое приложение будет

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

ются между разными частями кода внутри фреймворка

Flask.

Прежде всего, мы создаем дерево каталогов:

lst flask_app_l/
арр.ру

templates/
first_app.html
Файл арр. ру содержит основной код, который будет выполняться интер­
претатором

фреймворк

Python для запуска веб-приложения Flask. В каталоге templates
Flask будет искать статические НТМL-файлы для визуализации в

веб-браузере. Теперь взглянем на содержимое файла арр. ру:

--- - (3511 - - -- ------- --- - --- -- ---- --- - ------- . .

Глава

9.

Встраивание модели машинного обучения в веб-приложение

flask

f1:ощ

арр =

@арр.

ннроrt.

Flask, render_ template

Flask ( name
route ( '/')

clef index ( ) :

render_template ( 'first_app.html')

ret:lirп

name
run ()

:i.f

main



арр.

Давайте обсудим индивидуальные части кода по очереди.

1.

Мы запускаем приложение как одиночный модуль. Таким образом, мы
инициализируем новый экземпляр

бы фреймворку

( templa tes)

2.

URL,

_name_,

что­

было известно, что подкаталог НТМL-шаблонов

располагается в том же каталоге, где находигся приложение.

который должен инициировать

Далее функция
находящийся

4.

с аргументом

Затем мы применяем декоратор маршрута (@арр.

указагь

3.

Flask

Flask

route ( '/') ), чтобы
выполнение функции index.

index просто визуализирует НТМL-файл first _ арр. html,
в подкаталоге templates.

Наконец, мы используем функцию

run

для запуска приложения на

сервере, когда этот сценарий непосредственно выполняется интер­

претатором

name

Python, что
-- '
main

гарантируется оператором

Рассмотрим содержимое файла

first

арр.




First app


Hi, this is my first Flask web


r.~ Основы HTML

н~ На тот случай, если синтаксис
заметку!

i f с условием

html:

арр

!

вам не знаком, по ссылке

HTML
https: / /developer .mozilla. org/ru/docs/WeЬ/HTML
но удобное руководство по основам HTML.

------------------- - -------------··-- [352]

доступ­

[лава

9. Встраивание модели машинного обучения в веб-приложение

Здесь мы просто заполняем пустой файл НТМL-шаблона с помощью эле­

мента

(элемент блочного уровня),
Hi, this is my first Flask web арр ! .
Фреймворк

Flask

который содержит предложение:

позволяет запускать приложения локально, что удоб­

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

lst flask_app _ 1:
pythonЗ арр. ру

Мы должны увидеть в терминальном окне строку такого вида :

* Running on http://127.0.0.1:5000/
*Выполняется на

http: l/ 127.0.0 . 1:5000/

Строка содержит адрес локального сервера. Мы можем ввести этот ад­
рес в адресной строке веб-браузера, чтобы посмотреть на веб-приложение в
действии. Если все было сделано корректно , тогда отобразится простой веб­
сайт с содержимым


Нi,

Hi, this is my first Flask web

<
this is

арр

!

(рис.

о

127.0.0.1

ту
Рис.

9.2).

» +

first Flask web арр!
9.2.

Пример веб-приижения

Flask

в действии

Проверка до



--· --------------------------------------- ---------- [ 3561 --- -- ------ -------· --------------· ---------------------------- -----------------

Глава

9.

Встраивание модели машинного обучения в веб-приложение

First app



{% from" formhelpers.html" import render_field %)
What's your name?


{{ render_ field(form.sayhello) }}





Мы загружаем СSS-файл в разделе заголовка НТМL-разметки

first_
html. Теперь размер всех текстовых элементов внутри НТМL-элемента
body должен измениться. В разделе тела HTML мы импортируем макрос
формы из _ formhelpers. html и визуализируем форму sayhello, ко­
арр.

торую указали в файле арр. ру. Кроме того, мы добавили кнопку к тому
же самому элементу

form,

чтобы пользователь мог отправлять запись из

текстового поля . Отличия между первоначальной и модифицированной вер­
сиями файла

first _ app. html

original first _ app. html file

•PP""/t.i Lle>

-.iH 1•>11:'• t.

-

'Чti.v>What 1

c-;rom lll:thoci

you:c nanietc:/4.lv:.
ро1\. мt.1on••1tiello">

(( rencttr_(1elctctoro.1ayt\ol1.o) }}
(,./ctl>




First app



Hello {{ name ))


Поскольку в предыдущем разделе мы раскрыли много основ, на рис.
представлен обзор ранее созданных файлов.
tro. flut iaiport r1~. r.М.r_L88p1•t.e , r~•t.

rc-. wt.tcx:- Ui:port. roz.,

lu•

lcklet,.pell.Ul.l>
.•.;.>

... ...

.llitllol'l:t:n.lro~I

••Yh•1lo •

1roport pandas as pd
>>> df = pd.read_csv('https://raw.githubusercontent.co m/rasbt/'
'python-machine-learning-book-Зrd-edition'

'/master/chlO/housing.data.txt',
header=None,
sep=' \s+')
>>>

df.colшnns

[ 'CRIM' , ' ZN' , ' INDUS' , 'CHAS ' ,
'NOX', 'RМ', 'AGE', 'DIS', 'RAD',
'ТАХ' , 'PTRATIO' , 'В' , 'LSTAT' , 'MEDV' ]

>» df.head()
Чтобы проверить, успешно ли загрузился набор данных, мы отобразим из
него первые пять строк (рис.

10.3).

--------------(382)--------------·~--

Глава

CRIM
о

0.00632

1

ZN INDUS
18.0

СНАS
о

2.31

10.
NOX

Прогнозирование значений непрерывных целевых переменных".

RM AGE

0.538 6.575

DIS RAD
1

296.О

78.9 4.9671

2

0.02731

О.О

7.07

о

0.469 6.421

2 0.02729

о.о

7.07

о

0.469

0.03237

о.о

2.18

о

0.458 6.998

45.8 6.0622

4 0.06905

о.о

2.18

о

0.458

7.147

54.2 6.0622

з

7.185

ТАХ

4.0900

65.2

61.1

4.9671

PТRATIO

в

LSTAT MEDV

15.3 396.90

4.98

24.0

9.14

21.б

242.О

17.8 396.90

2 242.0

17.8 392.83

4.03

34.7

3

222.О

18.7 394.63

2.94

33.4

3

222.0

18.7 396.90

5.33

36.2

----

Рис.

10.3.

Первые пять строк набора данных

Housing

\\:~ Получение набора данных Housing

н~ Копия набора данных Housing (и всех других наборов данных, ис­
заметку!

пользуемых в книге) включена в состав загружаемого архива с кодом примеров для книги. Указанные копии можно задействовать при

автономной работе или в случае временной недоступности ресур­
са по ссылке

https: / /raw. gi thubusercontent. com/rasbt/

python-machine-learning-book-Зrd-edition/master/

chlO/housing. data. txt. Скажем, чтобы загрузить набор данных
Housing из какого-то локального каталога, следующий оператор:
df = pd. read_csv (
'https://raw.githubusercontent.corn/rasbt/'
'python-rnachine-learning-book-Зrd-edition/'

'rnaster/chlO/housing.data.txt',
header=Noпe,

sep='\s+')
понадобится заменить таким оператором:

df = pd.read_csv(' ./housing.data.txt',
sep=' \s+')

Визуализация важных характеристик набора данных
Исследовательский анализ данных (е.\рlоп1tтт

clata a11alysis ---

ЕЛА) яв­

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

трументов из графического набора

EDA,

которые могут помочь визуально

обнаружить присутствие выбросов, выяснить распределение данных и уло­
вить взаимосвязи между признаками.

Первым делом мы создадим матрицу графиков рассеяния, которая поз­
волит визуализировать в одном месте попарные взаимосвязи между разны­

ми признаками в наборе данных. Выводить матрицу графиков рассеяния

--[383)------------

Глава

10.

Прогнозирование значений непрерывных целевых переменных."

мы будем с использованием функции

scatterplotmatrix
MLxtend (ht tp: / / rasbt. gi thub. io/mlxtend/), которая

из библиотеки
содержит раз­

нообразные удобные функции, предназначенные для приложений МО и на­
уки о данных на языке
Пакет

mlxtend

Python.

mlxtend устанавливается с помощью команды conda install
или pip install mlxtend. После завершения установки пакет

можно импортировать и создать матрицу графиков рассеяния, как показано
ниже:

>>>
>>>
>>>
>>>

import matplotlib .pyplot as plt
from mlxtend.plotting lmport scatterplotmatrix
cols = [ 'LSTAT' , 'INDUS', 'NOX' , 'RМ', 'MEDV' ]

scatterplotmatrix(df[cols] . values, figsize=(lO, 8),
names=cols, alpha=0.5)
>» plt. tight_layout ()
>>> plt. show ()
На рис.

10.4

видно, что матрица графиков рассеяния снабжает нас полез­

ной графической сводкой

"iS

100

u

so

о

взаимосвязям в наборе данных.

no

0
о

20

LSTAT

S 20 ~

'1

100

8§ so

о

о
о

[i]
о

20

20
INDUS

INDUS

~ ~ .,,20~·
~2: ~ ~ /. •
о

0.50

о

0.75

0.50

0.75

§so~

8

0.75

0.50

NOX

NOX

NOX

.,,20~
Б

~

~ 0.75 [ i ]
z o.so
5.0

7 .S

5.0

.,,2o f i : j .

б

~

.

-

о: 6

10.4.

-

25
MEDV

MEDV

Рис.

2: 8 ~ 8с1000

sz 0.75
~~--.-

50

25

7.5
~

0.50

о

50

MEDV

CJ
S.O

7.S
~

~

25

~ ioo
о

о

!:SJ

~

u

50

4

о

25
MEDV

50

25

50

MEDV

Матрица ,?рафиков рассеяния

- - (384)--- - - - ·- --·------·----

fлава

10.

Прогнозирование значений непрерывных целевых переменных".

Из-за ограничений пространства и в интересах читабельности мы пост­

роили графики только для пяти столбцов из набора данных: LSTAT, INDUS,

NOX, RМ и MEDV. Тем не менее, вы можете самостоятельно создать матрицу
графиковрассеяния полного объекта

DataFrame,

чтобы продолжить иссле­

дование набора данных, для чего передавать функции

scatterplotmatrix

различные имена столбцов или включить в матрицу все переменные, опус­

тив селектор столбцов.
С помощью матрицы графиков рассеяния мы теперь можем быстро оце­
нить на глаз, каким образом данные распределены и содержат ли они вы­
бросы. Например, здесь видно, что есть линейная связь между RM и ценами
на дома, MEDV (пятый столбец в четверной строке). Кроме того, глядя на

гистограмму в нижнем правом углу матрицы графиков рассеяния, мы мо­
жем отметить, что переменная

MEDV

похоже имеет нормальное распределе­

ние, но содержит несколько выбросов.

\\:~ Предположение о нормальности линейной реrрессии

н~ Обратите внимание, что вопреки общему мнению обучение линей­
заметку!

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

выходит за рамки настоящей книги

Analysis"

("Introduction to Linear Regression

(Введение в линейный регрессионный анализ), Д. Монтго­

мери, Э. Пек и Дж. Вайнинг,

Wiley (2012

г.), с.

318-319).

Просмотр взаимосвязей с испоnьзованием корреnяционной матрицы
В предыдущем разделе мы визуализировали распределения данных для

переменных набора данных

Housing

в форме гистограмм и графиков рас­

сеяния. Далее мы создадим корреляционную матрицу для количествен­
ной оценки и суммирования линейных взаимосвязей между переменными.

Корреляционная матрица тесно связана с ковариационной матрицей, которую

мы обсуждали в разделе "Понижение размерности без учителя с помощью
анализа главных компонентов" главы

5.

По существу мы можем трактовать

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

--------·--[385)------

[лава

10.

Прогнозирование значений непрерывных целевых переменных."

Корреляционная матрица представляет собой квадратную матрицу, содер­

жащую значения коэффициента корреляции смешанною момента Пирсона

(Pea"son

proiluct-·11нm1el'll cmтelation

coeJ]icient),

часто сокращаемого до

r

Пирсона, который измеряет линейную зависимость между парой признаков.

Коэффициент корреляции находится в диапазоне от

-1

имеют идеальную положительную корреляцию, если

r = 1,

ции, если

r

до

1.

Два признака

никакой корреля­

= О, и идеальную отрицательную корреляцию, если

r

= -1. Как

упоминалось ранее, коэффициент корреляции Пирсона может вычисляться
просто как ковариация между двумя признаками х и у (числитель}, деленная

на произведение их стандартных отклонений (знаменатель):

Здесь с помощью
признака, й'ху

-

µ

обозначается выборочное среднее соответствующего

ковариация между признаками х и у, а й'х и й'у

-

стандарт­

ные отклонения признаков.

\\~ Ковариация ипи коррепяция дпя стандартизиров~нных признаков

н~ Мы можем показать, что ковариация между парои стандартизиро­
заметку!

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

лучить их меры

z,

которые мы обозначим как х' и у':

Вспомните, что мы вычисляет ковариацию (генеральной совокуп­
ности) между двумя признаками следующим образом:

Так как стандартизация центрирует переменную признака в нулевом
среднем, мы можем вычислить ковариацию между масштабирован­
ными признаками:

(386)----

[лава

10.

Прогнозирование значений непрерывных целевых переменных".

1 п
а'ху = -п~
"' (х'- О)( у' - О)
1

С помощью повторной подстановки мы получаем такой результат:

а'

ху

Наконец, мы можем упростить это уравнение до следующего вида:

В приведенном ниже примере кода мы будем применять функцию

corrcoef

из

NumPy

на пяти столбцах признаков, которые ранее визуали­

зировали в матрице графиков рассеяния, и также использовать функцию

hea tmap

из

MLxtend

для вывода массива с корреляционной матрицей в

виде тепловой карты:

>>> from mlxtend.plotting import heatmap
>>> irnport nwnpy as np
>>>ст= np.corrcoef (df[cols] .values.T)
>>> hm = heatmap(cm,
row_names=cols,
column_names=cols)
»> plt. show ()
На результирующей тепловой карте (рис.

10.5)

несложно заметить, что

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

------[387]-

Глава

10.

Прогнозирование значений непрерывных целевых переменных ...

1.0
LSTAT

0.8
О.б

INDUS

0.4

0.2

NOX

о.о

- 0.2

RM

-0 .4
MEDV

Рис.

10.5.

-0 . б

Корреляционная .натрuца, представ.1е1111ая в виде теп1овой карты

Для подгонки линейной регрессионной модели нас интересуют те при­
знаки, которые имеют высокую корреляцию с целевой переменной

MEDV.

Взглянув на предыдущую корреляционную матрицу, мы заметим, что це­

левая переменная MEDV показывает наибольшую корреляцию с переменной

LSTAT (-0. 7 4); однако, как вы можете помнить из исследования матрицы
графиков рассеяния, между LSTAT и MEDV существует ясная нелинейная
связь. С другой стороны, корреляция между RM и MEDV также относительно
высока (О.

70).

С учетом линейной связи между этими двумя переменны­

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

Реализация линейной регрессионной

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

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

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

. ------[ 3881 - -----

Глава

10.

Прогнозирование значений непрерывных целевых переменных."

подразделах мы заполним недостающие фрагменты головоломки, приме­
няя обычный Аtетод наименьиtuх квадратов

(tmlinm·y lcast

щит·сs

-- 01.S),

иногда также называемый ли11еi111ым :11,1етодом наименьших квадратов, для
оценки значений параметров прямой линейной регрессии, которые сводят к

минимуму сумму квадратов расстояний по вертикали (остатков или ошибок)
до обучающих образцов.

Использование rрадиентноrо спуска
для выяснения параметров реrрессии

Вернемся к нашей реализации адаптивного линейного нейрона
главы

2.

(Atialine)

из

Вы должны помнить, что искусственный нейрон применяет линейную

функцию активации. Мы также определяли функцию издержек

JO,

которую

минимизировали для выяснения весов через алгоритмы оптимизации, такие

как градиентный спуск ((;I)) и стохастический ?радиентный спуск (S(:;П).

Функцией издержек в

Adaline

была су.м.ма квадратичных ошибок

торая идентична функции издержек, используемой для

(.S.\E),

ко­

OLS:

J( w=-Lly
) 1 ~( (i) -уA(i)) 2
2
Здесь у

-

i=I

1
спрогнозированное значение у= wтх (отметим, что член -

применяется просто ради удобства для выведения правила обновления

По существу регрессию

OLS

можно понимать как

Adaline

2
GD).

без единичной

ступенчатой функции, так что вместо меток классов -1 и 1 мы получаем
непрерывные целевые значения. Чтобы продемонстрировать это, давайте

возьмем реализацию

Adaline

на основе

GD

из главы

2

и удалим единичную

ступенчатую функцию с целью реализации нашей первой линейной регрес­
сионной модели:

class LinearRegressionGD(object):
def

init (.,:; ::, eta=0.001, n_iter=20):
eta = eta
:>>

lr.n_iter+l), lr.cost_)

plt.plot(raпge(l,

»> plt. ylabel ( 'SSE')
>>> plt. xlabel
>>> plt. show ()
На рис.

10.6

('Эпоха')

видно, что алгоритм

GD

сходится после пятой эпохи.

1
240

220
200
180
160
140
5.0

2 .5

7.5

10.0

12.5

15.0

17 .5

20 .0

Эпоха

Рис.

10.6.

График издержек алгоритма

GD

Давайте визуально выясним, насколько хорошо прямая линейной рег­

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

>>> def lin_regplot(X, у, model):
plt.scatter(X, у, c='steelЫue', edgecolor='white', s=70)
plt.plot(X, model.predict(X), color='Ьlack', lw=2)
л;,t. urn

None

--- -- - -- ---- - -- - - [391 ] - --

[лава

10.

Прогнозирование значений непрерывных целевых переменных ...

Теперь мы применим готовую функцию

lin_ regplot

для вывода графи­

ка зависимости цены на дом от количества комнат:

>>> lin_regplot(X_std, y_std, lr)
>>> plt . xlabel('Cpeднee количество комнат [RМ] (стандартизированное)')
>>> plt.ylabel ('Цена в тыс. долларов [MEDV] (стандартизированная)')
»> plt . show ()
На результирующем графике (рис.

10.7)

можно заметить, что прямая ли­

нейной регрессии отражает общую тенденцию роста цен на дома с увеличением количества комнат.

>-

2

-111

1

•• ••



3



Q"'

w 111
::!: ~
• 111

i:;
i:;
о

о

о.

s
м
. s

1:1:

(,)

:i5
...

...

111

1:1:

111

1!(,)

:i::

ф

о

о.

111

:i::

-1

::r-2

-4

-3

-2

-1

Среднее количество комнат

о

[RM]

1

2

3

(стандартизированное)

Рис. 1О. 7. График зависимости цены на дом от количества ко.wнат
в модели, основанной на

GD

Хотя такое наблюдение имеет смысл, данные также говорят нам о том,
что во многих случаях количество комнат не особенно хорошо объясняет
цены на дома. Позже в главе мы обсудим, как оценивать количественно эф­
фективность регрессионной модели . Интересным наблюдением может слу­
жить выстраивание в линию нескольких точек данных в у=

3,

что наводит

на мысль об отсечении цен. В определенных приложениях также может
быть важно сообщать спрогнозированные значения выходных переменных
в исходном масштабе. Для масштабирования спрогнозированных цен об­
ратно на ось "Цена в тыс. долларов" мы можем просто применить метод

inverse transform

объекта

StandardScaler:
(392)---- - - - -- -- --

Глава

10.

Прогнозирование значений непрерывных целевых переменных."

>>> num rooms std = sc_x.transform(np.array([[S.0]]))
>>> price_std = lr.predict(num_rooms_std)
>>> рr~лt("Цена в тыс. долларов: %.Зf" % \
sc_y.inverse_transform(price_std))
Цена в тыс. долларов: 10.840
В этом примере кода мы используем ранее обученную линейную регрес­
сионную модель для прогнозирования цены пятикомнатного дома. Согласно

нашей модели стоимость такого дома составляет

$1 О 840.

В качестве стороннего замечания стоит упомянуть о том, что формально мы
не обязаны обновлять веса точки пересечения, если работаем со стандартизи­
рованными переменными, поскольку в таких случаях пересечение с осью у

всегда равно О. Быстро подтвердить сказанное можно, отобразив веса:

>>>

рriпt('Наклон:

Наклон:

>>>

%.Зf'

% lr.w_[l])

0.695

рппt.('Точка пересечения:

Точка пересечения:

%.Зf'

% lr.w_[O])

-О.ООО

Оценка коэффициентов реrрессионной модеnи с помощью

scikit-learn

В предыдущем разделе мы реализовали работающую модель для регрес­
сионного анализа; однако

в реальном приложении нас могут интересовать

более рациональные реализации. Например, многие оценщики

scikit-learn,

предназначенные для регрессии, задействуют реализацию метода наимень­

ших квадратов из библиотеки

SciPy (scipy. linalg. lstsq),

которая в свою

очередь использует высоко оптимизированный код, основанный на пакете

линейной алгебры
ки

scikit-learn

LAPACK.

Реализация линейной регрессии из библиоте­

также (лучше) работает с нестандартизированными перемен­

ными, поэтому она не применяет оптимизацию на основе (стохастического)
градиентного спуска, так что шаг стандартизации мы можем пропустить:

>>> t·roш sklearn. linear model ~1.щрох:t LinearRegression
>>> slr = LinearRegression ()
>>> slr. fit (Х, у)
>>> y_pred = slr.predict (Х)
>>> ргint.('Наклон: %.Зf' % slr.coef_[OJ)
Наклон: 9.102
>>> рr·;лt('Точка пересечения: %.Зf' % slr.intercept_
Точка пересечения: -34.671

[3931 - - - - - - - - - - - - - - - - - - -

Глава

10.

Прогнозирование значений непрерывных целевых переменных."

В результате выполнения приведенного выше кода мы можем заметить, что

модель

LinearRegression

из

scikit-leam,

подогнанная к стандартизирован­

ным переменным RM и MEDV, выдала другие коэффициенты модели, т.к. призна­
ки не были стандартизированы. Но когда мы сравним их с нашей реализацией

на основе

GD,

построив график зависимости MEDV от RM, то сможем качест­

венно увидеть, что она подобным образом хорошо подгоняется к данным:

>>> lin_regplot(X, у, slr)
>>> plt.xlabel ('Среднее количество комнат [RМ] (стандартизированное)')
>>> plt.ylabel('Цeнa в тыс. долларов [MEDV] (стандартизированная)')

»> plt. show ()
Скажем, мы можем видеть, что общий результат выглядит идентичным
нашей реализации на основе

GD

50



>-ar:
С1

ш

~

~



с;
с;

о

(рис .

10.8) .



•• ••

40



i


С11

о

30

Q.

s

r:t"
·S

i! "g.
(,)

С11


:z:

20

r:t
:z:

:!!

(,)
::r- 10
ф

о

4

5

6

Среднее количество комнат

Рис.

10.8.

8

7

[RM]

9

(стандартизированное)

Графи" зависимости цены на до.w от 1'.оличества ко,~шат
в модели

LinearRegression

\\:~ Аналитические решения задач линейной регрессии

н~ В качестве альтернативы применению библиотек МО обычный ме­
заметку! тод наименьших квадратов можно решить в аналитическом виде,

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

-

-

- - (3941 -- --

- - --- - ------ -- -------

---

Глава

10.

Прогнозирование значений непрерывных целевых переменных".

Вот как реализовать решение на

#

Python:

добавление вектора -столбца единиц

»>

ХЬ =

np.hstack((np.ones((X.shape[O], 1)),

>>> w = np.zeros(X.shape[l])
>>> z = np.linalg.inv(np.dot(Xb.T,
>>> w = np.dot(z, np.dot(XЬ.T, у))
>>> print('Haклoн: %.Зf' % w[l])
Наклон:

>>>

Х))

ХЬ))

9.102

print('Toчкa пересечения:

Точка пересечения:

%.Зf'

% w[O])

-34.671

Преимущество такого метода в том, что он гарантирует нахождение

оптимального решения аналитически. Тем не менее, при работе с
очень крупными базами данных получение обратной матрицы в этой
формуле (иногда называемой нормат1ьным уравнением) может ока­
заться слишком затратным в вычислительном плане или же матрица,

содержащая обучающие образцы, может быть вырожденной (необ­
ращаемой), из-за чего в ряде случаев мы отдаем предпочтение ите­
рационным методам.

Если вас интересуют дополнительные сведения о том, как получать

нормальные уравнения, тогда ознакомьтесь с главой

Linear Regression Model"

"The Classical

(Классическая линейная регрессионная

модель) из курса лекций доктора Стивена Поллока, которые он чи­

(http://www. le. ас. uk/users/
dsgpl/COORSES/MESOMET /ЕСМЕТХТ /Oбrnesrnet. pdf).

тает в Лестерском университете

Кроме того, если вы хотите сравнить решения на основе линейной
регрессии, полученные посредством

GD, SGD,

нормальных уравне­

ний, QR-разложения и сингулярного разложения, тогда можете вос­

пользоваться классом

LinearRegression,

реализованным в библио­

MLxtend (http: //rasbt.githuЬ.io/rnlxtend/user_guide/
regressor /LinearRegression/), который позволяет переклю­

теке

чаться между указанными вариантами. Еще одной великолепной

библиотекой, рекомендуемой для работы с регрессионными моде­
лями, является

Statsmodels,

которая реализует более сложные моде­

ли на основе линейной регрессии, как демонстрируется по ссылке

https://www.statsrnodels.org/staЫe/exarnples/index.

htrnl#regression.

[395)-----------

[лава

10.

Прогнозирование значений непрерывных целевых переменных".

Подгонка надежной регрессионной
модели с использованием

RANSAC

Присутствие выбросов может сильно повлиять на линейные регрессион­

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

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

Co11sc11sus -

RANSAC

(l{ЛNrlom

соглашение на основе случайных выборок), который подгоня­

ет модель к поднабору данных, называемых не-выбросами
Итерационный алгоритм

1.

SAmple

RANSAC

(i11/ic1).

можно подытожить следующим образом.

Выбрать случайное количество образцов, которые будут служить не­
выбросами, и выполнить подгонку модели.

2.

Проверить все остальные точки данных на подогнанной модели и до­

бавить те точки, которые попадают внутрь предоставленного пользо­

вателем порога не-выбросов.

3.

Повторно подогнать модель, используя все не-выбросы.

4.

Оценить ошибку подогнанной модели относительно не-выбросов.

5.

Закончить алгоритм, если эффективность достигла определенного

пользователем порога или прошло фиксированное число итераций; в
противном случае вернуться к шагу

1.

Давайте применим линейную модель в сочетании с алгоритмом

как реализовано в классе RANSACRegressor библиотеки

RANSAC,
scikit-\earn:

>>> from sklearn.linear_model import RANSACRegressor
>>> ransac = RANSACRegressor(LinearRegression(),
max_trials=lOO,
min_samples=SO,
loss='absolute_loss',
residual_threshold=S.O,
random_state=O)
>>> ransac.fit(X, у)

------------(396]

Глава

Мы указываем

Regressor

100

10.

Прогнозирование значений непрерывных целевых переменных".

для максимального количества итераций RANSAC

и с использованием

min_ samples=50

устанавливаем мини­

мальное число случайно выбранных обучающих образцов в
указанию аргумента

'absolute_loss'

для параметра

50. Благодаря
residual_metric

алгоритм вычисляет абсолютное расстояние по вертикали между подогнан­
ной прямой и обучающими образцами.
Устанавливая параметр

residual _ threshold

в

5. О,

мы разрешаем

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

5

единиц расстоя­

ния, что хорошо работает на этом конкретном наборе данных.

По умолчанию для выбора порога не-выбросов библиотека

scikit-leam

применяет оценку
отклонение

MAD, где MAD обозначает медианное абсолютное
(metlia11 a/Jsolute tlc11iation) целевых значений у. Тем не менее,

выбор подходящего значения для порога не-выбросов специфичен для за­
дачи, что является одним из недостатков алгоритма

RANSAC.

За послед­

ние годы было разработано множество разных подходов к автоматическому
выбору хорошего порога не-выбросов. Подробные обсуждения можно най­

ти в работе

"Automatic Estimation of the Inlier Threshold in Robust Multiple
Structures Fitting" (Автоматическая оценка порога не-выбросов в надежной
подгонке множественных структур), Р. Толдо, А. Фузелло, Springer (2009 г.),
представленной в книге с итогами международной конференции по анализу
и обработке изображений
с.

("lmage Analysis and Processing -

ICIAP 2009",

123-131 ).
После подгонки модели

RANSAC

давайте получим не-выбросы и выбро­

сы из подогнанной линейной регрессионной модели на основе

RANSAC

и

добавим их к графику с линейной подгонкой:

inlier- mask = ransac.inlier- maskoutlier_mask = np.logical_not(i~lier_mask)
line_X = np. arange (3, 10, 1)
line_y_ransac = ransac.predict (line_X [:, np.newaxis])
plt.scatter(X[inlier_mask], y[inlier_mask],
c='steelЫue', edgecolor='white',
marker='o', lаЬеl='Не-выбросы')
>>> plt.scatter(X[outlier_mask], y[outlier_mask],
c='limegreen', edgecolor='white',
marker='s', lаЬеl='Выбросы')
>>> plt.plot(line_X, line_y_ransac, color='Ьlack', lw=2)

>>>
>>>
>>>
>>>
>>>

------------(397)-----------

[лава

10.

Прогнозирование значений непрерывн1J1х целевых переменных...

>>> plt.xlabel('Cpeднee количество комнат [RМ ) ')
>>> plt.ylabel ('Цена в тыс. долларов [MEDV] ')
>>> plt.legend(loc='upper left')
>>> plt.show()
На результирующем графике (рис.

10.9)

видно, что линейная регрессион­

ная модель была подогнана на обнаруженном наборе не-выбросов, показанных в виде кружков.



50

Не-выбросы

>

• • • • •



Выбросы

40

а

w

==



30

ID

Q



С1.

са
с;

20

с;

Q



С(

11!

с.)

....:а








••

10

ID
са

:z:

о

ф

::r

-10
з

4

5

6

7

Среднее количество комнат

Рис.

10.9.

8

9

[RM]

Результат подгонки ли11ей11ой ре,~рессионной модели

на обнаружеююм наборе не-выбросов

Если вывести наклон и точку пересечения модели, выполнив приведен­
ный ниже код, то можно заметить, что прямая линейной регрессии будет
слегка отличаться от подгонки , которую мы получили в предыдущем разде­

ле, где алгоритм

RANSAC

не использовался :

>>> print('Slope: % .Зf' % ransac.estimator_.coef_[OJ)
Slope: 10.735
>>> print('Intercept: % .Зf ' % ransac.estimator_.intercept_
Intercept: -44.089
За счет применения

RANSAC

удалось уменьшить потенциальное влияние

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

ных. Таким образом , в следующем разделе мы рассмотрим другие подходы

(398) - - - -

Глава

10.

Прогнозирование значений непрерывных целевых переменных ...

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

Оценка эффективности линейных
реrрессионных моделей
В предыдущем разделе мы выяснили, как подгонять регрессионную мо­

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

во время обучения, чтобы получить менее смещенную оценку эффективнос­
ти модели.

Как обсуждалось в главе

6,

мы хотим разбить набор данных на отдельные

обучающий и испытательный поднаборы, где первый используется для под­
гонки модели, а второй

-

для оценки ее эффективности обобщения на не

встречавшиеся ранее данные. Вместо продолжения работы с простой рег­

рессионной моделью мы будем применять все переменные в наборе данных

и обучать множественную регрессионную модель:

>>> from sklearn.model selection import train_test_split
>>> Х = df.iloc[:, :-1] .values
>>> у= df [ 'MEDV']. values
>>> X_train, X_test, y_train, y_test = train_test_split(
>>>
>>>
>>>
>>>

Х, у, test_size=0.3, random_state=O)
slr = LinearRegression ()
slr.fit(X_train, y_train)
y_train_pred = slr.predict(X_train)
y_test_pred = slr.predict(X_test)

Учитывая, что модель использует множество объясняющих переменных,
на двумерном графике мы не можем визуализировать прямую (точнее ги­
перплоскость) линейной регрессии, но в состоянии вывести зависимость
остатков (разностей или расстояний по вертикали между действительны­
ми и спрогнозированными значениями) от спрогнозированных значений

для диагностирования нашей регрессионной модели. Графики остатков

(n:si{lual plot) являются широко применяемым графическим инструментом
для диагностирования регрессионных моделей. Они могут помочь выявить

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

---------·----[399)----------

Глава

10.

Прогнозирование значений непрерь1вньtх целевых переменньtх ...

Используя следующий код, мы строим график остатков, где просто вычи­
таем настоящие целевые переменные из спрогнозированных ответов:

>>> plt.scatter(y_train_pred, y_train_pred - y_train,
c='steelЫue',

rnarker='o', edgecolor='white',

lаЬеl='Обучающие данные')

>>> plt.scatter(y_test_pred, y_test_pred - y_test,
с='

limegreen', rnarker=' s', edgecolor=' whi te',

lаЬеl='Испытательные данные')

>>> plt. xlabel ( 'Спрогноэированные значения')
>>> рlt.уlаЬеl('Остатки')
>>> plt.legend(loc='upper left')
>>> plt.hlines(y=O, xrnin=-10, xrnax=SO, color='Ьlack', lw=2)

»> plt.xlim( [-10, 50))
>>> plt. show ()
В результате выполнения кода мы должны получить график остатков с

10.1 О) .

линией, проходящей вдоль оси х через начало отсчета (рис.



10



Обучающие данные
Испытательные данные

••



о
:s;

...""

"...
t)

-10

о



••





11

• ••

• •



••

-20


-10

10

о

;-



20

...• .

.

••
•"

~

"'. •




30

40

50

Сnроrноэированные значения

Рис.

10.10.

График остатков

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

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

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

----- [400) ----- - - --

-------- - - -·--·-

10.

Глава

Прогнозирование значений непрерывных целевых переменных ...

мы видим на графике остатков повторяющиеся шаблоны, то это значит, что

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

10.1 О.

Кроме того, графики остатков можно также применять для выявления вы­

бросов, которые представляются точками с большим отклонением от сред­
ней линии.

Еще одной полезной количественной мерой эффективности модели явля­
ется так называемая среднеквадратическая ошибка (теап щиш-еtl еп-т·

-

1VISГ), которая представляет собой просто усредненную величину издержек

SSE,

сведенную нами к минимуму с целью подгонки линейной регрессион­

ной модели. Мера

MSE

удобна для сравнения разных регрессионных моде­

лей или для настройки их параметров посредством решетчатого поиска и

перекрестной проверки, т.к. она нормализует

MSE = ~

SSE

по размеру выборки:

I(Y(i) - .Y(i) )

2

1=\

Давайте вычислим меру

MSE

при обучении и испытании:

>>> from sklearn.metrics import mean_squared_error
>>> рп.11 t ( 'MSE при обучении: %• Зf, при испытании: %• Зf' % (
mean_squared_error(y_train, y_train_pred),
mean_squared_error(y_test, y_test_pred)))
MSE при обучении: 19.958, при испытании: 27.196
Как видите, мера

MSE

на обучающем наборе составляет

испытательном наборе гораздо больше

-

27.20;

19.96,

а

MSE

на

это сигнализирует о том,

что в рассматриваемом случае модель переобучается обучающими данными.
Однако имейте в виду, что мера

MSE

является неограниченной в отличие

от правильности классификации, например. Другими словами, интерпрета­
ция меры

MSE

зависит от масштабирования набора данных и признаков.

Скажем, если бы цены на дома были представлены как числа, кратные

1000

(с суффиксом К), тогда та же самая модель выдавала бы более низ­

кую меру

MSE

в сравнении с моделью, которая работает с немасштабиро­

ванными признаками. В качестве дальнейшей иллюстрации этого момента:

($10К- $15К)2 < ($10000- $15000)2.
Таким образом, чтобы лучше интерпретировать эффективность моде­

ли, временами более полезно сообщать коэффициент детерлтнации R2
(coejJicient о/ 1lctem1i11atiot1), который можно понимать как стандартизиро[401)-·

Глава

10.

Прогнозирование значений непрерывных целевых переменных".

ванную версию MSE. Говоря иначе, R2 представляет собой долю дисперсии
ответа, которая захватывается моделью. Вот как определяется значение R2 :

R 2 = l- SSE
SST
Здесь

SSE -

сумма квадратичных ошибок, а

SST -

полная сумма квад­

ратов:

SST представляет собой просто дисперсию ответа.
Давайте быстро покажем, что на самом деле R2 - всего лишь масштаби­
рованная версия MSE:
Другими словами,

} (
1 "п (

(i)
"п
n ·"-li~I у

fiL..;~1 у

(i)

_

л(i) ) 2
у

-µу

=

)2

= l- MSE

Var(y)
Для обучающего набора данных значение
О до

1,

J

R-

ограничивается диапазоном от

но может стать отрицательным для испытательного набора. Если

?

R-= 1,

=

О.
тогда модель идеально подогнана к данным и соответственно MSE
При оценке на обучающих данных значение R2 модели равно 0.765,

не выглядит слишком плохо. Однако
составляет лишь

0.673.

R2

что
на испытательном наборе данных

Мы можем вычислить значения

?

R-

с помощью тако-

го кода:

>>> from sklearn .metrics import r2_score
>>> print('R"2 при обучении: %.Зf, при испытании:

R"2

при

%.Зf'

%

(r2_score(y_train, y_train_pred),
r2_score(y_test, y_test_pred)))
обучении: О. 765, при испытании: О. 673

[ 4021 -------------------------

fлава

10.

Прогнозирование значений непрерЬ1вных целевых переменных...

Испоnьзование реrуnяризированных
методов дnя реrрессии
В главе

3

мы обсуждали регуляризацию

-

подход к решению пробле­

мы переобучения, который предусматривает добавление дополнительной

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

(1·i(lgc

regressioн), регрессию .~етодом наименьшего абсолютного сокращения и
выбора

(least

aЬsolutc

ную сеть (elasfi(

ашf

sl11·i11.k11ge

sclection

O{Jt'гatм -··-

l,ASSO)

и эластич­

11et).

Гребневая регрессия представляет собой штрафуемую с помощью

L2

мо­

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

J(w) Гребневая = ~(y(i)
- 5И )1 +1t 11w11 22
~
i=I

Здесь:

L2:
Повышая значение гиперпараметра А., мы увеличиваем силу регуляриза­
ции и посредством этого сокращаем веса модели. Обратите внимание, что
мы не регуляризируем член для точки пересечения

w0 •

Альтернативным подходом, который может привести к разреженным
моделям, является

LASSO.

В зависимости от силы регуляризации опреде­

ленные веса могут стать нулевыми, что также делает

LASSO

полезным в

качестве приема выбора признаков с учителем:

- ~( у (i) -у
"(i)) +А 11 W 11 1
J ( W ) LASSo-L..J
2

i=I

Здесь штраф

L1

для

LASSO

определяется как сумма абсолютных вели­

чин весов модели:

Ll:lt llw l 1

=1tflw I
1

}=1

[403)--

Глава

10.

Прагназиравание значений непрерывных целевых переменных ...

LASSO заключается в том, что он вы­
если т > п, где п - количество обучаю­

Тем не менее, ограничение метода
бирает самое большее п признаков,

щих образцов. В определенных случаях применения выбора признаков это
может оказаться нежелательным. Однако на практике такая характеристика

LASSO

часто будет преимуществом, потому что она избегает насыщенных

моделей. Насыщение модели происходит, если количество обучающих эк­

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

женности и штраф
признаков, когда

L2,
т > п:

L 1 для

LASSO

считается

порождения разре­

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

Все упомянутые регрессионные модели с регуляризацией доступны в
библиотеке

scikit-learn.

Они применяются подобно обычной регрессионной

модели, но с указанием в параметре Л силы регуляризации, оптимизирован­
ной с помощью перекрестной проверки по

k блокам,

например.

Вот как можно инициализировать модель на основе гребневой регрессии:

>>> fr·orп sklearn. linear model
>>> ridge = Ridge(alpha=l.O)

всрогt.

Ridge

Обратите внимание, что сила регуляризации управляется параметром

alpha, который похож на параметр Л. Аналогично мы можем инициализи­
ровать регрессор LASSO посредством подмодуля linear _ model:

>>> from sklearn. linear model
>>> lasso = Lasso(alpha=l.0)
Наконец, реализация

LI

к

;шро:::·>:

ElasticNet

Lasso

позволяет варьировать соотношение

L2:

>>> rc:r-om sklearn. linear_model lmpor·t ElasticNet
>>> elanet = ElasticNet(alpha=l.O, ll_ratio=0.5)

---------- [404] .

fлава

10.

Прогнозирование значений непрерывных целевых переменных".

Скажем, если мы установим

будет эквивалентен регрессору

ll_ratio
LASSO.

в

1. О,

то регрессор

ElasticNet

Более детальные сведения о раз­

личных реализациях линейной регрессии ищите в документации по ссылке

https://scikit-learn.org/staЫe/modules/linear_model.html.

Превращение линейной реrрессионной модели
в криволинейную - полиномиальная реrрессия
В предшествующих разделах мы предполагали наличие линейной связи меж­

ду объясняющими переменными и переменными ответов. При нарушении допу­
щения о линейности один из подходов предусматривает использование полино­

миальной регрессионной модели путем добавления полиномиальных членов:
у= Wo
Здесь

d

+ W1X + W2X 2 + ". + wdxd

обозначает степень полинома. Хотя мы можем применять поли­

номиальную регрессию для моделирования нелинейной связи, она по-пре­
жнему считается множественной линейной регрессионной моделью из-за

коэффициентов линейной регрессии

w.

В последующих подразделах мы по­

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

Добавnение поnиномиаnьных чnенов с испоnьзованием

scikit-learn

Далее мы выясним, каким образом применять класс преобразователя

PolynomialFea tures

из scikit-leam для добавления квадратичного члена (d

= 2)

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

1.

Добавить полиномиальный член второй степени:

:t:r:om sklearn. preprocessinq .iropo:ct. PolynomialFeatures
»> Х = np.array([ 258.О, 270.0, 294.0, 320.О, 342.О,
368.О, 396.0, 446.0, 480.0, 586.0))\
[:, np.newaxis]
>>>у= np.array([ 236.4, 234.4, 252.8, 298.6, 314.2,
342.2, 360.8, 368.О, 391.2, 390.8))
>>> lr = LinearRegression ()
>>> pr = LinearRegression ()
>>> quadratic = Polynomia1Features(degree=2)
>>> X_quad = quadratic.fit_transform(X)

- -- [405] -------------

Глава

2.

10.

Прогнозирование значений непрерывн1>1х целевых переменн1>1х."

Выполнить подгонку простой линейной регрессионной модели для
сравнения:

>>> lr. fit (Х, у)
>>> Х fit = np.arange(250, 600, 10) [:, np.newaxis)
>>> y_lin_fit = lr.predict(X_fit)

3.

Выполнить подгонку множественной регрессионной модели на транс­

формированных признаках для полиномиальной регрессии:

>>> pr.fit(X_quad, у)
>>> y_quad_fit = pr.predict(quadratic.fit_transform(X_fit))

4.

Построить график с результатами:

>>> plt.scatter(X, у, lаЬеl='обучающие
>>> plt.plot(X_fit, y_lin_fit,
lаЬеl='линейная подгонка',

точки')

linestyle='--')

>>> plt.plot(X_fit, y_quad_fit,
lаЬеl='квадратичная подгонка')

>>> plt. xlabel ('Объясняющая переменная')
>>> рlt.уlаЬеl('Спрогнозированные или известные
>>> plt.legend(loc='upper left')

целевые значения')

»> plt. tight_layout ()
>>> plt.show()
На результирующем графике (рис.

10.11)

видно, что полиномиальная

подгонка улавливает связь между переменной ответа и объясняющей пере­
менной гораздо лучше, чем линейная подгонка.

":z:s

- - -

""':z:
':1'

"'::11
"'
""'

400

линейная подгонка
квадратичная подгонка

обучающие точки



с:

"

:j'

"'

:;;

...:z:

350



"'"'"'s
s
с:
s

300

ф

::11

:z:
:z:

""'CL
о

s

"'
о

i:

250



о

CL

r::

(.)

250

300

350

400

450

500

550

600

Объясняющая переменная

Рис. 1О.11. Срав11е11ие линейной и полино.ииапьной pe,~peccuu

---- - - -- - --- [406) -- ·-- - - ---

- - -- ---

fлава

10.

Прогнозирование значений непрерывных целевых переменных."

Далее мы рассчитаем метрики оценки MSE и R2 :

>>> y_lin_pred = lr.predict(X)
>>> y_quad_pred = pr.predict(X_quad)
>>> print('MSE при обучении линейной
модели:

%.Зf'

модели:

%.Зf,

квадратичной

% (

rnean squared_error(y, y_lin_pred),
rnean squared error(y, y_quad_pred)))
обучении линейной модели: 569.780, квадратичной

MSE при
61.330
>>> print('Rл2
модели:

Rл2 при

при обучении линейной модели:

%(
r2_score(y, y_lin_pred),
r2_score(y, y_quad_pred)))
обучении линейной модели: 0.832,

%.3f,

модели:

квадратичной

%.Зf'

квадратичной модели:

0.982
После выполнения кода можно заметить, что
зительно

570

(линейная подгонка) до

MSE уменьшается
примерно 61 (квадратичная

от прибли­
подгонка).

К тому же коэффициент детерминации отражает более тесную подгонку квад­

ратичной модели (R2 = 0.982) по сравнению с линейной моделью (R2 = 0.832)
в этой конкретной игрушечной задаче.

Моделирование непинейных связей в наборе данных

Housing

В предыдущем подразделе вы узнали, как строить полиномиальные при­
знаки для подгонки к нелинейным связям в игрушечной задаче; давайте те­
перь рассмотрим более конкретный пример и применим такие концепции к

набору данных

Housing.

Выполнив следующий код, мы смоделируем связь

между ценами на дома и LSTAT (процентная доля населения с более низким
социальным статусом) с использованием полиномов второго (квадратично­
го) и третьего (кубического) порядков и сравним ее с линейной подгонкой:

>>>

Х

= df[ [ 'LSTAT']] .values
df['MEDV'] .values

>>>у=

>>> regr = LinearRegression()
>>>
>>>
>>>
>>>
>>>

#

создать квадратичные признаки

quadratic = Polynornia1Features(degree=2)
cubic = Polynornia1Features(degree=3)
X_quad = quadratic.fit_transforrn(X)
X_cubic = cubic.fit_transforrn(X)

[407]----·

Глава

10.

Прогнозирование значений непрерывных целевых переменных".

>>> # выполнить подгонку к признакам
>>> X_fit = np.arange(X.min(), X.max(), 1) [:, np.newaxis]
>>> regr = regr.fit(X, у)
>>> y_lin_fit = regr.predict(X_fit)
>>> linear_r2 = r2_score(y, regr.predict(X))
>>> regr = regr.fit(X_quad, у)
>>> y_quad_fit = regr.predict(quadratic.fit_transform(X_fit))
>>> quadratic_r2 = r2_score(y, regr.predict(X_quad))
>>> regr = regr.fit(X_cubic, у)
>>> y_cubic_fit = regr.predict(cubic.fit_transform(X_fit))
>>> cubic_r2 = r2_score(y, regr.predict(X_cuЬic))
>>> # вывести результаты
>>> plt.scatter(X, у, lаЬеl='обучающие
>>> plt.plot(X_fit, y_lin_fit,
lаЬеl='линейная (d=l),

точки',

$Rл2=%.2f$'

color='lightgray')
% linear_r2,

color='Ьlue',

lw=2,
linestyle=' : ')
>>> plt.plot(X_fit, y_quad_fit,
lаЬеl='квадратичная

(d=2),

$Rл2=%.2f$'

% quadratic_r2,

color=' red' ,
lw=2,
linestyle=' - ')
>>> plt.plot(X_fit, y_cubic_fit,
lаЬеl='кубическая

(d=З),

$Rл2=%.2f$'

% cubic_r2,

color=' green' ,
lw=2,
linestyle=' -- ')
>>> plt.xlaЬel ('%населения с более низким социальным
>>> pl t. ylabel ('Цена в тыс. долларов [MEDV] ' )
>>> pl t. legend (loc=' upper right' )
>>> plt. show ()
На рис.

10.12

статусом

[LSTAT] ')

показан результирующий график.

Как можно заметить, кубическая подгонка улавливает связь между цена­
ми на дома и

LSTAT лучше, чем линейная и квадратичная. Однако важно

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

реобучения.

[408)

Глава

10.

Прогнозирование значений непрерывных целевых переменных."

50

линейная (d=1), R2

=0.54

квадратичная (d=2), R2 = 0.64
кубическая (d=З) , R' = 0.66

40

обучающие точки

>
о

w

~

30

.:

с:
о

ct

u
:il

20

"'

10

...
"":z:

::1

...

"· ...

" . ..

о

о

5

10

20

15

25

30

% населения с более низким социальным статусом
Рис.

10.12.

··. ··. ..
35

[LSTAТ]

График зависимости цены на дом от процеюп11ой

доли 11аселения с более низким социш1ы1ым статусом

Таким образом, на практике рекомендуется оценивать эффективность мо­
дели на отдельном испытательном наборе данных, чтобы получить оценку

эффективности обобщения.

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

MEDV-LSTAT, можно выдвинуть гипотезу о том, что логарифмическое пре­
образование переменной признака LSTAT и квадратный корень MEDV могут
спроецировать данные на линейное пространство признаков, подходящее

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

жей на экспоненциальную функцию:

f(x)

= е-х

Поскольку натуральный логарифм экспоненциальной функции представ­
ляет собой прямую линию, мы предполагаем, что такое логарифмическое
преобразование может здесь пригодиться:

log(f (х))

= -х

Давайте проверим эту гипотезу, выполнив следующий код:

-

-

- - · - - - - - - - - (409) - · - - -- -- - - - - - -

Глава

10.

Прогнозирование значений непрерывных целевых переменных."

>>> #

трансформировать признаки

>» X_log = np.log(X}
>>> y_sqrt = np.sqrt (у}
>>>
>>> # выполнить подгонку к признакам
>>> X_fit = np.arange(X_log.min()-1,
X_log.max(}+l, 1) [:, np.newaxis]
>>> regr = regr.fit(X_log, y_sqrt}
>>> y_lin_fit = regr.predict(X_fit}
>>> linear_r2 = r2_score(y_sqrt, regr.predict(X_log)}
>>> # вывести результаты
>>> plt.scatter(X_log, y_sqrt,
lаЬеl='обучающие точки',

color='lightgray'}
>>> plt.plot(X_fit, y_lin_fit,
lаЬеl='линейная (d=l), $Rл2=%.2f$' % linear_r2,
color=' Ыuе',
lw=2}
>>> pl t. xlabel ( 'log (%населения с более низкимсоциальным статусом
[LSTAT] ) '}

>>> plt.ylabel('$\sqrt{Цeнa \;в\;
>>> pl t. legend ( loc=' lower left' )
>>> plt.tight_layout(}
>>> plt. show (}

\тыс.

долларов\;

[MEDV]

}$')

После преобразования объясняющей переменной в логарифмическое про­
странство и взятия квадратного корня из целевой переменной нам удалось уло­
вить связь между двумя переменными с помощью прямой линейной регрессии,

которая, кажется, лучше подгоняется к данным

1

(R-

= 0.69),

чем все рассмот-

ренные ранее полиномиальные трансформации признаков (рис.

10.13).

Обработка нелинейных связей
~

с использованием случаиных лесов
В настоящем разделе мы ознакомимся с регрессией на основе случай11ых
лесов, которая концептуально отличается от регрессионных моделей, опи­

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

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

- - - - - - - - - - - - ( 4 1 0 ] ---------

Глава

10.

Прогнозирование значений н е прерывных целевых переменных ".

словами, посредством алгоритма дерева принятия . решений мы подразделя­

ем входное пространство на меньшие области, которые становятся более уп­
равляемыми.

8

>
а

7

w

~
с:

б

с;

о

с:(

u
....:21

111

5

4

(\\

ф

::r

з

2

линейная(d=1) , R 2 =0.69
обучающие точки

о

1

2

з

4

log( % населения с более низким социальным статусом [LSTAT])
Рис.

10.13.

График завис имости цены 11а дом от процентной доли населения

с более низким социальным статусом после прим енения преобразования

Реrрессия на основе дерева принятия решений
Преимущество алгоритма дерева принятия решений заключается в том ,
что при работе с нелинейными данными он не требует каких-либо транс­

формаций признаков, т. к. деревья принятия решений анализируют по одно­
му признаку за раз, а не принимают во внимание взвешенные комбинации.

(Аналогичным образом для деревьев принятия решений не требуется нор­
мализация или стандартизация признаков.) Благодаря главе

3

вы знаете, что

дерево принятия решений выращивается за счет последовательного разде­
ления его узлов до тех пор, пока листовые узлы не станут чистыми или не

будет удовлетворен критерий остановки. Когда мы применяем деревья при­
нятия решений для классификации, то определяем энтропию как меру за­

грязненности, чтобы выяснить, какое разделение признака доводит до мак­
симума прирост информации

(1(;'), который в случае двоичного разделения

может быть записан так:

------ ·~-

[ 411) ~------------------

Глава

10.

Прогнозирование значений непрерывных целевых переменных".

JG(Dp,xi)=!(Dp)- ~вый!(девый)- Nп;авый
р

Здесь Х; -

!(Dправый)

р

признак, подлежащий разделению, NP -

ющих образцов в родительском узле,

количество обуча­

функция загрязненности,

I -

DP -

поднабор обучающих образцов в родительском узле, а Dлевый и Dправый

-

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

-

отыскать разделение признака, ко­

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

3

мы обсуждали загрязненность Джинн и энтро­

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

меру загрязненности узла t как

MSE:

1 ( t) = MSE ( t) =-1
N,
Здесь

N, -

L(

y(i) -

у,) 2

ieD,

количество обучающих образцов в узле

чающих образцов в узле t, y>> from sklearn.tree import DecisionTreeRegressor
>>> Х = df [ [ 'LSTAT' ] ] . values
>>>у= df['MEDV'] .values
>>> tree = DecisionTreeRegressor(max_depth=З)

-----------[412]-------

Глава

10.

Прогнозирование значений непрерывных целевых переменных ...

>>> tree.fit(X, у)
>>> sort_idx = Х. flatten () .argsort ()
>>> lin_regplot(X[sort_idx], y[sort_idx], tree)
>>> plt.xlaЬel ( '% населения с более низким социальным
>>> pl t. ylabel ('Цена в тыс. долларов [MEDV] ')
>>> plt. show ()
На результирующем графике (рис.

10.14)

С'!'атусом

[LSTAT] ')

видно, что дерево принятия ре­

шений выявляет основную тенденцию в данных. Однако ограничение этой

модели в том , что она не улавливает непрерывность и дифференцируемость
желательного прогноза. Кроме того, нам необходимо проявлять осмотри­

тельность при выборе подходящего значения для глубины дерева, чтобы не
допустить переобучения или недообучения модели; в данном случае глуби­
на

3

представляется хорошим выбором.

50

~

~
w

40

~
с:

~

30

(,)

"••

:2i

~ 20

111

:z:

С11

:r

.. ••••
•••••


10



"• •


о

5

10

20

15

25

30

35

% населения с более низким социальным статусом [LSTAT]
Рис.

10.14.

Линия подгонки при регрессии 11а основе дерева принятия решений

В следующем разделе мы рассмотрим более надежный способ подгонки
деревьев регрессии: случайные леса.

Реrрессия на основе сnучайноrо
Как было показано в главе

3,

neca

алгоритм случайного леса представляет со­

бой ансамблевый прием , который объединяет множество деревьев принятия

(413) - --- - -- - - - -

fлава

10.

Прогнозирование значений непрерывн111х целевых переменн111х ...

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

ствительны к выбросам в наборе данных и не требуют особо объемной на­
стройки параметров. Единственный параметр в случайных лесах, с которым

обычно приходится экспериментировать

-

количество деревьев в ансамбле.

Базовый алгоритм случайного леса для регрессии почти идентичен алгоритму

случайного леса для классификации, рассмотренному в главе

3,

с тем лишь

отличием, что для выращивания индивидуальных деревьев принятия реше­

ний мы используем критерий

MSE,

и спрогнозированная целевая переменная

вычисляется как средний прогноз по всем деревьям принятия решений.

А теперь давайте задействуем все признаки в наборе данных

Housing

для

подгонки регрессионной модели на основе случайного леса с применени­

ем

60%

образцов и оценим ее эффективность на оставшихся

40%

образцов.

Вот необходимый код:

>>>

= df.iloc[:, :-1] .values
df['MEDV'] .values
>>> X_train, X_test, y_train, y_test =\
train_test_split(X, у,
test_size=0.4,
random_state=l)
>>>
>>> from sklearn.ense111Ыe import RandomForestRegressor
>>> forest = RandomForestRegressor(n_estimators=lOOO,
criterion='mse',
random_state=l,
n_jobs=-1)
>>> forest.fit(X_train, y_train)
>>> y_train_pred = forest.predict(X_train)
>>> y_test_pred = forest.predict(X_test)
>>> print('MSE при обучении: %.Зf, при испытании: %.Зf' % (
mean_squared_error(y_train, y_train_pred),
mean_squared_error(y_test, y_test_pred)))
MSE при обуче!:fИи: 1.642, при испытании: 11.052
Х

>>>у=

>>>print('Rл2 при обучении:

Rл2 при

%.Зf,

при испытании:

r2_score(y_train, y_train_pred),
r2_score(y_test, y_test_pred)))
обучении: 0.979, при испытании: 0.878

[414]

%.Зf'

% (

Глава

10.

Прогнозирование значений непрерывных целевых переменных ...

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

(R2

= 0.878

на испытательном наборе данных).

Наконец, взглянем на остатки, относящиеся к прогнозу:

>>> plt.scatter(y_train_pred,

y_train_pred - y_train,
с=' steelЫue',

edgecolor='white',
marker=' о',
s=35,
alpha=0.9,
label=' обучающие данные' )
>>> plt.scatter(y_test_pred,
y_test_pred - y_test,
с=' limegreen' ,
edgecolor='white',
marker=' s',
s=35,
alpha=0.9,
lаЬеl='испытательные данные')

pl t. xlabel ( 'Спрогнозированные значения' )
pl t. ylabel ('Остатки')
plt.legend(loc='upper left')
plt.hlines(y=O, xmin=-10, xmax=50, lw=2,
»> plt.xlim( [-10, 50])
»> plt. tight_layout ()
>>> plt. show ()
>>>
>>>
>>>
>>>

color='Ьlack')

Как уже было подытожено коэффициентом R2, по выбросам в направле­
нии оси у мы видим, что модель подгоняется к обучающим данным лучше,
чем к испытательным данным. К тому же распределение остатков вокруг
нулевой центральной точки не выглядит полностью случайным, указывая

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

10.15,

демонстрирует

крупное улучшение в сравнении с графиком остатков линейной модели, ко­
торый мы строили ранее в главе.

[ 4151

Глава

Прогнозирование значений непрерывных целевых переменных".

10.

20



обучающие данные

15

• ••

10
:s:

"......

5

t\I

-:•

r

(.)

о


~

о

"

-5

••
lli

• "

-10
-10

10

о

20

40

30

50

Спрогнозированные значения

Рис.

10.15.

График остатков модели на основе случайно,~о леса

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

Другими словами, ошибка прогнозов не должна быть связана с любой ин­
формацией, содержащейся в объясняющих переменных; наоборот, она обя­
зана отражать случайность реальных распределений или повторяющихся

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

-

просачивание объясняющей информации в остатки .

К сожалению, не существует универсального подхода к решению про­

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

н~

заметку!

Регрессия с помощью метода опорных векторов
В главе

3

также был представлен ядерный трюк, который может ис­

пользоваться в сочетании с методом опориых векторов

(S \ ,\/)

для

классификации и полезен, когда мы имеем дело с нелинейными зада­
чами . Хотя обсуждение выходит за рамки книги, методы

- - - --

- --

-

-

--

-

(416] - - - --

SVM

можно

-- -- - -- - -- --

Глава

10.

Прогнозирование значений непрерывных целевых переменных".

также применять в нелинейных задачах регрессии. Заинтересован­
ные читатели могут найти дополнительные сведения о методах

для регрессии в замечательном отчете

Classification and Regression"

SVM
"Support Vector Machines for

(Методы опорных векторов для клас­

сификации и регрессии), С. Ганн и другие (технический отчет

ISIS
номер 14, 1998 г.), доступном по ссылке http: / / ci teseerx. ist.
psu.edu/viewdoc/download?doi=l0.l.l.579.6867&rep=
repl&type=pdf. Регрессор SVM также реализован в библиоте­
ке scikit-leam; описание его использования предлагается по ссыл­
ке http://scikit-learn.org/staЫe/modules/generated/

sklearn.svm.SVR.html#sklearn.svm.SVR.

Резюме
В начале главы вы узнали о простом линейном регрессионном анализе

для моделирования связи между единственной объясняющей переменной
и непрерывной переменной ответа. Затем мы обсудили удобный прием ис­
следовательского анализа данных, направленный на поиск повторяющихся

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

Мы построили первую модель путем реализации линейной регрессии

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

scikit-leam,

предназначенные для регрессии, и также реализовали надежный

метод регрессии

RANSAC

как прием обработки выбросов. Для оценки про­

гнозирующей эффективности регрессионных моделей мы вычисляли сумму

квадратичных ошибок и связанную с ней метрику R2 . Вдобавок мы обсуди­
ли полезный графический подход к диагностированию проблем, касающих­
ся регрессионных моделей: график остатков.
После выяснения, как применять к регрессионным моделям регуляризацию,

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

на основе полиномиальной трансформации признаков и случайных лесов.

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

мемся еще одной интересной подобластью МО

-

обучением без учителя

-

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

[417)

11
РАБОТА С НЕПОМЕЧЕННЫМИ
ДАННЫМИ

-

КЛАСТЕРНЫЙ АНАЛИЗ

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

-

категории

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

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

Учитывая исследовательскую природу, кластеризация является захваты­
вающей темой, и в главе вы ознакомитесь со следующими концепциями,
которые могут помочь организовать данные в содержательные структуры:



нахождение центров подобия с применением популярного алгоритма

K-Means


(метод К-средних);

принятие восходящего подхода к построению иерархических деревьев
кластеризации;



идентификация произвольных форм объектов с использованием подхо­
да с кластеризацией на основе плотности.

Глава

11.

Работа с непомеченными данными

кластерный анализ

-

Группирование объектов по подобию
с применением аnrоритма

K-Means

В текущем разделе мы обсудим один из самых популярных алгоритмов
кластеризации

-

K-Means,

который широко используется в научном сооб­

ществе и индустрии. Кластеризация (или кластерный анализ) представляет
собой прием, позволяющий находить группы подобных объектов, т.е. объ­
ектов, которые больше связаны друг с другом, чем с объектами из других
групп. Примеры бизнес-ориентированных приложений кластеризации вклю­
чают группирование документов, музыкальных произведений и фильмов по
разным темам или

поиск заказчиков, разделяющих похожие интересы, на

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

Кnастеризация

K-Means

с испоnьзованием

Как вскоре будет показано, алгоритм

scikit-learn

K-Means

не только чрезвычайно

легко реализовать, но он еще и очень эффективен с вычислительной точ­
ки зрения

в сравнении с другими алгоритмами

объяснять его популярность. Алгоритм

кластеризации, что может

K-Means

относится к категории

кластеризации на осиове прототипов. Позже в главе мы обсудим осталь­
ные две категории кластеризации

-

иерархическую кластеризацию и клас­

теризацию иа основе плотности.

Кластеризация на основе прототипов означает, что каждый кластер пред­

ставляется прототипом, который обычно будет либо цеитроидом (средним)
подобных точек с непрерывными признаками, либо медоидом (наиболее
репрезентативной точкой или точкой, сводящей к минимуму расстояние
до всех остальных точек, которые принадлежат индивидуальному кластеру)

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

K-Means

очень хорош в идентификации кластеров со сферической формой, одним

из его недостатков является необходимость заранее указывать количество
кластеров

k.

Выбор неподходящего значения для

k может

привести к плохой

эффективности кластеризации. Далее в главе мы рассмотрим метод локтя
(еllюн• met/ю(f) и графики силуэтов

(sil110111:ttc plot) -

удобные приемы для

оценки качества кластеризации, которые помогают определить оптимальное

количество кластеров

k.

--------------------------- [420] - - ---

Глава

Хотя кластеризацию

11.

Работа с непомеченными данными

K-Means

кластерный анализ

-

можно применять к данным высокой раз­

мерности, ради удобства визуализации мы проработаем примеры , используя
простой двумерный набор данных:

>>> f:r·o.m sklearn .datasets iшpo:r:·t make_Ыobs
>>> Х, у = make_Ыobs (n_samples=lSO,
n_features=2,
centers=З,

cluster_std=0.5,
shuffle='l'r ue ,
random_state=O)

>>> import matplotlib.pyplot a s plt
>>> plt.scatter(X[:,

О],

Х[:,

1],

c='white',
marker=' о',
edgecolor='Ьlack',

s=SO)

»> plt. grid ()
»> plt. tight_layout ()
>>> plt. show ()

150

Только что созданный набор данных состоит из

случайно сгенерирован­

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

о о

5

4

о о о
о

3

е

(Q о

2

о-

о ~с0 ~о


О О

о о

О

е

о~В6~

9.

С).,

0 {9' u~

о

11.1.

О

Оо

00
о
о

СЬQо

о

о

о

~Оо~

1

бС

~

00

о!::?

CDIQOO О
0 00 О
о

о

-2
Рис.

о

о

11.1.

-1

о

1

2

3

График рассеяния для просто,~о дву.нерного набора дшшых

. . [ 421 1····-·------·-···-----·-

Глава

11.

Работа с непомеченнь1ми данными

кластерный анализ

-

В реальных приложениях кластеризации мы не располагаем какой-либо

изначально точной информацией о категориях образцов (информация пре­
доставляется как эмпирические данные в противоположность выведению);

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

-

сгруппировать образцы на ос­

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

K-Means,
1.

который сводится к следующим четырем шагам.

Случайным образом выбрать

k

центроидов из образцов в качестве на­

чальных центров кластеров.

2. Назначить каждый образец ближайшему центроиду µ>> plt.scatter(X[y_k.m ==О, 0],
х [ у- k.m == о ' 1 ] '
s=50, c='lightgreen',
marker='s', edgecolor='Ьlack',
lаЬеl='кластер 1')
>>> plt. scatter (Х [y_k.m == 1, О],
X[y_k.m == 1, 1],
s=SO, c='orange',
marker='o', edgecolor='Ыack',
lаЬеl='кластер

2')

>>> plt.scatter(X[y_k.m == 2, О],
X[y_km == 2, 1],
s=50, c='lightЫue',
marker='v', edgecolor='Ыack',
lаЬеl='кластер 3')
>>> plt.scatter(k.m.cluster_centers [:,О],
k.m.cluster_centers [:, 1],
s=250, marker='*',
c='red', edgecolor='Ьlack',
label=' центроиды' )
>>> plt.legend(scatterpoints=l)
»> plt. grid ( )»> plt. tight _ layout ()
»> plt. show ()

------·-------·-----·-- ( 4 2 4 ) - - - - - - - - - · - - -

Глава

11.

Работа с непомеченными данными

кластерный анализ

-

Как видно на графике рассеяния, представленном на рис.
ция

K-Means

11.2,

реализа­

разместила три центроида в центрах сфер, что выглядит под­

ходящим группированием для этого набора данных.

о о

5



у

~

v~~

з

;

кластер
кластер

\1

кластер 3

1
2

о о Do

\1
\1

~ 'V

2

\1

о
о

!У о



\1 \1

vJ~v

lj:j.

о~

g~~o * центроиды

о

4

~

'tPo о

\1

о

\1

~о~~
f12> о~-

1

i?o

CD и присвоить его М.

3. Для каждого образца xU), отсутствующего в М, найти минимальное
квадратичное расстояние d(x>>

рrint('Искажение:

Искажение:

scikit-leam

явно вычис­

не придется, т.к. после подгонки моде­

inertia_:

%.2f' % km.inertia_)

72.48

На основе внутрикластерного значения
ся графическим инструментом

мы можем воспользовать­

так называемым методом локтя

-

оценки оптимального количества кластеров
жем сказать, что с увеличением

SSE

k

k

-

для

в имеющейся задаче. Мы мо­

искажение будет понижаться. Причина в

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

значение

k,

при котором искажение начинает увеличиваться быстрее всего,

что прояснится, если построить график искажения для разных значений

>>> distortions = []
>>> for i in range(l, 11):

km =

КМeans(n_clusters=i,

init='k-means++',
n_init=lO,
max_iter=ЗOO,

random_state=O)
km.fit(X)
distortions.append(km.inertia_
>>> plt.plotirange(l,11), distortions, marker='o')
>>> рlt.хlаЬеl('Количество кластеров')
>>> pl t. ylabel ( 'Искажение' )
>>> plt.tight_layout()

»> plt. show ()
-----------(430]----

k:

[лава

11.

Работа с непомеч е нными данными

Как видно на результирующем графике (рис.

точке

k

= 3,

свидетельствуя о том, что

k

=3

-

11.3),

-

кластерный анализ

локоть находится в

хороший выбор для этого

набора данных.

Количественная оценка качества кластеризации

через графики силуэтов
Еще одной внутренней метрикой для оценки качества кластеризации яв­
ляется анализ силуэтов, который может применяться также к рассматрива­

емым далее в главе алгоритмам кластеризации , отличающимся от

K-Means.

Анализ силуэтов может использоваться как графический инструмент для

построения графика, который отображает меру плотности группирования
образцов в кластерах .

700
600
С11

s

500

ж

С11

ЭЕ

са

400

:.:

о

:s;

300
200
100
о

2

6

4

8

10

Количество кластеров

Рис.

11.3.

График иска:же11ия для разного числа кластеров

Чтобы вычислить коэффициент сШ1уэта

(sillmucUe coej(icieнl) одиночного

образца в наборе данных, мы можем выполнить описанные ниже три шага.

1. Вычислить связность кластера

aUJ как среднее расстояние между об­
разцом x,
т.к. b(i) количественно определяет, насколько образец не похож на образцы
из других кластеров, и aU> сообщает о том, в какой степени он похож на ос­
тальные образцы в своем кластере.
Коэффициент силуэта доступен в виде функции

из модуля

metrics

silhouette_samples

библиотеки

тельно импортировать

scikit-leam, а для удобства можно дополни­
функцию silhouette _ scores, которая вычисляет

средний коэффициент силуэта по всем образцам, что эквивалентно вызо­
ву

numpy .mean (silhouette_samples ( ...

)).С помощью приведенного

ниже кода мы строим график коэффициентов силуэта для кластеризации

К-Меапs при

>>> km =

k = 3:

КМeans(n_clusters=З,

init='k-means++',
n_init=lO,
max_iter=ЗOO,

tol=le-04,
random_state=O)
>>> y_km = km.fit_predict(X)
>>>
>>>
>>>
>>>
>>>
>>>

i:mport nwnpy as np
f':r.0111 matplotlib iroport ст
fron1 sklearn .metrics .import silhouette_ samples

cluster_labels = np.unique(y_km)
n_clusters = cluster_labels.shape[O]
silhouette_ vals = silhouette_ samples (Х,
y_km,
metric='euclidean')
>>> y_ax_lower, y_ax_upper = О, О
»> yticks = []

---[432]

Глава

11.

Работа с непомеченными данными

-

кластерн~1й анализ

>>> for i, с in enшnerate(cluster_labels):
c_silhouette_vals = silhouette_vals [y_km == с]
c_silhouette_vals.sort()
y_ax_upper += len(c_silhouette_vals)
color = cm.jet(float(i) / n_clusters)
plt.barh(range(y_ax_lower, y_ax_upper),
c_silhouette_vals,
height=l.O,
edgecolor='none',
color=color)
yticks.append( (y_ax_lower + y_ax_upper) / 2.)
y_ax_lower += len(c_silhouette_vals)
>>> silhouette_avg = np.mean(silhouette_vals)
>>> plt.axvline(silhouette_avg,
color="red",
linestyle="--")
>>> plt.yticks(yticks, cluster_labels + 1)
>>> pl t. ylabel ('Кластер')
>>> рlt.хlаЬеl('Коэффициент силуэта')
>>> plt.tight_layout()
»> plt. show ()
Визуальное обследование результирующего графика коэффициентов си­
луэта (рис.

11.4)

позволяет быстро разглядеть размеры разных кластеров и

идентифицировать кластеры, содержащие выбросы.

о.о

0.1

0.2

0.3

0.4

0.5

О. б

0,7

Коэффициент силуэта

Рис.

11.4.

График коэффициентов сш1уэта

для кластеризации

K-Means

[433)-··

при

k=3

0.8

Глава

11.

Работа с непомеченными данными

Однако на рис.

11.4

кластерный анализ

-

можно заметить, что коэффициенты силуэта далеки

от О, что в данном случае является индикатором хорошей кластеризации.

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

K-Means

только двумя центроидами:

>>> km =

КМeans(n_clusters=2,

init='k-means++',
n_init=lO,
max_iter=ЗOO,

tol=le-04,
random_state=O)
>>> y_km = km.fit_predict(X)

>>> plt. scatter (Х [y_km ==

О, 0),
X[y_km == О, 1),
s=50, c='lightgreen',
edgecolor='Ьlack',

marker=' s',
lаЬеl='кластер

>>> plt. scatter (Х [y_km == 1,

1')

О],

[y_km == 1, 1),
s=50,
c='orange',

х

edgecolor='Ыack',

marker=' о',
lаЬеl='кластер 2')
>>> plt.scatter(km.cluster_centers_[:, 0),

km.cluster_centers_[:, 1),
s=250,
marker=' * ',
с=' red',
lаЬеl='центроиды')

»>
»>

»>

plt. legend ()
plt.grid()
plt.tight_layout()
plt. show t)

На результирующем графике (рис.

11.5)

видно, что один из центроидов

попал между двумя из трех сферических группирований точек образцов.

-----------(434)-----------·

[лава

11. Работа с непомеченными данными - кластерный анализ

Хотя такая кластеризация не считается абсолютно плохой, она субопти­
мальна.

Важно отметить, что при решении реальных задач обычно мы не распо­

лагаем такой роскошью, как визуализация наборов данных в виде двумер­
ных графиков рассеяния, поскольку имеем дело с данными более высокой
размерности.

о о

5

з

о

о

о

оо

0

~

о

о

0~86~

о
ОО

*

о о

(9
о

'8

о

кластер

О

кластер2

*

о

о

О

1

центроиды

Оо

о

о

cQ о

2

о

~

o~~Q~CЬ::i
>> cluster_labels = np.unique(y_km)
>>> n_clusters = cluster_labels.shape[OJ
>>> silhouette_vals = silhouette_samples(X,
y_km,
metric='euclidean')
>>> y_ax_lower, y_ax_upper = О, О
»> yticks = []
>>> for i, с in enumerate(cluster_labels):
c_silhouette_vals = silhouette_vals[y_km ==с]
c_silhouette_vals.sort()
y_ax_upper += len(c_silhouette_vals)
color = cm.jet(float(i) / n_clusters)

- - (435]

Глава

11.

>>>
>>>
>>>
>>>
>>>
»>
>>>

Работа с непомеченными данными

-

кластерн1~1й анализ

plt.barh( r ange(y_ax_lower, y_ax_upper),
c_silhouette_vals,
height=l.O,
edgecolor='none',
color=color)
yticks.append((y_ax_lower + y_ax_upper) / 2.)
y_ax_lower += len(c_silhouette_vals)
silhouette_avg = np.mean(silhouette_vals)
plt.axvline(silhouette_avg, color="red", linestyle="--")
plt.yticks(yticks, cluster_labels + 1)
plt. ylabel ('Кластер')
plt. xlabel ('Коэффициент силуэта')
plt. tight_layout ()
plt.show()

На полученном графике коэффициентов силуэта (рис.

11.6)

видно , что те­

перь силуэты заметно отличаются по длине и ширине, что подтверждает от­

носительно плохую или, во всяком случае, субопти.А1альиую кластеризацию.

2
а.

,_QI
(,)

"'
~
1

0.1

о. о

0.2

0.3

0.4

0.5

0.6

0.7

0.8

Коэффициент силуэта

Рис.

11.6.

График коэффzщиентов силуэта

для кластеризации

K-Means

при

k=2

Орrанизация кnастеров в виде иерархическоrо дерева
В этом разделе мы рассмотрим альтернативный подход к кластеризации на
основе прототипов

-

иерархическую кластеризацию. Одно из преимуществ

алгоритмов иерархической кластеризации заключается в том, что они позволя-

- - - - -- - - - - - -------- [ 436) - ------ -

fлава

11.

Работа с непомеченными данными

-

кластерный анализ

ют строить де11дрогра"v1мы (древовидные диаграммы с визуализацией двоич­
ной иерархической кластеризации), которые могут помочь с интерпретацией
результатов за счет содержательной систематизации. Другим преимуществом
является то, что нам не нужно указывать количество кластеров заранее.

Существуют два основных подхода к иерархической кластеризации

агломеративиый (a~~lo111erati1•c) и дивизивиый

-

(tf i1·isi i•t:). В дивизивной ие­

рархической кластеризации мы начинаем с одного кластера, который охва­

тывает весь набор данных, и итеративно разделяем его на меньшие кластеры

до тех пор, пока каждый кластер не станет содержать один образец. В на­
стоящем разделе мы сосредоточим внимание на агломеративной иерархи­

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

Группирование кластеров в восходящей манере
Есть два стандартных алгоритма для агломеративной иерархической клас­

теризации

-

метод одииочной связи (si11,~le

(um1plcte /i11ka,1;e).

li11ka:;c)

и метод полиой связи

С применением метода одиночной связи мы вычисляем

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

ров для выполнения объединения мы сравниваем самые непохожие члены.
Сказанное иллюстрируется на рис.

11.7.

Наиболее похожие члены
(метод одиночной связи)


Самые непохожие члены
(метод полной связи)

Рис.

-

-

11. 7.

Метод одиноч11ой связи и онетод полной связи

- - - -- - - - -- --[ 437) ----------------------

fлава

11.

Работа с непомеченными данными

-

кластерный анализ

\\:~ Альтернативные типы связей

н~ Другие широко используемые алгоритмы для агломеративной ие­
заметку!

рархической кластеризации включают метод сред11ей связи (m'сп1ке

li11k11:.;e)

и метод связи Уорда

( liиl'IE li11ka:.;e).

В методе средней связи

мы объединяем пары кластеров, основываясь на минимальных сред­
них расстояниях между всеми членами групп в двух кластерах. В ме­

тоде связи Уорда объединяются два кластера, которые приводят к ми­
нимальному увеличению совокупной внутрикластерной ошибки

SSE.

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

1.

Рассчитать матрицу расстояний для всех образцов.

2.

Представить каждую точку данных как одноэлементный кластер.

3.

Объединить два ближайших кластера, основываясь на расстоянии
между самыми непохожими (дальними) членами.

4.

Обновить матрицу подобия.

5.

Повторять шаги

2-4

до тех пор, пока не останется единственный кластер.

Далее мы обсудим, как рассчитывать матрицу расстояний (шаг

1).

Но

сначала давайте сгенерируем случайные данные образцов, чтобы работать

с ними: строки представляют различные наблюдения (идентификаторы

ID_О -

>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

ID_ 4), а столбцы iшpo:rt.

разные признаки (Х, У, Z) образцов:

pandas as pd

i.mport:. numpy as np

np.random.seed(123)
variaЫes = [ 'Х', 'У', 'Z']
labels = ['ID_O', 'ID_l', 'ID 2', 'ID_З', 'ID 4']
Х = np.random. random_sample ( [5, 3]) *10
df = pd.DataFrame(X, columns=variaЫes, index=labels)
df

После выполнения показанного выше кода мы должны увидеть кадр дан­

ных, содержащий случайным образом сгенерированные образцы (рис.

11.8).

------------·-·-------------- [ 4381 -~-------------------------------

Глава

Работа с непомеченными данными

11.

------

кластерный анализ

------------

х

z

у

ID_O

6.964692 2.861393 2.268515

ID_1

5.513148 7.194690 4.231065

ID_2

9.807642 6.848297 4.809319

ID_З

3.921175 3.431780 7.290497

ID_4 4.385722

-

0.596779 3.980443

- · - - - - - - - - - - - - --- - - - - - - - - - - - - - - -

Рис.

11.8. Кадр дат1ых со с.тучаiто с,'е11ерирован11ы.ни образцами

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

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

spatial. distance

библиотеки

pdist

из

SciPy:

>>> froш scipy. spatial. distance import pdist, squareform
>>> row_dist = pd.DataFrame(squareform(

pdist(df, metric='euclidean')),
columns=labels, index=labels)
>>> row dist
В коде мы вычисляем евклидово расстояние между каждой парой вход­

ных образцов в наборе данных, основываясь на признаках Х, У и
Плотная матрица расстояний, возвращенная функцией

pdist,

Z.
предостав­

ляется в качестве входа функции squareforщ которая создаст симметрич­
ную матрицу попарных расстояний (рис.

ID_O

ID_1

11.9).
ID_2

ID_З

ID_4

ID_O

0.000000 4.973534 5.516653 5.899885 3.835396

ID_1

4.973534 0.000000 4.347073 5.104311

ID_2

5.516653 4.347073 0.000000 7.244262 8.316594

ID_З

5.899885 5.104311

ID_4

3.835396 6.698233 8.316594 4.382864 0.000000

Рис.

11.9.

6.698233

7.244262 0.000000 4.382864

Си.и.иетричная .~штрица попар11ых расстояний

- - - - - - - - - - - - - - (439) -

Глава

Работа с непомеченными данными

11.

кластерный анализ

-

Затем мы применим к кластерам метод полной связи, используя функцию

linkage
возвратит

Тем не

cluster. hierarchy библиотеки SciPy, который
так называемую матрицу связей (N11kagc malt·ix).
менее, прежде чем вызывать функцию linkage, давайте внима­
из подмодуля

тельно посмотрим на документацию по ней:

>>> fr·om scipy. cluster. hierarchy
>>> help(linkage)
[

iшpo:r:t.

linkage

... ]

Параметры:

у

: ndarray
Плотная или избыточная матрица расстояний.

Сжатая матрица

расстояний представляет собой плоский массив,

содержащий верхний

треугольник матрицы расстояний. Это та форма,

которую возвращает

pdist.

В качестве альтернативы коллекцию из т векторов

наблюдений в

method : str,

n

измерениях можно передать как массив

необязательный

Используемый метод связи.
ниже в разделе

metric : str,

m на n.

Полное описание ищите

"Методы связи".

необязательный

Используемая метрика расстояния.

Список допустимых метрик

расстояния ищите в документации по функции

distance.pdist.

Возвращает:

Z : ndarray
Иерархическая кластеризация,

[

... ]

закодированная как матрица связей.

Из описания функции мы делаем вывод, что для входного атрибута мож­
но применять сжатую матрицу расстояний (верхний треугольник), возвращен­
ную из

pdist.

В качестве альтернативы мы могли бы предоставить перво­

начальный массив данных и в аргументе функции

'euclidean'.

linkage

указать метрику

Однако мы не должны использовать определенную ранее мат­

рицу расстояний

squareform,

т.к. она приведет к выдаче не тех значений

расстояний, которые ожидались. Подводя итоги, вот три возможных сценария.



Некорректный подход

-

применение матрицы расстояний

squareform,

как в следующем фрагменте кода, приведет к получению неправиль­
ных результатов:

>>> row clusters = linkage(row_dist,
method='complete',
metric='euclidean')

---------------------------- [440]

Глава



Корректный подход

11.

Работа с непомеченными данными

-

кластерный анализ

использование сжатой матрицы расстояний, как

-

в показанном ниже фрагменте кода, в результате даст правильную мат­
рицу связей:

>>> row clusters = linkage(pdist(df, metric='euclidean'),
method='complete')



Корректный подход

аналогично предыдущему сценарию примене­

-

ние полной входной матрицы образцов, как в приведенном далее фраг­
менте кода, также обеспечит получение правильной матрицы связей:

>>> row clusters = linkage(df.values,
method='complete',
metric='euclidean')
Чтобы более тщательно рассмотреть результаты кластеризации, мы мо­
жем превратить их в объект DataFrame из
ваемый в

pandas

(лучше всего просматри­

Jupyter Notebook):

>>> pd.DataFrame(row_clusters,
columns= ['Метка строки 1',
'Метка строки 2' ,
'Расстояние' ,
'К-во элементов в кластере'],

index=[ 'Кластер %d' % (i+l) for i in
rar!ge (row_clusters. shape [0])])
На рис.

11.1 О

видно, что матрица связей состоит из нескольких строк, где

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

Метка строки

1

Метка строки

2

Расстояние К-во элементов в кластере

1

о.о

4.0

З.835396

2.0

Кпастер2

1.0

2.0 4.347073

2.0

КластерЗ

з.о

5.0 5.899885

з.о

Кпастер4

6.0

7.0 8.316594

5.0

Кластер

Рис.

11.10.

Результирующая матрица связей

[4411 - - - - - - - - - - - - - - - - - -

Глава

11.

Работа с непомеченными данными

-

кластерный анализ

Имея рассчитанную матрицу связей, мы можем визуализировать резуль­
таты в форме дендрограммы:

>>> from scipy. cluster. hierarchy нnport dendrogram
>>> # сделать дендрограмму в черном цвете (часть 1 из 2)
>>> # from scipy.cluster.hierarchy import set_link_color__palette
>>> # set_link_color__palette(['Ьlack'])
>>> row_dendr = dendrogram(row_clusters,
labels=labels,

# сделать дендрограмму в черном
# color_threshold=np. inf

цвете

(часть

2

из

2)

)

>» plt. tight_layout ()
>>> pl t. ylabel ('Евклидово
>>> plt. show ()

расстояние'

)

Выполнив предыдущий код, легко заметить, что ветви в результирующей

11.11 ). Цветовая схема про­
Matplotlib, который зациклен для по­

дендрограмме показаны разными цветами (рис.
исходит из списка цветов библиотеки

рогов расстояния в дендрограмме. Например, чтобы отобразить дендрограм­

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

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

были сформированы во время агломеративной иерархической кластериза­
ции; скажем,

мы видим,

что

на основе метрики евклидова расстояния

иболее похожими оказываются образцы
образцы

на­

ID О и ID_ 4, за которыми следуют

ID_ 1 и ID_ 2.
в

С11

~
~

б

~ 5



Q.

4

ID

о

>> axl. set_title ('Кластеризация K-Means')

- - - -- [448]

Глава

11.

Работа с непомеченными данными

-

кластерный анализ

AgglomerativeClustering(n_clusters=2,
affinity='euclidean',
linkage='complete')
>>> у_ас = ac.fit_predict(X)
>>> ax2.sc~tter(X[y_ac ==О, 0),
Х(у_ас ==О, 1],
>>>ас=

с=' lightЫue',
edgecolor 2 'Ьlack',

marker=' о',
s 40,
2

1')
>>> ax2.scatter(X[y_ac == 1, 0],
Х[у_ас == 1, 1),
с=' red',
lаЬеl='кластер

edgecolor='Ьlack',

marker=' s',
s=40,
lаЬеl='кластер 2')
>>> ах2.sеt_titlе('Агломеративная
>» plt. legend ()
»> plt. tight_layout ()
>» plt. show ()

кластеризация')

Глядя на визуализированные результаты кластеризации (рис.
видим, что метод

K-Means

11.15),

мы

оказался неспособным разделить два кластера,

и алгоритм иерархической классификации тоже не справился с этими слож­
ными формами.
Кластеризация

Аrломеративная кластеризация

K-Means

1.00

о кластер

1.00

0.75

0.75

0.50

0.50

0.25

0.25

0.00

0.00

- 0.25

- 0.25

-0.50

-0.50
-1 .0 -0.5
Рис.

о.о

11.15.

0.5

1.0

1.5

2.0

Результаты кластеризации

1

111 кластер 2

-1 .0 -0.5

K-Means

~

о.о

0.5

1.0

1.5

и агломератив11ой

иерархической кластеризации методо.11,1 полной связи

- - ( 4 4 9 ) - - - - --



2.0

Глава

11.

Работа с непомеченными данными

кластерный анализ

-

В заключение мы опробуем на имеющемся наборе данных алгоритм

DBSCAN,

чтобы посмотреть, в состоянии ли он найти два кластера в форме

полумесяца, применяя подход на основе плотности:

>>> from sklearn. cluster import DBSCAN
>>> dЬ = DBSCAN(eps=0.2,
min_ samples=S,
metric='euclidean')

>>>
>>>

у_dЬ

=

dЬ.fit_predict(X)

plt.scatter(X[y_dЬ ==О,

0],
1],

Х[у_dЬ ==О,

с=' lightЫue',
edgecolor='Ьlack',

marker=' о',
s=40,
1')
== 1, О],
Х[у_dЬ == 1, 1],
с=' red',
lаЬеl='кластер

>>>

plt.scatter(X[y_dЬ

edgecolor='Ьlack',

marker=' s' ,
s=40,
lаЬеl='кластер

2')

>» plt. legend ()
>» plt. tight_layout ()
>>> plt. show ()
Как показано на рис.

11.16,

алгоритм

DBSCAN

сумел успешно вы­

явить формы в виде полумесяца, что подчеркивает одну из сильных сторон

DBSCAN -

возможность кластеризации данных произвольной формы.

Тем не менее, мы обязаны также упомянуть о нескольких недостатках
алгоритма

DBSCAN.

С увеличением количества признаков в наборе данных

(при условии фиксированного числа обучающих образцов) растет отрица­
тельное воздействие "проклятия размерности". Особенно остро проблема
проявляется, когда используется метрика евклидова расстояния. Однако про­

блема "проклятия размерности" не уникальна для

DBSCAN;

она также ока­

зывает влияние на другие алгоритмы кластеризации, в которых применяется

метрика евклидова расстояния, например,
ческой кластеризации. Вдобавок в

метра

(MinPts

K-Means и алгоритмы иерархи­
алгоритме DBSCAN есть два гиперпара­

и е), которые нуждаются в оптимизации для выдачи хороших

результатов кластеризации.

·------------(450]--

Глава

11.

Работа с непомеченнЬ1ми данными

-

кластерный анализ

О кластер

1.00



1

кластер2

0.75
0.50
0.25
0.00
-0.25
-0.50
-1.0
Рис.

11.16.

-0.5

о.о

0.5

1.0

2.0

1.5

Резулыпаты Ю1астеризации по аr1шритму

Нахождение подходящей комбинации

MinPts

и

DBSCAN

s может стать

проблематич­

ным, если разницы в плотности внутри набора данных относительно велики.

~~ Кластеризация на основе графов

н~ До сих пор мы видели три самых фундаментальных категории ал­
заметку!

горитмов кластеризации: кластеризацию на основе прототипов

K-Means,

агломеративную иерархическую кластеризацию и клас­

теризацию на основе плотности посредством

DBSCAN.

Тем неме­

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

кластеризации ,

которая

в

главе

не

раскрывалась

-

кластеризация на основе графов. Вероятно, наиболее знаменитыми
членами семейства алгоритмов кластеризации на основе графов яв­
ляются алгоритмы спектралыюй кластеризации.
Несмотря на наличие множества разных реализаций спектральной

кластеризации, все они разделяют одну общую черту

-

использу­

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

ше можно, прочитав великолепную работу Ульрики фон Люксбург


оп

spectral clustering" (Учебное пособие по спектральной
Statistics and Computing, 17(4): с. 395-416 (2007 г.),
которая свободно доступна по ссылке http: / / ar x i v .org/
pdf/0711. 0189vl. pdf.
tutorial

кластеризации),

-

--- [ 451 )-- -- --

fлава

11.

Работа с непомеченными данными

-

кластерный анализ

Обратите внимание, что на практике не всегда очевидно, какой алгоритм

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

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

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

Таким образом, в контексте "проклятия размерности" устоявшаяся прак­
тика предусматривает применение приемов понижения размерности до вы­

полнения самой кластеризации. Такие приемы понижения размерности для
непомеченных наборов данных включают анализ главных компонентов и
ядерный анализ главных компонентов с ядром
ты в главе

5.

RBF,

которые были раскры­

Вдобавок чрезвычайно распространенной практикой является

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

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

Резюме
В главе вы узнали о трех алгоритмах кластеризации, которые могут по­

мочь в обнаружении скрытых структур либо информации в данных. Сначала
рассматривался подход на основе прототипов

-

алгоритм

K-Means,

который

кластеризирует образцы в сферические формы, базируясь на указанном ко­
личестве центроидов кластеров. Поскольку кластеризация является методом

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

Затем мы взглянули на другой подход к кластеризации

-

агломератив­

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

DBSCAN -

алгоритм, группирующий точки на основе ло­

кальных плотностей и способный обрабатывать выбросы, а также иденти­
фицировать несферические формы.

----[452]-

Глава

11.

Работа с непомеченными данными

-

кластерный анализ

После такого экскурса в область обучения без учителя самое время пред­
ставить некоторые из наиболее захватывающих алгоритмов МО для обу­
чения с учителем: многослойные искусственные нейронные сети. Пройдя
через недавнее возрождение, нейронные сети еще раз стали самой горячей

темой для исследовательских работ в области МО. Благодаря разработанным

в последнее время алгоритмам глубокого обучения нейронные сети счита­
ются передовой технологией для решения многих сложных задач, таких как

классификация изображений и распознавание речи. В главе
собственную многослойную нейронную сеть. В главе
с библиотекой

TensorFlow,

13

12

мы построим

мы будем работать

которая предназначена для очень эффективного

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

~~-

---- 14531 -----------------------------

12
РЕАЛИЗАЦИЯ
МНОГОСЛОЙНОЙ

ИСКУССТВЕННОЙ
НЕЙРОННОЙ СЕТИ С НУЛЯ

ак вам наверняка известно, глубокому обучению уделяется большое
к
.
.

внимание в прессе и вне всяких сомнений оно является самой горячей

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

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

ванные на
сетей

Python библиотеки для ГО и архитектуры глубоких иейронных
(1ieep 11еип1/ 1tctн1 01·k -- ONN), которые особенно хорошо подходят

для анализа изображений и текстов.
В главе будут раскрыты следующие темы:



представление о концепциях многослойных нейронных сетей;



реализация с нуля фундаментального алгоритма обучения с обратным
распространением для нейронных сетей;



обучение базовой многослойной нейронной сети для классификации
изображений.

fлава

12.

Реализация многослойной искусственной нейронной сети с нуля

Моделирование сложных функций с помощью
искусственных нейронных сетей
Наше путешествие по алгоритмам МО в этой книге начиналось с ис­

кусственных нейронов в главе

2.

Искусственные нейроны исполняют роль

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

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

ных сетей уходят корнями в 1940-е годы, когда Уоррен Мак-Каллок и Уолтер
Питтс впервые описали, как могли бы работать нейроны ("А

of the ideas immanent in nervous activity"

(Логическое исчисление идей, при­

сущих нервной деятельности), У. Мак-Каллок и У. Питтс,

Mathematical Biophysics, 5(4):

с.

logical calculus

115-133, 1943

The Bulletin of

г.).

Тем не менее, в течение десятилетий, следующих за первой реализа­

цией модели нейрона Мак-Ка.1лока-Питтса
1950-х годах,

-

-

персептрона Розенблатта в

многие исследователи и специалисты-практики в области

МО начали постепенно терять интерес к нейронным сетям, т.к. никто не

располагал хорошим решением для обучения нейронной сети с множеством
слоев. Затем интерес к нейронным сетям снова вспыхнул в

1986

году, когда

Д. Румельхарт, Дж. Хинтон и Р. Уильямс (повторно) открыли и популяризи­

ровали алгоритм обратного распространения для более рационального обу­

чения нейронных сетей, который более подробно обсуждается позже в главе

("Learning representations

Ьу

back-propagating errors"

(Изучение представле­

ний путем обратного распространения ошибок), Д. Румельхарт, Дж. Хинтон,
Р. Уильямс,

Nature, 323 (6088):

с.

533-536, 1986

г.). Читатели, интересую­

щиеся историей искусствен11ого иителлекта (11гt~f/cial i11tell(~c1ю·

---

А/),

МО и нейронных сетей, могут ознакомиться со статьей в Википедии, посвя­
щенной так называемой зшие искусственного иителлекта

-

периоду вре­

мени, в течение которого значительная часть научного сообщества утратила
интерес к исследованию нейронных сетей

{https: //ru.wikipedia.org/

wiki/Зимa _искусственного_интеллекта).
Однако нейронные сети никогда не были настолько популярными, как в
наши дни, благодаря многим крупным открытиям, совершенным в предыду-

--------·-----

[456]-----------

fлава

12.

Реализация многослойной искусственной нейронной сети с нуля

щем десятилетии, которые в итоге привели к тому, что мы теперь называем

алгоритмами и архитектурами ГО

нейронными сетями, состоящими из

-

множества слоев. Нейронные сети являются горячей темой не только в на­

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

Facebook, Microsoft, Amazon, Uber

Google,

делающих крупные инвестиции

в искусственные нейронные сети и исследования ГО.
На сегодняшний день сложные нейронные сети, приводимые в действие

алгоритмами ГО, считаются современными решениями таких сложных за­
дач, как распознавание изображений и речи. В число широко распростра­
ненных примеров повседневно применяемых продуктов, движимых ГО, вхо­

дят поиск картинок и переводчик

Google -

приложение для смартфонов,

которое способно автоматически распознавать текст в изображениях с це­
лью его перевода в реальном времени более чем на

20

языков.

Крупные технологические и фармацевтические компании создали мно­
жество захватывающих приложений на основе сетей

DNN,

далеко не пол­

ный список которых представлен ниже:



приложение

DeepFace для снабжения метками изображений от
Facebook ("DeepFace: Closing the Gap to Human-Level Performance in
Face Verification" (DeepFace: сокращение отрыва от человеческого уров­
ня эффективности в верификации лиц), Я. Тейгман, М. Янь, М. Ранзато

и Л. Вулф, Конференция по компьютерному зрению и распознаванию
образов



IEEE (CVPR),

приложение

с.

DeepSpeech

1701-1708 (2014
от

Baidu,

которое способно обрабатывать голо­

совые запросы на китайском языке

speech recognition" (DeepSpeech:

г.));

("DeepSpeech: Scaling up end-to-end

масштабирование сквозного распозна­

вания речи), А. Ханнун, К. Кейс, Дж. Каспер, Б. Катанзаро, Г. Даймос,
Э. Элсен, Р. Пренджер, С. Сатиш, Ш. Сенгупта, А. Коутс и Э. Ын, пре­
принт



arXiv:1412.5567 (2014

г.));

новая служба перевода на разные языки от

Google ("Google's Neural
Machine Translation System: Bridging the Gap between Human and
Machine Translation" (Нейронная система машинного перевода Google:
преодоление разрыва между человеческим и машинным переводом),
препринт

arXiv:1412.5567 (2016

г.));

(457)-------·

Глава



12.

Реализация многослойной искусственной нейронной сети с нуля

новаторские
рования

для

приемы

токсичности

обнаружения

наркотиков

и

прогнози­

("Toxicity prediction using Deep Learning"

(Прогнозирование токсичности с использованием глубокого обуче­
ния), А. Майр, Г. Кламбауэр, Т. Унтерсинер, С. Хохрайтер, препринт

arXiv: 1503.01445 (2015


г.));

мобильное приложение, которое способно обнаруживать рак кожи с
точностью, аналогичной точности диагностики профессионально обу­
ченными дерматологами

("Dermatologist-level classification of skin cancer
with deep neural networks" (Классификация рака кожи уровня дермато­
логов с помощью глубоких нейронных сетей), А. Эстева, Б. Купрель,
Р. Новоа, Дж. Ко, С. Светтер, Х. Блау и С. Трун,

7639,


с.

115-118 (2017

Nature 542,

номер

г.));

прогнозирование трехмерной структуры белка по последовательностям
генов

("De novo structure prediction with deep-learning based scoring"

(Снова о прогнозировании структуры с помощью глубокого обучения
на основе подсчета), Р. Эванс, Д. Джампер, Д. Киркпатрик, Л. Сифр,
Т. Грин, С. Цинь, А. Зидек, С. Нельсон, А. Бридгленд, Х. Пенедонес,
С. Петерсен,

К. Симонян,

С. Кроссан,

Д. Джоне,

Д. Сильвер,

К. Кавукчуоглу, Д. Хассабис и Э. Сеньор, в тринадцатом экспери­
менте

CASP (Critical Assessment of Techniques for Protein Structure
Prediction - критическая оценка прогнозирования белковых структур),
1-4 декабря 2018 г. );



обучение вождению при плотном трафике на основе исключительно
данных наблюдений, таких как видеопотоки из камер

("Model-predictive
in dense traffic"
driving
for
regularization
uncertainty
policy learning with
(Политика обучения прогнозирующих моделей с регуляризацией не­
определенности для вождения при плотном трафике), М. Хенафф,

А. Чанзани, Я. Лекун,

Conference

оп

Conference Proceedings of the International
ICLR, 2019 г.).
Representations,
Leaming

Краткое повторение однослойных нейронных сетей
Настоящая глава целиком посвящена многослойным нейронным сетям,

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

- - - - ---------

Глава

12.

Реализация многослойной искусственной нейронной сети с нуля

концепций однослойных нейронных сетей, которые мы представляли в гла­

ве

2,

а именно

-

казанного на рис.

алгоритма адаптивного линейного нейрона

(Allaline),

по­

12.1.
Функция
активации

'1

Сnрогно­
,......_У зированная
метка
класса

Xm ,•, _ __ .)

:

Функция
общего

ступенчатая

входа

функция

Весовые

... - - - ,

коэффи­

Входные

циенты

Единичная

значения

Рис.

В главе

2

12.1.

Алгоритм

мы реализовали алгоритм

Adaline

Adaline

для выполнения двоичной

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

по обучающему набору) мы обновляли весовой вектор

w,

используя следу­

ющее правило обновления:

w := w +

Лw,

где Лw

= -17VJ(w)

Другими словами, мы вычисляли градиент на основе полного обучаю­
щего набора данных и обновляли веса модели, делая шаг в направлении,
противоположном градиенту

VJ(w).

Для нахождения оптимальных весов мо­

дели мы оптимизировали целевую функцию, которую определяли как фун­
кцию издержек

J(w)

в форме суммы квадратичных ошибок

мы умножали градиент на коэффициент

-

скорость

(SSE). Вдобавок
обучения 17, который

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

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

w1 в

весовом векторе

(459]

w:

[лава

12.

Реализация многослойной искусственной нейронной сети с нуля

~J( w) =-I(y(i)-a(i))xY)
дw}

Здесь

i

/iJ -

активация

целевая метка класса индивидуального образца x(i) и aIIII",

w.i. tt:

imgpath.read(lб))

- --···· [ 4691 ---------------------------·--

fлава

12.

Реализация многослойной искусственной нейронной сети с нуля

images = np.fromfile(imgpath,
dtype=np.uint8).reshape(
len(labels), 784)
images = ( (images / 255.) - .5) * 2
retцrn

Функция

images, labels

load _ mnist

возвращает два массива, первый из которых яв­

ляется (п х т)-мерным массивом

разцов и т
состоит из

1О ООО

NumPy (images),

- число признаков (точек в нашем
60000 обучающих образцов цифр, а

где п

количество об­

-

случае). Обучающий набор
испытательный набор

-

из

образцов.

Изображения в наборе данных

MNIST

содержат

28 х28

пикселей, причем

каждый пиксель представлен значением интенсивности в оттенках серого.

Мы развертываем изображение из

28 х28

пикселей в одномерные векторы­

строки, которые представляют строки в массиве

images (784 значения на
(labels), возвращаемый функци­
ей load_mnist, содержит соответствующую целевую переменную метки
классов (целые числа от О до 9) рукописных цифр. Способ чтения изображе­
строку или изображение). Второй массив

ния поначалу может показаться несколько странным:

magic, n = struct.unpack('>II', lbpath.read(8))
labels = np.fromfile(lbpath, dtype=np.uint8)
Чтобы понять, как работают эти две строки кода, давайте просмотрим
описание набора данных

доступное по ссылке

MNIST,

http://yann.

lecun.com/exdЬ/mnist/:
[смещение]

[тип]

[значение]

0000

32-битное целое

Ох00000801

0004
0008
0009

32-битное

метка

метка

(2049)

[описание]
магическое число
(сначала старший бит)

байт без

знака

байт без

знака

60000
??
??

байт без

знака

??

целое

количество

элементов

метка

........
хххх

С помощью приведенных ранее двух строк кода мы сначала читаем из

файлового буфера магическое число, которое является описанием файлово­
го протокола, и количество элементов

fromfile

(n),

а затем с использованием метода

загружаем последующие байты в массив

NumPy.

-[ 470) - - - · - - - - - - - - - - - - - - - - - -

fпава

Значение

'> I I'

struct. unpack,

• >

12.

Реализация многослойной искусственной нейронной сети с нуля

параметра

frnt,

передаваемое как аргумент функции

можно разбить на две части:

указывает на порядок хранения последовательности байтов

-

от

старшего к младшему; если вам не знакомо понятие порядка от стар­

шего к младшему и от младшего к старшему, тогда почитайте статью

"Порядок байтов" в Википедии

(https: / /ru. wikipedia. org/wiki/

Порядок_ байтов);

• I указывает на целое число без знака.
Наконец, мы также нормализуем значения пикселей МNIST в диапазоне
от

-1

до

1 (первоначально

было от О до

255)

посредством следующей стро­

ки кода:

images

=

((images / 255.) - .5) * 2

Как обсуждалось в главе

2,

причина в том, что при таких условиях градиен­

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

[-1, 1] -

распространенный подход, показывающий хорошие результаты на практике.

Га~ Пакетная нормализация

н~ Распространенным трюком, направленным на улучшение сходи­
заметкуl

мости градиентной оптимизации через масштабирование входов,

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

17.

Кроме того, вы можете оз­

накомиться с дополнительными сведениями о пакетной нормали­

зации, прочитав великолепную статью Сергея Иоффе и Кристиана

Сегеди

"Batch Normalization: Accelerating Deep Network Training Ьу
Reducing Internal Covariate Shift" (Пакетная нормализация: ускоре­
ние обучения глубоких сетей путем сокращения внутреннего кова­
риационного сдвига), написанную в

2015

году

(https: //arxiv.

org/abs/1502. 03167).

[471)-----------

Глава

12.

Реализация многослойной искусственной нейронной сети с нуля

Выполнив приведенный далее код, мы загрузим
разцов и

1О ООО

60 ООО

обучающих об­

испытательных образцов из локального каталога, где про­

изводилась распаковка архивов с набором данных
фрагменте кода предполагается, что загруженные

MNIST. (В следующем
файлы MNIST были рас­

пакованы в том же каталоге, в котором выполняется код.)

>>> X_train, y_train = load_mnist('', kind='train')
>>> print ('Строк: %d, столбцов: %d'
% (X_train.shape[O], X_train.shape[l]))
Строк:

60000,

столбцов:

784

>>> X_test, y_test = load_mnist (' ', kind=' tlOk')
>>> print ( 'Строк: %d, столбцов: %d'
Строк:

% (X_test.shape[OJ, X_test.shape[l) ))
10000, столбцов: 784

Чтобы получить представление о том, как выглядят изображения в набо­
ре данных

MNIST,

давайте визуализируем примеры цифр

0-9

после восста­

новления 784-пиксельных векторов из матрицы признаков в исходное изоб­
ражение

28 х 28, которое
библиотеки Matplotlib:

мы можем вывести посредством функции

imshow

>>> iroport. matplotlib.pyplot a.s plt
>>> fig,

ах=

plt.subplots(nrows=2, ncols=S,
sharex=Tл1e, sharey=True)
>>> ах = ах. flatten ()
>>> for i in range ( 10) :
img = X_train[y_train == i) [О] .reshape(28, 28)
ax[i) .imshow(img, cmap='Greys')

»> ax[O].set_xticks([J)
>>> ax[OJ .set_yticks([J)

»> pl t. tight _ layout ()
»> plt. show ()
Мы должны увидеть график из 2х5 фигур, показывающих типичное изоб­
ражение каждой уникальной цифры (рис.

--------(472]

12.5).

Глава

12.

Реализация многослойной искусственной нейронной сети с нуля

Рис.

12.5.

Типичные изображения цифр

Вдобавок мы выведем также примеры одной цифры , чтобы посмотреть,
как на самом деле отличается почерк :

>>> fig,

>>>
>>>

»>
»>
>>>
>>>

plt . subplots(nrows=S,
ncols=S,
sharex=True ,
sharey=True)
ах = ах. flatten ()
fo r i in z·ange (25):
img = X_train[y_train == 7) [i] .reshape(28, 28)
ax[i] .imshow(img, cmap='Greys')
ах [О] . set_xticks ( [])
ах[О] . set_yticks( [])
plt.tight_layout()
plt. show ()
ах=

После выполнения кода мы должны увидеть первые
сания цифры

7

(рис.

25

вариантов напи­

12.6).

Рис.

12.6.

Варианты написания цифры

7

[ 473) - - - - -- - - ------ -- - -

fлава

12.

Реализация многослойной искусственной нейронной сети с нуля

Завершив все описанные выше шаги, имеет смысл сохранить отмасш­
табированные изображения в формате, который обеспечит более быструю

загрузку в новом сеансе

чтобы избежать накладных расходов, свя­

Python,

занных с повторным чтением и обработкой данных. Когда используют­
ся массивы

NumPy,

рациональный и удобный способ сохранения много­

мерных массивов на диск предлагает функция

savez библиотеки NumPy.
(Официальная документация находится по ссылке https: / /docs. scipy.
org/doc/numpy/reference/generated/numpy. savez. html.)
Выражаясь кратко, функция savez аналогична модулю pickle из
Python, который мы применяли в главе 9, но оптимизирована для сохране­
ния массивов NumPy. Функция savez создает ziр-архивы данных, выпуская
файлы . npz, которые содержат файлы в формате . npy. Если вы хотите уз­
нать больше об этом формате, то можете обратиться к удобному пояснению,
включающему обсуждение достоинств и недостатков, в документации по

NumPy:https://docs.scipy.org/doc/numpy/reference/generated/
numpy. lib. format. html#module-numpy. lib. forma t. Кроме того,
вместо savez мы будем использовать функцию savez compressed, ко­
торая имеет такой же синтаксис, как у savez, но дополнительно сжимает
выходной файл до существенно меньшего размера (в данном случае с около

400

Мбайт до приблизительно

22

Мбайт). В следующем фрагменте кода мы

сохраняем обучающий и испытательный наборы данных в архивный файл

mnist_scaled.npz:
>>> import numpy as np
>>>

пр.

savez _ compressed ( 'mnist _ scaled. прz' ,
X_traiп=X_traiп,
y_traiп=y_traiп,

X_test=X_test,
y_test=y_test)
После создания файлов

. npz

мы можем загружать предварительно обра­

ботанные массивы изображений МNIST с применением функции
лиотеки

>>>

load

биб­

NumPy:

mпist = пр.

Переменная

load ( 'тпi s1:.. sca J ~(J. r1pz.' )

mnist

теперь ссылается на объект, способный получать до­

ступ к четырем массивам данных,

которые

---·---------(474)

мы

предоставили

в ключевых

Глава

аргументах функции

12.

Реализация многослойной искусственной нейронной сети с нуля

savez _ compressed. Эти входные
files объекта mnist:

массивы теперь пе­

речисляются в списке атрибута

>>> mnist. files
['X_train', 'y_train', 'X_test', 'y_test']
Например, для загрузки обучающих данных в текущий сеанс

Python

мы

получаем доступ к массиву Х_ train следующим образом (подобно доступу
к словарю

>>>

х

Python):

_train

= mnist [ 1 х_train 1 ]

Вот как мы можем извлечь все четыре массива данных, используя
спuсковое включение

(lisl t·om/nel1cnsio11):

>>> X_train, y_train, X_test, y_test = [mnist[f] for
f in mnist.files]
Обратите внимание, что хотя предшествующие примеры с функциями

np. savez _ compressed

и

np. load

несущественны для выполнения кода

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

NumPy.

\\а~ Загрузка набора данных

MNIST с использованием scikit-learn

н~ В настоящее время загрузить набор данных MNIST можно более
заметку!

удобно с применением новой функции

теки

scikit-leam.

fetch _ openml

из библио-

Например, с помощью приведенного ниже кода вы

можете создать обучающий набор с

ный набор с 10 ООО образцов,
www.openml.org/d/554:

50

ООО образцов и испытатель­

извлекая набор данных из

https: / /

>>> f.гorn sklearn.datasets import; fetch_openml
>>> f:r:om sklearn .model_ selection import train_ test spli t
>>> Х, у= fetch_openml ( 'mnist 784', version=l,
return_ X_y='Гrue)
>>>у= y.astype(int)

»>

Х

= ( (Х

/ 255.) - • 5)

*

2

>>> X_train, X_test, y_train, y_test =\
train_test_split(
Х, у, test_size=lOOOO,
random_state=123, stratify=y)

- - - - - - - - - - - - - - - - - [475]

f11ава

12.

Реализация многослойной искусственной нейронной сети с нуля

Имейте в виду, что распределение записей

MNIST

на обучающий и

испытательный наборы данных будет отличаться от такого распреде­
ления при ручном подходе, описанном ранее в разделе. Таким обра­
зом, в последующих разделах вы будете наблюдать немного другие

результаты при загрузке набора данных с использованием функций

fetch _ openml

и

train_ test_ spli t.

Реаnизация мноrосnойноrо персептрона
В этом подразделе мы реализуем с чистого листа многослойный персеп­

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

из хранилища

GitHub (https:

//github.corn/rasЬt/python-rnachine­

learning-book-Зrd-edition) и работать с реализацией

MLP,

снабженной

комментариями и выделением синтаксиса для лучшего восприятия.

Если

вы

не

выполняете

код

из

сопровождающего

файла

Jupyter

Notebook или не располагаете доступом в Интернет, тогда скопируйте
код NeuralNetMLP из данной главы в сценарный файл Python (скажем,
neuralnet. ру) внутри текущего рабочего каталога и затем импортируйте
его в текущем сеансе Python посредством следующей команды:
neuralnet

NeuralNetMLP

Код будет содержать разделы, которые мы еще не раскрывали, такие как
алгоритм обратного распространения, но большая часть кода должна выгля­

деть знакомой; она основана на реализации

Adaline

в главе

2

и обсуждении

прямого распространения в предшествующих разделах.

Не переживайте, если не все в коде станет понятным незамедлительно;

мы разберем определенные части далее в главе. Однако отслеживание кода
на этой стадии может облегчить усвоение теории позже.
Ниже приведена реализация многослойного персептрона:

import numpy as np
import sys
class

NeuralNetмLP(object):

"""Нейронная сеть прямого распространения/ классификатор
на основе многослойного персептрона.

-----------[476]--------

Глава

12.

Реализация многослойной искусственной нейронной сети с нуля

Параметры·
п

hidden : int (по умолчанию: 30)
Количество скрытых элементов.

12 : float (по умолчанию: О.)
Значения лямбда для регуляризации
Регуляризация отсутствует, если

(по умолчанию:

epochs : int

L2.
12=0 (принято

по умолчанию)

100)

Количество проходов по обучающему набору.
(по умолчанию:

eta : float

О.

001)

Скорость обучения.

shuffle : bool (по умолчанию: True)
Если True, тогда обучающие данные
каждую эпоху,

тасуются

чтобы предотвратить циклы.

minibatch_size : int

(по умолчанию:

1)

Количество обучающих образцов на мини-пакет.

seed : int

(по умолчанию:

None)

Случайное начальное значение для инициализации весов
и тасования.

Атрибуты

eval

: dict

Словарь,

в котором собираются показатели издержек,

правильности при обучении и правильности при испытании

для каждой эпохи во время обучения.

"""
init

def

(sc~l Г,

n_hidden=ЗO,

12=0., epochs=lOO, eta=0.001,
shuffle=True, minibatch_size=l, seed=None):
sc1 !: .random = np.random.RandomState (seed)
:)с 1 f • n hidden = n hidden
' {n_examples, n_hidden]
z h

=

#шаг

a_h

=

np.dot(X, self.w_h) + self.b_h
2: активация скрытого
self. sigmoid(z_h)

слоя

# шаг 3: общий вход выходного слоя
# скалярное произведение {n_examples, n_hidden}
и [n_hidden, п classlabels}
#
# -> [n_examples, n_classlabels]

z out
#

шаг

а

out

=

4:

,'.w_out) +

np.dot(a_h,

·.. Ь out

активация выходного слоя

_ . _sigmoid ( z_out)

return z_h, a_h, z_out, a_out
def _compute_cost(s•

:, y_enc, output):

""" Вычисляет функцию издержек.
Параметры

у_епс

:

массив,

форма =

(n_examples, n_labels)

Метки классов в унитарном коде.

output : массив, форма

=

{n_examples, n_output_units}

Активация выходного слоя

(прямое распространение)

Возвращает

-- [478] ------------------~---

Глава

12.

Реализация многослойной искусственной нейронной сети с нуля

cost : float
Регуляризированные издержки

"""
L2 term =

*

: ·• .12
(np.sum(
np.sum(

J.w_h ** 2.) +
.w_out ** 2.)))

terml
-у_ enc * (np. log ( output) )
term2
(1. - y_enc) * np.log(l. - output)
cost = np. sum ( terml - term2) + L2 term
return cost
def predict ( sc

J

,

Х)

:

"""Прогнозирует метки классов.
Параметры

Х

:

массив,

форма =

[n_examples, n_features]

Входной слой с первоначальными признаками.
Возвращает

массив,

y__pred :

форма =

[n_examples]

Спрогнозированные метки классов.

,,",,
z_h, a_h, z_out, a_out =,с,
forward(X)
y_pred = np.argmax(z_out, axis=l)
return y_pred
def fit (

', X_train, y_train, X_valid, y_valid):

""" Выясняет веса из данных.
Параметры

X_train :

массив,

форма =

[n_examples, n_features]

Входной слой с первоначальными признаками.

y_train :

массив,

форма =

[n_examples]

Целевые метки классов.

X_valid: array, shape = [n_examples, n_features]
Признаки образцов для проверки во время обучения

y_valid :

массив,

форма=

[n_examples]

Метки образцов для проверки во время обучения
Возвращает

self

"""
----[4791--

[лава

12.

Реализация многослойной искусственной нейронной сети с нуля

#

n_output = np.unique(y_train) .shape[O]

количество

#меток классов

n_features = X_train.shape[l]
#######################

#

Инициализация весов

#

#######################

#

веса для входного слоя
Г

->

скрытого слоя

.b_h = np. zeros ( · • .n_hidden)
.random.normal(loc=O.O, scale=0.1,
.w h =
size=(n_features,
·''' '· .n_hidden))

# веса для
.Ь out
··
··· .w out

скрытого слоя

=

->

выходного слоя

np. zeros (n_output)
('"-' . .:- .random. norrnal (loc=O. О, scale=O .1,
size=(·.c: • .n_hidden,
n_output))

' . epochs) ) # для форма та
epoch_strlen = len(st.r(
= { 'cost': [], 'train асс': [], 'valid асс': \
'ё .eval
[] }

y_train_enc =

: . onehot (y_train, n_output)

# итерация по эпохам обучения
for i in range( : •.. epochs):
#итерация по мини-пакетам

indices = np.arange(X_train.shape[O])
if

·.!'.shuffle:
' ': . random. shuffle (indices)

for start_idx in range(O, indices.shape[O] -\
•' .minibatch size +\
'.minibatch_size):
1,
batch idx = indices[start_idx:start_idx +\
• t .minibatch_size]

#

прямое распространение

z_h, a_h, z_out, a_out = \
. r • _ forward (Х _ train [batch_ idx] )
############################

#

Обратное распространение

#

############################

[480] - - - · - ·

fлава

12.

Реализация многослойной искусственной нейронной сети с нуля

# {n_examples, n_classlabels]
delta_out = a_out - y_train_enc[batch_idx]
# {n_examples, n_hidden]
sigmoid_derivative_h = a_h * (1. - a.:.__-h)

#

скалярное произведение

{n_examples,
[n_classlabels, n_hidden]
# -> [n_examples, n_hidden]

п

classlabels]



delta_h = (np.dot(delta_out, sel.E.w_out.T)
sigmoid_derivative_h)
#

*

скалярное произведение

[n_features, n_examples]
[n_examples, n_hidden]
# -> {n_features, n_hidden]
grad_w_h = np.dot(X_train[batch_idx] .Т, delta h)


grad_b_h - np.sum(delta_h, axis=O)

#

скалярное произведение

[n_hidden, n_examples]
{n_examples, n_classlabels]
-> {n_hidden, n_classlabels]



#

grad_w_out
grad_b_out

#

= np.dot(a_h.T,
к

delta_out)
np.sum(delta_out, axis=O)

Регуляризация и обновления весов

delta_w_h ...
del ta_ ь_h =
self.w h -=
se[.f.b h -=

(grad_w_h + S(':H .12*:~ ', t_tr.shape)
5) --> (5, 3)

Изменение формы тензора (скажем, превращение одномерного вектора
в двумерный массив):


>>>
>>>
(5,



t = tf. zeros ( ( 30, ) )
t_reshape = tf.reshape(t, shape=(5, 6))
print(t_reshape.shape)
6)

Удаление излишних измерений (измерений, имеющих размер

l,

кото­

рые не нужны):

>>>
>>>
>>>
(1,

t = tf.zeros((l, 2, 1, 4, 1))
t_sqz = tf. squeeze (t, axis= (2, 4))
print(t.shape, ' --> ', t_sqz.shape)
2, 1, 4, 1) --> (1, 2, 4)

Применение математических операций к тензорам
Применять математические операции, в частности операции линейной

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

--------(509)-------------

fлasa

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

Первым делом давайте создадим два случайных тензора, один с равно­
мерным распределением в диапазоне

[-1, 1)

и еще один со стандартным

нормальным распределением:

>>> tf.random.set_seed(l)
>>> tl = tf.random.uniform(shape=(5, 2),
minval=-1.0, maxval=l.0)

>>> t2 = tf.random.normal(shape=(5, 2),
mean=O.O, stddev=l.0)
Обратите внимание, что

tl

и

t2

имеют ту же самую форму. Теперь для

вычисления поэлементного произведения

t1

и

t2

мы можем применять сле­

дующий код:

»> t3 = tf.multiply(tl, t2) .numpy()
»> print (t3)
[ [-0.27 -0.874]
[-0.017 -0.175]
[-0.296 -0.139]
[-0.727 0.135]
[-0.401 0.004]]
Чтобы рассчитать среднее, сумму и стандартное отклонение по опреде­
ленной оси (или осям), мы можем использовать

tf.math.reduce_mean(),
tf .math. reduce_sum() и tf .math. reduce_std ().Например, среднее
каждого столбца в tl можно вычислить так:
>>> t4 = tf.math.reduce_mean(tl, axis=O)
>>> print (t4)
tf.Tensor( [0.09

0.207], shape=(2,), dtype=float32)

Матричное произведение tl и t2 (т.е. t 1 х

t/, где т означает транспониро­

вание) можно вычислить с применением функции

>>> t5 = tf. linalg .matmul (tl, t2,
>>> print(t5.numpy())

tf. linalg .matmul ():

transpose_b=Trtю)

[[-1.144 1.115 -0.87 -0.321 0.856]
[ 0.248 -0.191 0.25 -0.064 -0.331]
[-0.478 0.407 -0.436 0.022 0.527]
[ 0.525 -0.~34 0.741 -0.593 -1.194]
[-0.099 0.26
0.125 -0.462 -0.396]]

t/

С другой стороны, вычисление t 1 х
выполняется путем транспонирова­
ния tl, что в результате дает массив 2х2:
-----------[510)--------

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью

TensorF/ow

>>> t6 = tf. linalg .matmul (tl, t2, transpose_a=True)

»> pr.int(t6.numpy())
[ [-1. 711 0.302]
[ 0.371 -1.049]]

Наконец, функция tf. norm () удобна для расчета нормы LP тензора.
Скажем, мы можем вычислить нормы L 2 тензора tl следующим образом:
>>> norm_tl = tf.norm(tl, ord=2, axis=l) .numpy()
>>> print.(norm_tl)
[1.046 0.293 0.504 0.96

0.383]

Чтобы проверить корректность расчета нормы L 2 тензора tl приведен­
ным выше кодом, можно сравнить результаты посредством функции из биб­
лиотеки

NumPy: np. sqrt (np. sum(np. square (tl), axis=l)).

Расщепление, укnадывание стопкой и объединение тензоров
В этом подразделе мы рассмотрим операции

TensorFlow для

расщепления

одного тензора на множество тензоров и наоборот: укладывание стопкой и
объединение тензоров в единственный тензор.
Предположим, что у нас есть одиночный тензор и его нужно расщепить

на два или большее число тензоров. Для такого действия в
дусмотрена удобная функция

tf. spli t (),

TensorFlow

пре­

которая разделяет входной тен­

зор на список тензоров равных размеров. Мы можем определить желатель­
ное количество расщеплений как целое число с использованием аргумента

num_ or_ size_ spli ts

и расщепить тензор по заданному измерению, ука­

занному с помощью аргумента

axis.

В таком случае общий размер входно­

го тензора по указанному измерению должен быть кратным желательному
числу расщеплений. В качестве альтернативы мы можем предоставить жела­
тельные размеры в списке. Ниже приведены примеры для обоих вариантов.



Предоставление количества расщеплений (общий размер должен быть
кратным указанному количеству):

>>> tf.random.set_seed(l)
>>> t = tf. random. uniform ( ( 6,))
>>> p:r·iпt (t.numpy())
[0.165 0.901 0.631 0.435 0.292 0.643]

>>> t_splits = tf.split(t, num_or_size_splits=3)
>>> [item.numpy() for item in t_splits]
[array([0.165, 0.901], dtype=float32),
array([0.631, 0.435], dtype=float32),
array( [О.292, 0.643], dtype=float32)]

--------(511)--------------

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

В показанном примере тензор размера

6

был разделен на список из

трех тензоров, каждый из которых имеет размер



2.

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

5

на тензоры с размерами

3

и

2:

>>> tf. random. set_seed( 1)
>>> t = tf.random.uniform( (5,))
>>> print:(t.numpy())
[0.165 0.901 0.631 0.435 0.292]
>>> t_splits = tf.split(t, num_or_size_splits=[3, 2])
>>> [item.numpy() t:or item in t_splits]
[array([0.165, 0.901, 0.631], dtype=float32),
array([0.435, 0.292], dtype=float32)]
Иногда мы работаем с множеством тензоров и нуждаемся в их объедине­
нии или укладывании стопкой, чтобы создать единственный тензор. В таком

случае окажутся полезными функции
ки

TensorFlow.

tf. stack ()

и

tf. concat ()

библиоте­

Например, давайте создадим одномерный тензор А размера

содержащий единицы, и одномерный тензор в размера

2,

после чего объединим их в одномерный тензор С размера

3,

содержащий нули,

5:

tf .ones ( (3,))
>>> В = tf. zeros ( (2,))
>>> С = tf. concat ([А, В], axis=O)
>>> print(C.numpy())
>>>А=

[1. 1. 1.

О.

О.]

Если мы создадим одномерные тензоры А и В, оба с размером

можем уложить их стопкой, образовав двумерный тензор

3,

тогда

S:

tf.ones ( (3,))
>>>В= tf.zeros( (3,))
>>> S = tf.stack([A, В], axis=l)
>>> p.r.ii-1t.(S.numpy())
>>>А=

[[1.

О.]

[1.

О.]

[1.

О.]]

В АРl-интерфейсе

TensorFlow

есть много операций, которые вы може­

те применять для построения модели, обработки данных и решения дру-

---·----·------------- [512)-----------------·-

[пава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

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

и функций ищите в документации

по ссылке

ht tps: / /www.
tensorflow.org/versions/r2.0/api_docs/python/tf.
TensorFlow

Построение входных конвейеров
с использованием

АРl-интерфейса

tf. da ta -

Da taset библиотеки Tensorflow

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

Как упоминалось в начале текущей главы, АРI-интерфейс

ляет собой оболочку вокруг

TensorFlow,

Keras

предназначенную для построения

нейросетевых моделей. Для обучения моделей АРI-интерфейс
гает метод

. f i t ().

представ­

Keras

предла­

В случаях, когда обучающий набор данных довольно мал

и может быть загружен в виде тензора в память, модели
роенные с помощью АРI-интерфейса

этот тензор через их метод

. fi t ()

Keras)

TensorFlow

(пост­

могут напрямую задействовать

для обучения. Тем не менее, в типовых

сценариях использования, когда набор данных слишком большой и потому
не умещается в памяти, нам понадобится загружать данные из основного
устройства хранения (скажем, жесткого диска или твердотельного накопи­

теля) порциями, т.е. пакет за пакетом (обратите внимание на употребление
в этой главе термина "пакет" вместо "мини-пакет", что позволяет оставать­

ся ближе к терминологии

TensorFlow).

Кроме того, может возникнуть необ­

ходимость сконструировать конвейер обработки данных для применения к

данным определенных трансформаций и шагов предварительной обработки
вроде центрирования по среднему, масштабирования или добавления шума,

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

TensorFlow

пре­

доставляет специальный класс для конструирования эффективных и удоб­
ных конвейеров предварительной обработки. В этом разделе мы предло­

жим обзор различных методов для конструирования объектов

TensorFlow,

Da taset

из

включая трансформации наборов данных и распространенные

шаги предварительной обработки.

[513]--·---

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

Создание объекта Da taset из существующих тензоров
Если данные уже существуют в форме объекта тензора, списка

Python
NumPy, тогда мы можем легко создать набор данных с исполь­
зованием функции tf. data. Dataset. frorn_ tensor _ slices ().Указанная
функция возвращает объект класса Dataset, который мы можем применять
или массива

для прохода по индивидуальным элементам во входном наборе данных.
В качестве примера рассмотрим следующий код, который создает набор
данных из списка значений:
>>>а= [1.2, 3.4, 7.5, 4.1, 5.0, 1.0]
>>> ds = tf.data.Dataset.frorn_tensor_slices(a)
>>> print. (ds)


Вот как проходить по записям набора данных:

>>> for itern in ds:
print(itern)
tf.Tensor(l.2, shape=(),
tf.Tensor(З.4, shape=(),
tf.Tensor(7.5, shape=(),
tf.Tensor(4.l, shape=(),
tf.Tensor(5.0, shape=(),
tf.Tensor(l.O, shape=(),

dtype=float32)
dtype=float32)
dtype=float32)
dtype=float32)
dtype=float32)
dtype=float32)

Если необходимо создать из этого набора данных пакеты с желательным
размером

3,

то мы можем поступить так:

>>> ds_batch = ds.batch(З)
>>> for i, elern in enumerate(ds_batch, 1):
print('пaкeт {}:'.forrnat(i), elem.numpy())
пакет 1 : [ 1. 2 З . 4 7 . 5]
пакет 2 : [ 4 . 1 5 .
1. ]
В результате из набора данных создаются два пакета, причем первые три
элемента попадают в пакет

. batch ()

#1,

а оставшиеся элементы

имеет необязательный аргумент

- в пакет #2. Метод
drop rernainder, который

будет полезен в случаях, когда количество элементов в тензоре не кратно

желательному размеру пакета. Стандартным значением

False. Дополнительные примеры,
.batch (),будут приведены в разделе

drop_rernainder

является

иллюстрирующие поведение

метода

"Тасование, создание пакетов

и повторение" далее вглаве.

----[514]

Глава

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

13.

Объединение двух тензоров в общий набор данных
Часто данные могут находиться в двух или большем числе тензоров.
Скажем, мы могли бы иметь тензор для признаков и тензор для меток. В та­
ких случаях нам необходимо построить набор данных, который объединит
эти тензоры и позволит извлекать элементы тензоров в кортежах.

Предположим, что у нас есть два тензора,
значения признаков, размером

3

t _х

каждое, а тензор

и t _у. Тензор t _ х хранит
t _у содержит метки клас­

сов. В рассматриваемом примере мы сначала создаем эти два тензора:

>>> tf.random.set_seed(l)
>>> t_x = tf.random.uniform( [4, 3], dtype=tf.float32)
>>> t_y = tf.range(4)
Теперь мы хотим создать из двух тензоров общий набор данных. Обратите
внимание на наличие обязательного соответствия "один к одному" между
элементами тензоров:

>>>
>>>
>>>
>>>
>>>

х:
х:
х:
х:

ds_x = tf.data.Dataset.from_tensor_slices(t_x)
ds_y = tf.data.Dataset.from_tensor_slices(t_y)
ds_joint = tf .data. Dataset. zip ( (ds_x, ds_y))
for example in ds_joint:
priпt('
х:', example[O].numpy(),
у:', example [1] .numpy ())
[0.165°0.901
[0.435 0.292
[0.976 0.435
[О.605 0.637

0.631]
0.643]
0.66 ]
0.614]

у: о
у:
у:
у:

1
2
3

Здесь мы сначала создаем два набора данных с именами

Затем мы используем функцию

zip

ds _ х

и

ds _у.

для построения общего набора данных.

В качестве альтернативы мы можем создать общий набор данных с приме­
нением метода

tf. da ta. Da taset. f rom_ tensor_ s lices ( ) :

>>> 'ds_joint = tf .data.Dataset. from_tensor_slices ( (t_x, t_y))
>>;> for example in ds _j oint:
priпt.

х:
х:
х:

х:

('

х:

', example [0] .numpy (),
у:', example[l] .numpy())

[0.165 0.901 0.631]
[0.435 0.292 0.643]
[0.976 0.435 0.66 ]
[О.605 0.637 0.614]

у:

о

у:

1
2
3

у:

у:

---[515)

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

В результате мы получаем тот же самый вывод.

Следует отметить, что частым источником ошибки может быть утрата по­
элементного соответствия между исходными признаками (х) и метками (у),

скажем, если два набора данных тасуются по отдельности. Однако после
объединения в один набор данных можно безопасно применять операции.

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

ds _j oint

и применять масштабирование признаков с целью приве­

дения значений к диапазону

t _х

[-1, 1), поскольку
1), основанном

находятся в диапазоне [О,

в текущий момент значения
на случайном равномерном

распределении:

>>> ds trans = ds_joint.map(lamЬda
>>> for example in ds_trans:

х:
х:
х:
х:

print:('

х:',

'

у:',

х,

у:

(х*2-1.О,

у))

example[O].numpy(),
example[l] .numpy())

[-0. 67
0.262]
о.воз
[-0.131 -0.416 0.285]
[ о. 952 -о. 13 о. 32 ]
[ о. 21 0.273 0.229]

у:
у:

у:
у:

о

1
2
3

Применение трансформации такого рода может требоваться в функции,
определяемой пользователем. Например, если есть набор данных, создан­
ный из списка имен файлов с изображениями на диске, тогда мы можем

определить функцию для загрузки изображений по именам файлов и при­
менять ее посредством вызова метода

. map ( ) .

Позже в главе вы увидите

пример применения множества трансформаций к набору данных.

Тасование, создание пакетов и повторение
Как упоминалось в главе

2,

для обучения нейросетевой модели с исполь­

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

. Ьа tch ( )

объекта

набора данных. Теперь в дополнение к созданию пакетов вы научитесь та­

совать наборы данных и проходить по ним. Мы продолжим работать с пре­
дыдущим набором данных

ds_joint.

[516)------·--

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью

TensorFlow

Первым делом давайте создадим перетасованную версию набора данных

ds joint:
>>> tf. random. set _ seed ( 1)
>>> ds = ds_joint.shuffle(buffer_size=len(t
>>> Eor example in ds:
print (' х: ', example [О] .numpy (),
у: ' , example [ 1] . numpy () )
х:
х:
х:
х:

0.435
0.292
0.901
0.637

(0.976
[0.435
(0.165
[0.605

0.66]
0.643]
0.631]
0.614]

у:
у:

х))

2
1

у:

о

у:

3

Строки тасуются без потери соответствия "один к одному" между элемен­
тами в х и у. Метод

. shuffle ()

требует аргумента по имени

buffer_ size,

который определяет количество элементов в наборе данных, группируемых

перед тасованием. Элементы в буфере выбираются случайным образом, а
их место в буфере назначается следующим элементам в исходном (не пере­
тасованном) наборе данных. Таким образом, если мы выберем небольшое
значение для

buffer_ size,

то не сможем обеспечить идеальное тасование

набора данных.

Когда набор данных мал, выбор относительно небольшого значения

buffer_ size

может отрицательно сказаться на эффективности прогнозиро­

вания нейронной сети, т.к. набор данных, возможно, окажется рандомизиро­

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

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

(buffer_size=len(t_x)).

Вспомните, что набор данных разделяется на пакеты для обучения мо­
дели путем вызова метода
набора данных

>>> ds =

ds _j oint

. batch ().

Давайте создадим такие пакеты из

и посмотрим, как выглядит один пакет:

ds_joint.batch(batch_size=З,

drop_remainder=i'aJsr2)
>>> batch_x, batch_y = next(iter(ds))
>>> priпt.( 'Пакет-х:\n', batch_x.numpy())

[ 517] -

-----------------------~-------

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

Пакет-х:

[[0.165 0.901 0.631]
[0.435 0.292 0.643]
[0.976 0.435 0.66 ]]

>>>

pririt( 'Пакет-у:

Пакет-у:

'

batch_y.numpy())

1 2]



Вдобавок при обучении модели на протяжении множества эпох нам не­

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

>>> ds = ds_joint.batch(3) .repeat(count=2)
>>> f:r i, (batch_x, batch_y) in enumerate (ds):
print(i, batch_x.shape, batch_y.numpy())
о (3, 3)
1 (1, 3)
2 (3, 3)

[0 1 2]
[3]
[О 1 2]

3 (1, 3) [3]
В результате появляются две копии каждого пакета. Если мы изменим
порядок следования этих операций, т.е. выполним сначала создание пакетов

и затем повторение, то результат будет другим:

>>> ds = ds_joint.repeat(count=2) .batch(3)
>>> foz: i, (batch_x, batch_y) in enumerate(ds):
print(i, batch_x.shape, batch_y.numpy())
о

(3, 3)

[0 1 2]

1 (3, 3) [3 о 1]
2 (2, 3) [2 3]
Обратите внимание на отличие между пакетами. Когда мы сначала созда­
ем пакеты и затем повторяем, то получаем четыре пакета. С другой сторо­
ны, когда повторение осуществляется первым, то создаются три пакета.

Наконец, для лучшего понимания поведения рассмотренных трех опера­

ций (создание пакетов, тасование и повторение) мы проведем ряд экспери­
ментов с ними в различных порядках. Первым делом мы объединим опера­
ции в порядке

( 1)

тасование,

(2)

создание пакетов и

(3)

повторение:

[5181 ~----------·-·-----~---

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

## Порядок 1: тасование -> создание пакетов -> повторение
>>> tf.random.set_seed(l)
>>> ds = ds_joint.shuffle(4) .batch(2) .repeat(3)
>>> for i, (batch_x, batch_y) in enumerate(ds):
(2, 3)
1 (2, 3)
2 (2, 3)
3 ( 2' 3)
4 ( 2' 3)
5 (2, 3)
о

priot.(i, batch_x.shape, batch_y.numpy())
[2 1]
[О 3]
[О 3]
[1 2]
[3 0]
[1 2]

А теперь давайте испытаем другой порядок

(1)

тасование и

(3)

-

(2)

создание пакетов,

повторение:

## Порядок 2: создание пакетов -> тасование ->повторение
>>> tf. random. set_seed (1)
>>> ds = ds_joint.batch(2) .shuffle(4) .repeat(3)
>>> for i, (batch_x, batch_y) in enumerate (ds):
print. (i, batch_x. shape, batch_y .numpy ())
О

(2, 3)
1 (2, 3)
2 (2, 3)



1]
[2 3]
[О 1]

3 (2, 3) [2 3]
4 (2, 3)

[2 3]

5 (2, 3) [0 1]
В то время как первый пример кода (тасование, создание пакетов, повто­
рение), похоже, перетасовал набор данных ожидаемым образом, во втором
сценарии (создание пакетов, тасование, повторение) мы видим, что элемен­

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

чения, у. Все пакеты включают либо пару значений
тавшуюся пару значений
перестановки:

[ у=2,

[ у=О,

у= 1]

,

либо ос­

мы не наблюдаем другие возможные

[ у=2, у=З] ;
[ y=l, у=З]

у=О],

и т.д. Обратите внимание, что для

гарантии несовпадения этих результатов вы

лать повторение с числом более

3,

скажем,

можете принять решение сде­

. repea t ( 2 О) .

Итак, сумеете ли вы предсказать, что произойдет в случае применения
операции тасования после операции повторения, например,
кетов,

(3)

повторение,

(1)

тасование? Проверьте.

- - - - - - - · - - - - - - - [519]

(2)

создание па­

[лава

13.

Распараллеливание процесса обучения нейронных сетей с помощью

TensorF/ow

Распространенной ошибкой является двукратный вызов метода

.batch ()

для строки в наборе данных. В таком случае извлечение

элементов из результирующего набора данных создаст пакет пакетов

образцов. По существу каждый вызов

. Ьа tch ()

на наборе данных

будет увеличивать на единицу ранг извлеченных тензоров.

Создание набора данных из файnов на nокаnьном диске
В настоящем разделе мы построим набор данных из файлов с изображе­
ниями, которые хранятся на диске. В архиве с примерами для главы имеется

подкаталог са t
котов и собак

_ dog_ images, содержащий
в формате JPEG.

шесть файлов с изображениями

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

ки

TensorF\ow: tf. io для чтения содержимого файла с изображением и
tf. image для декодирования низкоуровневого содержимого и изменения

размеров изображения.

[:~ Модули tf. io и tf. image

н~ Модули tf. io и tf. image предоставляют много дополнительных
заметку!

и полезных функций, рассмотрение которых не входит в цели,

преследуемые книгой. Вы можете ознакомиться с этими функция­
ми в официальной документации:

• https://www.tensorflow.org/versions/r2.0 /api_docs/
python/tf/io для tf. io
• https://www.tensorflow.org/versions/r2.0 /api docs/
python/tf/ image для tf. image
Прежде чем начать, давайте взглянем на содержимое файлов с изобра­
жениями. Мы будем генерировать список файлов с изображениями посред­

ством библиотеки

pathlib:

>>> .LШfcKJ!:\: pathlib
>>> imgdir_paEh = pathlib.Path( 'cat_dog_images')
>>> file_list = sorted( [str(path) .for path in
imgdir_path. glob ( '*. jpg')] )

(520)----

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

['cat_dog images/dog-03.jpg', 'cat_dog_images/cat-01.jpg',
'cat_dog_images/cat-02.jpg', 'cat_dog_images/cat-03.jpg',
'cat_dog images/dog-01.jpg', 'cat_dog_images/dog-02.jpg']
Далее мы визуализируем примеры изображений с применением

Matplotlib:

>>> i mport matplotlib.pyplot as plt

»> fig = plt. figure ( figsize= ( 10, 5))
>>> for i, file in en·u merate (file_list):

img_raw = tf.io . read_file(file)
img = tf . image. decode _ image ( img_raw)
print( 'Форма изображения: ', img.shape)
ах= fig.add_subplot(2, 3, i+l)
ax.set_xticks([]); ax.set_yticks([])
ax.imshow(img)
ax.set_title(os.path.basename(file), size=15)
>>> plt.tight_layout()
»> plt. show ()
Форма изображения :
(900, 1200, 3)
Форма изображения:
(900, 1200, 3)
Форма изображения:
(900, 1200, 3)
Форма изображения:
(900, 742, 3)
Форма изображения:
(800, 1200, 3)
Форма изображения:
(800, 1200, 3)
Примеры изображений показаны на рис.

Рис.

13.3.

13.3.

Примеры изображений котов и собак

[521 ] -

- -- -

- - - -- - - -

[лава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorFlow

Благодаря одной лишь визуализации и выведенным формам изображений

мы уже видим, что изображения имеют разные соотношения размеров сто­
рон. Если вы выведете соотношения размеров сторон (или формы массивов

данных) этих изображений, то заметите, что некоторые изображения облада­
ют высотой

900 пикселей и шириной 1200 пикселей (900 х 1200), некоторые
1200, а одно - 9ООх742. Позже мы выполним предваритель­

являются 8ООх

ную обработку изображений, приведя их к согласованным размерам. Еще
один момент, который необходимо учесть, заключается в том, что метки для
изображений предоставляются внутри их имен файлов. Таким образом, мы
извлекаем метки для изображений из списка имен файлов, назначая метку 1
изображениям собак и метку О изображениям котов:

(1 if 'dog' in os.path.basename(file) else
for file in file_list]
>>> print(labels)
(1, О, О, О, 1, 1]

>>> labels

=

О

Теперь у нас есть два списка: список имен файлов (или путей к каждо­
му изображению) и список их меток. В предыдущем разделе вы узнали два
способа создания общего набора данных из двух тензоров. Здесь мы будем
использовать второй подход:

>>> ds files labels

= tf.data.Dataset.from_tensor slices(
(file_list, labels))

>>> for item in ds - files - labels:

print(item[O] .numpy(), item[l] .numpy())
b'cat_dog_images/dog-03.jpg' 1
b'cat_dog_images/cat-01.jpg' О
b'cat_dog_images/cat-02.jpg' О
b'cat_dog_images/cat-03.jpg' О
b'cat_dog_images/dog-01.jpg' 1
b'cat_dog_images/dog-02.jpg' 1
Мы назвали этот набор данных

ds _ files _ labels,

т.к. он содержит

имена файлов и метки. Далее нам нужно применить к набору данных

ds _ files _labels

трансформации: загрузить содержимое изображения из

его файла, декодировать низкоуровневое содержимое и изменить размеры до
желаемых, скажем,

80 х 120.

Ранее уже было показано, как применять лям­

бда-функцию с использованием метода

[522)

. map ( ) .

Однако поскольку на этот

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью Teпs orF/ow

раз нам необходимо применить множество шагов предварительной обработ­

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

. map ( ) :

>>> de f load_and_preprocess (path, label):
image = tf.io.read_file(path)
image = tf.image.decode_jpeg(image, channels=З)
image = tf.image.resize(image, [img_height, img_width])
image /= 255. О
r eti1 r n image, label

»> img_width, img_height = 120, 80
>>> ds_images_labels = ds_files_labels . map(load_and_preprocess)
>>>
»> fig = plt. figure ( figsize= ( 10, 6))
>>> f o .r i, example in enume rate (ds_images_labels) :
ах= fig.add_suЬplot(2, З, i+l)
ax.set_xticks([]); ax.set_yticks([])
ax.imshow(example[O])
ax.set_title('{}'.format(example[l] .numpy()),
size=15)
>>> plt.tight_layout()
>>> plt . show ()
Результатом будет визуализация извлеченных примеров изображений
вместе с их метками , представленная на рис.

Рис.

- - - -- - --

1

о

о

о

1

1

13.4.

-

13.4.

Извл еченны е при'11еры изображений вместе с их .'11еmками

- - --

-

- [523)

- - -

-- - - - - -- -

-

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью

Функция

TensorF/ow

является оболочкой для всех четы­

load_and_preprocess ()

рех шагов, включая загрузку низкоуровневого содержимого, его декодирова­

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

Извnечение доступных наборов данных
из бибnиотеки
Библиотека

tensorf low da tasets
tensorflow_ datasets

предлагает аккуратную коллекцию

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

или иной набор данных, в форме

BibTeX.

Еще одно преимущество в том,

что все наборы данных предварительно обработаны и готовы к использова­
нию как объекты

tf. data. Dataset,

поэтому можно напрямую применять

все функции, раскрытые в предшествующих разделах. Итак, давайте взгля­
нем на эти наборы данных в действии.
Прежде

datasets

всего,

потребуется установить библиотеку

посредством

pip

tensor f low

в командной строке:

pip install tensorflow-datasets
А теперь импортируем модуль

tensorflow_ datasets

и посмотрим на

список доступных наборов данных:

>>> iшport tensorflow_datasets as tfds
>>> print ( len (tfds. list_builders ()))
101
>>> print(tfds.list_builders() [:5])
['abstract_reasoning', 'aflw2k3d', 'amazon_us reviews',
'bair_robot_pushing_small', 'Ьigearthnet']
Выполнение предыдущего кода показывает, что в текущее время доступен

101

набор данных (на момент написания главы, но их количество, вероятно,

будет увеличиваться), и мы выводим первые пять наборов данных. Извлечь
набор данных можно двумя способами, которые мы раскроем ниже на при­
мере извлечения двух наборов данных

-

----------(524]

CelebA ( celeb_а)

и

MNIST.

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

Первый подход состоит из трех шагов.

1.

Вызов функции построителя набора данных.

2.

Выполнение метода

3.

Вызов метода

download_and_prepare ().

as _ dataset ().

Давайте отработаем первый шаг для набора данных

CelebA

и выведем

соответствующее описание, которое предоставляется внутри библиотеки:

>>> celeba _ Ьldr = tfds. builder ( 'celeb_а' )
>>> p.rint

(celeba_Ыdr. info. features)
FeaturesDict({'image': Image(shape=(218, 178, 3), dtype=tf.uint8),
'landmarks': FeaturesDict({'lefteye_x': Tensor(shape=(),
dtype=tf.int64), 'lefteye_y': Tensor(shape=(), dtype=tf.int64),
'righteye_x': Tensor(shape=(), dtype=tf.int64), 'righteye_y':

>>>

print(celeba_Ыdr.info.features['image'])

Image(shape=(218, 178, 3), dtype=tf.uint8)
>>>

print(celeba_Ыdr.info.features['attributes']

.keys())

dict_keys(['S_o_Clock_Shadow', 'Arched_Eyebrows',
>>>

print(celeba_Ьldr.info.citation)

@inproceedings{conf/iccv/LiuLWT15,
added-at = {2018-10-09ТОО:ОО:ОО.000+0200},
author = {Liu, Ziwei and Luo, Ping and Wang, Xiaogang and Tang, Xiaoou},
biburl = {https://www.bibsonomy.org/bibtex/250e4959beбldЬ325d2
f02cld8cd7bfbЬ/dЬlp},

booktitle = {ICCV},
crossref = {conf/iccv/2015},
ее= {http://doi.ieeecomputersociety.org/10.1109/ICCV.2015.425},
interhash = {3f735aaa11957e73914bbe2ca9d5e702},
intrahash = {50e4959beбldЬ325d2f02cld8cd7bfbb},
isbn = {978-1-4673-8391-2},
keywords = {dЬlp},
pages = {3730-3738},
puЫisher = { IEEE Computer Society},
timestamp = {2018-10-11Т11:43:28.000+0200},
title = {Deep Learning Face Attributes in the Wild.},
url={http://dЬlp.uni-trier.de/dЬ/conf/iccv/iccv2015.html#LiuLWТ15},

year

=

2015

}

-------(525)-----------

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

В результате мы получаем полезную информацию, способствующую по­

ниманию структуры набора данных
варя с тремя ключами:

CelebA. Признаки хранятся в виде
'image ', 'landmarks' и 'attributes'.

сло­

Элемент

' image' ссылается на изображение лица знаменитости. Элемент
' landmar ks ' ссылается на словарь извлеченных точек лица, таких как мес­
тоположение глаз, носа и т.д. Элемент 'attributes' представляет собой
словарь из 40 атрибутов лица человека на изображении вроде мимики, ма­
кияжа, прически и т.п.

Затем мы вызовем метод

download_ and_prepare () .

Он загрузит дан­

ные и сохранит их на диске в указанном подкаталоге для всех наборов дан­
ных

TensorF\ow.

Если вы уже делали это ранее, то метод просто проверит,

загружены ли данные, и не будет загружать их повторно в случае существо­
вания данных в указанном местоположении:

>>>

celeba_Ыdr.download_and_prepare()

Далее мы создаем объект набора данных:

>>> datasets = celeba Ыdr.as dataset(shuffle_files=False)
>>> datasets.keys()
dict_keys(['test', 'train', 'validation'])
Набор данных уже расщеплен на обучающий, испытательный и прове­
рочный наборы. Чтобы посмотреть, как выглядят примеры изображений, мы
можем выполнить следующий код:

>>> ds _ train = datasets [ 'train' ]
>>> assert isinstance(ds_train, tf.data.Dataset)
>>> example = next(iter(ds_train))
>>> prin.t (type (example))

>>> print(example.keys())
dict_keys(['image', 'landmarks', 'attributes'])
Обратите внимание, что элементы в этом наборе данных поступают в
словаре. Если мы хотим передать такой набор данных модели ГО с учите­

лем во время обучения, тогда должны переформатировать его в виде корте­
жа (признаки, метка). Для метки мы будем использовать категорию

'Male'

из атрибутов. Мы достигнем этого за счет применения трансформации через

map():
------------(526)--

[лава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

>>> ds_train =

item:
(item[ 'image'],
tf.cast(item['attributes'] ['Male'], tf.int32)))

ds_train.map(lamЬda

В заключение давайте создадим пакеты из набора данных, извлечем па­
кет, содержащий

18

образцов, и визуализируем их вместе с метками:

>>> ds_train = ds_train.batch(18)
>>> images, labels = next (iter (ds_train))
>>> print(images.shape, labels)
(18, 218, 178, 3) tf.Tensor([O О О 111О11О11О1О111],
shape=(18, ), dtype=int32)
>>> fig = plt.figure(figsize=(12, 8))
>>> for i, (image, label) in enuшerate (zip (images, labels)):
ах= fig.add_subplot(З, 6, i+l)
ax.set_xticks([]); ax.set_yticks([])
ax.imshow(image)
ax.set_title('{}'.format(label), size=15)
>>> plt. show ()
Образцы и их метки, извлеченные из набора данных
ны на рис.

Рис.

ds _ train,

показа­

13.5.

1

1

о

1

1

1

о

о

о

1

1

о

1

о

1

о

о

о

13.5.

Образцы и их метки, извлече11ные из набора данных

-

- - [527] -

-

-- - --

ds_ train

-- - -

[лава

13.

Распаралпепивание процесса обучения нейронных сетей с помощью TeпsorF/ow

Вот и все, что потребовалось предпринять для получения и использова­

ния набора данных с изображениями

CelebA.

Мы продолжим исследованием второго подхода к получению набора

tensorflow_ datasets. Существует функция-оболочка по име­
load (), которая объединяет три шага по извлечению набора данных в

данных из
ни

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

MNIST:

>>> mnist, mnist_info = tfds.load( 'mnist', with_info=Trt1e,
shuffle_files=Fa1se)
>>> pr.:шt (mnist_info)
tfds.core.Datasetlnfo(
name= 'mnist',
version=l.0.0,
description='The МNIST database of handwritten digits. ',
urls=['https://storage.googleapis.com/cvdf-da tasets/mnist/'],
features=FeaturesDict({
'image': Image(shape=(28, 28, 1), dtype=tf.uint8),
'label': ClassLaЬel(shape=(), dtype=tf.int64, num_classes=lO)
}

,

total_num_examples=70000,
splits={
'test': ,
'train':
}

,

supervised_keys=('image', 'label'),
citation="""
@article{lecun2010mnist,
title={МNIST handwritten digit database},
author={LeCun, Yann and Cortes, Corinna and Burges, CJ},
journal={ATT LaЬs [Online]. AvailaЫist},
volume={ 2},
year={2010}

""" ,
redistribution_info=,
>>> print(mnist.keys())
dict keys ( [ 'test', 'train'] )

·--(528]

Глава

13.

Распараллеливание процесса обучения нейронных сетей с пом ощью

Легко заметить, что набор данных

MNIST

TensorF/ow

разбит на две части. Теперь мы

можем взять часть для обучения, применить трансформацию с целью преоб­
разования элементов из словаря в кортеж и визуализировать



образцов:

>>> ds _ train = mnist [ 'train' ]
>>> ds_train = ds_train.map(lamЬda item:
(item[ 'image'], item[ 'label']))
>>> ds_train = ds_train.batch(lO)
>>> batch = next (iter (ds_train))
>>> print(batch[O] .shape, batch[l))
(10, 28, 28, 1) tf.Tensor([8 4 7 7 О 9 О 3 3 3], shape=(lO,),
dtype=int64)
>>> fig = plt.figure(figsize= (15, 6))
>>> for i, (image,label) in enumerate(z i p(batch[O], batch[l])):
ах• fig.add_subplot(2, 5, i+l)
ax.set_xticks([]); ax.set_yticks([])
ах. imshow(image [:, : , О], cmap""' gray_r')
ax.set_title('{}'.format(label), size=15)
>>> plt. show ()
Извлеченные примеры рукописных цифр из набора данных
демонстрированы на рис.

Рис.

MNIST

про­

13.6.

Ш~@Ш§J
lQl Г§l lIO ml7l

13.6.

Извлеченные примеры рукописных цифр из набора да1111ых

MNIST

На этом наш обзор построения наборов данных и манипулирования ими,

а также получения наборов данных из библиотеки

t e nso r f l ow_ data s e t s

завершен. Далее мы выясним, как строить нейросетевые модели в

(529) - -

TensorFlow.

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

\\а~ Руководство по стилевому оформлению кода

Tensorflow

н~ Обратите внимание, что в официальном руководстве по стилево­
заметку!

му оформлению кода TensorFlow (https: / /www. tensorflow.
org / communi ty / s tyle _guide) рекомендуется использовать
для отступов в коде двухсимвольные промежутки. Тем не ме­

нее, в настоящей книге для отступов применяются четыре сим­

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

стилевому оформлению кода
лять синтаксис

Python

и помогает корректно выде­

во многих текстовых редакторах и

сопровождаю­

щих тетрадях

Jupyter Notebook (https: / /gi thub. com/rasbt/
python-machine- learning-book-Зrd-edi tion).

Построение нейросетевой модели в

TensorFlow

До сих пор в главе речь шла о базовых служебных компонентах

TensorFlow,

предназначенных для манипулирования тензорами и упорядочения данных

в форматы, которые допускают выполнение прохода во время обучения.
В текущем разделе мы, наконец, реализуем первую прогнозирующую мо­

дель в

TensorFlow.

Поскольку библиотека

же и сложнее библиотек для МО вроде

TensorF\ow
scikit-learn,

немного гибче, но так­
мы начнем с простой

линейной регрессионной модели.

АРl-интерфейс

Keras

Keras

в

Tensorflow (tf. keras)

представляет собой высокоуровневый АРI-интерфейс для нейрон­

ных сетей, который изначально разрабатывался с целью запуска поверх дру­

гих библиотек, таких как

TensorF\ow

и

Theano.

Библиотека

Keras

предлагает

дружественный к пользователю и модульный программный интерфейс, поз­
воляющий легко прототипировать и строить сложные модели с помощью

всего лишь нескольких строк кода.

PyPI

Keras

можно установить независимо от

и затем сконфигурировать для использования

своего внутреннего механизма. Библиотека

TensorF\ow и ее модули доступны
модуль tf. keras стал основным

через

Keras
tf. keras.

TensorF\ow

в качестве

тесно интегрирована в

В версии

TensorF\ow 2.0

и рекомендуемым подходом к реализации

моделей. Его преимущество связано с тем, что он поддерживает специфич­

ную для

TensorF\ow

функциональность, такую как конвейеры наборов дан-

- - - - - - - - - - - - - - - [530]

fлава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

ных посредством

t f. da ta,

которые обсуждались в предыдущем разделе.

Для построения нейросетевых моделей в книге мы будем применять модуль

tf. keras.
Как вы увидите в последующих подразделах, АРI-интерфейс

(tf. keras)

Keras

делает построение нейросетевых моделей исключительно лег­

ким. Наиболее часто используемый подход к построению нейронной сети
в

TensorFlow

предусматривает применение класса

tf. keras. Sequential,

который позволяет укладывать стопкой слои для формирования сети. Стопка
слоев может быть предоставлена модели, определенной как

Sequential (),

в списке

Python.

tf. keras.

В качестве альтернативы слои могут до­

бавляться по одному с использованием метода

. add ( ) .

Кроме того,

tf. keras делает возможным определение модели за счет со­
здания подкласса класса tf. keras .Model. Это дает нам больший контроль
над прямым проходом путем определения в классе модели метода call ()
для явного указания прямого прохода. Мы рассмотрим примеры обоих под­

ходов к построению нейросетевых моделей с применением АРI-интерфейса

tf. keras.
Наконец, как будет показано в последующих подразделах, построенные с
использованием АРI-интерфейса
ваны и обучены с помощью

tf. keras модели могут быть
методов . compile () и . fit ().

скомпилиро­

Построение nинейной реrрессионной модеnи
В текущем подразделе мы построим простую модель для решения задачи

линейной регрессии. Первым делом мы создадим игрушечный набор дан­
ных в

NumPy

и визуализируем его:

>>> X_train = np.arange (10). reshape ( (10, 1))
>>> y_train = np.array([l.O, 1.3, 3.1, 2.0, 5.0, 6.3,
6.6, 7.4, 8.0, 9.0])

>>> plt .plot (X_train, y_train,
>>> plt .xlabel ( 'х')

'о',

markersize=lO)

»> plt.ylabel ('у')
>>> plt.show()
В результате обучающие образцы отобразятся в виде графика рассеяния,
показанного на рис.

13.7.

[531] - - - - - - - - - - - - - - - - - - - - -

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow



9



• •

в

7
б



>- 5

4



3
2

• •

1



2

о

б

4

8

х

Рис.

Затем

мы

13. 7.

График рассеяния с пбучающи;wи образца.ни

стандартизируем

(центрировав

признаки

вокруг средне­

го и поделив на стандартное отклонение) и создаем объект

из

Dataset

TensorFlow:
>>> Х train norm = (Х train - np.mean(X_train))/np.std(X_train)
>>> ds_train orig = tf.data.Dataset.from_tensor_slices(
(tf.cast(X_train_norm, tf.float32),
tf.cast(y_train, tf.float32)))
Далее

z

= wx +

В

определить

можем

мы

модель для

линейной

регрессии

Ь. Здесь мы собираемся воспользоваться АРl-интерфейсом

tf. keras

как

Keras.

имеются предварительно определенные слои для построения

сложных нейросетевых моделей, но сначала нужно выяснить, как опреде­

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

Для задачи регрессии мы определим новый класс, производный от клас­

са

tf. keras. Model.

Создание подкласса

пользовать инструменты

Keras

tf. keras. Model

позволяет ис­

для исследования, обучения и оценки моде­

ли. В конструкторе нашего класса мы определим параметры модели,

w

и Ь,

которые соответствуют параметрам веса и смещения. В заключение мы оп­

ределим метод

call ()

для установления того, как модель будет генериро­

вать свой выход на основе входных данных:

---------- ------- ----- ----- --- [532] -- ---- - - -- ----- -- - -- -

Глава

>>> c1ass

13.

Распараллеливание процесса обучения нейронных сетей с помощью

TensorF/ow

МуМоdеl

def

(tf. keras .Model):
init (~,i•):
super(MyModel, , .1·). init ()
:''~:.:.w = tf.VariaЬle(O.O, name='weight~)
'"'.:.:. i' .Ь = tf. VariaЫe (0. О, name='Ьias')

def call (:'(' U,

х):

ret1.irr! '';>

ci•эf

14.

loss_fn(y_true, y_pred):
tf.reduce_mean(tf.square(y_true -

xet1. 1пJ.

>>>

dc::f

Ниже приведен код:

у

pred))

train (model, inputs, outputs, learning_rate):
tf.GradientTape() >> ds_train_orig = ds_orig.take(lOO)
>>> ds_test = ds_orig.skip(lOO)
Далее, как уже демонстрировалось в предшествующих разделах, нам не­

обходимо применить трансформацию посредством метода

. map ( ) ,

чтобы

преобразовать словарь в кортеж:

>>> ds_train_orig = ds_train_orig.map(
lamЬda х: (х [ 'features'], х [ 'label']))
>>> ds_test = ds_test.map(
lamЬda х: (х [' features'] ,

х

[ 'label'] ) )

Теперь мы готовы воспользоваться АРl-интерфейсом

Keras

для эффектив­

ного построения модели. В частности, с применением класса

Sequential

мы можем уложить стопкой несколько слоев

Keras

tf. keras.
и построить

Keras находится по ссылке
https://www.tensorflow.org/versions/r2.0 /api_docs/python/
tf/keras/layers. Для решения нашей задачи мы собираемся использо­
вать слой Dense (tf. keras. layers. Dense), который также известен как
пошюсвязный (/11/ly coш1e>> iris_model = tf.keras.Sequential([
tf.keras.layers.Dense(lб, activation='sigmoid',
name=' fcl', input_shape=(4,)),
tf.keras.layers.Dense(3, name='fc2',
activation='softmax')])
>>> iris_model.summary()
Model: "sequential"
Layer (type)

Output Shape

Param #

fcl (Dense)

(None, 16)

80

fc2 (Dense)

(None, 3)

51

Total params: 131
TrainaЫe params: 131
Non-trainaЫe params:

О

Обратите внимание, что мы определили форму входа для первого слоя

input shape= ( 4, ) и потому уже не обязаны вызывать
. build () для использования iris _model. sununary ().
Выведенная сводка по модели показывает, что первый слой (fcl) имеет
80 параметров, а второй - 51 параметр. Вы можете проверить это с помо­
количество входных элементов и
щью выражения (п;п + 1) х поиt• где п;п посредством

nout -

количество выходных элементов. Вспомните, что для полносвязного

(плотного) слоя обучаемыми параметрами являются матрица весов размера
п;п х поиt и вектор смещений размера поиt· Кроме того, как видите, мы при---------~[540)

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

меняем сигмоидальную функцию активации для первого слоя и многопе­
ременную активацию для последнего (выходного) слоя. Мноrопеременная
активация в последнем слое

используется с целью поддержки

многоклас­

совой классификации, поскольку у нас есть три метки классов (вот почему
выходной слой содержит три нейрона). Мы обсудим различные функции ак­
тивации и их приложения позже в главе.

Затем мы скомпилируем модель, указав функцию потерь, оптимизатор и
метрики для оценки:

>>> iris_model.compile(optimizer='adam',

loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
Теперь мы можем обучить модель. Мы укажем
и

2

100

для количества эпох

для размера пакета. В приведенном ниже коде мы построим бесконечно

повторяющийся набор данных, который будет передаваться методу

для обучения модели. В рассматриваемом случае, чтобы метод

f i t ()
fi t () имел

возможность отслеживать эпохи, он должен знать количество шагов в каж­

дой эпохе.

Имея размер обучающих данных

(100)

и размер пакета

можем определить количество шагов в каждой эпохе

(batch_size), мы
(steps _per_ epoch):

>>>
>>>
>>>
>>>

num_ epochs = 100
training_size = 100
batch size = 2
steps_per_epoch = np.ceil(training_size / batch_size)

>>>
>>>
>>>
>>>

ds _ train
ds_train
ds_train
ds_train

=
=
=
=

ds _ train_ orig. shuf.fle (buffer_ size=training_size)
ds_train.repeat()
ds_train.batch(batch_size=batch_size)
ds_train.prefetch(buffer_size=lOOO)

>>> history = iris_model.fit(ds_train, epochs=num_epochs,

steps__per_epoch=steps_per_epoch,
verbose=O)
Возвращенная переменная

history

хранит потерю при обучении и пра­

вильность при обучении (т.к. они были указаны в качестве метрик в вызове

iris _ model. compile

())после каждой эпохи. Мы можем задействовать ее

для визуализации кривых обучения:

--------(541)-------------

fлаеа 13. Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

>>> hist = history.history
>>> fig = plt.figure(figsize=(12, 5))
>>>ах= fig.add_suЬplot (1, 2, 1)
>>> ax.plot(hist['loss'], lw=З)
>>> ax.set_title('Пoтepя при обучении', size=15)
>>> ax.set_xlabel('эпoxи', size=15)
>>> ax.tick_params(axis='both', which='major', labelsize=lS)
>>>ах= fig.add_subplot (1, 2, 2)
>>> ax.plot(hist['accuracy'], lw=З)
>>> ах.sеt_titlе('Правильность при обучении', size=15)
>>> ах. set _ xlabel ('эпохи', size=lS)
>>> ax.tick_params(axis='both', which='major', labelsize=15)
>» pl t. show ()
Кривые обучения (потеря при обучении и правильность при обучении)
показаны на рис.

13.9.
Правильность при обучении

Потеря при обучении

17

0.9-

10
0.8
Ofl
U/-

06
Од

0.6 -

u:г

0.5
f\O

.1']

о

100

70

10

80

!iO

HJo)

Эпохи

Эпохи

Рис.

13.9.

Кривые обучепия

Оценка обученной модеnи на испытатеnьном наборе данных
Поскольку для метрики оценки в
значение

'accuracy',

iris _model. compile ()

было указано

теперь мы можем напрямую оценить модель на ис­

пытательном наборе данных:

>>> results ~ iris_model.evaluate(ds_test.batch(50), verbose=O)
Правильность при
>>> prin t ('Потеря при испытании: { : . 4 f}
испытании: {:. 4f}'. format (*results))
Правильность при испытании: О. 9800
Потеря при испытании: О. 0692

-

--[542)

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью

TensorF/ow

Обратите внимание, что мы также должны создать пакеты из испытатель­
ного набора данных, чтобы обеспечить корректность измерения (ранг) входа
модели. Как обсуждалось ранее, вызов

извле­

ченных тензоров на

иметь

1.

Входные

. batch () увеличивает ранг
данные для . evaluate () обязаны

одно назначенное измерение для пакета, хотя здесь (при оценке) размер паке­
та роли не играет. Таким образом, если мы передаем

методу

. evaluate

(),то весь испытательный набор данных будет обрабо­

тан в одном пакете размером
обработаются

50

ds_batch.batch(50)

50,

но случае передачи

пакетов размером

ds _ Ьа tch. Ьа tch ( 1)

1.

Сохранение и повторная загрузка обученной модеnи
Обученные модели можно сохранять на диске для применения в буду­
щем. Вот как это делается:

>>> iris_model.save('iris-classifier.hS',
overwrite=1'rпe,
include_optimizer=Trпe,

save_format='hS')
В первом параметре указывается имя файла. Вызов

iris _ model. save ()

сохранит архитектуру модели и все параметры, которые она узнала. Тем
не менее, если вы хотите сохранить только архитектуру, тогда можете вос­

пользоваться методом

iris_model.to_json(), который сохраняет кон­
фигурацию модели в формате JSON. Если же вы хотите сохранить только
веса модели, то можете вызывать метод iris _model. save _ weights ().
В save _ format можно указать либо 'h5' для формата HDF5, либо 'tf'
для формата TensorF\ow.
А теперь давайте повторно загрузим сохраненную модель. Поскольку
мы сохранили и архитектуру, и веса модели, повторно загрузить параметры

очень легко с помощью единственной строки кода:

>>> iris_model_new

=

tf.keras.models.load_model('iris-classifier.hS')

Попробуйте проконтролировать архитектуру модели, вызвав

iris _ model _

new. summary ().
Наконец, мы оценим новую повторно загруженную модель на испыта­

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

------------------------ [543] -

fлава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

>>> results = iris_rnodel_new.evaluate(ds_test.batch(33), verbose=O)
>>> pr in t. ('Потеря при испытании: {: . 4 f}
Правильность при
испытании:

Потеря при

{:. 4f}'. forrnat (*results))
испытании: О. 0692
Правильность

при испытании:

О.

9800

Выбор функций активации для
мноrослойных нейронных сетей
До сих пор ради простоты в контексте многослойных нейронных сетей

прямого распространения мы обсуждали только сигмоидальную функцию
активации; она использовалась в скрытом и выходном слоях при реализации

многослойного персептрона в главе

12.

Обратите внимание, что для краткости в этой книге мы называем сиг-

моидальную логистическую функцию и ( z) =-1

1 _,



просто сuгмоuдаr1ыюzi

функцией, что общепринято в литературе по МО. В последующих подразде­

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

Формально в качестве функции активации в многослойных нейронных

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

как в

Adaline

(см. главу

2).

Однако на практике было бы не особенно по­

лезно применять линейные функции активации для скрытого и выходного
слоев, потому что мы хотим привнести нелинейность в типовую искусст­

венную нейронную сеть, чтобы получить возможность решения сложных

задач. В конце концов, сумма линейных функций дает линейную функцию.
Логистическая (сигмоидальная) функция активации, которую мы исполь­
зовали в главе
в мозге

-

12,

пожалуй, наиболее близко имитирует концепцию нейрона

мы можем думать о ней, как о вероятности, возбудится нейрон

или нет. Тем не менее, логистическая (сигмоидальная) функция активации
может оказаться проблематичной при наличии высокого отрицательного
входа, т.к. в этом случае выход сигмоидальной функции будет близким к
нулю. Если сигмоидальная функция возвращает выход, близкий к нулю, тог­
да нейронная сеть будет обучаться очень медленно и возрастет вероятность
того, что во время обучения она попадет в локальные минимумы. Именно
потому разработчики часто отдают предпочтение гиперболическому танген­
су в качестве функции активации внутри скрытых слоев.

- - - - - - - - - - - - - - - - - - (544] --------·--------------

Глава

13.

Распараллеливание процесса обуцения нейронных сетей с помощью TeпsorF/ow

До того, как перейти к обсуждению функции гиперболического тангенса,

давайте подведем краткие итоги по основам логистической функции и рас­
смотрим обобщение, которое делает ее более полезной для задач многознач­
ной классификации.

Краткое повторение nоrистической функции
Как упоминалось в начале раздела, логистическая функция фактически
является особым случаем сигмоидальной функции. В разделе, посвященном
логистической регрессии, главы

3

речь шла о том, что мы можем применять

логистическую функцию для моделирования вероятности того, что образец
х принадлежит к положительному классу (классу

1)

в задаче двоичной клас­

сификации.

Общий вход
Z

z

показан в следующем уравнении:


Т
= W0X0 + W1X1 +···+ WmXm = ..::.,i=O
W;X; = W Х

Вот как будет вычисляться логистическая функция:

Флогистическая( z) =-1

1



Обратите внимание, что
ресечение с осью у, т.е. х0

-z

w0

представляет собой элемент смещения (пе­

= О).

Чтобы привести более конкретный пример,

предположим, что у нас есть модель с двумерными точками данных х и сле­

дующими весовыми коэффициентами, присвоенными вектору

w:

>>> import numpy as np
>>>

Х

»> w

= np.array( [1, 1.4, 2.5]) ##первое значение должно быть равно 1
=

np.array( [0.4, 0.3, 0.5])

>>> def net_input (Х, w):
return np.dot(X, w)

>>> def logistic(z):
return 1.0 / (1.0 + np.exp(-z))

>>> def logistic_activation (Х, w):
z

=

net_input

(Х,

w)

return logistic(z)

>>> pririt('P(y=llx)

=

%.Зf'

% logistic_activation(X, w))

P(y=llx) = 0.888

-----------(545]-·----·-·---------·-----

f11ава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

Если мы рассчитаем общий вход

(z)

и используем его для логистической

активации нейрона с указанными значениями признаков и весовыми коэф­
фициентами, то получим значение О.

888,

которое можно интерпретировать

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

В главе

12

мы применяли прием унитарного кодирования для представле­

ния многоклассовых достоверных меток и спроектировали выходной слой,

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

множество элементов с логистической активацией, не выпускает содержа­
тельные и интерпретируемые значения вероятностей:

>>> # W : массив с формой= (n_output_units, n_hidden_units+l)
>>> #
первый столбец содержит элементы смещения
>>> W = np.array([[l.1, 1.2, 0.8, 0.4],
[0.2, 0.4, 1.0, 0.2],
[0.6, 1.5, 1.2, о. 7]])
>>> #А : массив данных с формой= (n_hidden_units + 1, n_samples)
>>> #
первый столбец должен содержать значения 1
»>А= np.array([[l, 0.1, 0.4, 0.6]])
»> Z = np.dot (W, А[О])
>>> y_probas = logistic(Z)
>>> рrint('Общий вход: \n', Z)
Общий вход:

[ 1. 7 8 о. 7 6 1. 65]
>>> print ('Выходные

элементы:

\n', y_probas)

Выходные элементы:

[ 0.85569687

0.68135373

0.83889105]

В выводе видно, что результирующие значения не могут интерпретиро­

ваться как вероятности для задачи с тремя классами. Причина в том, что
значения не дают в сумме

1.

Темне менее, этот факт не является крупной

проблемой, если мы используем модель только для прогнозирования меток

классов, а не вероятностей членства в классах. Один из способов прогно­
зирования метки класса на основе полученных ранее выходных элементов
предусматривает применение значения максимума:

>>> y_class = np.argmax(Z, axis=O)
>>> print( 'Спрогнозированная метка
Спрогнозированная метка класса:

класса:

О

----[546)--

%d' % y_class)

[лава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

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

тическую функцию

softmax,

-

многопеременную логис­

которая поможет решить такую задачу.

Оценка вероятностей классов в мноrоклассовой классификации
через мноrопеременную nоrистическую функцию
В предыдущем разделе мы показали, как можно получить метку класса
с использованием

функции

argmax.

Ранее в разделе "Построение много­

слойного персептрона для классификации цветков в наборе данных

Iris"

в

последнем слое модели на основе многослойного персептрона мы опреде­

ляли

activation=' softmax'. Функция softmax является мягкой формой
функции argmax; вместо предоставления одиночного индекса класса она
выдает вероятность каждого класса. Следовательно, она позволяет вычис­

лять содержательные вероятности в многоклассовых окружениях (полино­
миальная логистическая регрессия).

В функции
щим входом

z

softmax

вероятность того, что определенный образец с об­

принадлежит i-тому классу, может быть вычислена с помо­

щью члена нормализации в знаменателе, т.е. суммы экспоненциально взве­

шенных линейных функций:

Чтобы посмотреть на функцию
соответствующий код

softmax

в действии, давайте напишем

Python:

>>> def softmax (z):
ret1н:n

np.exp(z) /

np.sшn(np.exp(z))

>>> y_probas = softmax (Z)
>>> prin t ( 'Вероятности: \n' ,

у_probas)

Вероятности:

[ 0.44668973

>>>

0.16107406

0.39223621]

np.sшn(y_probas)

1.0

---[547]-------

[лава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

Несложно заметить, что спрогнозированные вероятности классов теперь
дают в сумме

1,

как и можно было ожидать. Также примечательно то, что

спрогнозированная метка класса оказывается такой же, как и в случае при­

менения функции

argmax

к логистическому выходу.

Содействовать пониманию результата функции

softrnax

может ее пред­

ставление как норwализоватю?о выхода, который удобен для получения

прогнозов принадлежности к классам в мноrоклассовых конфигурациях.
Следовательно, при построении модели для мноrоклассовой классификации

в

TensorFlow мы можем использовать функцию tf. keras. acti vations.
softrnax (),чтобы оценить вероятности членства в каждом классе для вход­

ного пакета образцов. В приведенном ниже коде мы покажем, как приме­
нять функцию активации

softrnax

из

TensorFlow,

преобразовав

z в тензор с

дополнительным измерением, зарезервированным для размера пакета:

>>>

iшpo.r.·t.

tensorflow as tf

>>> Z_tensor = tf.expand_dims (Z, axis=O)
>>> tf.keras.activations.softmax(Z_tensor)

Расwирение выходноrо спектра с испоnьзованием

rипербоnическоrо танrенса
Еще одной сигмоидальной функцией, часто применяемой в скрытых
слоях искусственных нейронных сетей, является ?unерболический

( tanh),

mau?euc

который можно интерпретировать как масштабированную версию

логистической функции:

1

~о?uстическая( z) = 1+ e-z
Ф.1anh (z)2х Ф,ло.•11ст11чес~·Ш/ (2z)-1
-

ez -e-z

-ez +e-z

-

Преимущество гиперболического тангенса перед логистической функ­
цией в том, что он имеет более широкий выходной спектр и диапазоны в
открытом интервале

(-1, 1),

которые могут улучшить сходимость алгорит­

ма обратного распространения

("Neural Networks for Pattern Recognition"

-[548] -------

fпава

13.

Распараллеливание процесса обучения нейронных сетей с помощью

(Нейронные сети для распознавания образов), К. Бишоп,

Press,

с.

TensorF/ow

Oxford University

г.)).

500-501 (1995

Напротив, логистическая функция возвращает выходной сигнал, находя­

щийся в открытом интервале (О,

1).

Чтобы сравнить логистическую функ­

цию и гиперболический тангенс, мы построим графики двух сигмоидаль­
ных функций:

>>> intport matplotlib.pyplot as plt
>>> def tanh (z):

np.exp(z)
е _ т = np . ехр (- z)
retu:i::·в (е_р - e_m) /
е_р =

(е_р

+ e_m)

>>> z = np.arange(-5, 5, 0.005)
>>> log_act = J.ogistic(z)
>>> tanh_ act = t:a.nh ( z)

»> plt.ylim( [-1.5, 1.5])
$ z$' )
рlt.уlаЬеl('активация $\phi(z)$')
plt.axhline(l, color='Ьlack', linestyle=':')
plt.axhline(0.5, color='Ьlack', linestyle=': ')
plt.axhline(O, color='Ыack', linestyle=':')
plt.axhline(-0.5, color='Ьlack', linestyle=':')
plt.axhline(-1, color='Ьlack', linestyle=':')
plt.plot(z, tanh_act,
linewidth=З, linestyle='--',
label=' tanh' )
>>> plt.plot(z, log_act,

>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

plt. xlabel ( 'общий

вход

linewidth=З,

label=' логист. ' )
>>> plt.legend(loc=' lower right')
»> pl t. tight _ layout ()
Как видно на рис.

13 .1 О,

формы двух сигмоидальных кривых выглядят

очень похожими, но выходное пространство у функции
больше, чем у функции

tanh

в два раза

logistic.

Обратите внимание, что в целях иллюстрации мы реализовали функции

logistic и tanh многословно.
tanh из NumPy.

На практике мы можем использовать функ­

цию

------- [549)-------------------

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

1.5
1.0

-;:;-$

0.5 " ............... " .. " .. " ..""."" ....... J "." ....... """"" .. " ... ......... .
1

g:

:s:

.,,111-:z

' '"

о.о

,

." ..... .. " " .. "" ..... 1..... " ... ... """. " " .. ""." ........ .
"

:s:

....

"

С(

"

1
-0.5

. ....••...•••• " .• """ . " " " "."". 1 .•.•.•.•• " .•• " •.. "." .. . ". """""." ... "

-1 .0

· · -·--------... ~· ~·····························~······ ··"··· ·· "· · · · ·· ····

-1.5

~--,-------.-----,.-----т-----,.--~

~

1

-

• tanh

логист.

-4

о

-2

4

2

Общий входz

Рис.

13.10.

Графики двух сu,",,10идалы1ых функций для сравнения

ло>> np.tanh(z)
array([-0.9999092, -0.99990829, -0.99990737,
о. 99990829] )
о. 999907 37'

... '

0.99990644,

>>> tf.keras.activations.tanh(z)

Вдобавок логистическая функция доступна в модуле

special

из

SciPy:

>>> f:roш scipy. special .impoгt: expi t
>>> expit (z)
array([0.00669285, 0.00672617, 0.00675966, ... , 0.99320669,
0.99324034, 0.99327383])
Подобным образом для выполнения того же самого вычисления можно ис-

пользовать функцию

tf. keras. activations. sigmoid ()

из

TensorFlow:

>>> tf.keras.activations.sigrnoid(z)


-- (550] ------ ----- ------ ----

---------

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью

TensorFlow

Активация на основе выпрямnенноrо nинейноrо элемента
Выпрямлеииый лииейиый эле.меит

(1-ectUfc1l li11e1u 1mit --- ReHJ) -

еще

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

ReLU,

мы должны сделать шаг на­

зад и понять проблему исчезновения градиентов, присущую активациям на
основе гиперболического тангенса и логистической функции.
Пусть у нас первоначально имеется общий вход

ется на

z2

= 25.

z 1 = 20,

который изменя­

Вычислив активацию в форме гиперболического тангенса,

мы получаем ф(z 1 )

= 1.0

и ф(z 2 )

= 1.0,

что указывает на отсутствие измене­

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

z

становится большим. В результате выяснение весов на

стадии обучения крайне замедляется, т.к. члены градиента могут быть очень

близкими к нулю. Активация

ReLU

ReLU

решает такую проблему. Математически

определяется следующим образом:

ф(z)
Кроме того,

ReLU -

= тах(О,

z)

нелинейная функция, которая хорошо подходит для

изучения сложных функций с нейронными сетями. Более того, производ­
ная

ReLU

относительно входа всегда равна

1

для положительных входных

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

ReLU

в

TensorF\ow:

>>> tf.keras.activations.tanh(z)

'о.
о.
В следующей главе мы будем использовать

ReLU

в качестве функции ак­

тивации для многослойных сверточных нейронных сетей.

Теперь, больше зная о различных функциях активации, которые обычно при­
меняются в искусственных нейронных сетях, мы завершим настоящий раздел об­

зором разнообразных функций активации, встречающихся в книге (рис.

13.11 ).
Список всех функций активации, доступных в АРI-интерфейсе Keras,
находится по ссылке https: / /www.tensorflow.org/versions/r2. О/
api_docs/python/tf/keras/activations.

-[551]---

-

Глава

13.

Распараллеливание проце сса обучения нейронных сетей с помощью TeпsorF/ow

Функция

Пример

Уравнение

активации

ф(z)

Линейная

Adaline ,

=z

линейная
регрессия

Единичная
ступенчатая

ф(z)

(функция
Хевисайда)

Знака
(знаковая)

Кусочнолинейная

=

ф(z)=

{~5

{-1

Гиперболический
тангенс (tanh)

ф(z)=

ф(z)=

Рис.

13.11.

z :S -~
-iS :s;z:s;j-2
z~~

Разновидность
персептрона

ez-e-z
ez + e-z

zO

--F
1

персептрона

Метод
опорных
векторов

регрессия ,

1 + e-z

*

Разновидность

Логистическая

1

ф(z)={:

ReLU

zO

~

ф(z)= {;+у,

Логистическая
(сигмоидальная)

zO

Одномерный
граф

многослойная
нейронная сеть

Многослойная
нейронная сеть ,
рекуррентные

нейронные сети

--+=
-+=

::F

Многослойная
нейронная сеть ,
сверточные

нейронные сети

Функции активации, встречающиеся в книге

·(552]--- ·

1

~

Глава

13.

Распараллеливание процесса обучения нейронных сетей с помощью TeпsorF/ow

Резюме
В этой главе вы научились пользоваться

TensorFlow -

библиотекой с от­

крытым кодом для численных расчетов, особо сконцентрированной на ГО.
Хотя из-за дополнительной сложности, связанной с поддержкой ГП, библи­
отека

TensorFlow

менее удобна в применении по сравнению с

NumPy,

она

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

Кроме того, вы узнали об использовании АРI-интерфейса
отеки

TensorFlow

Keras

библи­

для построения сложных моделей МО и нейронных се­

тей и эффективного их запуска. Мы исследовали построение моделей в

TensorFlow путем их определения с нуля посредством создания
класса tf. keras. Model. Реализация моделей может оказаться

подклассов
утомитель­

ной, поскольку нам приходится оперировать на уровне умножений матрицы

на вектор и определять все детали каждой операции. Однако преимущество

в том, что мы как разработчики получаем возможность комбинировать такие
базовые операции и строить более сложные модели. Вдобавок вы ознакоми­
лись с модулем

tf. keras. layers,

который позволяет строить нейросете­

вые модели гораздо легче, чем их реализация с нуля.

Наконец, вы узнали о различных функциях активации, а также ознакоми­
лись с их поведением и случаями применения. В частности, мы рассмотре­

ли функции

tanh, softmax

и

ReLU.

В следующей главе мы продолжим путешествие и погрузимся глубже в

TensorFlow,

открыв для себя работу с декорированием функций

и оценщиками

TensorFlow.

TensorFlow

Попутно вы освоите много новых концепций, та­

ких как переменные и столбцы признаков.

- - - - - - - - - - - - - - - - - - [553] -------------------------

14
ПОГРУЖАЕМСЯ ГЛУБЖЕ МЕХАНИКА TENSORfLOW

главе 13 мы объяснили, как определять и манипулировать тензорами, а
в также
работать с АРI-интерфейсом tf. data для построения входных
конвейеров. Более того, мы построили и обучили многослойный персепт­

рон для классификации цветков в наборе данных, используя АРI-интерфейс

Keras

библиотеки

TensorFlow (tf. keras).

Теперь, когда у вас есть практический опыт работы с обучением нейрон­
ной сети

TensorFlow

и МО, самое время углубиться в библиотеку

TensorFlow

и исследовать предлагаемый ею богатый набор функциональных средств,

который сделает возможным реализацию более развитых моделей ГО в пос­
ледующих главах.

В этой главе мы задействуем различные аспекты АРI-интерфейса

TensorFlow

для реализации нейронных сетей. В частности, мы снова будем

применять АРI-интерфейс

Keras,

который предоставляет множество уров­

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

TensorFlow

позволяет реализовывать специальные слои нейрон­

ных сетей, что крайне полезно в исследовательских проектах, которые тре­

буют дополнительной подстройки. Позже в главе мы реализуем специаль­
ный слой такого рода.

Чтобы проиллюстрировать разные способы построения моделей с ис­
пользованием АРI-интерфейса

Keras, мы также рассмотрим классическую
(XOU). Первым делом мы построим многослой­
Применением класса Sequential. Затем мы исследуем

задачу исключающего ИЛИ

ный персептрон с

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

другие методы, такие как создание подклассов класса

tf. keras. Model

для определения специальных слоев. В заключение мы раскроем высоко­

уровневый АРl-интерфейс

TensorFlow

по имени

tf. estima tor,

который

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



понятие графов
сию

TensorFlow
TensorF\ow v2;

и работа с ними, а также переход на вер-



декорирование функций для компиляции графов;



работа с переменными



решение классической задачи



построение сложных нейросетевых моделей с использованием класса

Model


XOR

и понятие емкости модели;

и функционального АРl-интерфейса библиотеки

Keras;

расчет градиентов с применением автоматического дифференцирова­
ния и класса



TensorF\ow;

tf. GradientTape;

работа с оценщиками

TensorF\ow.

Ключевые средства
Библиотека

TensorF\ow

Tensorf low

предлагает масштабируемый, многоплатформен­

ный программный интерфейс для реализации и запуска алгоритмов МО.
Относительную стабильность и зрелость АРl-интерфейс
в выпуске

1.0,

TensorF\ow

обрел

2017 году, а в своем последнем выпуске 2.0,
2019 году и применяется в настоящей книге, он

появившемся в

который стал доступным в

был серьезно реконструирован.
С момента первоначального выпуска в

2015

году

TensorF\ow

стала самой

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

TensorF\ow

была построена на

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

гов объявления и оценки, что затрудняет интерактивное конструирование и
работу с нейронными сетями.

------------[556]

Глава

14.

Погружаемся глубже

-

механика

TensorFlow

Серьезно относясь ко всем пользовательским отзывам, команда разработ­
чиков в версии

TensorFlow 2.0

решила сделать принятыми по умолчанию

динамические вычислительные графы, которые повышают удобство созда­

ния и обучения нейронных сетей. В следующем разделе мы раскроем ряд
важных изменений версии

TensorF\ow v2

по сравнению с

TensorFlow v 1.х.

Динамические вычислительные графы позволяют чередовать шаги объявле­
ния и оценки, так что версия библиотеки

TensorFlow 2.0 выглядит более ес­
тественной для пользователей Python и NumPy, нежели ее предшествующие
версии. Однако имейте в виду, что TensorFlow 2.0 по-прежнему разрешает
пользователям работать со "старым" АРI-интерфейсом TensorFlow vl.x че­
рез подмодуль tf. compat. Это содействует более плавному переходу поль­
зователей на новый АРI-интерфейс TensorFlow v2.
Ключевым средством TensorF\ow, которое уже упоминалось в главе 13,
является способность работать с одним или несколькими ГП, что позволяет

пользователям очень эффективно обучать модели ГО на больших наборах
данных в крупномасштабных системах.
Хотя

TensorFlow

представляет собой библиотеку с открытым кодом, ко­

торая может свободно использоваться кем угодно, ее разработка финанси­
руется и поддерживается

Google.

В процесс вовлечена крупная команда

специалистов по программному обеспечению, постоянно расширяющих

и совершенствующих библиотеку. Благодаря открытости кода библиотека

TensorF\ow также
компании Goog\e,

имеет сильную поддержку со стороны разработчиков вне
которые активно вносят свой вклад и обеспечивают поль­

зовательскую обратную связь.

В итоге библиотека

TensorF\ow

становится более полезной как для иссле­

дователей из научных кругов, так и для разработчиков. Добавочное следс­

твие из указанных факторов заключается в том, что

TensorFlow

снабжается

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

Последней, но не менее важной характеристикой библиотеки

TensorFlow,

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

------(557) - - - - - - - - - -

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

Вычислительные rрафы

Tensorflow:
Tensorflow v2

переход на
Библиотека

TensorFlow

выполняет свои вычисления на базе ориеити­

роваииого ациклического графа

v 1.х

(lfi1-ec te1f acyclic gmpJ1 - ОЛб). В TensorFlow

такие графы можно явно определять посредством низкоуровневого АРI­

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

(eap:er exec11tio11)

TensorFlow v2,

обсудим энергичное выполнение

и парадигму динамических графов, а также рассмотрим де­

корирование функций для более быстрых вычислений.

Понятие вычисnитеnьных rрафов
Внутренне библиотека

TensorFlow

опирается на построение вычислитель­

ного графа, который затем применяется для выведения взаимосвязей между

тензорами на всем пути от входа до выхода. Пусть у нас есть тензоры а, Ь и с
ранга О (скаляры) и мы хотим оценить уравнение

z

=2

х (а

-

Ь) +с. Такая

оценка может быть представлена как вычислительный граф, показанный на
рис .

14.1.
Вычислительный граф, реализующий
уравнение

z=2х



-

Ь) +с

а, Ь, с: входные тензоры (скаляры)

rp r 2 : тензоры промежуточных результатов
z: тензор финального результата
Рис.

14.1.

Вычислительный граф для оц е нки уравне11ия

z = 2 х (а - h)

+

с

- - - -- -- - - - ( 5 5 8 ] - - -- - -- - --

Глава

На рис.

14.1

14.

Погружаемся глубже

-

механика

TensorF/ow

видно, что вычислительный граф выглядит просто как сеть

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

количество тензоров в качестве выхода. Библиотека

TensorFlow

строит этот

вычислительный граф и использует его для надлежащего расчета градиен­
тов. В последующих подразделах мы рассмотрим несколько примеров со­

здания графа в стиле версий
денного на рис.

TensorFlow v 1.х

и

v2

для вычисления, приве­

14.1.

Создание rрафа в

Tensorflow v1 .х

В ранней версии низкоуровневого АРI-интерфейса

TensorFlow (vl .х)

граф

должен был объявляться явно. Ниже перечислены индивидуальные шаги для

построения, компиляции и оценки вычислительного графа в

TensorFlow vl .х.

1.

Создать новый пустой вычислительный граф.

2.

Добавить в вычислительный граф узлы (тензоры и операции).

3.

Провести оценку (выполнить) граф:
а) начать новый сеанс;
б) инициализировать переменные в графе;

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

v2,

TensorFlow

давайте рассмотрим простой пример, который проиллюстрирует создание

графа в

TensorFlow vl .х

для оценки, показанной на рис.

14.1.

Переменные

а, Ь и с являются скалярами (одиночными числами) и мы определим их как
константы

TensorFlow.

Затем можно создать граф, вызвав tf.

Graph ().

Переменные и операции представляют узлы графа, которые мы определяем
следующим образом:
##Стиль TF vl.x
»> g = tf.Graph()

>>>

1ni:t\
а

g.as_default():
= tf. constant ( 1, name=' а')
Ь = tf.constant(2, name='b')
с= tf.constant(З, name='c')
z = 2* (а-Ь) + с

[559]

[лава

14. Погружаемся глубже - механика TensorF/ow

В коде мы сначала определяем граф
Затем мы добавляем узлы в граф

g

g

посредством

с применением

g=tf. Graph ().
wi th g. а s _ de f а ul t ( ) .

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

В

TensorFlow v 1.х

сеанс представляет собой среду, в которой могут вы­

полняться операции и тензоры графа. Класс

Session

был удален из вер­

сии

TensorFlow v2; однако на данный момент он все еще доступен через
подмодуль t f. compa t, чтобы обеспечить совместимость с TensorF\ow v 1.х.
Объект сеанса можно создать вызовом tf. compat. vl. Session (), кото­
рый способен принимать в качестве аргумента существующий граф (здесь g),
как в tf. Session (graph=g).
После запуска графа в сеансе TensorFlow мы можем выполнить его узлы,

т.е. оценить его тензоры или выполнить его операции. Оценка каждого от­
дельного тензора влечет за собой вызов его метода

eval ()

внутри теку­

щего сеанса. Во время оценки специфического тензора в графе библиотека

TensorFlow

должна выполнить все предшествующие узлы в графе, пока не

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

После определения статического графа в предыдущем фрагменте кода мы
можем выполнить этот граф в сеансе
##Стиль

TensorFlow

и оценить тензор

z:

TF vl.x

>>> w:i.th tf.compat.vl.Session(graph=g) as sess:
pr.·int ('Результат: z =', sess. run ( z))
Результат:

z

=

Перенос графа в

1

TensorFlow v2

Далее давайте посмотрим,

TensorFlow v2.

В версии

как имеющийся

TensorFlow v2

код можно перенести

в

по умолчанию используются дина­

мические (в противоположность статическим) графы (в

TensorFlow

это так­

же называется энергичным выполнением), которые позволяют оценивать

операцию на лету. Следовательно, мы не обязаны явно создавать граф и се­

анс, что делает поток разработки более удобным:

------------(560)----

fлава

14.

Погружаемся глубже

-

механика

TensorF/ow

TF v2
tf.constant(l, name='a')
>>> Ь = tf.constant (2, name='b')
>>>с= tf.constant(З, name='c')
>>> z = 2* (а - Ь) + с
>>> tf. print ('Результат: z= ', z)
Результат: z = 1
##

Стиль

>>>а=

3аrрузка входных данных в модеnь: стиnь

TensorFlow v1 .х

Еще одно важное усовершенствование версии

TensorFlow v2

по сравне­

нию с

TensorFlow vl .х касается того, каким образом можно загружать дан­
TensorFlow v2 мы можем напрямую подавать данные в
форме переменных Python или массивов NumPy. Тем не менее, когда приме­
няется низкоуровневый АРI-интерфейс TensorFlow vl .х, мы должны созда­
ные в модели. В

вать переменные-заполнители для снабжения модели входными данными.

Продолжая приведенный ранее пример простого вычислительного графа,

z

=2

х (а

Ь)

-

+

с, давайте предположим, что а, Ь и с

-

входные тензо­

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

feed dict:
##Стиль TF-vl.x
>» g = tf. Graph ()
>>> wi th g. as_default () :
а= tf.compat.vl.placeholder(shape=None,
dtype=tf.int32, name='tf_a')
Ь = tf.compat.vl.placeholder(shape=None,
dtype=tf.int32, name='tf_b')
с= tf.compat.vl.placeholder(shape=None,
dtype=tf.int32, name='tf_c')
z = 2* (а-Ь) + с

>>> with tf .compat.vl.Session(graph=g) as sess:
feed_dict={a:l, Ь:2, с:З}
рrint('Результат: z =', sess.run(z, feed_dict=feed_dict))
Результат: z = 1
Заrрузка входных данных в модеnь: стиnь
В
цию

TensorFlow v2

Python

Tensorf low v2

все это можно сделать, просто определяя обычную функ­

с входными аргументами а, Ь и с, например:

[561)-··

Глава

Погружаемся глубже

14.

## Стиль TF-v2
>>> def compute_z

-

механика

(а,

Ь,

TensorF/ow

с):

rl = tf. suЬtract (а, Ь)
r2 = tf .multiply(2, rl)
z = tf.add(r2, с)
return z
Чтобы выполнить вычисление, мы можем вызывать функцию

compute z

Tensor в качестве аргументов. Обратите внимание, что функ­
ции TensorFlow вроде add, suЬtract и mul tiply также позволяют пре­
доставлять входные данные более высоких рангов в форме объекта Tensor
библиотеки TensorFlow, массива NumPy или возможно других объектов
Python, таких как списки и кортежи. В следующем примере кода мы пре­
с объектами

доставляем скалярные входные данные (ранг О), а также входные данные
ранга

1

и

2

в виде списков:

>>> t f. pr int ( 'Скалярные

входные данные:

Скалярные входные данные:

>>>

tf.print('Bxoдныe данные ранга

Входные данные ранга

>>>

1:', compute_z([l], [2], [3]))

1 : [ 1]

tf.print('Bxoдныe данные ранга

Входные данные ранга

' , compute_ z ( 1, 2, 3) )

1

2:',compute_z([[l]], [[2]], [[3]]))

2 : [ [ 1] ]

В текущем разделе вы узнали, что переход в

TensorFlow v2

делает стиль

программирования простым и эффективным за счет устранения шагов, свя­
занных с явным созданием графа и сеанса. Теперь, когда вы видели, как

TensorFlow v2, в оставшихся материа­
лах книги мы сосредоточимся только на TensorFlow v2. Далее мы более под­
робно обсудим декорирование функций Python внутри графа, позволяющее

соотносятся версии

TensorFlow v 1.х

и

ускорять вычисление.

Увеnичение вычисnитеnьной мощности

с помощью декораторов функций
В предыдущем разделе вы видели, что мы можем с легкостью написать

обычную функцию

Python

и задействовать операции

TensorFlow.

Однако вы­

числения в режиме энергичного выполнения (через динамические графы) не

настолько эффективны, как выполнение статических графов в

[562)

TensorFlow v 1.х.

fлава

14.

Погружаемся глубже

-

механика

TensorFlow

Таким образом, в

TensorFlow v2 предлагается инструмент под названи­
AutoGraph, который способен автоматически трансформировать код
Python в код графов TensorFlow для более быстрого выполнения. Вдобавок
в TensorFlow предоставляется простой механизм для компиляции обычной
функции Python в статический граф TensorFlow с тем, чтобы сделать вычис­
ем

ления более эффективными.
Чтобы посмотреть, как все работает на практике, мы возьмем предыду­

щую функцию
нением

compute z и аннотируем
декоратора @tf. function:

ее для компиляции графа с приме­

>>> @tf. function
def compute_z

(а,

Ь,

с):

rl = tf.subtract(a, Ь)
r2 = tf.rnultiply(2, rl)
z = tf.add(r2, с)
z

x:eturл

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

вать и вызывать таким же способом, как прежде, но теперь
дет создавать статический граф на основе входных

TensorFlow бу­
аргументов. Язык Python

поддерживает динамическую типизацию и полиморфизм, поэтому мы в со­

стоянии определить функцию наподобие

def f

(а, Ь)

: return

а+Ь и за­

тем вызывать ее с применением входных данных целочисленного типа, типа

с плавающей точкой, спискового или строкового типа (вспомните, что а +Ь

является допустимой операцией для списков и строк). Несмотря на то что

графы

TensorFlow

требуют статических типов и форм,

tf. function

под­

держивает возможность динамической типизации. Например, давайте вызо­

вем функцию

>>>
>>>
>>>

compute z

со следующими входными данными:

tf.print('Cкaляpныe входные данные:',
tf.print('Bxoдныe данные ранга

tf.print('Bxoдныe данные ранга

cornpute_z(l, 2, 3))
1:', cornpute_z([l], [2], [3]))
2:', compute_z([[l]], [[2]],

[[3]]))
Вывод будет таким же, как ранее. Здесь для конструирования графа на

основе входных аргументов библиотека
трассировки. Для такого механизма

TensorFlow использует механизм
трассировки TensorFlow генерирует кор­

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

[5631 - - - - - -

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

Вот какие ключи генерируются:



для аргументов

tf. Tensor

ключ основан на их формах и типах дан­

ных;



в случае типов

Python,

таких как списки, их

id ()

применяется для

генерации ключей кеша;



для простых значений

Python

ключи кеша базируется на входных ве­

личинах.

После вызова декорированной функции

TensorFlow

проверит, не был

ли граф с соответствующим ключом сгенерирован ранее. Если граф такого
рода не существует, тогда

TensorFlow

сгенерирует новый граф и сохранит

новый ключ. С другой стороны, если мы хотим ввести ограничения на спо­

соб вызова функции, то при ее определении можем указать входную сигна­
туру посредством кортежа из объектов

tf. TensorSpec. Скажем, давайте
переопределим предыдущую функцию compute z и укажем, что разреше­
ны только тензоры ранга 1 типа tf. int32:
>>> @tf.function(input_signature=(tf.TensorSpec(shape=[Noae],
dtype=tf.int32),
tf.TensorSpec(shape=[None],
dtype=tf.int32),
tf. TensorSpec (shape= [№11е],
dtype=tf.int32),))
def compute_z (а, Ь, с):
rl = tf. suЬtract (а, Ь)
r2 = tf.multiply(2, rl)
z = tf.add(r2, с)
ret1Jrn z
Теперь функцию можно вызывать с использованием тензоров ранга

(или списков, допускающих преобразование в тензоры ранга

>>> tf.print('Bxoдныe
>>> tf .print ('Входные

данные ранга

данные ранга

1

1):

1:', compute_z([l], [2], [3]))
1: ', compute_z ( [1, 2], [2, 4],

[3, 6]))
Тем не менее, вызов функции с применением тензоров ранга, отличаю­
щегося от

1,

приведет к ошибке, т.к. ранг не будет совпадать с указанной

входной сигнатурой:

------- [564] __..,_.

Глава

>>> tf. print

14.

Погружаемся глубже

('Входные данные ранга О:

###

приведет к ошибке

>>>

tf.print('Bxoдныe данные ранга

механика

TensorF/ow

', compute_ z ( 1, 2, 3)

2:', compute_z([[l], [2]],
[[2],
[[3],

###

-

[4]],
[6]]))

приведет к ошибке

В этом разделе вы узнали, как аннотировать обычную функцию

чтобы библиотека

TensorFlow

Python,

скомпилировала ее в виде графа для бо­

лее быстрого выполнения. Далее мы рассмотрим переменные

TensorFlow:

способы их создания и использования.

Объекты VariaЬle библиотеки Tensorf low
для хранения и обновления параметров модели
В главе

13

мы раскрыли объекты

Tensor.

В контексте

VariaЫe представляет собой особый объект

TensorFlow

Tensor,

объект

который позволя­

ет сохранять и обновлять параметры моделей во время обучения. Объект
VariaЫe можно создать путем вызова класса

tf. VariaЫe

с начальны­

ми значениями, указанными пользователем. В следующем коде мы генери­

руем объекты VariaЫe типов

float32, int32, bool

>>>а= tf.VariaЬle(initial_value=З.14,

>>>

string:

name='var_a')

p.rн1t. (а)

>>
>>>

и

Ь

=

'var_a:O' shape=() dtype=float32,

tf.VariaЫe(initial_value=[l,

2,

З],

numpy=З.14>

name='var_b')

pгir;t. (Ь)



shape=(З,)

dtype=int32, numpy=array([l, 2,

З],

>>> с = tf. VariaЫe (initial_value= [T:r:i.н'J, H'a1s~?.], dtype=tf .bool)
>>> pr.i.nt. (с)
>> pr·int(w.value())
tf.Tensor([S О 6), shape=(3,), dtype=int32)
Когда аргумент

read_ value

установлен в

True

(что принято по умолча­

нию), эти операции будут автоматически возвращать новые значения после
обновления текущих значений объекта VariaЫe. Установка

False подавляет автоматический возврат обновленного
Var iаЫе по-прежнему будет обновляться на месте).

read_ value

в

значения (но объект
Вызов

w. value ()

будет возвращать значения в формате тензора. Имейте в виду, что изменять
форму или тип объекта VariaЫe во время присваивания нельзя.
Вспомните, что для нейросетевых моделей необходима инициализация

их параметров случайными весами, чтобы разрушить симметрию во время

обратного распространения, иначе многослойная нейронная сеть окажется
не полезнее однослойной нейронной сети наподобие логистической рег­

рессии. При создании объекта VariaЫe из TeпsorFlow мы также можем
использовать схему случайной инициализации. Библиотека TeпsorFlow спо­

собна генерировать случайные числа, основанные на разнообразных распре­
делениях с помощью

tf. random (см. https: / /www.tensorflow.org/
versions/r2. 0/api_docs/python/tf/random). В приведенном ниже
примере

мы

взглянем

на ряд стандартных

методов

рые доступны в

инициализации,

кото­

Keras (см. https: / /www.tensorflow.org/versions/
r2. 0/api _ docs/python/tf /keras/ ini tializers).
------------(566)--

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

Итак, давайте посмотрим, как можно создать объект VariaЫe с иници­
ализацией Глоро, которая является классической схемой случайной инициа­

лизации, предложенной Ксавье Глоро и Йошуа Бенджи. Для этого мы созда­
дим операцию по имени

ini t

как объект класса

GlorotNormal.

Затем мы

вызовем созданную операцию и предоставим желательную форму выходно­
го тензора:

>>> tf.random.set_seed(l)
>>> init = tf.keras.initializers.GlorotNormal()
>>> tf.print(init(shape=(3,)))

[-0.722795904 1.01456821 0.251808226]
Теперь мы можем задействовать эту операцию для инициализации объек­
та VariaЫe формы 2х3:

»> v =

tf.VariaЬle(init(shape=(2,

3)))

>>> tf .print (v)

[[0.28982234 -0.782292783 -0.0453658961]
[0.960991383 -0.120003454 0.708528221)]
\\:~ Инициализация Ксавье (или Глоро)

н~ На ранней стадии развития ГО путем наблюдений выяснилось, что
заметку!

инициализация весов со случайным равномерным или случайным
нормальным распределением часто могла приводить к низкой эф­
фективности модели во время обучения.

В

201 О

году Глоро и Бенджи исследовали влияние инициализации

и предложили новаторскую более надежную схему инициализации,

облегчающую обучение глубоких нейронных сетей. Основная идея

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

тивном случае во время обучения некоторые слои могут получить
слишком много внимания, тогда как другие слои будут отставать.

Согласно научной статье Глоро и Бенджи, если мы хотим инициали­
зировать веса значениями с равномерным распределением, то долж­

ны выбирать интервал равномерного распределения следующим об­
разом:

W- Равномерное [-------------------~

J6

, ----.==Jб=б==J

~п.х+пвых ~п.х+пвh1х

(567]

---------

Глава

14.

Погружаемся глубже

Здесь пвх пвых -

-

механика

TensorF/ow

количество входных нейронов, умноженных на веса, а

количество выходных нейронов, которые передают значения

следующему слою. Для инициализации весов значениями с гауссо­

вым (нормальным) распределением рекомендуется выбирать стандартное отклонение а=

Библиотека

TensorFlow

fi

---;:::===

~пвх +пвых

поддерживает инициализацию Ксавье с рав­

номерным и нормальным распределением весов.

Дополнительную информацию о схеме инициализации Глоро и
Бенджи, включая математический вывод и доказательство, можно

найти в их исходной статье ("Understanding the difficulty of training
deep feedforward neural networks" (Осмысление сложности обуче­
ния глубоких нейронных сетей прямого распространения), Ксавье

Глоро и Йошуа Бенджи (201 О год)), которая свободно доступна по
ссылке

http: / /proceedings. mlr. pres s /v9 / glorotl Оа/
glorotlOa.pdf.

А теперь, чтобы поместить это в контекст более практичного сценария
применения, мы посмотрим, как можно определить объект VariaЫe внут­
ри базового класса

tf. Module.

Мы определим две переменные

-

одну

обучаемую и одну необучаемую:

>>> class MyModule (tf .Module):
def

ini t
( \f):
init = tf.keras.initializers.Glo rotNormal()
; ; . wl = tf. VariaЫe (init (shape= (2, 3)),
trainaЬle=True)

cf.w2 =

tf.VariaЬle(init(shape=(l,

2) ),

trainaЫe=False)

>>> m = MyModule ()
>>> print( 'Все переменные
Все переменные модуля:

>>>

модуля:', [v.shape for v in m.variaЫes])
[ TensorShape ( [ 2, 3] ) , TensorShape ( [ 1, 2] ) ]

p:ciпt( 'Обучаемая переменная:',

[v.shape for v in
m.trainaЫe_variaЫes])

Обучаемая переменная:

[ TensorShape ( [ 2, 3] ) ]

Как несложно заметить в примере кода, создание подкласса класса

tf. Module

дает нам прямой доступ ко всем переменным, определенным

--------~-------

[568)

Глава

14.

Погружаемся глубже

в имеющемся объекте (экземпляре специального класса
атрибут

-

механика

TensorF/ow

MyModule)

через

. variaЬles.

В заключение давайте взглянем на использование переменных внутри

функции, декорированной с помощью
VariaЫe из

TensorFlow

tf. function.

Определяя объект

внутри обычной (не декорированной) функции, мы

могли бы ожидать, что новый объект VariaЫe будет создаваться и инициа­
лизироваться каждый раз, когда функция вызывается. Однако

tf. function

будет стараться повторно использовать объект VariaЫe, основываясь на
трассировке и создании графа. Следовательно,

TensorFlow

не разрешает со­

здание объекта VariaЫe внутри декорированной функции, в результате
чего показанный ниже код приведет к возникновению ошибки:

>>> @tf. function
.•• def f

(х):

w = tf. VariaЫe ( [1, 2, 3))



f( [1])

ValueError: tf.function-decorated function tried to create
variaЫes on non-first call.
Ошибка значения:
создать

функция, декорированная

tf. function,

попыталась

переменные при не первом своем вызове.

Один из способов избежать проблемы предусматривает определение объ­
екта VariaЫe за пределами декорированной функции и его потребление
внутри этой функции:

>>> w = tf.VariaЬle(tf.random.uniform((3, 3)))
>>> @tf. function
••• l"ief compute_z (х):

return tf.matmul(w,

х)

>>> х = tf.constant ( [ [1], [2], [3]], dtype=tf.float32)
>>> tf.print(compute_z(x))

Расчет градиентов посредством автоматического

дифференцирования и

GradientTape

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

алгоритмы оптимизации, такие как стохастический градиентный спуск

(SC.'fJ).

Кроме того, градиенты имеют другие приложения наподобие диагностиро-

[569)-----·----

fяава

14. Погружаемся глубже - механика TensorF/ow

вания сети с целью выяснения, почему нейросетевая модель вырабатывает

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

Расчет rрадиентов потери по отношению к обучаемым переменным
Библиотека
рование,

TensorFlow

которое

поддерживает авттwатическое дифференци­

можно трактовать

как

реализацию

цепно?о

правила для

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

промежуточные тензоры,

TensorFlow

обеспечивает контекст для расчета гра­

диентов таких вычисленных тензоров относительно их зависимых углов в

вычислительном графе. Чтобы рассчитать эти градиенты, мы должны "заре­
гистрировать" вычисления посредством

tf. GradientTape.

Давайте проработаем простой пример, в котором вычислим z

= wx + Ь

и определим потерю как квадратичную ошибку между целью и прогнозом,

Потеря= (у - z) 2 . В более общем случае, где мы можем иметь множест­
во прогнозов и целей, мы вычисляем потерю как сумму квадратичных

L

ошибок, т.е. Потеря = 1.(У; - z/ Для реализации такого вычисления в
TensorFlow мы определим параметры модели w и Ь как переменные, а вход
х и у как тензоры. Вычисление

z

и потери мы поместим внутрь контекста

tf. GradientTape:

>» w =
>» Ь =

tf.VariaЬle(l.0)
tf.VariaЬle(0.5)

>>> print(w.trainaЫe,
True True
>>>

b.trainaЫe)

= tf.convert_to_tensor( [1.4))
tf.convert_to_tensor( [2.1))
>>> wi th tf. GradientTape () as tape:
z = tf.add(tf.multiply(w, х), Ь)
loss = tf.reduce_sum(tf.square(y - z))
х

>>>у=

>>> dloss_dw = tape.gradient(loss, w)
>>> tf.printt'dL/dw: ', dloss_dw)
dL/dw: -0.559999764
При вычислении значения

z

мы могли бы думать об обязательных опе­

рациях, записываемых на "ленту градиентов", как о прямом проходе в ней-

[570)------------------

Глава

v

роннои сети.

М

ы применяем

14.

Погружаемся глубже

.
tape. gradient

-

механика

дПотеря

для вычисления

Поскольку пример очень прост, мы можем получить производные

TensorF/ow

дw

дПотеря
дw

=

= 2x(wx + Ь - у) в символьной форме, чтобы убедиться в том, что рассчитан­
ные градиенты совпадают с результатами, которые были получены в преды­
дущем фрагменте кода:

#

проверка рассчитанного rрадиента

>>> tf .print (2*х* (w*x+b-y))

(-0.559999764]
\\.~ Понятие автоматического дифференцирования

н~ Автоматическое дифференцирование представляет собой набор вы­
заметку!

числительных приемов для расчета производных или градиентов
произвольных арифметических операций. В течение этого процес­
са градиенты вычисления (выраженные как последовательность
операций) получаются за счет накопления градиентов посредством

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

ния, давайте обсудим последовательность вычислений у= f(g(h(x)))
с входом х и выходом у. Ее можно разбить на ряд шагов:
8

Ио= Х

8

И1 =

8

И2 = g(и1)



И3

h(x)

= Ли2)



Производную

ду
дх

можно рассчитать двумя разнымиспособами:

диз

прямое накопление, которое начинается с -д
х

ду

ду ди1

ное накопление, начинающееся с а= ад
Ио

ние, что

TensorF\ow

И1

диз ди2

=-д -д ,

Ио

и2

Ио

и обрат-

. Обратите

внима-

использует обратное накопление.

Расчет rрадиентов по отношению к необучаемым тензорам
Класс

tf. GradientTape

автоматически поддерживает градиенты

для обучаемых переменных. Тем не менее, для отслеживания необучае­
мых переменных и других объектов

Tensor

--·-·----------------- [571]

нам необходимо добавить к

Глава

14. Погружаемся глубже - механика TensorFlow
дополнительную модификацию вида

GradientTape

Скажем, если нас интересует расчет

дПотеря

дующим образом:

дх

,

tape. watch ().

то код будет выглядеть сле-

>>> with tf.GradientTape() as tape:
tape.watch(x)
z = tf. add (tf .multiply (w, х), Ь)
loss = tf.reduce_sum(tf.square(y - z))

>>> dloss_dx = tape.gradient(loss,

х)

>>> tf .print ( 'dL/dx: ', dloss_dx)
dL/dx: [-0.399999857]
Состязательные образцы
Расчет градиентов потери по отношению к входному образцу
применяется для генерирования состязательных образцов (или

состязательных атак
состязательные

(atf1 asm·ial attщ:k)). В компьютерном зрении
образцы это такие образцы, которые генериру­
1

ются путем добавления к входному образцу небольшого и незначи­
тельного шума (или возмущений), что в результате приводит к его

неправильной классификации нейронной сетью. Раскрытие состя­
зательных образцов выходит за рамки тематики настоящей книги,
но если вам

интересно, тогда можете ознакомиться с первоначаль­

ной работой Кристиана Сегеди и др.

networks"

"Intriguing properties of neural

(Занимательные свойства нейронных сетей), свободно до­

ступной по ссылке

https: //arxiv. org/pdf/1312. 6199 .pdf.

Сохранение ресурсов для множества вычислений rрадиентов
Когда мы отслеживаем вычисления в контексте

tf. GradientTape,

лен­

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

tape. gradient ()

ре­

сурсы будут освобождены и лента очистится. Следовательно, если мы хотим
рассчитать более одного градиента, например,
понадобится сделать ленту постоянной:

дПотеря
дw

и

дПотеря
дЬ

,

тогда

>>> with tf.GradientTape(persistent=True) as tape:
z = tf.add(tf.multiply(w, х), Ь)
loss = tf.reduce_sum(tf.square(y - z))

-----------(572)-----------

[лава

14.

Погружаемся глубже

-

механика

TensorF/ow

>>> dloss_dw = tape.gradient (loss, w)
>>> tf.print ( 'dL/dw: ', dloss_dw)
dL/dw: -0.559999764

>>> dloss_c:IЬ = tape.gradient(loss,
>>> tf .print ( 'dL/db: ', dloss_c:IЬ)
dL/clЬ:

Ь)

-0.399999857

Однако имейте в виду, что это необходимо только при желании рассчиты­

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

persistent=False.

Наконец, если мы вычисляем градиенты члена потери относительно па­
раметров модели, то можем определить оптимизатор и применить градиен­

ты для оптимизации параметров модели с использованием АРI-интерфейса

tf. keras:
>>> optimizer = tf. keras. optimizers. SGD ()
>>> optimizer.apply_gradients (zip ( [dloss_dw,
>>> tf. print ( 'Обновленный
Обновленный вес:

>>> tf.print

вес:

dloss_c:IЬ],

[w,

Ь]))

' , w)

1.0056

('Обновленное смещение:',

Обновленное смещение:

Ь)

0.504

Как вы должны вспомнить, первоначальный вес и смещение составляли

w = 1.0

и Ь

= 0.5, а применение

модели изменило их на

градиентов потери относительно параметров

w = 1.0056

и Ь

= 0.504.

Упрощение реализаций распространенных
архитектур посредством АРl-интерфейса Keras
Вы уже встречали несколько примеров построения нейросетевой моде­
ли прямого распространения (скажем, многослойного персептрона) и опре­
деления последовательности слоев с использованием класса

библиотеки

Keras.

Sequential

Прежде чем рассматривать различные подходы к конфи­

гурированию таких слоев, давайте кратко повторим базовые шаги, построив
модель с двумя плотными (полносвязными) слоями:

--------(573)-------------

[лава

14. Погружаемся глубже - механика TensorF/ow

>>> model = tf. keras. Sequential ()
>>> model.add(tf.keras.layers.Dense(units=16, activation='relu'))
>>> model.add(tf.keras.layers.Dense(units=32, activation='relu'))
>>> # # позднее создание переменных
>>> model.build(input_shape=(None, 4))
>>> model. summary ()
Model: "sequential"
Layer (type)

Output Shape

Param #

dense (Dense)

multiple

80

dense_l (Dense)

multiple

544

Total params: 624
TrainaЫe params: 624
Non-trainaЫe params:
Модель:

Слой

О

"sequential"
Форма выхода

(тип)

dense (Dense)

во

множественная

dense 1 (Dense)

544

множественная

Всего параметров:

624

Обучаемых параметров:

624

Необучаемых параметров:

В вызове

Кол-во параметров

model. build ()

О

мы указали форму входа, создавая перемен­

ные после определения модели для этой конкретной формы, а также отоб­
разили количество параметров каждого слоя:

слоя и

16

х

32 + 32

= 544 для

16

х

4 + 16

=

80

для первого

второго слоя. После того как переменные (или

параметры модели) созданы, мы можем получать доступ к обучаемым и не­

обучаемым переменным:

>>> ## вывод переменных модели
>>> for v in model.variaЫes:
pr·int('{:20s}'.format(v.name),

v.trainaЫe,

(574]-----

v.shape)

Глава

dense/kernel:O
dense/Ьias:O

dense l/kernel:O
dense 1/Ьias:O

True
True
True
True

14.

Погружаемся глубже

-

механика

TensorF/ow

(4, 16)
(16,)
(16, 32)
(32,)

В нашем случае каждый слой имеет весовую матрицу по имени

kernel

и вектор смещений. Далее мы сконфигурируем созданные слои, например, за
счет применения к параметрам разных функций активации, инициализаторов
переменных или методов регуляризации. Полный список доступных вариан­

тов для указанных категорий можно найти в официальной документации:
выбор функций активации посредством



tf. keras. acti vations https://www.tensorflow.org/versions/r 2.0/api docs/
python/tf/keras/activations;



инициализация



t f. ke r

s .
ini tiali zers - ht tps: / /www. tensorf low. org /versions/
r2.0/api_docs/python/tf/keras/initializ ers;
параметров

слоя

посредством

а

применение регуляризации к параметрам слоя (во избежание пере­
обучения) посредством

tf. keras. regularizers - https: / /
www.tensorflow.org/versions/r2.0/api docs/python/tf/
keras/regularizers.

В показанном ниже примере кода мы конфигурируем первый слой, указы­
вая инициализаторы для переменных ядра и смещения. Затем мы конфигури­
руем второй слой, указывая реrуляризатор

L 1 для

ядра (весовой матрицы):

>>> model = tf.keras.Sequential()
>>> model. add (
tf.keras.layers.Dense(
units=16,
activation=tf.keras.activations.relu,
kernel initializer= \
tf.keras.initializers.glorot_uniform(),
Ьias_initializer=tf.keras.initializers.Constant(2.0)

))

>>> model. add (
tf.keras.layers.Dense(
units=32,
activation=tf.keras.activations.sigmoid,
kernel_regularizer=tf.keras.regularizers.11
))

- - - - - - · - - [575)

Глава

14.

Погружаемся глубже

-

механика

TensorFlow

Кроме того, в дополнение к конфигурированию индивидуальных слоев
мы также можем сконфигурировать модель во время ее компиляции. Мы

можем указать тип оптимизатора и функцию потерь для обучения, равно
как и метрики для использования при составлении отчетности об эффек­

тивности на обучающем, проверочном и испытательном наборах данных.
Исчерпывающий список всех доступных вариантов приведен в официаль­
ной документации:



оптимизаторы посредством

tf. keras. optimizers - https: / /
www.tensorflow.org/versions/r2.0/api_docs/python/tf/
keras/optimizers;

•функции потерь посредством

tf. keras. losses - https: / /
www.tensorflow.org/versions/r2.0/api_docs/python/tf/
keras/losses;

•метрики

эффективности

посредством

tf. keras .metrics https://www.tensorflow.org/versions/r2.0/api docs/
python/tf/keras/metrics.

-$-

Выбор функции потерь

Совет

применяемыми методами являются

Что касается выбора алгоритма оптимизации, то самыми широко

SGD

и

Adam.

Выбор функции

потерь зависит от задачи; скажем, для задачи регрессии вы можете

использовать потерю в форме среднеквадратической ошибки.

Семейство функций потери перекрестной энтропии предлагает воз­
можные варианты для задач классификации, которые подробно об­
суждались в главе

15.

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

6)

в сочетании с надлежащими метриками для задачи. Скажем, для

оценки классификационных моделей подходящими метриками бу­
дут точность и полнота, правильность, площадь под кривой

(AUC),

а также доли ложноотрицательных и ложноположительных класси­

фикаций.

----(576)-----------------

Глава

14.

Погружаемся глубже

-

механика

TensorFlow

В рассматриваемом примере мы скомпилируем модель с использованием

оптимизатора

SGD,

потери перекрестной энтропии для двоичной классифи­

кации и характерного списка метрик, включающего правильность, точность
и полноту:

>>> model.compile(

optimizer=tf.keras.optimizers.SGD(learning_ra te=0.001),
loss=tf.keras.losses.BinaryCrossentropy(),
metrics=[tf.keras.metrics.Accuracy(),
tf.keras.metrics.Precision(),
tf.keras.metrics.Recall(),])
При обучении этой модели с помощью вызова метода

mode 1 . f i t ( ... )

будет возвращаться хронология потери и указанных метрик для оценки эф­
фективности обучения и проверки (если применяется проверочный набор
данных), которую можно использовать для диагностирования поведения во

время обучения.
Далее мы выполним более практичный пример: решим задачу класси­
фикации

XOR

с применением АРl-интерфейса

использовать класс

tf. keras. Seguential,

Keras.

Сначала мы будем

чтобы построить модель.

Попутно вы узнаете о емкости модели в плане обработки нелинейных гра­

ниц решений. Затем мы раскроем другие способы построения моделей, ко­

торые обеспечат нам большую гибкость и контроль над слоями сети.

Решение задачи классификации
Задача классификации

XOR -

XOR
это классическая задача анализа емкос­

ти модели в отношении захватывания нелинейной границы решений между

двумя классами. Мы сгенерируем игрушечный набор данных из

200

обуча­

ющих экземпляров с двумя признаками (х 0 , х 1 ), взятыми из равномерного
распределения с диапазоном

ку для обучающего образца

[-1, 1).
i

Далее мы назначаем достоверную мет­

согласно следующему правилу:

y> import tensorflow as tf
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> tf.random.set_seed(l)
>>> np.random.seed(l)
>>>
>>>
»>

= np.random . uniform(low=-1, high=l, size=(200, 2))
у= np.ones (len (х))
у[х[:, О] * х[:, 1]>> x_train = х [: 100, : ]
»> y_train = y[ : lOOJ
>>>x_valid=x[lOO:, : ]
»> y_valid = y[lOO:]
>>> fig = plt.figure(figsize=(б, 6))
»> plt .plot (х [у==О, 0),
х[у==О, 1), 'о', alpha=0.75, markersize=lO)
>>> plt.plot(x[y==l, О],
x[y==l, 1], ' < ', alpha=0.75, markersize=lO)
>>> plt.xlabel(r'$x_ l$', size=15)
>>> plt.ylabel(r'$x_2$', size=l5)
>» plt. show ()
Результатом выполнения кода будет представленный на рис .

14.2

график

рассеяния обучающих и проверочных образцов, показанных с разными мар­
керами в зависимости от их меток классов.

J(fO

о

75

050
025

.;;

ООО

-{) 25
-{) 50
-{) 75

• ••• "~" .,.1.."
• •• • • ~"" "" "
••• • • " "
•"-Ai • " " "
•••
••• ••••
• •• •
"
"
""
• •• •
" ~ "


.. ..,

ф

••

-l 00
-lCO -075 -{)50 -{)25

ООО

о

25

о

50

о

75

100

Х1

Рис.

14.2.

Графш,· рассеяния обучающих и провероч11ых образцов

(578) - - - - - · · - -

Глава

14.

Погружаемся глубже

механика

-

TensorF/ow

В предыдущем подразделе мы обсудили важные инструменты, которые

необходимы для реализации классификатора в

TensorFlow.

Теперь нам нуж­

но решить, какую архитектуру мы должны выбрать для этой задачи и на­

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

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

но труднее обучать (и они предрасположены к переобучению). На практике
всегда полезно начинать с простой модели в качестве базовой линии, напри­
мер, однослойной нейронной сети вроде логистической регрессии:

>>> model = tf. keras. Sequential ()
>>> model.add(tf.keras.layers.Dense(units=l,
input_shape=(2,),
activation='sigmoid'))
>>> model. summary ()

Model: "sequential"
Layer (type)

Output Shape

dense (Dense)

(None, 1)

Total params: 3
TrainaЫe params: 3
Non-trainaЫe params:

Param #
3

О

Общий размер параметров для такой простой логистической регрессион­
ной модели составляет

смещений размера

1.

чим ее на протяжении

3:

весовая матрица (или ядро) размера 2х

1

и вектор

После определения модели мы скомпилируем и обу­

200

эпох с применением размера пакета

2:

>>> model.compile(optimizer=tf.keras.optimizers.SG D(),
loss=tf.keras.losses.BinaryCrossentropy(),
metrics=[tf.keras.metrics.BinaryAccuracy()])
>>> hist = model.fit(x_train, y_train,
validation_data=(x_valid, y_valid),
epochs=200, batch_size=2, verbose=O)

(579]

Глава

14.

Погружаемся глубже

-

механика

Обратите внимание, что

TensorF/ow

mode l. f i t ( )

возвращает хронологию эпох

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

MLxtend

для визуализации

проверочных данных и границы решений.

Библиотеку

MLxtend

можно установить посредством

conda install mlxtend
pip install mlxtend



conda

или

pip:

conda-forge

Следующий код вычерчивает график, отражающий эффективность при
обучении и смещение области решений:

>>> fro:m mlxtend.plotting

impo:rt~

plot_decision_regions

>>> history = hist.history
>>> fig = plt.figure(figsize=(lб, 4))
>>>ах= fig.add_subplot(l, 3, 1)
>>> plt.plot(history['loss '], lw=4)
>>> plt.plot(history['val_l oss'], lw=4)
>>> plt. legend ( ['Потеря при обучении', 'Потеря
fontsize=l5)
>>> ax.set_xlabel( 'Эпохи', size=l5)

при проверке'],

fig.add_subplot(l, 3, 2)
plt.plot(history['binary _accuracy'], lw=4)
plt.plot(history['val_bi nary_accuracy'], lw=4)
pl t. legend ( ['Правильность при обучении',
'Правильность при проверке'], fontsize=l5)
ах. set _ xlabel ('Эпохи', size=l5)

>>>ах=

>>>
>>>
>>>
>>>

>>>ах= fig.add_subplot(l, 3, 3)
>>> plot_decision_regions(X= x_valid, y=y_valid.astype(np.inte ger),
clf=model)
>>> ax.set_xlabel(r'$x_l$', size=l5)
>>> ax.xaxis.set label_coords(l, -0.025)
>>> ax.set_ylabel(r'$x_2$', size=l5)
>>> ax.yaxis.set_label_coor ds(-0.025, 1)
>>> pl t. show (J

Результат показан на рис.

14.3;

он содержит три части

-

график потерь,

график правильности и график рассеяния проверочных образцов вместе с
границей решений.

----------------- [ 5801 - -- - - - - - - - - - - - - - - - - -

Глава

14.

По гружаемся глубже

TensorF/ow

><

е е2·----

-

механика

-

Потеря nри обучении
Потеря nрм проверке

а

.

1Q

06~

о

а"о а~·

•*
J'lf•а tА dli
"
oD
r:tJ..."

-(\ \

-1

O•I

1ОО

11!

!'»

1 1~

....• rP "'

'*

о.

g

Qi

11

Прее ильностъ при nроеерке

О •З

о

1

Qll'(,.~··~
,.

1 .

00

Эпохи



1) .
о 6S

200

-iS

-1 ~

-OS



0'1

IQ

в

Х1

Эпохи

Рис. 14. З. Эффектив11ость при обучетш и c.weщeuue обл асти рещений

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

XOR.

Как

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

прямого распространения с единственным

скрытым слоем

и относи­

тельно большим количеством скрытых элементов способна относительно

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

XOR

предусматривает добавление скрытого слоя и сравнение различных ко­

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

Альтернативно мы также можем добавить дополнительные скрытые

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

сопоставимой емкости модели требуется меньше параметров . Тем не менее ,
недостаток глубоких моделей (в сравнении с широкими моделями) связан

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

- - - - - - - - - -- - (581 ] ----

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

>>> tf.random.set_seed(l)
>>> model = tf. keras. Sequential ()
>>> model.add(tf.keras.layers.Dense(units=4, input_shape=(2,),
activation='relu'))
>>> model.add(tf.keras.layers.Dense(units=4, activation='relu'))
>>> model.add(tf.keras.layers.Dense(units=4, activation='relu'))
>>> model.add(tf.keras.layers.Dense(units=l,
activation='sigmoid'))
>>> model. summary ()
Model: "sequential_4"

Layer (type)

Output Shape

Param #

dense 11 (Dense)

(None, 4)

12

dense 12 (Dense)

(None, 4)

20

dense 13 (Dense)

(None, 4)

20

dense 14 (Dense)

(None, 1)

5

Total params: 57
TrainaЫe params: 57
Non-trainaЫe params:

О

>>> # # компиляция:
>>> model.compile(optimizer=tf.keras.optimizers.SG D(),
loss=tf.keras.losses.BinaryCrossentropy(),
metrics=[tf.keras.metrics.BinaryAccuracy()])
>>> ## обучение:
>>> hist = model.fit(x_train, у train,
validation_data=(x_valid, y_valid),
epochs=200, batch_size=2, verbose=O)
Повторив приведенный ранее код для визуализации, мы получим графи­
ки, представленные на рис.

14.4.

Теперь мы видим, что модель в состоянии вывести нелинейную границу
решений для имеющихся данных, достигая 100%-ной правильности на обу­
чающем наборе. Правильность на проверочном наборе составляет

95%,

что

говорит о небольшой степени переобучения модели.

-------------------------- (582) --------------------------

Глава

14.

Погружаемся глубже

Потеря при обучении
Потеря при

механика

"

1 s.

А

npoeeixe

ОО·

06
С3



cs
04

--

с:

Правильность при обучении
Правилы+ость при проверке

1

"" 9# ,"".~
"

'"о!
"•~о
.1•1Ь•.,...
1
:

CI

С 7

о

;:;;

10

се

01

TensorF/ow

><

1~
-

-

А А .6

1

-1 G ·

dЬ aD

••/ r:f' С11
'ilt
Qi

8

о'о

1'0

·11 .

сз

100

S>> tf.random.set_seed(l)
>>> ## входной слой:
>>> inputs = tf.keras.Input(shape=(2,))

- --- -- (583) ------------ - - - - - - -- - - -

Глава

14.

Погружаемся глубже

-

механика

TensorFlow

>>> ## скрытые слои
>>> hl = tf.keras.layers.Dense(un its=4, activation='relu') (inputs)
>>> h2 = tf.keras.layers.Dense(u nits=4, activation='relu') (hl)
>>> hЗ = tf.keras.layers.Dense(u nits=4, activation='relu') (h2)
>>> ## ВЫХОД:
>>> outputs = tf.keras.layers.Dense(u nits=l, activation='sigmoid')
... (hЗ)
>>> ## конструирование модели:
>>> model = tf.keras.Model(inputs=i nputs, outputs=outputs)
>>> model. summary ()
Модель компилируется и обучается аналогично тому, как мы поступали
ранее:

>>> # # компиляция:
>>> model.compile(
optimizer=tf.keras.optim izers.SGD(),
loss=tf.keras.losses.Bin aryCrossentropy(),
metrics=[tf.keras.metric s.BinaryAccuracy()])
>>> ## обучение:
>>> hist = model. fit (
x_train, y_train,
validation_data=(x_vali d, y_valid),
epochs=200, batch_size=2, verbose=O)
Реализация моделей на основе класса

Model библиотеки Keras

Альтернативный способ построения сложных моделей предусматрива­
ет создание подклассов класса

подходе мы

создаем новый класс,

определяем

tf. keras. Model. При таком
производный от tf. keras. Model, и

функцию _ini t _ ( ) как конструктор. Для определения обратного про­
хода применяется метод

call ().

В функции конструктора,

ini t

(),

мы определяем слои как атрибуты класса, так что к ним можно обращаться
через ссылочный атрибут

self.

Затем в методе

call ()

мы указываем, ка­

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

[584]

fлава

14. Погружаемся глубже - механика TensorF/ow

>>> class MyМodel(tf.keras.Model):
def
init (~~lf):
super (MyModel, ~;( 0 Jf.) ._init_()
~'с.} f .hidden_l = tf. keras. layers .Dense l
units=4, activation='relu')
~,,,' .r • hidden_ 2 = tf. keras. layers. Dense (
units=4, activation='relu')
s.~ J У. hidden_ З = t f. keras. la yers. Dense (
units=4, activation='relu')
:···" i J' .output_layer = tf. keras .layers.Dense (
units=l, activation='sigmoid')

def call (;,,\t, inputs):
h = :< Li.• hidden_l (inputs)
h = ·о:с i С' .hidden_2 (h)
h = s~:; f .hidden_З (h)
ret\1rn :>... ~1.output_layer(h)
Обратите внимание, что для всех скрытых слоев мы применяем одно и

то же имя

h,

тем самым делая код более читабельным и легким в отслежи­

вании.

Класс модели, производный от

tf. keras .Model

с помощью создания

подклассов, наследует основные атрибуты модели, такие как

compile ()

и

fi t

bui ld ( ) ,

().Следовательно, после определения этого нового клас­

са мы можем компилировать и обучать модель подобно любой другой моде­
ли, построенной посредством

Keras:

>>> tf.random.set_seed(l)
>>> model = MyModel ()
>>> model.build(input_shape=(Noпe, 2))
>>> model. summary ()
>>> ## компиляция:
>>> model.compile(optimizer=tf.keras.optimizers.SGD(),
loss=tf.keras.losses.BinaryCrossentropy(),
metrics=[tf.keras.metrics.BinaryAccuracy()])
>>> ## обучение:
>>> hist = model.fit(x_train, у train,
validation data=(x_valid, y_valid),
epochs=200, batch_size=2, verbose=O)

[585]

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

Реаnизация специаnьных сnоев

Keras

В ситуациях, когда мы хотим создать новый слой, который еще не подде­

рживается библиотекой
ный от класса

Keras, то можем определить новый класс, производ­
tf. keras. layers. Layer. Поступать так особенно удобно

при проектировании нового слоя или настройке существующего слоя.

Чтобы проиллюстрировать концепцию реализации специальных слоев,
мы рассмотрим простой пример. Пусть нам нужно определить новый линей­

ный слой, который вычисляет

w (х + е) + Ь,

где е ссылается на случайную пе­

ременную в качестве переменной шума. Для реализации такого вычисления
мы определим новый класс как подкласс класса

tf. keras. layers. Layer.
_ini t _ ( )

В новом классе мы должны определить метод конструктора

и метод

cal 1 () .

В конструкторе мы определим для нашего специального

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

если ему предоставляется

input _ shape.

Альтернативно мы можем отло­

жить инициализацию переменных (скажем, если заранее не знаем точную

форму входа) и делегировать ее методу

build ()

с целью позднего созда­

ния переменных. Вдобавок мы можем определить метод

get config ()

для сериализации, т.е. модель, использующая наш специальный слой, будет

способна эффективно сохраняться с применением средств сохранения и за­
грузки библиотеки

TensorFlow.

Давайте обратимся к конкретному примеру и определим новый слой по
имени

w(x

NoisyLinear,

который реализует упоминаемое выше вычисление

+ е) + Ь:

>>> class NoisyLinear(tf.keras.layers.Layer):
def _init_(._;, ; ·, output_dirn, noise_stddev=0.1, **kwargs):
:" l С .output_dirn = output_dirn
. : . noise stddev = noise stddev
super(NoisyLinear, · ; ') ._init_(**kwargs)
def build('•• .. , input _ shape) :
: · r .w = ;. · .add_weight (narne='weights',
shape=(input_shape[l],
· · · : . output _ dirn) ,
initializer='randorn_normal',
trainaЫe='Гrue)

[586] --------------~------·-----------

f11ава

14.

,,•J.t.• Ь = ··'"' ···.add_weight

Погружаемся глубже

механика

-

TensorFlow

(shape=(~c: 1 _1'

.output_dim,),
initializer='zeros',

trainaЫe=True)

def call (:>>> tf. random. set_seed (1)
>>> noisy_layer = NoisyLinear(4)
>>> noisy_layer.build(input_shape=(Noпe, 4))
>>> х = tf.zeros(shape=(l, 4))
>>> tf .print (noisy_layer (х, training=Trнe))
[ [0 0.00821428 о 0)]
>>>
>>>
>>>
>>>
[[0

##реконструкция из

config:

config = noisy_layer.get_config()
new_layer = NoisyLinear.from_config(config)
tf .print (new_layer (х, training=Trпe))
0.0108502861 о 0))

В предыдущем фрагменте кода мы вызывали слой два раза на том же

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

NoisyLinear

добавляет к входному тензору случайный

шум.

А теперь давайте создадим новую модель, похожую на предшествую­
щую модель для решения задачи классификации
дем применять класс

Sequential

из

Keras,

XOR.

Как и ранее, мы бу­

но на этот раз использовать

в качестве первого скрытого слоя многослойного персептрона наш слой

NoisyLinear.

Вот необходимый код:

>>> tf.random.set_seed(l)
>>> model = tf. keras. Sequential ( [
NoisyLinear(4, noise_stddev=0.1),
tf.keras.layers.Dense(units=4, activation='relu'),
tf.keras.layers.Dense(units=4, activation='relu'),
tf.keras.layers.Dense(units=l, activation='sigmoid')])
>>> model.build(input_shape=(None, 2))
>>> model. sшпmary ()

[588] ---------------~-

Глава

14.

Погружаемся глубже

-

механика

TensorFlow

>>> ## комm1Ляция:
>>> model.compile(optimizer=tf.keras.optimizers.SGD(),
loss=tf.keras.losses.BinaryCrossentropy(),
metrics=[tf.keras.metrics.Bin~ryAccuracy()])

>>> ## обучение:
>>> hist = model.fit(x_train, y_train,
validation_data=(x_valid, y_valid),
epochs=200, batch_size=2,
verbose=O)
>>> ## вычерчиванИе графиков:
>>> history = hist. history
»> fig = plt.figure(figsize=(lб, 4))
>>>ах= fig.add_subplot(l, З, 1)
>>> plt.plot(history['loss'], lw=4)
>>> pl t. plot (history [ 'val _ loss'] , lw=4)
>>> plt.legend(['Пoтepя при обучении', 'Потеря
fontsize=15)
>>> ах. set _xlabel ( 'Эпохи' , size=15)
>>>ах=

fig.add_subplot(l,

З,

при проверке'],

2)

>>> plt.plot(history['Ьinary_accuracy'], lw=4)
>>> plt.plot(history['val_Ьinary_accuracy'], lw=4)
>>> pl t. legend ( [ 'Правильность при обучении' ,
'Правильность при проверке'], fontsize=15)
>>> ax.set_xlabel('Эпoxи', size=15)
fig.add_subplot(l, З, 3)
plot_decision_regions(X=x_valid, y=y_valid.astype(np.integer),
clf=model)
ax.set_xlabel(r'$x_1$', size=15)
ax.xaxis.set_label_coords(l, -0.025)
ax.set_ylabel(r'$x_2$', size=15)
ax.yaxis.set_label_coords(-0.025, 1)
plt. show ()

>>>ах=

>>>
>>>
>>>
>>>
>>>
»>

На рис.

14.5

представлены результирующие графики.

Наша цель заключалась в том, чтобы выяснить, каким образом определять
новый специальный слой, созданный в форме подкласса класса

layers. Layer,
слою Keras. Хотя

tf. keras.

и применять его подобно любому другому стандартному

в рассмотренном конкретном примере слой

NoisyLinear

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

·------(589)-----------

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow
)(

10

tJ

а



1 s.
06

09

os

.

10.

;~а t .1•.~

со

01
С3



о!


100

l S.~

ьз

Правмлыюстъ nри проверке

"'



...."*•

100

lSO

cf0

rP

gO

tfj

11

QI

-1 s

-i s -i с

100

s

со

cs



1's

Х1

Эпохи

Эпохи

Рис.

•А •

s.

- 1 о.

С6

о~

1

ilt"~·:p
".

os.

08

о•

о

14.5.

М11огослой11ый персептро11 со слоем

NoisyLinear

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

Оценщики

TensorFlow

До сих пор в главе мы были сосредоточены главным образом на низко­

уровневом АРI-интерфейсе

TensorFlow.

Мы использовали декораторы для

модификации функций, чтобы явно компилировать вычислительные гра­

фы с целью повышения эффективности расчетов. Затем мы работали с
АРI-интерфейсом

Keras

и реализовывали нейронные сети прямого рас­

пространения, к которым добавляли специальные слои. В текущем разде­
ле мы переключим внимание на оценщики

tf. estimator

TensorFlow.

В АРI-интерфейсе

инкапсулируются внутренние шаги задач МО, такие как

обучение, прогнозирование (выведение) и оценка. Оценщики более инкапсу­
лированы (т.е. защищены от внешнего вмешательства), но и более масшта­
бируемы в сравнении с предшествующими подходами, раскрытыми в главе.
К тому же АРI-интерфейс

tf. estimator

добавляет поддержку для прого­

на моделей на множестве платформ, не требуя внесения в код крупных из­
менений, что делает их более подходящими для так называемой "производ­
ственной стадии" в индустриальных приложениях. Кроме того, библиотека

TensorF\ow

поступает с подборкой готовых оценщиков для распространен­

ных архитектур МО и ГО, которые удобны в сравнительных исследованиях,

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

-

- - - - - --

-

-

(590]--

-

--

Глава

14.

Погружаемся глубже

-

механика

TensorFlow

В оставшихся разделах главы вы узнаете, как использовать такие готовые

оценщики и создавать оценщик из существующей модели

Keras.

Одним из

важнейших элементов оценщиков является определение столбцов признаков
как механизм для

импортирования данных в модель, основанную на оцен­

щиках, что мы раскроем в следующем разделе.

Работа со столбцами признаков
В приложениях МО и ГО мы можем столкнуться с разнообразными типа­
ми признаков: непрерывными, неупорядоченными категориальными (имен­

ными) и упорядоченными категориальными (порядковыми). Вспомните, что
в главе

4

мы рассматривали различные типы признаков и выясняли, каким

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

данные могут быть либо непрерывными, либо дискретными, в контексте
АРI-интерфейса

TensorFlow

"числовые" данные конкретно имеют отноше­

ние к непрерывным данным типа с плавающей точкой.

Иногда наборы признаков состоят из смеси признаков разных типов. В то
время как оценщики

TensorFlow

проектировались для поддержки всех типов

признаков, мы должны указывать, как каждый признак будет интерпрети­
роваться оценщиком. Например, рассмотрим сценарий с набором из семи
признаков, изображенный на рис.
Признаки, показанные на рис.

14.6.
14.6 (год

выпуска модели, количество ци­

линдров, рабочий объем, количество лошадиных сил, вес, разгон и проис­

хождение), были получены из набора данных

Auto MPG,

который является

общим эталонным набором данных МО для прогнозирования эффективнос­
ти расхода топлива автомобилем в .нилях 1щ галлон

(miles ре1· gallm1 ---

М />(~).

Полный набор данных вместе с описанием доступен в хранилище
для МО по ссылке

UCI
ht tps: / / archi ve. ics. uci. edu/ml/ da tasets/

auto+mpg.
Мы собираемся трактовать пять признаков из набора данных

Auto MPG

(количество цилиндров, рабочий объем, количество лошадиных сил, вес и
разгон) как "числовые" (здесь непрерывные) признаки. Год выпуска моде­

ли можно рассматривать как упорядоченный категориальный (порядковый)
признак. Наконец, происхождение можно считать неупорядоченным катего­
риальным (именным) признаком с тремя возможными дискретными значе­

ниями,

1, 2

и

3,

которые соответствуют США, Европе и Японии.

----- [591 ) - - - - - - - - - - -

\D
"'
N

Низкоуровневые данные

(ModelYear)

Год выпуска модели

{1970-1982}

(Cylinders)

Числовой
признак

Количество цилиндров to-+---+-----...i

Рабочий объем

(Displacement)

1

1

признак

Столбцы признаков

признак

Сгруппированный

{(70-72), (73-75),
(76-78), (79-82)}

Числовой признак

Числовой признак

Числовой признак

индикатор

или

Вложение

Числовой признак

~1

Категориальный 1--+1

----------.i

1

Количество лошадиных сил
(Horsepower)
Вес

(Weight)
Разгон

(Accelera tion)
Происхождение

(Origin)
{США, Европа, Япония}

14.6.

Набор из семи признаков
Рис.

Модель

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

Давайте сначала загрузим данные и применим необходимые шаги предва­

рительной обработки, такие как расщепление набора данных на обучающий
и испытательный наборы плюс стандартизация непрерывных признаков:

>>> import pandas as pd
>>> dataset_path = tf.keras.utils.get_file(

"auto-mpg.data",
("http://archive.ics.uci.edu/ml/machine-learni ng"
"-databases/auto-mpg/auto-mpg.data"))
>>> column_names = [

'MPG', 'Cylinders', 'Displacement',
'Horsepower', 'Weight', 'Acceleration',
'ModelYear', 'Origin']
>>> df = pd.read_csv(dataset_path, names=column_names,

na_values = '?', comment='\t',
sep•' ', skipinitialspace=True)
>>> ## отбросить строки
»> df " df. dropna ()
>>> df

с отсутствующими

(NA) значениями

= df.reset_index(drop=True)

>>> ##расщепление на обучающий и испытательный
>>> iinport sklearn
>>> iroport sklearn.model_selection

наборы:

= sklearn.model_selection.train_test_split(
df, train_size=0.8)
>>> train_stats = df_train.describe() .transpose()

>>> df train, df test

>>> numeric_column_names = [

'Cylinders', 'Displacement',
'Horsepower', 'Weight',
'Acceleration']
>>> df_train_norm, df_test_norm = df_train.copy(), df_test.copy()
>>> for col name in numeric column names:

-

-

-

mean = train_stats. loc [col_name, 'mean']
std = train_stats. loc [col_name, 'std']
df_train_norm. loc [:, col_name] = (
df_train_norm.loc[:, col_name] - mean)/std
df_test_norm.loc[:, col_name] = (
df_test_norm. loc [:, col_name] - mean) /std
>>> df_train_norm.tail()

(593]

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

Результат представлен на рис.

МРО

Cytlnders

Dlsplвcement

203

28.0 -0.824303

-0.901020

255

19.4

0.351127

72

13.0

236

37

14.7.

Horиpower

Welght Accelenltlon ModelY_. Ortgln

-0.736562 -0.950031

0.255202

76

0.413800

-0.340982

0.293190

0.548737

78

1.526556

1.144256

0.713897

1.339617

-0.625403

72

30.5 -0.824303

-0.891280

-1.053025 -1.072585

0.475353

77

-1.359240

71

14.0

1.526556

1.638916

1.563051
Рис.

14. 7.

1.470420

з

Результирующий кадр дат1ых

Созданный предыдущим фрагментом кода объект
содержит пять столбцов со значениями типа

float.

DataFrame

из

pandas

Эти столбцы будут об­

разовывать непрерывные признаки. В следующем коде мы будем использо­

вать функцию

TensorF\ow

numeric_column

из модуля

feature_column

библиотеки

для трансформации непрерывных признаков в структуру дан­

ных под названием столбец признаков, с которой могут работать оценщики

TensorF\ow:
>>> numeric_features = []
>>> for col name in numeric column names:

numeric_features.append(
tf.feature_column.numeric_column(key=col_name))
Далее мы сгруппируем довольно мелкозернистую информацию о годах
выпуска в участки, чтобы упростить задачу обучения модели. Выражаясь
более конкретно, мы назначим каждый автомобиль одному из четырех
участков "годов":
О, если год

участок

=

< 73
{ 1, если 73 ~ год < 76
2, если 76 ~ год < 79
3, если год 2: 79

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

--------- -----

bucketized column

[594)

вместе с тремя

--------------------------

[лава

граничными значениями интервалов:

14. Погружаемся глубже - механика TensorF/ow

[73, 76, 79].

Указанные значения пред­

ставляют собой значения отсечения справа и служат для определения полу­
замкнутых интервалов, например, (---оо,

73), [73, 76), [76, 79)

и

[79,

оо). Ниже

приведен код:

>>> feature_year = tf .feature_column.numeric_column (key='ModeJ.Year')
>>> bucketized_ features = []
>>> bucketized_features.append(
tf.feature column.bucketized_column(
source_column=feature_year,
boundaries=[73, 76, 79]))
Ради согласованности мы добавили такой сгруппированный признак в
список

Python,

хотя сам список состоит только из одного элемента. В после­

дующих шагах мы объединим этот список со списками, созданными из дру­
гих признаков, и передадим результат в качестве входа модели, основанной

на оценщиках

TensorFlow.

Далее мы продолжим определением списка для неупорядоченного катего­
риального признака

Origin.

В

TensorFlow

предусмотрены разные способы

создания столбцов категориальных признаков. Если данные содержат назва­
ния категорий (скажем, в строковом формате вроде

"US"

(США),

"Europe"

(Европа) и

"Japan" (Япония)), тогда мы можем задействовать функцию
tf.feature_column.categorical column_with_vocabulary_list,

передав ей список возможных уникальных имен категорий. Если список

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

tf.feature_column.categorical_column_with_vocabulary_file.
При вызове указанной функции мы просто предоставляем файл, который

содержит все категории/слова, так что нам не придется хранить список всех
возможных слов в памяти. Кроме того, если признаки уже ассоциированы
с индексами категорий из диапазона [О,

num_ ca tegories), то мы можем
применять функцию tf. feature column. categorical column
wi th_ identi ty. Тем не менее, в таком случае признак Origin задается
как целочисленные значения 1, 2, 3 (в противоположность О, 1, 2), которые
не соответствуют требованию категориальной индексации, т.к. ожидается,
что индексы начинаются с О.

В приведенном ниже фрагменте кода мы создаем словарный список:

[595)---·-·

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

>>> feature_origin =

tf.feature_column.categorical_column_with_voc abulary_list(
key=' Origin',
vocabulary_list=[l, 2, 3))
Определенные оценщики, такие как

DNNClassifier

и

DNNRegressor,

принимают только то, что называется "плотными столбцами". По этой
причине следующим шагом будет преобразование существующего столб­
ца категориального признака в плотный столбец подобного рода, для чего
существуют два способа: использование столбца вложений посредством
emЬedding_colurnn или столбца индикаторов через

indicator_column.

Столбец индикаторов преобразует категориальные индексы в векторы в уни­

тарном коде, т.е. индекс О будет закодирован как
(О,

1,

[ 1,

О, О], индекс

1-

как

О] и т.д. С другой стороны, столбец вложений отображает каждый ин­

декс на вектор случайных чисел типа

float,

который может быть обучен.

Когда количество категорий велико, применение столбца вложений с чис­
лом измерений, меньшим количества категорий, может улучшить произво­

дительность. В следующем фрагменте кода мы будем использовать подход
со столбцом индикаторов для категориального признака, преобразуя его в
плотный столбец:

>>> categorical _ indicator_ features = []
>>> categorical_indicator_features.append(

tf.feature_column.indicator_column(feature_or igin))
В этом разделе мы рассмотрели самые распространенные подходы к со­

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

TensorFlow.

Однако существует несколько дополнительных столбцов призна­

ков, не обсуждаемых здесь, в том числе хешированные и перекрестные столб­

цы. Исчерпывающие сведения обо всех столбцах признаков доступны в офи­

циальной документации TensorFlow по ссылке https: / /www. tensorflow.
org/versions/r2.0/api_docs/python/tf/fe ature column.

Машинное обучение с использованием rотовых оценщиков
После конструирования обязательных столбцов признаков мы можем, на­

конец, задействовать оценщики

TensorFlow.

Работа с готовыми оценщиками

может быть подытожена в виде четырех шагов.

--------- [596] -

Глава

14.

Погружаемся глубже

-

механика

TensorFlow

1.

Определить входную функцию для загрузки данных.

2.

Преобразовать набор данных в столбцы признаков.

3.

Создать объект оценщика (применяя готовый оценщик или создавая
новый путем преобразования модели
Использовать методы оценщика

4.

Keras

в оценщик).

train (), evaluate ()

Продолжая пример с набором данных

Auto MPG

и

predict ().

из предыдущего раздела,

мы применим описанные четыре шага с целью иллюстрации практического

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

TensorFlow,

состоящий из кортежа с входными признаками и метками (достоверные зна­
чения

MPG).

Обратите внимание, что признаки должны находиться в фор­

мате словаря, ключи которого обязаны соответствовать именам столбцов
признаков.

Начав с первого шага, мы определим входную функцию для обучающих
данных следующим образом:

>>> def train_input_fn(df_train, batch_size=8):
df = df_train.copy()
train_ х, train_y = df, df. рор ( 'MPG' )
dataset = tf.data.Dataset.from tensor_slices(
(dict(train_x), train_y))
# тасование, повторение и разбиение на пакеты образцов.
return dataset.shuffle(lOOO) .repeat() .batch(batch_size)
Как видите, в этой функции мы применяем

разования объекта

DataFrame

библиотеки

dict (train х) для преоб­
pandas в словарь Python. Давайте

загрузим пакет из этого набора данных, чтобы посмотреть, на что он похож:

>>> ds = train_input_fn(df_train_norm)
>>> batch = next (iter (ds))
>>> pr·int ('Ключи:', batch (0). keys ())
Ключи: dict_keys(['Cylinders', 'Displacement', 'Horsepower',
'Weight', 'Acceleration', 'ModelYear', 'Origin'])
>>> print(

batch[O] [ 'ModelYear'])
tf.Tensor([74 71 81 72 82 81 70 74),
shape=(8,), dtype=int32)
'Пакет с годами выпуска:',

Пакет с годами вьmуска:

- - - - - - - - - ··--[597]-----·--

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

Также нам понадобится определить входную функцию для испытательно­
го набора данных, который будет использоваться при оценке модели после
обучения:

>>> def eval_input_fn(df_test, batch_size=8):
df = df_ test. сору()
test_x, test_y = df, df.pop( 'MPG')
dataset = tf.data.Dataset.from tensor_slices(
(dict (test_x), test_y))
ret.urп dataset .batch (batch_size)
С переходом ко второму шагу нам нужно определить столбцы признаков.
Мы уже определили список, содержащий непрерывные признаки, список
для столбца сгруппированного признака и список для столбца категориаль­

ного признака. Теперь мы можем объединить упомянутые индивидуальные
списки в единственный список, содержащий все столбцы признаков:

>>>

= (
numeric f eatures +
bucketized features +
categorical_indicator_features)

all_feature_colшnns

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

рег­

рессии, мы будем применять класс

При

MPG является типичной задачей
tf. estirnator. DNNRegressor.

создании объекта регрессионного оценщика мы предоставим список стол­
бцов признаков и укажем количество скрытых элементов, которые желаем
иметь в каждом скрытом слое, используя аргумент

hidden_ uni ts.

В при­

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

-



32

элементов:

>>> regressor = tf.estimator.DNNRegressor(
feature_colшnns=all_feature_colшnns,

hidden_units=[32, 10),
model_dir='models/autompg-dnnregressor/')
Дополнительно предоставленный аргумент

rnodel _ dir

указывает ката­

лог для сохранения параметров модели. Одно из преимуществ оценщиков

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

обучения по непредвиденной причине (вроде неисправности сети питания)

-[598) ------~------·--------·-----

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

мы легко можем загрузить последнюю сохраненную контрольную точку и

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

mode 1 _ di r.

Если мы опускаем аргумент

model _ dir, то оценщик создаст случайный временный каталог (например,
в среде Linux будет создан случайный подкаталог в каталоге /tmp/), пред­
назначенный для такой цели.

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

обучить вызовом метода

train (),

для которого требовалась ранее опреде­

ленная входная функция:

>>> EPOCHS = 1000
>>> ВАТСН SIZE = 8

»> total_steps = EPOCHS * int(np.ceil(len(df_train) / BATCH_SIZE))
>>> prin.t(

'Шаги обучения:',

Шаги обучения:

total_steps)

40000

>>> regressor.train(

input fn=larnЬda:train_input fn(
df_train_norm, batch_size=BATCH_SIZE),
steps=total_steps)
Вызов

. train ()

будет автоматически сохранять контрольные точки во

время обучения модели. Затем мы можем загрузить последнюю контроль­
ную точку:

>>> reloaded_regressor = tf.estimator.DNNRegressor(

feature_columns=all_feature_columns,
hidden_units=[32, 10],
warm_start_from='models/autompg-dnnregressor /',
model_dir='models/autompg-dnnregressor/')
Далее для оценки прогнозирующей эффективности обученной модели мы
можем использовать метод

evaluate ():

eval_results = reloaded_regressor.evaluate(
input_fn=larnЬda:eval_input fn(df_test_norm, batch_size=B))
>>> p.r.шt( 'Средняя потеря {: .4f}' .format (
eval_results['average_loss']))
Средняя потеря 15.1866
>~>

(599] -------·-

Глава

14.

Погружаемся глубже

-

механика

TensorFlow

Наконец, для прогнозирования целевых значений на новых точках дан­
ных мы можем применять метод

predict ().

Для целей рассматриваемого

примера мы предполагаем, что в реалистичном приложении испытательный

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

>>> pred_res = regressor.predict(
input_fn=lamЬda: eval_input_fn(
df_test_norm, batch_size=8))
>>> pr·iпt (next (iter (pred_res)))
{ 'predictions': array( [23. 747658], dtype=float32)}
Несмотря на то что предшествующие фрагменты кода завершили иллюс­
трацию четырех шагов, требующихся для использования готовых оценщи­
ков, давайте ради практики рассмотрим еще один готовый оценщик: рег­

рессор на основе деревьев с градиентным бустингом,

BoostedTreeRegressor.

Так как входные функции и столбцы признаков

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

200

tf. estimator.

BoostedTreeRegressor

3

и

4.

Для шага

3

мы

и сконфигурируем его так,

деревьев.

-~:

Бустинr деревьев принятия решений

Совет

ве

Ансамблевые алгоритмы, включая бустинг, были раскрыты в гла­

7.

Алгоритм на основе деревьев с градиентным бустингом пред­

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

https://mediurn.com/mlreview/gradient-boo sting-fromscratch-le31 7ae4587d.
>>> boosted_t!ee = tf.estimator.BoostedTreesRegressor(
feature_columns=all_feature_columns,
n_batches_per_layer=20,
n_trees=200)

--[600)----------

fлава

14.

Погружаемся глубже

-

механика

TensorF/ow

>>> boosted_tree.train(
input_fn=lamЬda:train_input_fn(

df_train_norrn, batch_size=BATCH_SIZE))

>>> eval_results = boosted_tree.evaluate(
input_fn=lamЬda:eval_input_fn(

df_test_norrn, batch_size=8))

>>>

{ : . 4 f} ' . forrnat (
eval_results['average_loss']))
потеря 11.2609

po.n. t ( 'Средняя потеря

Средняя

Как видите, регрессор на основе деревьев с градиентным бустингом до­

бился меньшей средней потери, чем

DNNRegressor.

Для небольших набо­

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

TensorFlow

для регрессии. В следующем подразделе мы рассмотрим типич­

ный пример классификации с использованием оценщиков.

Использование оценщиков для классификации рукописных цифр

MNIST

Для решения такой задачи классификации мы собираемся применять

оценщик

DNNClassifier

из библиотеки

TensorFlow,

который позволяет

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

оценщиков, а в этом разделе мы их повторим. Первым делом мы импор­

тируем подмодуль

ствовать для

tensorflow_ datasets (tfds), который можно задей­
загрузки набора данных MNIST и указания гиперпараметров

модели.

'

1 "

-">> import tensorflow_datasets as tfds
>>> import tensorflow as tf
>>> import numpy as np
>>> BUFFER SIZE = 10000

»> ВАТСН SIZE = 64
>» NUM EPOCHS = 20
/ BATCH_SIZE)

>>> steps_per_epoch =

np.ceil(бOOOO

Обратите внимание, что

steps _per epoch

определяет количество ите­

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

13).

Далее мы определим вспомога­

тельную функцию, которая будет выполнять предварительную обработку
входного изображения и его метки. Так как входное изображение изначаль­

'uint8' (в диапазоне [О, 255]), мы будем применять функ­
цию tf. image. convert image dtype для преобразования его типа в
tf. float32 (и таким образом в диапазон [О, 1]):

но имеет тип

>>> def preprocess (item):
image = i tem [ 'image']
label = item['label']
image = tf.image.convert_image_dtype(
image, tf.float32)
image = tf. reshape ( image, (-1, ) )
return {'image-pixels' :image}, label[ ... , tf.newaxis]
Шаг

1.

Определение двух входных функций (первой для обучения и вто­
рой для оценки):

>>> ## Шаг 1: определение двух входных функций
>>> def train_input_fn ():
datasets = tfds.load(name='mnist')
mnist_train = datasets('train']
dataset = mnist_train.map(preprocess)
dataset = dataset.shuffle(BUFFER_SIZE)
dataset = dataset.batch(BATCH_SIZE)
return dataset.repeat()

- - - - - - - - - - - - (602)--

fлава

14. Погружаемся глубже - механика TensorF/ow

>>> def eval_input_fn ():
datasets = tfds.load(name='mnist')
mnist_test = datasets['test']
dataset = mnist_test.map(preprocess) .batch(BATCH_SIZE)
return dataset
Отметим, что словарь признаков имеет только один ключ,

' irnage-pixels '.

Мы будем использовать этот ключ в следующем шаге.
Шаг

2.

Определение столбцов признаков:

>>> ##Шаг 2: столбцы признаков
>>> image_feature_column = tf.feature_column.numeric column(
key='image-pixels', shape=(28*28))
Обратите внимание, что мы определяем столбцы признаков размера

(т.е.

28 х 28),
MNIST после
Шаг

3.

784

который представляет собой размер входных изображений
того, как они разглажены.

Создание нового объекта оценщика. Здесь мы определяем два
скрытых слоя:

32

элемента в первом и

16

элементов во втором.

Мы также указываем количество классов (вспомните, что набор данных

MNIST состоит из изображений 1О
мента n classes:

разных цифр,

0-9)

с применением аргу­

>>> ## Шаг 3: создание объекта оценщика
>>> dnn_classifier = tf.estimator.DNNClassifier(
feature_columns=[image_feature_column],
hidden_units=[32, 16],
n_classes=lO,
model_dir='models/mnist-dnn/')
Шаг

4.

Использование оценщика для обучения, оценки и прогнозирования:

>>> ##Шаг 4: обучение и оценка
>>> dnn_classifier.train(
input_fn=train_input_fn,
steps=NUM_EPOCHS * steps_per_epoch)
>>> eval_result = dnn_classifier.evaluate(
input_fn=eval_input_fn)
>>> p:i:·int (eval_result)
{'accuracy': 0.8957, 'average_loss': 0.3876346,
'loss': 0.38815108, 'global_step': 18760}

[603)-----------

[лава

14.

Погружаемся глубже

-

механика

TensorF/ow

К настоящему моменту вы научились использовать готовые оценщики и
применять их для предварительной оценки с целью выяснения, например,

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

Keras,

чем и займемся в следующем подразделе.

Создание специаnьноrо оценщика из существующей модеnи
Преобразование модели

Keras

Keras

в оценщик полезно как в научном сооб­

ществе, так и в производственной среде в ситуациях, когда вы разработали

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

пределенное обучение и автоматическое сохранение контрольных точек.
Вдобавок другим станет легче использовать эту модель, в частности, будет
устранена путаница при интерпретировании входных признаков за счет ука­

зания столбцов признаков и входной функции.
Чтобы выяснить, как можно создать собственный оценщик из модели

Keras,

мы займемся предыдущей задачей классификации

XOR.

Для начала

мы восстановим данные и расщепим их на обучающий и проверочный на­
боры:

>>> tf.random.set_seed(l)
>>> np.random.seed(l)
>>> ## Создание данных
>>> х = np.random.uniform(low=-1, high=l, size=(200, 2))
>>>у= np.ones(len(x))
>» у[х[:, О] * х[:, 1]>>x_train=x[:lOO, :]
>» y_train = y[:lOO]
>>>x_valid=x[lOO:, :]
>» y_valid = y[lOO:]
Давайте теперь построим модель

Keras,

которая позже будет преобразо­

вана в оценщик. Как и ранее, мы определим модель с применением класса

Sequential. На этот раз мы также добавим входной слой, определенный
как tf. keras. layers. Input, чтобы назначить имя входу данной модели:

----------(604]------

[лава

14. Погружаемся глубже - механика TensorF/ow

>>> model = tf.keras.Sequential([

tf.keras.layers.Input(shape=(2,), name='input-features'),
tf.keras.layers.Dense(units=4, activation='relu'),
tf.keras.layers.Dense(units=4, activation_='relu'),
tf.keras.layers.Dense(units=4, activation='relu'),
tf.keras.layers.Dense(l, activation='sigmoid')
] )
Далее мы пройдем через четыре шага, описанные в предыдущем подраз­
деле. Шаги

и

1, 2

4

будут такими же, как те, что мы использовали с готовы­

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

1

и

2,

обязательно должно совпадать

с именем, которое мы определили во входном слое модели. Код выглядит
следующим образом:

>>> ## Шаг 1: определение входных функций
>>> def train input fn(x train, у train, batch_size=8):

dataset = tf.data.Dataset.from_tensor_slices(
({'input-features':x_train}, y_train.reshape(-1, 1)))

#

тасование, повторение и разбиение на пакеты образцов.

r:etнrn

>>>

dataset.shuffle(100) .repeat() .batch(batch_size)

eval_input_fn (x_test, y_test=None, batch_size=8):

c:i~?.f

if y_test is None:

dataset = tf.data.Dataset.from_tensor_slices(
{'input-features' :x_test})
elE;e:

dataset = tf.data.Dataset.from_tensor_slices(
({'input-features' :x_test}, y_test.reshape(-1, 1)))
#

тасование, повторение и разбиение на пакеты образцов.

гetнrn

dataset.batch(batch_size)

>>> ## Шаг 2: определение
>>> features = [

столбцов признаков

tf.feature_column.numeric_column(
key='input-features: ', shape=(2,))
На шаге

3

вместо того, чтобы создать объект одного из готовых оцен­

щиков, мы преобразуем модель в оценщик с использованием функции

tf. keras. estimator .model to estimator.

Перед преобразованием

модель необходимо скомпилировать:

-------[605] - - - - - - - - - - - -

Глава

14.

Погружаемся глубже

механика

-

TensorFlow

>>> model.compile(optimizer=tf.keras.optimizers.SGD(),

loss=tf.keras.losses.BinaryCrossentropy(),
metrics=[tf.keras.metrics.BinaryAccuracy()])
>>> my_estimator = tf.keras.estimator.model_to_estimator(

keras_model=model,
model_dir='models/estimator-for-XOR/')
В заключение на шаге

4

мы можем обучить модель с применением наше­

го оценщика и оценить ее на проверочном наборе данных:

>>> ##Шаг 4: использование оценщика
>>> num_epochs = 200
>>> batch size = 2
>>> steps_per_epoch = np.ceil(len(x_train) / batch size)
>>> my_estimator.train(
input_fn=lamЬda:

train_input_fn(x_train, y_train, batch_size),
steps=num_epochs * steps_per_epoch)

>>> my_estimator.evaluate(

eval_input_fn(x_valid, y_valid, batch_size))
0.96, 'loss': 0.081909806, 'global_step': 10000}

input_fn=lamЬda:

{'Ьinary_accuracy':

Как видите, преобразовывать модель

Keras

в оценщик очень легко. Это

позволяет нам легко воспользоваться сильными сторонами оценщиков, та­

кими как распределенное обучение и автоматическое сохранение контроль­
ных точек во время обучения.

Резюме
В главе мы раскрыли наиболее важные и полезные возможности библи­

отеки

TensorF\ow. Мы начали
к версии TensorF\ow v2.

с обсуждения перехода от версии

v 1.х

В частности, мы применяли подход с дина­

мическими вычислительными графами

TensorF\ow

TensorF\ow

(так называемый режим

энергичного выполнения), который делает реализацию вычислений более

удобной в сравнении с использованием статических графов. Мы также ис­
следовали семантику определения объектов VariaЫe из
параметров модели, аннотируя функции

tf. function

Python

TensorFlow

как

с помощью декоратора

для повышения вычислительной эффективности посред­

ством компиляции графа.

---[606]·----

Глава

14.

Погружаемся глубже

-

механика

TensorF/ow

После рассмотрения концепции расчета частных производных и гради­
ентов произвольных функций мы более подробно раскрыли АРI-интерфейс

Keras.

Он снабжает нас дружественным к пользователю интерфейсом для

построения более сложных глубоких нейросетевых моделей. Наконец, мы
задействовали АРI-интерфейс

tf. estimator

библиотеки

TensorFlow,

что­

бы предоставить согласованный интерфейс, которому обычно отдают пред­
почтение в производственной среде. В заключение мы выяснили, как преоб­
разовывать модель

Keras

в специальный оценщик.

После раскрытия главных механизмов библиотеки

TensorFlow

в сле­

дующей главе мы представим концепцию, лежащую в основе архитектур
сверточных нейронных сетей (L'm11,oluti01щl нет·аl 11etи·ork

Сети

CNN

- CNN)

для ГО.

являются мощными моделями, которые характеризуются высокой

эффективностью в области компьютерного зрения.

(607]

15
КЛАССИФИКАЦИЯ
ИЗОБРАЖЕНИЙ С ПОМОЩЬЮ
ГЛУБОКИХ СВЕРТОЧНЫХ

НЕЙРОННЫХ СЕТЕЙ

предыдущей главе мы подробно рассматривали разнообразные аспекты
в
TensorF\ow.
.

Вы ознакомились с тензорами и декориро­

АРI-интерфейса

ванными функциями, а также научились работать с оценщиками

TensorFlow.

В текущей главе вы узнаете о сверточных нейронных сетях (co11н1l11iirnшl

m·1mi/' нt>fн.·мk -- CNN) для классификации изображений. Мы начнем с об­
суждения базовых строительных блоков сетей

CNN,

подход. Затем мы глубже погрузимся в архитектуру
реализовывать сети

CNN

в

TensorFlow.

используя восходящий

CNN

и выясним, как

В главе будут раскрыты следующие

темы:



операции свертки в одном или двух измерениях;



строительные блоки архитектур



реализация глубоких сверточных нейронных сетей в



методики дополнения данных с целью увеличения эффективности

CNN;
TensorFlow;

обобщения;



реализация классификатора изображений лиц на основе сети
прогнозирования пола человека.

CNN

для

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Строитеnьные бnоки сверточных нейронных сетей
Сверточные нейронные сети

(CNN)

представляют собой семейство мо­

делей, появление которых было вдохновлено способом работы зрительной
коры головного мозга при опознавании объектов. Разработка сетей

CNN

на­

чалась в 1990-х годах, когда Ян Лекун и его коллеги предложили новатор­

скую архитектуру нейронных сетей для классификации рукописных цифр
по их изображениям

Network"

("Handwritten Digit Recognition with

а

Back-Propagation

(Распознавание рукописных цифр с помощью сети обратного рас­

пространения), Я. Лекун и др" конференция по нейронным системам обра­
ботки информации

(NeurIPS), 1989

г.).

\\:~ Зрительная кора rоловноrо мозrа человека

н~ Первоначальное открытие того, как функционирует зрительная кора
заметку!

нашего головного мозга, сделали Дэвид Х. Хьюбел и Торстен Визель
в

1959

году, когда ввели микроэлектрод в первичную зрительную

кору анестезированной кошки. Затем они заметили, что нейроны
головного мозга по-разному реагируют после проецирования

перед

кошкой различных световых шаблонов. В конечном итоге это при­
вело к открытию разных слоев зрительной коры. Хотя первичный

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

Благодаря выдающейся эффективности сетей

CNN

при решении задач

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

2019 году, Ян Лекун вместе с остальными двумя исследователями,
Йошуа Бенджи и Джеффри Хинтоном, имена которых уже упоминались в
предшествующих главах, получили премию Тьюринга (самую престижную
премию в информатике) за свой вклад в область искусственного интеллекта.
В последующих разделах мы обсудим более широкую концепцию сетей

CNN

и посмотрим, почему сверточные архитектуры часто описываются как

"слои выделения признаков". Затем мы углубимся в теоретическое опреде­
ление вида операции свертки, которая обычно применяется в сетях

CNN,

и

проработаем примеры для вычисления сверток в одном и двух измерениях.

----[610]

---------------------

[лава 15. Классификация изображений с помощью глубоких сверточных нейронных сетей

Понятие сетей

CNN

и иерархий признаков

Успешное извлечение зшнет11ых (значшwых) признаков

-

ключевой ас­

пект эффективности любого алгоритма МО, и традиционные модели МО
опираются

на входные

признаки,

которые

могут поступать от эксперта

в

предметной области или основываться на вычислительных методиках вы­
деления признаков. Определенные типы нейронных сетей, такие как

CNN,

способны автоматически выявлять в сырых данных признаки, которые на­

иболее полезны для отдельно взятой задачи. По этой причине слои

CNN

принято считать средствами выделения признаков: начальные слои (нахо­
дящиеся сразу после входного слоя) извлекают 11изкоуров11евые приз11аки

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

Определенные типы многослойных нейронных сетей, в особенности

глубокие сверточные нейронные сети, создают так называемую иерархию
приз11аков, объединяя низкоуровневые признаки в послойной манере для

формирования высокоуровневых признаков. Например, если бы мы имели
дело с изображениями, тогда начальные слои извлекали бы низкоуровне­
вые признаки наподобие граней и пятен, которые объединялись бы вместе
с целью образования высокоуровневых признаков . Высокоуровневые при­
знаки способны создавать более сложные формы , такие как общие контуры
объектов вроде зданий, котов и собак. На рис.

15.1

видно, что сеть

CNN

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

Рис.

15.1.

Карты признаков (фотография Алекса11дра Да.м;.1ера из веб-сайта

[611 ] -----

Unsplash)

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

На такой локальный участок пикселей ссылаются как на локальное

рецепторное поле. Сети

CNN

обычно будут очень хорошо работать при ре­

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



Разреженная связность. Одиночный элемент в карте признаков связы­
вается только с небольшим участком пикселей. (Это сильно отличается

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

главе



12

полносвязной сети, которая связана с целым изображением.)

Сошнестное использование параметров. Для разных участков входного

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

уменьшает количество весов (параметров) в сети, и мы будем наблюдать улуч­
шение способности захвата заwетных признаков. В контексте данных изобра­

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

Сети

CNN

обычно состоят из нескольких сверточных слоев и слоев под­

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

в конце. Полносвязные слои по существу представляют собой многослой­
ный персептрон, где каждый входной элемент

элементом} с весом w!i (см. главу

i

связан с каждым выходным

12).

Важно отметить, что слои подвыборки, также называемые объединяющини

слоюни, не имеют каких-либо обучаемых параметров; например, в объединя­
ющих слоях отсутствуют веса или элементы смещения. Однако сверточные
и полносвязные слои располагают весами и смещениями, которые оптими­

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

одномерных сверток мы займемся типовыми двумерными свертками, обыч­
но применяемыми к двумерным изображениям.

-[ 612] -··- ·-·-----

fлава 15. Классификация изображений с помощью глубоких сверточных нейронных сетей

Выполнение дискретных сверток
Дискретная свертка (или просто свертка) является фундаментальной
операцией в сети

CNN

и потому важно понимать, как она работает. В этом

разделе мы представим математическое определение и обсудим ряд наивных
алгоритмов для вычисления сверток одномерных тензоров (векторов) и дву­

мерных тензоров (матриц).

Обратите внимание, что формулы и описания, приводимые в настоящем
разделе, предназначены только для понимания, каким образом работают
операции свертки в сетях

пакетах, как

TensorFlow,

CNN.

Позже в главе будет показано, что в таких

доступны более эффективные реализации сверточ­

ных операций.

Га~ Математические обозначения

н~ Для обозначения размера многомерного массива (тензора) в главе
заметку!

мы будем использовать подстрочные индексы; скажем, Ап х п
1 2

-

двумерный массив размера п 1 хп 2 • Мы применяем квадратные скобки

[ ] для

обозначения индексации в многомерном массиве.

Например,

A[i,j]

означает элемент по индексу

того, мы используем специальный символ

i,j

матрицы А. Кроме

* для

обозначения опе­

рации свертки между двумя векторами или матрицами, который не

следует путать с символом операции умножения* в языке

Python.

Дискретные свертки в одном измерении
Давайте начнем с нескольких базовых определений и обозначений, кото­

рые мы собираемся применять. Дискретная свертка для двух одномерных
векторов х и
называемый

w обозначается
сигнаrюм), а w -

как у

= х * w,

где вектор х

-

наш вход (иногда

фильтр или ядро. Математически дискретная

свертка определяется следующим образом:
+оо

y=x*w~y[i]=

Ix[i-k]w[k]
k=-00

Как упоминалось ранее, квадратные скобки

[]

начения индексации элементов в векторе. Индекс

используются для обоз­

i

проходит по всем эле­

ментам выходного вектора у. В предыдущем уравнении есть два странных
аспекта, которые нуждаются в прояснении: индексы от -оо до +оо и отрица­
тельная индексация для х.

- - - - - - - - - ---- [613] ----------------------

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Факт суммирования по индексам от -оо до +оо выглядит странным в ос­
новном потому, что в приложениях МО мы всегда имеем дело с конечными
векторами признаков. Например, если х содержит

О,

1, 2, .. " 8, 9,

тогда индексы -оо:-1 и

1О:+оо



признаков с индексами

выходят за допустимые гра­

ницы вектора х. Следовательно, для корректного вычисления суммы в пре­
дыдущем уравнении предполагается, что векторы х и

w заполнены

нулями.

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

Такой процесс называется допол11е11ие.н пулями и просто допол11е11ие.н.
Количество нулей, дополняемых с каждой стороны, обозначается как р. На

рис.

15.2

приведен пример дополнения одномерного векторах.
Первоначальный вектор х :

Рис.

/ 5.2.

Дополнение нуляwи од11шнерного веk-тора х

Пусть изначально входной вектор х и вектор фильтра

w

содержат соот­

ветственно п и т элементов, где т ~ п. Таким образом, дополненный вектор

хР имеет размер п

+ 2р.

Практическое уравнение для вычисления дискрет­

ной свертки изменится следующим образом:

у = х * w ~ у [i] =

k=m - 1

L хР [ i + т - k] w [k]
k=O

Теперь, когда проблема с бесконечными индексами решена, мы займемся

второй проблемой

-

индексацией вектора х посредством

важно отметить, что при суммировании векторы х и

i + т - k. Здесь

w индексируются

в раз­

ных направлениях. Вычисление суммы с одним индексом, идущим в обрат­
ном направлении, эквивалентно вычислению суммы с обоими индексами в

прямом направлении после зеркального обращения одного из векторов, х

или

w,

как только они будут дополнены. Затем мы можем просто вычис-

---- -- - --------- ----- [6141 - - - ---- -------------------- -·-

[лава

Классификация изображений с помощью глубоких сверточных нейронных сетей

15.

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

(повернули) фильтр

w,

чтобы получить повернутый фильтр

вычисляем скалярное произведение
мента

y[i],

где

x[i : i +

т]

-

x[i: i + m]·wr для

wr.

Далее мы

получения одного эле­

участок х размера т. Такая операция повто­

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

15.3

показан пример вычисления

первых трех выходных элементов для х = [3 2 1 7 1 2 5 4] и w = (_!_, ~,
2 4

Шаг

1:

i)).
4

повернуть фильтр

х

Шаг

2: для каждого выходного

элемента

i

вычислить скалярное

произведение

x[i:i+4]·w'
2 ячейки)

(сместить фильтр на

у[О)
-+ у(О]

y[l]
-+

= Зх 1/4 + 2х1+lx3/4 +7х1/ 2
=7
= 1х 1/4 + 7xl + lx З/4 +2х1/2

y[l] = 9

= 1х 1/4 + 2х1 + Sx З/4 +4х1/2
-+ y[Z] = 8
у[2]

Рис.

15.3.

_

{ ! 3 1 2 1 1 1 7 l __~_J_:J_~_J_~ _J
!1/41 1 13/ 41 1/2 l ____ __l_ ___ __l_ ___ __l_ ____ _J
r1 з 1 2 ! 1 1 7 11 1 2 1 s 14 1
[~~~~~Г~J 1141 1 1з141 112 [~~~Г~~J

l

_ ~_J_:J_~J-~__l 1 2
{~ lt___
__J____J_____ _l_ _____ !1/41 1
1

1

5 14

13/

1

41 1/2 I

Пpu.wep вычисления первых трех выходных элементов скалярного

произведе11ия

x[i : i + m]·w"

В предыдущем примере видно, что размер дополнения равен нулю (р

Обратите внимание, что повернутый фильтр

wr

= О).

каждый раз смещается на

две ячейки. Такое смещение является еще одним гиперпараметром сверт­

ки

-

страйдом

s.

В представленном выше примере страйд равен двум

(s = 2).

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

обсуждаются в следующем разделе.

- - (615)

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

\\.~ Взаимная корреляция

н~ Взаимная корреляция (или просто корреляция) между входным
заметку! вектором и фильтром обозначается как у

=х * w и

родственницу свертки, но с небольшим отличием

очень похожа на
во взаимной

-

корреляции умножение выполняется в одном и том же направлении.

Следовательно, поворачивать фильтр



каждом измерении не тре­

буется. Математически взаимная корреляция определяется так:
+ао

у = х * w~ у [ i]

= I х [ i + k ] w[ k]
k=->> :i.щport numpy as np
>>> def· convld (х, w, р=О, s=l):
w_rot = np.array(w[: :-1])
x_padded = np.array(x)
:1"fp>O:
zero_pad = np.zeros(shape=p)
х padded = np.concatenate([zero_pad,
x_padded,
zero__pad] )
res = []
fo.r i in range(O, int(len(x)/s),s):
res.append(np.sum(x_padded[i:i+w_rot.shape[O]J
w_rot))
return np.array(res)

*

>>> ## Проверка:
>>> х = [1, 3, 2, 4, 5, 6, 1, 3]
>>> w = [1, О, 3, 1, 2]
>>> p:r:int ('Реализация Convld: ',
Реализация

convld (х, w, р=2, s=l))
Convld: [ 5. 14. 16. 26. 24. 34. 19. 22.]

>>> p.rir.t ('Результаты NumPy: ',
Результаты

np.convolve(x, w, mode='same'))
NumPy: [ 5 14 16 26 24 34 19 22]

До сих пор мы уделяли внимание сверткам для векторов (одномерным
сверткам). Мы начали с одного измерения, чтобы облегчить понимание ле­

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

CNN,

ориентированных на решение задач с изображениями.

---[619)----------

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Выполнение дискретной свертки в двух измерениях
Концепции, которые объяснялись в предшествующих разделах, легко рас­
ширяются на два измерения. Когда мы имеем дело с двумерными входами, та­

W т хт, где т 1 ~ п 1 и т 2 ~ п 2 ,
2
1
тогда результатом двумерной свертки между Х и W будет матрица У= Х W.
кими как матрица Хп хп, и матрицей фильтра
1

2

*

Вот математическое определение:
+ оо

+со

L L

У= X*W--t Y[i,J]=

X[i-k1'J-k2]W[k1'k2]

k1=-оо k2 =-оо

Обратите внимание, что если мы опустим одно из измерений, то полу­
чим в точности то же самое уравнение, которое использовалось ранее для

вычисления свертки в одном измерении. Фактически все упомянутые выше

приемы вроде дополнения нулями, поворота матрицы фильтра и выбора
страйдов также применимы к двумерным сверткам при условии, что они

расширяются на оба измерения независимо . На рис.
двумерная свертка входной матрицы размером

8х 8

15.5

демонстрируется

с использованием ядра

размером З х З. Входная матрица дополняется нулями с р

= 1.

В результате

выход двумерной свертки будет иметь размер 8х8.

г ·-

..:
·--~

Ядро
(З х З)

····---!

Вход
(8 х 8)

.. .1.. ..... J......

J. .} ..J. . . !.·- -~

Дополняющие нули

Рис.

15.5.

Двумерная свертка входной матрицы размером 8х8
с применением ядра раз.мерам З

-

- - - - - --

--(620]

xJ

Глава

Классификация изображений с помощью глубоких сверточных нейронных сетей

15.

Следующий пример иллюстрирует вычисление двумерной свертки меж­

ду входной матрицей Хзхз и матрицей ядра
и страйдом

s

(2, 2).

=

W з х з с дополнением р

= ( 1,

1)

Согласно указанному дополнению к каждой стороне

входной матрицы добавляется один слой нулей, давая в результате дополненную матрицу

хдополиетшя

(

5х5

рис.

15.6).

w

х
о

с

п

)

с

2

1

2

о

с

о

1

о

с

5
1

7

3

о

G

v

\)

v

о

Рис.

15.6.

0.5 0.7 0.4

*

0.3 0.4 0.1
0.5

1

0.5

Результирующая дополненная матрица

Повернутым фильтром для предыдущего фильтра будет:

0.5

1 0.51
0.4 0.3

wr =r0.1

0.4 0.7 0.5

Обратите внимание, что поворот

-

это не транспонирование матрицы.

Чтобы получить повернутый фильтр в

W[ : : -1, : : -1 ] .

NumPy,

мы записываем W_

rot=

Далее мы сдвигаем матрицу фильтра вдоль дополненной

~
хдополненная
б
входнои матрицы
подо но скользящему окну и вычисляем сум-

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

15.7

обозначается опера­

0.

Результатом будет матрица У размера

2 х 2.

Давайте реализуем также и двумерную свертку в соответствии с описан­
ным наивным алгоритмом.

- - --

-

-(621] -

- - - -- - -- -

[лава 15. Классификация изображений с помощью глубоких сверточных нейронных сетей

0.5 1 0.5
0.1 0.4 0.3

хдополне1111ая

о

о

о

о

2

1

о

о

5

о

о

1

7

2
1
3

о

о

о

о

_J __

о
о

о

r__

0.4 0.7 0.5

__ 11 __

j
'

--~

о
о
1
1

1
1

'
'

0.5 1 0.5
0.1 0.4 0.3
0.4 0.7 0.5
Рис.

15. 7.

При.мер получения дву.wерной свертки

Пакет

через

scipy. signal предлагает способ вычисления
функцию scipy. signal. convol ve2d:

двумерной свертки

>>> import numpy as np
>>> import scipy.signal
>>> def conv2d(X, W,

р=(О, 0), s=(l, 1)):
W_rot = np.array(W) [: :-1,: :-1)
X_orig = np.array(X)
nl = X_orig.shape[O] + 2*р[О]
n2 = x_orig.shape[l] + 2*p[l]
Х_padded = np. zeros ( shape= ( nl, n2) )
X_padded[p[O]:p[O]+X_orig.shape[O],
p[l] :p[l]+X_orig.shape[l]] = X_orig

res = []
for i in range(O, int( (X_padded.shape[O] - \
W_ rot . shape [О] ) / s [О] ) + 1, s [О] ) :
res.append( [])
for j in range(O, int( (X_padded.shape[l] - \
W_ rot. shape [ 1] ) / s [ 1] ) + 1, s [ 1] ) :
X_sub = X_padded[i:i+W_rot.shape[O],
j:j+W_rot.shape[l]]
res[-1] .append(np.sum(X_sub * W_rot))
return(np.array(res))
>>> Х = ([1, З, 2, 4], (5, 6, 1, 3], [1, 2, О, 2), (3, 4, 3, 2]]
>>> W = [ (1, О, 3], [1, 2, 1], (0, 1, 1]]

>>>

pr1n t ('Реализация Conv2d: \n',

conv2d(X, W, p=(l, 1), s={l, 1)))

(622) - - -- - - --- - - - - - - - -

Глава

15.

Conv2d:
32. 13.]
24. 13.]
25. 17.)
14.
9.))

Реализация

[[
[
[
[

11.
19.
13.
11.

Классификация изображений с помощью глубоких сверточных нейронных сетей

25.
25.
28.
17.

>>> print ('Результаты SciPy: \n',
Результаты

[ [11 25 32
[19 25 24
[13 28 25
[111714

scipy.signal.convolve2d(X, W, mode='same'))
SciPy:
13)
13]
17)
9]]

~ Эффективные аnrоритмы дnя вычисnения сверток

н~ Мы предоставили наивную реализацию для вычисления двумерной

заметку! свертки с целью лучшего понимания концепций. Однако такая реа­
лизация крайне неэффективна в плане требований к памяти и вычис­

лительной сложности. Следовательно, она не должна использоваться
в реальных приложениях нейронных сетей.

Один из аспектов заключается в том, что матрица фильтра в дейс­
твительности не поворачивается в большинстве инструментов, по­

добных

TensorF\ow.

Кроме того, в последние годы были разработа­

ны намного более эффективные алгоритмы, которые для вычисления
сверток применяют преобразование Фурье. Также важно отметить,

что в контексте нейронных сетей размер ядра свертки обычно гораз­
до меньше размера входного изображения.

Например, современные сети

CNN

обычно используют размеры ядер

1х1, ЗхЗ или 5х5, для которых были спроектированы эффективные

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

почитать статью Эндрю Лэвина и Скотта Грея

Convolutional Neural Networks"

"Fast Algorithms for

(Быстрые алгоритмы для сверточ­

ных нейронных сетей), свободно доступную по ссылке

https: / /

arxiv.org/abs/1509.09308.
·---[623)-----------·

[лава

15. Классификация изображений с помощью глубоких сверточных нейронных сетей

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

CNN.

Сnои подвыборки
В сверточных нейронных сетях подвыборка, как правило, применяется
в двух формах операций объединения: объединение по максимуму

poo!ing)

и объединеиие по среднему

(max -

или m ·eп1gc -pooli11g).

(mem1--pooli11g

Объединяющий слой обычно обозначается с помощью Рп~ х п 2 • Здесь под­
строчный индекс определяет размер близлежащей области (количество
смежных пикселей в каждом измерении), где выполняется операция полу­

чения максимума или среднего. Мы ссылаемся на такую близлежащую об­
ласть как на размер объединения.
Работа подвыборки демонстрируется на рис.

15.8.

Операция объединения

по максимуму получает максимальное значение из близлежащей области пик­
селей, а операция объединения по среднему вычисляет их среднее значение.
Объединение (РЗхЗ )

1
п

")

1

1

6

)

Объединение

Объединение

по максимуму

по среднему

'

J

-----·-- - - - -

,

,

5

-- - -.,._

)
11...

1
)

r

6
v

4

)

8

"

"

- -7·-.
----

r,
"-

з

')

з

'

1

'

15.8.

Работа подвыборки

Рис.

Примечание :
k= З хЗ
страйд=З

Преимущество объединения двояко.



Объединение (объединение по максимуму) привносит локальную инва­

риантность. Это означает, что небольшие изменения в локальной близ­
лежащей области не изменяют результат объединения по максимуму.

Следовательно, инвариантность помогает генерировать признаки, бо­
лее устойчивые к шуму во входных данных. Ниже представлен при-

- --[624) ---·- - -· - - --- - - · - -·-

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

мер, который показывает, что объединение по максимуму двух входных
матриц Х 1 и Х 2 дает в итоге тот же самый выход:

10 255 125 о 170 100
70 255 105 25 25 70
255 о 150 о
10 10
255 10 10 150 20
о
70 15 200 100 95
о
35 25 100 20 о
60

Х1=

100 100 100 50
95 255 100 125
80 40 10 10
255 30 150 20
30 30 150 100
70 30 100 200

Х2=



объединение no максимуму 12х 2

100 50
125 170
125 150
120 125
70 70
70 95

170]

255 125
255 150 150
70 200 95

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

\\:~ Пересекающееся или непересекающееся объединение

н~ Традиционно предполагается, что объединение должно быть непе­
заметку!

ресекающимся. Объединение обычно выполняется на непересекающихся близлежащих областях, которые можно сделать, устанав­
ливая параметр страйда равным размеру объединения. Например,

непересекающийся объединяющий слой Р ni х п2 требует параметра
страйда

s = (п 1 ,

п 2 ). С другой стороны, пересекающееся объединение

случается, когда страйд меньше размера объединения. Пример ис­
пользования пересекающегося объединения в сверточной сети опи­
сан в работе А. Крижевски, И. Сацкевера и Д. Хинтона

"ImageNet

Classification with Deep Convolutional Neural Networks" (Классифи­
кация ImageNet с помощью глубоких сверточных нейронных сетей),
которая свободно доступна по ссылке https: / /papers. nips.
cc/paper/4824-imagenet-classification-w ith-deepconvolutional-neural-networks.

[625]---

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Хотя объединение по-прежнему является важной частью многих архитек­

тур сетей

CNN,

также было разработано несколько архитектур

CNN,

в ко­

торых объединяющие слои не используются. Вместо применения объединя­
ющих слоев для сокращения размера признаков исследователи используют

сверточные слои со страйдом

2.

В известной мере вы можете представлять себе сверточный слой со
страйдом

2

как объединяющий слой с обучаемыми весами. Если вас инте­

ресует эмпирическое сравнение разных архитектур сетей

CNN,

спроектиро­

ванных с и без объединяющих слоев, тогда мы рекомендуем ознакомиться
с исследовательской работой

"Striving for Simplicity: The All Convolutional

Net" (Стремление к простоте: все сверточные сети), написанной Йостом
Тобиасом Спрингербергом, Алексеем Досовицким, Томасом Броксом и
Мартином Ридмиллером, которая свободно доступна по ссылке

https: / /

arxiv.org/abs/1412.6806.

Группирование всеrо вместе

-

реализация

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

многослойных нейронных сетей. Мы можем сказать, что самой важной опе­
рацией в традиционной нейронной сети является перемножение матриц.

Например, мы применяем перемножение матриц для расчета предваритель­

ных активаций (или общих входов) как в

z = Wx +

Ь. Здесь х

столбец (матрица !R/,nxI), представляющий пиксели, а W -

-

вектор­

весовая матрица,

связывающая входы пикселей с каждым скрытым элементом.
В

А

сети

CNN

= W * Х + Ь,

такая

где Х

-

операция

заменяется

операцией

свертки

как в

матрица, представляющая пиксели с расположе­

нием высотахширина. В обоих случаях предварительные активации пере­
даются функции активации, чтобы получить активацию скрытого элемента
А= ф(Z), где ф- функция активации. Кроме того, вспомните, что еще одним

строительным блоком сети

CNN

является подвыборка, которая может встре­

чаться в форме объединения, как было описано в предыдущем разделе.

[626]---------------

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Работа с множественными входными иnи цветовыми канаnами
Вход сверточного слоя можетсодержать один и более двумерных массивов
или матриц с измерениями

N 1xN2 (скажем, высотой и шириной изображения
в пикселях). Такие матрицы N 1xN2 называются ка11алами. Традиционные реа­
лизации сверточных слоев ожидают на входе представления тензора ранга 3,
например, трехмерного массива XN х N х с
1

2

вх

, где

Свх -

количество входных

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

CNN.

Если изображение цветное и использует цветовой ре­

RGB, тогда Свх = 3 (для красного, зеленого и синего цветовых каналов
RGB). Тем не менее, если изображение представлено в оттенках серого, то
мы имеем Свх = 1, потому что есть только один канал со значениями интен­

жим
в

сивности пикселей в оттенках серого.

Чтение файла изображения
При работе с изображениями мы можем читать их в массивы
с применением типа данных

uint8

NumPy

(8-битное целое число без зна-

ка), чтобы сократить расход памяти в сравнении, например, с

32-

16-,

или 64-битными целочисленными типами. Беззнаковые 8-бит­

ные целые числа имеют значения в диапазоне [О,

255],

которых до­

статочно для хранения информации о пикселях изображений

RGB,

также принимающие значения в том же самом диапазоне.

В главе

13

вы узнали, что библиотека

TensorFlow

предлагает модуль

для загрузки/сохранения и манипулирования изображениями через
подмодули

tf. io

и

tf. image.

Давайте кратко повторим, как про­

читать изображение (это изображение

images

RGB

находится в подкаталоге

каталога примеров для текущей главы

https: / /gi thub.

com/rasbt/python-machine-learning-book-Зrd-edition/

tree/master / chl5):
>>>
>>>
>>>
>>>

impor·t tensorflow э,; tf
img_ raw = tf. io. read_ file ( 'example-image. png')
img = tf.image.decode_image(img_raw)
pr.· i п t ( 'Форма изображения: ' , img. shape)
Форма изображения: (252, 221, 3)
При построении моделей и загрузчиков данных в

TensorFlow

для

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

tf. image.

- - - - - - - - - - - · - - - - - - - - - [627) -----

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Давайте посмотрим, как можно прочитать изображение в сеансе

Python

с применением пакета

посредством

conda

или

pip

imageio,

который следует установить

в командной строке:

> conda install imageio
или

> pip install imageio
После установки

imageio

мы можем вызвать функцию

imread

и

прочитать то же самое изображение, которое использовалось ранее,
с помощью пакета

imageio:

>>> irnport imageio
>>> img = imageio. imread ( 'example-image. png')
>>> print('Фopмa изображения:', img.shape)
Форма изображения: (252, 221, 3)
>>> print ('Количество каналов:', img. shape [2])
Количество каналов: 3
>>> pr ш t. ( 'Тип данных изображения: ' , img. dt уре)
Тип данных изображения: uint8
»> print.(img[100:102, 100:102, :))
[ [ [179 134 110]
(182 136 112]]
[ (180 135 11]
(182 137 113]]]
Теперь, когда вы знакомы со структурой входных данных, возникает
вопрос: как охватить множественные входные каналы операцией свертки,

которая обсуждалась в предшествующих разделах? Ответ очень прост: мы
выполняем операцию свертки для

каждого канала отдельно

и затем скла­

дываем результаты вместе, используя суммирование матриц. Свертка, свя­

занная с каждым каналом (с), имеет собственную матрицу ядра

W[:, :,

с].

Общий результат предварительной активации вычисляется посредством сле­
дующего уравнения:
С,и

Для заданного образца Хп,хп 2 хс~,'
матрицы ядра Wт 1хт 2хС

~

и значения смещения Ь

Z Свертки

= L W [: , : , С] * Х [: , : , С]
c=I

Предварительная активация:

Z = Z Свертки + Ьс

Карта признаков: А= ф(Z)

-[628] --- - - - - - - - - - - - - - - - - -

fпава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Финальный результат, А, является картой признаков. Обычно сверточный

слой сети

CNN

имеет более одной карты признаков. Если мы применяем

несколько карт признаков, тогда тензор ядра становится

ширинахвысотахСвххС6 ых. Здесь ишринахвысота

личество входных каналов и Свых

-

четырехмерным:

размер ядра, Свх

-

-

ко­

количество выходных карт признаков.

Итак, давайте включим количество выходных карт признаков в предыдущее
уравнение, обновив его, как показано ниже:
с"

Z Свертки [ :,:,k ] = "L.J W [ : ,: ,c,k ] * Х [ : ,:

,с ]

c=I

матрицы Ядра Wm 1х т 2хСвх хев ых

::::>

и значения смещения Ьс

Z [:':' k] = Z Свертки [:
А[: ,: ,k]=Ф(Z[:

6h/X

' : '

k] + Ь [k]

,: ,k])

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

15.9,

где

показан сверточный слой, за которым следует объединяющий слой. В этом
примере есть три входных канала. Тензор ядра является четырехмерным.

Каждая матрица ядра обозначается как

m1xm 2,

и таких матриц три, по одной

на входной канал . Вдобавок имеется пять ядер, которые приходятся на пять
выходных карт признаков. Наконец, предусмотрен объединяющий слой для

подвыборки карт признаков.
Сверточный

Объединяющий

слой

слой

Сумма
по входным

каналам

Рис.

15.9.

Вычисл ение свертки в пейрттой сети

. -·-[ 629) ------·---·------·----------

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

\\.:~ Сколько обучаемых параметров существует в предыдущем примере?

н~ Чтобы проиллюстрировать преимущества свертки, совместно,..,о
заметку!

использования параметров и разреженной связности, давайте про­
работаем пример. Сверточный слой в сети на рис.

15.9

представляет

собой четырехмерный тензор. Следовательно, есть т 1 хт 2 х3х5 па­
раметров, ассоциированных с ядром. Кроме того, для каждой вы­
ходной карты признаков сверточного слоя имеется вектор смещения.

Таким образом, размер вектора смещения равен

5.

Объединяющие

слои не имеют каких-либо (обучаемых) параметров, поэтому мы мо­
жем записать так:

т 1 х т2 х

3

х

5+5

Если входной тензор имеет размер п 1 х п 2 х

3

и предполагается, что

свертка выполняется в режиме одинакового дополнения, тогда вы­

ходные карты признаков будут иметь размер п 1 х п 2 х

5.

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

использования полносвязного слоя число параметров для весовой

матрицы выражалось бы так:

(п 1 х п 2 х 3) х (п 1 х п 2 х 5) = (п 1 х п 2 ) 2 х 3 х 5
Вдобавок размер вектора смещения составляет п 1 х п 2 х

5

(по од­

ному элементу смещения для каждого выходного элемента). При

условии, что т 1

<

п 1 и т2

< п2 ,

разница в количествах обучаемых

параметров оказывается огромной.

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

тем складываем результаты, как было проиллюстрировано на рис.

15.9.

Однако

свертки могут быть расширены и на трехмерные объемы, если вы работаете

2015 года
30 Convolutional Neural Network for Real-Time Object Recognition"

с трехмерными наборами данных, например, как показано в работе

"VoxNet: А
(VoxNet: трехмерная

сверточная нейронная сеть для распознавания объектов в

реальном времени), написанной Дэниэлом Матурана и Себастьяном Шерером,

которая свободно доступна по ссылке

https://www.ri.cmu .edu/
pub files/2015/9/voxnet_ maturana scherer iros15.pdf.
------------------- --·--- [630) ----------------·--- ··--·---

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

В следующем разделе речь пойдет о регуляризации нейронной сети.

Реrуnяризация нейронной сети с помощью откnючения
Выбор размера сети, как традиционной (полносвязной) нейронной сети,
так и

CNN,

всегда был сложной задачей. Например, для достижения при­

емлемой эффективности должны подстраиваться размер весовой матрицы и
количество слоев.

В главе

14

вы узнали о том, что простая сеть без скрытых слоев может

захватывать только линейную границу решений, которой не будет достаточ­
но для решения задачи

XOR

или какой-то аналогичной. Под емкостью сети

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

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

Один из способов решения проблемы предусматривает построение сети
с относительно высокой емкостью (на практике мы хотим выбрать емкость,

которая немного выше, чем необходимо) для хорошей работы на обучаю­

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

лемой эффективности обобщения на новых данных, таких как удерживае­
мый испытательный набор.
В главе

3

мы раскрывали регуляризацию

L 1 и L2.

Там в разделе "Решение

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

L1

и

L2,

могут предотвратить или ослабить эффект переобуче­

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

L2

L1

и

L2

могут

оказывается более

частым выбором из двух, существуют другие методы регуляризации ней­
ронных сетей наподобие отключения, которое мы обсудим в текущем раз-

---[631]

fлава

15. Классификация изображений с помощью глубоких сверточных нейронных сетей

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

L2

внутри сверточной или полносвяз­

ной (плотной) сети вы можете добавить к функции потерь штраф
этого при использовании АРI-интерфейса

kernel _ regularizer

Keras

L2.

Для

просто установите аргумент

отдельного слоя (затем он автоматически модифи­

цирует функцию потерь надлежащим образом):

>>>

f.i:·o:ш

tensorflow

impor·t

keras

>>> conv_layer = keras.layers.Conv2D(
filters=lб,
kernel_size=(З,3),

kernel_regularizer=keras.regularizers.12(0.001))
>>> fc_layer = keras.layers.Dense(
units=lб,

kernel_regularizer=keras.regularizers.12(0.001))
В последние годы появилась еще одна популярная методика для регу­
ляризации (глубоких) нейронных сетей во избежание их переобучения,
которая называется отключением (if"opout). Она была представлена в ста­
тье Нитиша Шривастава, Джеффри Хинтона, Алекса Крижевского, Ильи
Сатскевера и Руслана Слахутдинова

networks from overfitting"

"Dropout:

а

simple way to prevent neural

(Отключение: простой способ предохранения ней­

ронных сетей от переобучения),

Journal of Machine Learning Research 15.1,
свободно доступной по ссылке ht tp: / /www.

стр.

1929-1958 (2014 г.),
jmlr.org/papers/volumel5/srivastaval4a/srivastaval4a.pdf.

Отключение обычно применяется к скрытым элементам более высоких сло­

ев и работает следующим образом: в течение стадии обучения нейронной
сети на каждой итерации некоторая доля скрытых элементов случайным об­

разом отбрасывается с вероятностью Ротбрасывания (или сохраняется с вероят­

ностью Рсохранения

=1-

Ротбрасывания). Эта вероятность отключения задается

пользователем и часто выбираемым вариантом является р

= 0.5,

как обсуж­

далось в упомянутой выше статье Нитиша Шривастава и др. Когда опреде­
ленная доля входных нейронов отбрасывается, ассоциированные с оставши­

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

[632]

Глава

Классификация изображений с помощью глубоких сверточнЬ1х нейроннЬlх сетей

15.

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

Такое случайное отключение в состоянии эффективно препятствовать пе­
реобучению. На рис.

ятностью р =

0.5

15.1 О

иллюстрируется применение отключения с веро­

во время стадии обучения, благодаря чему половина ней­

ронов случайным образом становится неактивной (отбрасываемые элементы
выбираются на случайной основе при каждом прямом проходе процесса

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

Оценка:

вероятность

используются

отключения

все элементы

р=50 о/о

о
о
о
о

х
х

х

о
Рис.

15.10.

о
о
о

о
о
о
о
о

о
о

-' 0
о
о
о
о
о

При.мер отключения с вероятностью р

= 0.5

Важно запомнить, что элементы могут отключаться случайным обра­
зом только во время обучения, тогда как на стадии оценки (выведения) все

скрытые элементы должны быть активными (например, Ротбрасывания = О или
Рсохранения

=

1).

Для гарантирования того, что все активации имеют тот же

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

счет деления активации пополам, если вероятность отключения была уста­
новлена в р

= 0.5).

Однако поскольку постоянно масштабировать активации при выработке
прогнозов неудобно,

TensorFlow

и другие инструменты масштабируют акти­

вации во время обучения (например, удваивая активации, если вероятность

отключения была установлена в р

= 0.5).

На такой подход обычно ссылают­

ся как на инверсное отключение.

(633) - ---

---- --

- -----------

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

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

В главе

7

было показано, что при ансамблевом обучении мы независимо

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

самбли моделей работают лучше одиночных моделей. Тем не менее, при ГО
обучение нескольких моделей, а также сбор и усреднение выхода множества
моделей будет затратным в вычислительном плане. Отключение предлагает
обходной путь с эффективным способом обучения множества моделей за
раз и вычисления их средних прогнозов во время испытания или прогнози­
рования.

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

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

свыше М = 2h моделей, где h -

количество скрытых элементов.

Тем не менее, ограничение и аспект, который проводит различие меж­

ду отбрасыванием и обычным объединением в ансамбли, заключаются в
том, что мы разделяем веса по таким "разным моделям", что можно рас­

сматривать как форму регуляризации. Затем во время "выведения" (скажем,

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

членства в классах, возвращенной моделью

i,

может быть представлен так:
l

Рлнсамбля

-

-

м

[ ПР {i)



j=l

Трюк, лежащий в основе отбрасывания, связан с тем, что среднее геомет­

рическое ансамблей моделей (здесь М моделей) может быть приближенно
рассчитано путем масштабирования прогнозов последней (или финальной)

модели, выбранной во время обучения, с коэффициентом 1/(1 - р). Это будет
гораздо менее затратным, чем явное вычисление среднего геометрического

- - - - - - - - - - - - - - [634]--

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

с применением предыдущего уравнения. (На самом деле, если мы возьмем

линейные модели, то приближение в точности эквивалентно настоящему
среднему геометрическому.)

Функции потерь дnя кnассификации
В главе

ReLU,

13

мы исследовали различные функции активации, такие как

сигмоидальная функция и гиперболический тангенс

из этих функций активации вроде

ReLU

(tanh).

Некоторые

используются главным образом в

промежуточных (скрытых) слоях нейронной сети для добавления к модели
нелинейностей. Но другие функции наподобие сигмоидальной (для двоич­
ной классификации) и мноrопеременной логистической (для многоклассовой
классификации) добавляются в последний (выходной) слой, давая на выходе
модели вероятности членства в классах. Если сигмоидальная или многопере­

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

Сосредоточив внимание на задачах классификации, в зависимости от
типа задачи (двоичная или многоклассовая) и типа выхода (логиты или ве­

роятности) мы должны выбрать подходящую функцию потерь для обучения
модели. Функцией потерь для двоичной классификации (с единственным

выходным элементом) является двоичная перекрестная энтропия, а для

многоклассовой классификации
В АРl-интерфейсе

Keras API

-

категориальная перекрестная энтропия.

для потери в виде категориальной перекрест­

ной энтропии предлагаются два варианта, соответствующие формату пред­
ставления достоверных меток

-

в унитарном коде (например, [О, О,

или в целочисленном виде (скажем, у=2), что в контексте

Keras

1,

О])

также назы­

вается "разреженным" представлением.

В таблице на рис.

15.11

описаны три функции потерь, доступные в

Keras

для работы во всех трех сценариях: двоичная классификация, многоклассо­
вая классификация с достоверными метками в унитарном коде и многоклас­

совая классификация с целочисленными (разреженными) метками. Каждая
из этих трех функций потерь также обладает возможностью получения про­
гнозов в форме логитов или вероятностей членства в классах.

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

---------------

[635)

Глава

15.

Классификация изображений с помощью глубоких св ерточных нейронных сетей

Примеры

Функция потерь

BinaryCrossentropy

CategoricalCrossentropy

Sparse
CategoricalCrossentropy
Рис.

15.11.

Исполь­
зование

Двоичная
классификация

Многоклассовая
классификация

Многоклассовая

y_t.rue1

а

y_true1D

y_pred:

1111

y_pred• •

y_true1 • • •
y_pred:

11111111

y_truet •

классификация

y_pred:

Функции потерь, доступные в

y_pred:

18111111

y_true:.

11111111

Keras

y_true:···

y_pred :

111111111

для задач классификации

Если мы предоставим функции потерь логиты в качестве входа и уста­
новим

from_ logi ts=True,

то при расчете потери и производной относи­

тельно весов соответствующая функция

Tensorf low

будет применять более

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

не требуют явного вычисления.

Ниже в коде показано, как использовать три функции потерь с двумя
форматами, где функциям потерь на входе предоставляются либо логиты,
либо вероятности членства в классах:

>>> import tensorflow_datasets as tfds
>>> ####### Двоичная
>>> bce_probas =

перекрестная энтропия

(ДПЭ)

tf. keras. losses. BinaryCrossentropy ( from_logits=E' a l se)

>>> bce_logits =
tf.keras.losses.BinaryCrossentropy(from_logits=Trнe )

>>> logits
>>> probas

tf.constant([0.8])
tf.keras.activations.sigmoid(logits)

»> tf .print (
{:. 4f}'. format (
y_pred=probas)),
'(сло гитами): {:.4f } '.format(
bce_logits(y_true=[l], y_pred=logits)))
вероятностями): 0.3711 (с логитами): 0.3711
'ДП Э

( с в ер о ятнос тями ) :

bce_p~obas(y_true=[l],

ДПЭ



(636) - --

- - --

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

>>> ####### Категориальная перекрестная энтропия (КПЭ)
>>> cce_probas = tf.keras.losses.CategoricalCrossentropy(
from_logi ts=E'alse)
>>> cce_logits = tf.keras.losses.CategoricalCrospentropy(
from_logits='I'rue)
>>> logits = tf.constant([[l.5, 0.8, 2.1]])
>>> probas = tf.keras.activations.softmax(logits)
>>> tf .print (

КПЭ



'КПЭ (с вероятностями): {: .4f)' .format (
cce_probas(y_true=[O, О, 1], y_pred=probas) ),
' (с логитами): {:. 4f)'. format (
cce_logits(y_true=[O, О, 1], y_pred=logits)))
вероятностями): 0.5996 (с логитами): 0.5996

>>> #######

Разреженная категориальная перекрестная
энтропия

(разреженная КПЭ)

>>> sp_cce_probas = tf.keras.losses.
SparseCategoricalCrossentropy(
from_logits=False)
>>> sp_cce_logits = tf.keras.losses.
SparseCategoricalCrossentropy(
from_logits='I'rue)
>>> tf .print (
'Разреженная КПЭ (с вероятностями): {:.4f) '.format(
sp_cce_probas(y_true=[2], y_pred=probas)),
' (с логитами): {:. 4f)'. format (
sp_cce_logits(y_true=[2], y_pred=logits)))
Разреженная КПЭ (с вероятностями): О. 5996 (с логитами): О. 5996

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

двоичной классификации. Обычно когда у нас есть задача двоичной класси­

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

1),

Р[класс

= 1].

ной классификации подразумевается, что Р[класс =О]

В задаче двоич­

= 1-

Р[класс

= 1];

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

решают возвращать для каждого обучающего образца два выхода и интерпре­
тируют их как вероятности каждого класса: Р [класс

= О]

против Р[ класс

= 1].

-------(637)------------

[лава 15. Классификация изображений с помощью глубоких сверточньtх нейронньtх сетей

В такой ситуации для нормализации выходов (чтобы они давали в сумме

1)

рекомендуется использовать многопеременную функцию (вместо логисти­
ческой сигмоидальной функции) и категориальная перекрестная энтропия
оказывается подходящей функцией потерь.

Реализация глубокой сверточной нейронной
сети с использованием
В главе

14

распознавания

TensorFlow

TensorFlow

мы применяли оценщики

рукописных

цифр с

TensorF\ow

для решения задач

использованием АРI-интерфейсов

разных уровней. Несложно вспомнить, что мы достигли почти

89%-ной правильности, применяя оценщик

DNNClassifier

с двумя скры­

тыми слоями.

Теперь давайте реализуем сеть

CNN

и посмотрим, сумеет ли она достичь

лучшей прогнозирующей эффективности по сравнению с многослойным
персептроном

(DNNClassifier)

при классификации рукописных цифр.

Обратите внимание, что полносвязные слои в главе

14

оказались способны­

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

такую ошибку.

Архитектура мноrоспойной сверточной нейронной сети
Архитектура сети, которую мы собираемся реализовать, показана на

рис.

15.12.

Входами будут изображения 28х28 пикселей в оттенках се­

рого. Учитывая количество каналов

(1

для изображений в оттенках серо­

го) и пакет входных изображений, размерность входного тензора составит
размер пакетах 2 8 х 28 х

1.

Входные данные проходят через два сверточных слоя с размером ядра
5х5. Первый сверточный слой имеет

рой

-

64

32

выходных карты признаков, а вто­

выходных карты признаков. За каждым сверточным слоем следует

слой подвыборки в форме операции объединения по максимуму, Р2 х 2 • Затем
полносвязный слой передает выход второму полносвязному слою, который

действует как финальный многопеременныit (s1~/t111ax) выходной слой.

--------------(638)--------

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Сверточный

5•5•32

~

Объединяющий

Сверточный

2•2

5•5•64

Объединяющий

2•2

,--А----.~~

Полно-

Полно­

связный

связный

,--А---, г-1'1

oQЧJ
28>>>
>>>
>>>
>>>
>>>
>>>
>>>

Классификация изображений с помощью глубоких сверточных нейронных сетей
impoгt:

##

tensorflow_datasets as tfds

Загрузка данных

mnist_Ыdr

= tfds.builder('mnist')

mnist_Ыdr.download_and_prepare()

datasets = mnist_Ыdr.as_dataset(shuffle_files=False)
mnist_ train_ orig = datasets [ 'train']
mnist _ test _ orig = datasets [ 'test']

Набор данных МNIST поступает с заранее определенной схемой расщеп­

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

мы используем необязательный

аргумент

установка препятствует началь­

. as_dataset ()
shuff le _ files=False. Такая

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

повлечет за собой повторное тасование набора данных при каждом извле­
чении мини-пакета данных. Можете проверить сказанное самостоятельно:
когда начальное тасование включено, количество меток в проверочных на­

борах изменяется из-за повторного тасования расщеплений на обучающую и

проверочную части. Результатом может оказаться ложная оценка эффектив­
ности модели, т.к. обучающий и проверочный наборы на самом деле будут

смешанными.) Мы можем расщепить на обучающий и проверочный наборы
следующим образом:

»> BUFFER SIZE = 10000
SIZE = 64
EPOCHS = 20

>>>

ВАТСН

»>

NUМ

>>> mnist_train = mnist train orig.map(
larnЬda

item: (tf.cast(item['image'], tf.float32)/255.0,
tf.cast(item['label'], tf.int32)))

>>> mnist_test = mnist_test_orig.map(
larnЬda

item: (tf.cast(item['image'], tf.float32)/255.0,
tf.cast(item['label'], tf.int32)))

>>> tf.rando~.set_seed(l)
>>> mnist_train = mnist_train.shuffle(buffer_size=BUFFER_SIZE,

reshuffle_each_iteration=False)
>>> mnist_valid = mnist_train.take(10000) .batch(BATCH_SIZE)
>>> mnist_train = mnist_train.skip(10000) .batch(BATCH_SIZE)

[640]

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

После подготовки набора данных мы готовы к реализации только что
описанной сети

CNN.

Реаnизация сверточной нейронной сети с испоnьзованием
АРl-интерфейса Keras бибnиотеки TensorFlow
Для реализации сети

Keras,

CNN

в

TensorFlow

мы применяем

который позволит уложить стопкой различные слои

Sequential из
- сверточный,

объединяющий и отключения, а также полносвязные (плотные) слои. АРI­
интерфейс

layers в Keras предлагает классы для каждого слоя: tf. keras.
layers. Conv2D для двумерного сверточного слоя, tf. keras. layers.
MaxPool2D и tf. keras. layers. AvgPool2D для подвыборки (объедине­
ния по максимуму и объединения по среднему) плюс tf. keras. layers.
Dropout для регуляризации с использованием отключения. Ниже мы де­
тально исследуем каждый из перечисленных классов.

Конфиrурирование слоев сверточной нейронной сети в
Конструирование слоя с помощью класса

Conv2D

Keras

требует указания ко­

личества выходных фильтров (которое эквивалентно числу выходных карт
признаков) и размеров ядер.
Кроме того, есть необязательные параметры, которые можно применять

для конфигурирования сверточного слоя. Самыми часто используемыми
параметрами являются страйды (со стандартным значением

1

по измере­

ниям х и у) и дополнение, которое может быть одинаковым или допусти­
мым. Список добавочных параметров конфигурации приведен в официаль­
ной документации:

https: / /www.tensorflow.org/versions/r2.
api_docs/python/tf/keras/layers/Conv2D.

О/

Полезно упомянуть о том, что обычно при чтении изображения стан­
дартным измерением для каналов будет последнее измерение тензорного
массива. Формат называется

NHWC,

изображений внутри пакета, Н и
С- каналы

W-

где

N

обозначает количество

высоту

(height)

и ширину

(number)
(width), а

(channels).

Обратите внимание, что по умолчанию класс

ление входов в формате
няют формат

NCHW.)

NHWC.

(Другие

Conv2D допускает представ­
инструменты вроде PyTorch приме­

Тем не менее, если вы столкнетесь с какими-то данны­

ми, каналы которых размещены в первом измерении (первое измерение после
измерения пакета или второе измерение с учетом измерения пакета), тогда

-----------------(641 ]--- - - -

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

вам понадобится поменять местами оси в данных, чтобы переместить каналы
в последнее измерение. Альтернативный способ работы с входом в формате

NCHW

предусматривает установку

data_format="channels_first".

После создания к слою можно обращаться, предоставляя четырехмерный
тензор, первое измерение которого зарезервировано для пакета образцов: в

da t

зависимости от аргумента

а_ f о rma t

второе или четвертое измерение

соответствует каналу, а два оставшихся измерения являются пространствен­
ными.

Как показано в архитектуре модели

CNN,

которую мы хотим построить,

за каждым сверточным слоем следует объединяющий слой для подвыборки
(сокращения размера карт признаков). Классы

MaxPool2D

и

AvgPool2D

конструируют слои объединения по максимуму и по среднему. Аргумент

pool_size

определяет размер окна (или близлежащей области), которое

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

strides

может применяться для конфигурирования

объединяющего слоя, как обсуждалось ранее.

Наконец, класс

Dropout

гуляризации с аргументом

будет конструировать слой отключения для ре­

ra te,

в котором определяется вероятность от­

ключения входных элементов во время обучения. Поведение при вызове
слоя управляется посредством аргумента по имени

training,

в котором

указывается, сделан ли вызов во время обучения или во время выведения.

Конструирование сверточной нейронной сети в

Keras

Теперь, когда вы ознакомились с этими классами, мы можем сконструи­
ровать модель

CNN,

представленную на рис.

будем использовать класс

Sequential

15.12.

В следующем коде мы

и добавим сверточные и объединя­

ющие слои:

>>> model = tf. keras. Sequential ()
>>> model.add(tf.keras.layers.Conv2D(

filters=32, kernel size=(5, 5),
strides=(l, 1), padding='same',
data_format='channels last',
name='conv_l', activation='relu'))
>>> model.add(tf .keras.layers.MaxPool2D(
pool_size=(2, 2), name='pool 1'))
>>> model.add(tf.keras.layers.Conv2D(

filters=64, kernel_size=(5, 5),
------------·-~-----·-1642)-----·-·-------~··--·--·-----·---

[лава

15. Классификация изображений с помощью глубоких сверточных нейронных сетей

strides=(l, 1), padding='same',
name='conv_2', activation='relu'))
>>> model.add(tf.keras.layers.MaxPool2D(
pool_size=(2, 2), name='pool_2'))
Пока что мы добавили к модели два сверточных слоя, для каждого из
которых указали ядро с размером 5х5 и дополнением
лось ранее, применение

padding=' same'

'same'.

Как обсужда­

предохраняет пространственные

измерения (вертикальное и горизонтальное) карт признаков, так что входы
и выходы имеют те же самые высоту и ширину (может отличаться только

число каналов в плане количества используемых фильтров). Слои объеди­
нения по максимуму с размером объединения

2 х2

и страйдами

2

вдвое со­

кратят пространственные измерения. (Следует отметить, что если параметр

strides

в

MaxPool2D

не указан, то по умолчанию он устанавливается

равным размеру объединения.)
Хотя на этой стадии мы можем рассчитать размер карт признаков вруч­

ную, АРI-интерфейс

Keras

предлагает для такой цели удобный метод:

>>> model.compute_output_shape(input_shape=(l6, 28, 28, 1))

TensorShape ( [16, 7, 7, 64])
После предоставления формы входа в виде кортежа метод

output _ shape

определяет, что выход должен иметь форму

указывающую на карты признаков с

64

compute
(16, 7, 7, 64),

каналами и пространственным раз­

мером 7х7. Первое измерение соответствует измерению пакета, для которо­

16. Взамен
input shape=(None,28,28, 1).

го мы произвольно выбрали

мы могли бы применить

None,

т.е.

Следующим мы добавим плотный (или полносвязный) слой для реали­
зации классификатора поверх сверточных и объединяющих слоев. Его вход

должен иметь ранг

2,

т.е. форму [размер пакетах количество входных эле­

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

>>> model.add(tf.keras.layers.Flatten())
>>> model.compute_output_shape(input_shape=(16, 28, 28, 1))

TensorShape ( [16, 3136])
Как показывает результат

compute output shape,

для плотного слоя настроены корректно.

--------------- [643] - -

измерения входа

Глава

15.

Классификация изображений с помощью глубоких сверточн111х нейронн111х сетей

Далее мы добавляем два плотных слоя со слоем отключения между
ними:

>>> model.add(tf.keras.layers.Dense(

units=1024, name=' fc_l',
activation='relu'))
>>> model.add(tf.keras.layers.Dropout(

rate=0.5))
>>> model.add(tf.keras.layers.Dense(

units=lO, name='fc_2',
activation='softmax'))
Последний полносвязный слой по имени
элементов для



'f

с_ 2

меток классов в наборе данных

' имеет 1О выходных
MNIST. Кроме того мы

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

что

классы

являются

взаимоисключающими

ти для каждого образца в сумме дают

1.

и

потому

вероятнос­

(Это означает, что обучающий

образец может принадлежать только одному классу.) Исходя из того, что

мы обсуждали в разделе "Функции потерь для классификации", какая
функция потерь здесь должна применяться? Вспомните, что для много­

классовой классификации с целочисленными (разреженными) метками
(как противоположность меткам в унитарном коде) мы используем класс

SparseCategoricalCrossentropy. В следующем коде вызывается ме­
тод bui ld ( ) для позднего создания переменных и компиляции модели:
>>> tf.random.set_seed(l)
>>> model.build(input_shape=(None, 28, 28, 1))
>>> model.compile(

optimizer=tf.keras.optimizers.Adam(),
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
metrics=['accuracy'])
Г:.~ Оптимизатор Adam

н~ Обратите внимание, что в приведенной реализации для обучения
заметку!

модели

CNN

Оптимизатор

мы применяем класс tf. keras. optimizers. Adam.
Adam (adaptive moment estimation - адаптивная оценка

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

-[644] -- ···---·

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Появлению
ции:

Adam способствовали
RМSProp и AdaGrad.

Ключевое преимущество

Adam

два популярных метода оптимиза­

связано с выбором размера шага об­

новления, выводимого из скользящего среднего моментов градиен­

та. Оптимизатор

подробно описан в работе Дидерика

Кингма и

А

(Adam:

Adam более
Джимми Ба "Adain:

Method for Stochastic Optimization"

метод стохастической оптимизации), которая свободно до­

ступна по ссылке

https: //arxiv. org/abs/1412. 6980.

Как вы уже знаете, мы можем обучить модель, вызвав метод f

i t ().

Важно отметить, что при использовании назначенных методов для обуче­

ния и оценки (наподобие

evaluate ()

и

predict

())автоматически уста­

навливается режим для слоя отключения, а скрытые элементы надлежащим

образом масштабируются, не заставляя нас беспокоиться об этом. Затем мы

обучим полученную модель

CNN

и задействуем созданный проверочный на­

бор для наблюдения за процессом обучения:

>>> history = rnodel.fit(mnist_train, epochs=NUM_EPOCHS,
validation_data=mnist_valid,
shuffle=True)
Epoch 1/20
782/782 [==============================] - 35s 45rns/step - loss:
0.1450 - accuracy: 0.8882 - val loss: О.ООООе+ОО - val_accuracy:
О.ООООе+ОО

Epoch 2/20
782/782 [==============================] - 34s 43rns/step - loss:
0.0472 - accuracy: 0.9833 - val loss: 0.0507 - val_accuracy: 0.9839
Epoch 20/20
782/782 [==============================] - 34s 44rns/step - loss:
0.0047 - accuracy: 0.9985 - val_loss: 0.0488 - val_accuracy: 0.9920
После окончания
обучения (рис.

20
15.13):

эпох обучения мы можем визуализировать кривые

>>> import: matplotlib.pyplot as plt
>>> hist = history.history
>>> x_arr = np.arange(len(hist['loss'])) + 1
»> fig = plt. figure ( figsize= (12, 4) )
>>>ах= fig.add_subplot(l, 2, 1)

-----------(645)------------------·-

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

>>> ax.plot(x_arr, hist['loss'], '-о', lаЬеl='Потеря
>>> ax.plot(x_arr, hist['val_loss'], '-->> ax.plot(x_arr, hist['val_accuracy'], '-->> celeba train = celeba_train.take(16000)
>>> celeba_valid = celeba_valid.take(lOOO)
>>> p:r·int.( 'Обучающий набор: {}'. format (count_items (celeba_train)))
Обучающий набор: 16000
>>>

рrint('Проверочный набор:

Проверочный набор:

{}'.format(count_items(celeba_valid)))

1000

Важно отметить, что если аргумент

shuffle files в вызове метода
celeba_Ыdr.as_dataset() не был установлен в False, то мы по­
прежнему будем видеть ] 6 ООО образцов в обучающем наборе и ] ООО образ­
цов в проверочном наборе. Тем не менее, в таком случае на каждой итера­

ции обучающие данные тасуются заново, после чего берется новый набор
из

] 6 ООО

образцов. В итоге это свело бы на нет нашу цель, которая заклю­

чается в том, чтобы намеренно обучить модель с помощью небольшого на­
бора данных. Далее мы обсудим дополнение данных как методику подъема
эффективности глубоких нейронных сетей.

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

[649)

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

степени переобучения. Хотя дополнение данных предназначено не только
для данных изображений, существует набор трансформаций, единственно
применимых к данным изображений, в число которых входят обрезка час­
тей изображения, переворачивание, изменение контрастности, яркости и

насыщенности. Давайте взглянем на ряд таких трансформаций, доступных
посредством модуля

tf. image.

В приведенном ниже блоке кода мы извле­

каем пять образцов из набора данных
пять типов трансформации:

2)

celeba train

обрезка изображения до граничной рамки,

1)

переворачивание изображения по горизонтали,

ности,

подстройка яркости и

4)

и применяем к ним

5)

3)

подстройка контраст­

обрезка изображения относительно центра

и приведение результирующего изображения к исходному размеру

(218, 178).

Результаты визуализируются в отдельных колонках для сравнения.

>>>
>>>
>>>
>>>

import matplotlib.pyplot as plt
#

извлечь

5

образцов

examples = []
for example in celeba_train.take(5):
examples.append(example['image'])

>>> fig =
>>> ##

plt.figure(figsize=(lб,

Колонка

1:

8.5))

обрезка до rраничной рамки

>>>ах= fig.add_subplot(2, 5, 1)
>>> ах. set _ ti tle ('Обрезка \nдо граничной рамки', size=15)
>>> ax.imshow(examples[OJ)
>>>ах= fig.add_subplot(2, 5, 6)
>>> img_cropped = tf.image.crop_to_bounding_box(
examples[O], 50, 20, 128, 128)

>>> ax.imshow(img_cropped)
>>> ##

Колонка

>>>ах=

2:

переворачивание

(по горизонтали)

fig.add_subplot(2, 5, 2)

>>> ах.sеt_titlе('Переворачивание (по горизонтали)', size=15)
>>> ax.imshow(examples(l])
>>>ах= fig.add_subplot(2, 5, 7)
>>> img_flipped = tf.image.flip_left_right(examples[l])
>>> ax.imshow(img_flipped)
>>> ##

Колонка

>>>ах=

3:

подстройка контрастности

fig.add_subplot(2, 5, 3)

>>> ax.set_title('Пoдcтpoйкa контрастности', size=15)
>>> ax.imshow(examples[2])
>>>ах= fig.add_subplot(2, 5, 8)

---

[650)-------·---~----

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

>>> img_adj_contrast = tf.image.adjust_contrast(
examples[2], contrast_factor=2)
>>> ax.imshow(img_adj_contrast)
>>> ##

Колонка

4: подстройка яркости

>>>ах= fig.add_suЬplot(2,

>>>
>>>

5, 4)
size=15)

ax.set_title('Пoдcтpoйкa яркости',

ax.imshow(examples[З])

fig.add_subplot(2, 5, 9)
>>> img_adj_brightness = tf.image.adjust_brightness(
examples[З], delta=0.3)
>>> ax.imshow(img_adj_brightness)
>>>ах=

>>> ##

Колонка

5: обрезка относительно центра

5, 5)
set_title ('Обрезка относительно центра\nи изменение
size=15)
>>> ax.imshow(examples[4])
>>>ах= fig.add_subplot(2, 5, 10)
>>> img_center_crop = tf.image.central_crop(
examples[4], 0.7)
>>> img_resized = tf.image.resize(
img_center_crop, size=(218, 178))
>>> ax.imshow(img_resized.numpy().astype('uint8'))
>>> plt. show ()

>>>ах= fig.add_suЬplot(2,

>>>

ах.

Результаты представлены на рис.

размера',

15.15.
Обрезка

Обрезка

Переворачивание

Подстройка

Подстройка

относительно центра

до граничной рамки

(по горизонтали)

контрастности

яркости

и изменениеразмера

"
"
"

100

lS

)S

so

so

75

75

100

100

11S

ш

11S

1>0

1so

ао

щ

17!

17,

200

......._..,,.

1'-1--~

100

SO

1SO

SO

100

1SO

SO

100

lSO

ю

100

150

50

100

lSO

200

•r..;;;;;;a--....... ,._....._,..,..,.._
200

SO

100

lIO

SO

100

lSO

ю

100

1!10

~

100

lSO

)>

so

"

100
щ

....
1'

"
Рис.

-·----------

\СО

15. / 5.

Результаты 11ри.мене1111я пяти разных трансфор.маций

----·--------- - - ----- (6511 - - -·--·

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

На рис.
рой

-

15.15

в первой строке показаны исходные изображения, а во вто­

их трансформированные версии. Обратите внимание, что для первой

трансформации (крайняя слева колонка) граничная рамка задана четырьмя

числами: координаты верхнего левого угла рамки (здесь х
также ширина и высота рамки (ширина=

128,

высота=

= 20, у= 50), а
128). Кроме того,

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

кетами вроде

imageio),

TensorFlow

(равно как и другими па­

является верхний левый угол изображения.

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

ется делать для дополнения данных во время обучения модели. Скажем, изоб­
ражение можно обрезать до случайной граничной рамки (когда координаты

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

0.5.
contrast_ factor

выбирается случайным образом из диапазона значений с равномерным распре­

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

размеру. Вот как выглядит код (поскольку мы имеем дело со случайными
элементами, то в целях воспроизводимости устанавливаем начальное слу­

чайное значение):

>>> tf.random.set_seed(l)

»> fig = plt. figure ( figsize= (14, 12) )
>>> for i, example in enumerate (celeba_train. take (3)):
image = example [ 'image' ]
ах= fig.add_subplot(3, 4, i*4+1)
ax.imshow(image)
if i == О:
ax.set_title('Opигинaл', size=15)

4, i*4+2)
img_cr6p = tf.image.random_crop(image, size=(178, 178, 3))
ax.imshow(img_crop)
i f i == О:
ах. set_title ('Шаг 1: случайная обрезка', size=15)
ах= fig.add_suЬplot(3,

-----------(652)

Глава

15. Класс ификация

изображений с помощью глубоких сверточных нейронных с е тей

4, i*4+3)
img_flip = tf . image.random_flip_left_right(img_crop)
ax.imshow(tf.cast(img_flip, tf.uint8))
i f i ==О:
ах. set_title ('Шаг 2: случайное переворачивание', size=15)
ах= fig.add_subplot(З,

4, i*4+4)
img_resize = tf.image.resize(img_flip, size=(128, 128))
ax.imshow(tf.cast(img_resize, tf.uint8))
if i ==О:
ax.set_title('Шaг 3: изменение размера', size=15)
ах= fig.add_subplot(З,

>>> plt.show()
Результаты применения случайных трансформаций можно видеть на
рис .

15.16.
Шаг

Оригинал

Шаг

1: случайная обрезка

2: случайное

переворачивание

Шаг 3: изменение размера

о
/~

25

so

20

50

100

75

175

100

ISO

125

40

60
80

175

100

150

120

200
о

50

100

150

50

100

150

50

100

lSO

so

100



tOO

1~0

00

100

1~

00

100

so

100

150

50

100

150

50

100

о
о

25

25



50

75
100

75

125

100

ISO

125

175

150

100
50

100

150

50

100

150

о

25

50
75
100

125

ISO
175
100

Рис.

15.16.

Резул ьтаты применения случайных трансформаций

- - --

-

-

-

- (653) - -- - - -- - --

-

-

[лава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

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

preprocess (), которая
'image' и 'attributes ',

В показанном ниже коде мы определим функцию
будет принимать словарь, содержащий ключи

а возвращать кортеж с трансформированным изображением и меткой, извле­
ченной из словаря атрибутов.
Тем не менее, мы будем применять дополнение данных только к обучаю­

щим образцам, но не к проверочным или испытательным. Вот необходимый
код:

>>> de.f preprocess (example, size=(64, 64), mode='train'):
image = example [ ' image' ]
label = example [ 'attributes'] [ 'Male']
if mode == 'train':
image_cropped = tf.image.random_crop(
image, size=(178, 178, 3))
image_resized = tf.image.resize(
image_cropped, size=size)
image_flip = tf.image.random_flip_left_right(
image_resized)
return image_flip/255.0, tf.cast(label, tf.int32)
else: # для не обучающих данны..х использовать обрезку
# по центру, а не случайную обрезку
image_cropped = tf.image.crop_to_bounding_box(
image, offset_height=20, offset_width=O,
target_height=178, target_width=178)
image_resized = tf.image.resize(
image_cropped, size=size)
return image_resized/255.0, tf.cast(label, tf.int32)
Чтобы посмотреть на дополнение данных в действии, давайте создадим

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

>>>

tf.randoщ.set

>>>
>>>
>>>
>>>

ds = celeba_train.shuffle (1000, reshuffle_each iteration=);'alзe)
ds = ds.take(2) .repeat(5)
ds = ds.map(larnЬda x:preprocess(x, size=(178, 178), mode='train'))
fig = plt.figure(figsize=(15, 6))

seed(l)

[654] ------------------··--------------

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

>>> for j,example in enurnerate(ds):
ах= fig.add_subplot(2, 5, j//2+(j%2)*5+1)
ax.set_xticks([])
ax.set_yticks([])
ax.imshow(example[O])
>>> plt. show ()
На рис.

15.17

представлены пять результирующих трансформаций для

дополнения данных на двух примерах изображений.

Рис.

15.17.

Дополнение да11ных в действии

Далее мы применим к обучающему и проверочному наборам созданную
функцию предварительной обработки. Мы будем использовать размер изоб­
ражения

( 64, 64).

Кроме того, мы укажем

обучающими данными и

mode=' eval'

mode=' train'

при работе с

при работе с проверочными данны­

ми, чтобы случайные элементы конвейера дополнения данных применялись
только к обучающим данным:

>>> import numpy as np
>>> ВАТСН SIZE = 32
>>> BUFFER SIZE = 1000
>>> IМAGE_SIZE = (64, 64)
>>> steps_per_epoch = np.ceil(16000/BATCH_SIZE)
>>> ds_train = celeba_train.map(
lamЬda х: preprocess(x, size=IМAGE_SIZE, mode='train'))
>>> ds_train = ds_train.shuffle(buffer_size=BUFFER_SIZE) .repeat()
>>> ds_train = ds_train.batch(BATCH_SIZE)
>>> ds_valid = celeba_valid.map(
lamЬda х: preprocess(x, size=IМAGE_SIZE, mode='eval'))
>>> ds_valid = ds_valid.batch(BATCH_SIZE)
- -· - -- -- · - -- - --

-

- --

[бSS]

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Обучение классификатора полов, основанноrо
на сверточной нейронной сети
К настоящему времени построение модели с помощью АРI-интерфейса

TensorFlow и ее обучение не должны вызывать вопросов. По
замыслу наша модель CNN получает входные изображения размера 64х64хЗ
(изображения имеют три цветовых канала и формат 'channels _ last ').

Keras

библиотеки

Входные данные проходят через четыре сверточных слоя для создания

32, 64, 128

и

256

карт признаков с использованием фильтров с ядром разме­

ра ЗхЗ. За первыми тремя сверточными слоями следует слой объединения
по максимуму, Р 2 х 2 • Вдобавок предусмотрено два слоя отключения для ре­
гуляризации:

>>> model = tf. keras. Sequential ( [
tf.keras.layers.Conv2D(
32, (3, 3), padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Dropout(rate=0.5),
tf.keras.layers.Conv2D(
64, (3, 3), padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Dropout(rate=0.5),
tf.keras.layers.Conv2D(
128, (3, 3), padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Conv2D(
256, (3, 3), padding='same', activation='relu')



])

Давайте выясним форму выходных карт признаков после применения
указанных слоев:

>>> model.compute_output_shape(input_shape=(None, 64, 64, 3))
TensorShape ( [None, 8, 8, 2 5 6] )
Существуют

256

карт признаков (или каналов) размера 8х8. Теперь мы

можем добавить полносвязный слой, чтобы получить выходной слой с

единственным элементом. Если мы изменим форму карт признаков (выров-----~--------·-----

[656]--·-----------------

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

няем их), тогда количество входных элементов в этом полносвязном слое

составит 8х8х256

= 16 384.

В качестве альтернативы мы возьмем новый

слой под названием слой объединения по ?лобалыюJну среднему, который
рассчитывает среднее каждой карты признаков по отдельности, тем самым

сокращая количество скрытых элементов до

256.

Затем мы можем добавить

полносвязный слой. Несмотря на то что мы явно не обсуждали слои объеди­

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

15.18

показан пример входных карт при­

знаков формы [размер пакетах64х64х8]. Каналы пронумерованы как

1, "., 7.

k

=О,

Операция объединения по глобальному среднему рассчитывает сред­

нее каждого канала, так что выход будет иметь форму [размер пакетах&].

(Примечание: класс

GlobalAveragePooling2D

в АРl-интерфейсе

Keras

автоматически сжимает выход. Без сжатия выхода форма выглядела бы как

[размер пакетах 1х1 х8], потому что объединение по глобальному среднему
сократит пространственное измерение 64х64 до 1х1.)

Форма:

[размер пакетах64х64х8]

Форма: [размер пакетах8]

1111111111а1111
1•1

Рис.

15.18.

1•1

IXI

1•1

1•1

IX\

1•1

1•1

При.мер входных карт признаков форны [ртмер пакетах 6 4 х б 4 х

8]

Следовательно, с учетом того, что в нашем случае форма карт признаков
перед этим слоем имеет вид [размер пакетах8х8х256], мы ожидаем полу­
чить в качестве выхода

256

элементов, т.е. формой выхода будет [размер

пакетах256]. Давайте добавим такой слой и заново рассчитаем форму выхо­
да, удостоверившись в том , что утверждение верно:

- -- ---[657]---

- - - - - - -·--- ··---·---------------

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

>>> model.add(tf.keras.layers.GlobalAveragePooling 2D())
>>> model.compute_output_shape(input_shape=(None, 64, 64, 3))

TensorShape ( [None, 256])
Наконец, мы можем добавить полносвязный (плотный) слой для получения
единственного выходного элемента. В данном случае мы можем указать функ­
цию активации

'sigmoid'

или просто использовать

acti vation=None,

так

что модель будет выдавать логиты (вместо вероятностей членства в клас­

сах), которые предпочтительнее при обучении модели в

TensorF\ow

и

Keras

из-за своей численной устойчивости, как обсуждалось ранее:

model.add(tf.keras.layers.Dense(l, activation=None))
tf. random. set_seed ( 1)
model.build(input_shape=(None, 64, 64, 3))
model. summary ()
Model: "sequential"
>>>
>>>
>>>
>>>

Layer (type)

Output Shape

Param #

===============================================================
conv2d (Conv2D)

multiple

896

max_pooling2d(MaxPooling2D)

multiple

о

dropout (Dropout)

multiple

о

conv2d 1 (Conv2D)

multiple

18496

max_pooling2d_l(MaxPooling2

multiple

о

dropout_l (Dropout)

multiple

о

conv2d 2 (Conv2D)

multiple

73856

max_pooling2d_2 (MaxPooling2

multiple

о

conv2d 3 (Conv2D)

multiple

295168

global_average_pooling2d(Gl

multiple

о

dense (Dense)

multiple

257

Total params: 388,673
TrainaЫe params: 388,673
Non-trainaЫe params: О

[658)

Глава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Следующий шаг

-

компиляция модели, но теперь мы должны решить,

какую функцию потерь применять. Мы имеем двоичную классификацию
с одиночным выходным элементом, что означает необходимость в исполь­
зовании класса

BinaryCrossentropy.

Кроме того, поскольку наш пос­

ледний слой не применяет сигмоидальную активацию (мы используем

activation=None),

выходами модели являются логиты, не вероятности.

Таким образом, мы будем указывать в

from _ logi ts=True,

BinaryCrossentropy

также и

чтобы функция потерь внутренне применяла сигмо­

идальную функцию, которая благодаря своему лежащему в основе коду эф­
фективнее, чем делать это вручную. Вот код для компиляции и обучения
модели:

>>> model.compile(optimizer=tf.keras.optimizers.Ad am(),
loss=
tf. keras. losses. BinaryCrossentropy ( from_ logi ts='I'r:ue),
metrics=['accuracy'])
>>> history = model.fit(ds train, validation_data=ds_valid,
epochs=20,
steps_per_epoch=steps_per_epoch)
Давайте визуализируем кривую обучения и сравним потерю и правиль­
ность при обучении и при проверке после каждой эпохи:

>>> hist = history.history
>>> x_arr = np.arange(1«0ш(hist['loss'])) + 1
>» fig = plt. figure (figsize= (12, 4))
>>>ах= fig.add_subplot(l, 2, 1)
>>> ax.plot(x_arr, hist['loss'], '-о', lаЬеl='Потеря при обучении')
>>> ax.plot(x_arr, hist['val_loss'], '-->> history = model.fit(ds_train, validation_data=ds_valid,
epochs=ЗO, initial_epoch=20,
steps_per_epoch=steps_per_epoch)

После того, как кривые обучения начнут нас устраивать, мы можем оце­
нить модель на удерживаемом испытательном наборе:

>>> ds_test = celeba_test.map(

x:preprocess(x, size=IМAGE_SIZE, mode='eval')) .batch(32)
>>> test_results = model.evaluate(ds_test)
>>> p:i:·int ('Правильность при испьггании: {:. 2f) %'
.format(test_results[l)*lOO))
Правильность при испытании: 94.75%
lamЬda

Итак, мы готовы получить результаты прогнозирования на ряде испы­
тательных образцов с применением метода

model. predict ().

Однако

вспомните, что модель выдает логиты, не вероятности. Если нас инте­
ресуют вероятности членства в классах для этой двоичной задачи с оди­

ночным выходным элементом, тогда мы можем использовать функцию

tf. sigmoid

для вычисления вероятности класса \.(В случае многоклас­

совой задачи мы применяли функцию

tf. ma th. softmax.)

В приведенном

·-- - ----------- [660)- ----- - --------

---- -

fлава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

ниже коде мы будем брать небольшой поднабор из



образцов из пред­

варительно обработанного испытательного набора данных
запускать

model. predict ()

(ds _ t е s t)

и

для получения логитов. Далее для каждого

образца мы рассчитаем вероятность его принадлежности к классу

1 (кото­

рый соответствует мужскому полу, основываясь на метках в наборе данных

CelebA)

и визуализируем образцы вместе с достоверны.;;нu .тwетка\щ ("ДМ")

и спрогнозированны.«и вероятностя.ми ("Вер"). Обратите внимание, что пе­

ред извлечением

ds_test,

10

образцов мы применяем

иначе метод

take ()

возвратил бы

unbatch () к набору данных
10 пакетов размера 32, а не 10

индивидуальных образцов:

»> ds = ds_test. unЬatch (). take (10)
>>> pred_logits = model.predict(ds.batch(10))

»> probas = tf.sigmoid(pred_logits)
>>> probas = probas. numpy () . flatten () *100
>>> fig = plt.figure(figsize=(15, 7))
>>> fc•.r: j,example in enunie:r:·ate(ds):
ах= fig.add_suЬplot(2, 5, j+l)
ax.set_xticks( []); ax.set_yticks( [])
ax.imshow(example[O])
if example [ 1] . numpy () == 1:
lаЬеl='мужской'

else:
label = 'женский'
ax.text(
0.5, -0.15, 'ДМ: {:s}\nВер(мужской)={:.Оf}%'
''.format(label, probas[j]),
size=lб,

horizontalalignment='center',
verticalalignment='center',
transform=ax.transAxes)
>>> plt.tight_layout()
»> plt. show ()
На рис.

15.20

показаны

10

примеров изображений вместе с достоверны­

ми метками и вероятностями их принадлежности к классу

1 (мужской).

Под каждым изображением выводятся вероятности принадлежности к
классу

l

(т.е. мужскому полу в соответствии с набором данных

CelebA).

Как

видите, наша обученная модель совершила только одну ошибку в выбран­
ном наборе из



испытательных образцов.

---· (661 ]--·----

[лава

15. Классификация изображений с помощью глубоких сверточнЬlх нейронных сетей

ДМ: мужской

ДМ: женский

Вер( мужской )=80%

ДМ: мужской

Вер( мужской )=99%

Рис.

15.20.

Вер( мужской )=0%

ДМ : женский

ДМ: мужской

Вер( мужской)= 100% Вер( мужской )=89%

ДМ: мужской
Вер( мужской )=89%

ДМ: мужской

ДМ: мужской

ДМ : женский

ДМ: женский

Вер(мужской)=О%

Вер( мужской )=0%

Вер( мужской )=99%

Вер( мужской)= 100%

Десять приwеров изображений с их достоверными .метка.ми

("ДМ")

и спрогнтированными вероятиостями ("Вер")

В качестве дополнительного упражнения мы рекомендуем попытаться

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

CNN.

Скажем, вы можете изменить вероятности отключения и

количество фильтров в различных сверточных слоях. Вы также могли бы за­

менить слой объединения по глобальному среднему плотным слоем. Если вы
будете использовать полный обучающий набор данных с моделью на основе

CNN,

обученной в этой главе, то должны достичь примерно 97-99%-ной

точности.

Резюме
В настоящей главе вы узнали о сетях

CNN

и их главных компонентах.

Мы начали с операции свертки и взглянули на одномерные и двумерные
реализации. Затем мы рассмотрели еще один тип слоев, который можно об­

наружить в ряде распространенных архитектур

CNN:

слои подвыборки, так­

же называемые объединяющими слоями . Мы в основном уделяли внимание

двум самым часто применяемым формам объединения: по максимуму и по
среднему.

·--- - -- -- - - ·-- - -- - - (662)---- ---·-

-----

-

-·--- ----

fлава

15.

Классификация изображений с помощью глубоких сверточных нейронных сетей

Далее, собрав все индивидуальные концепции вместе, мы реализовали

глубокие сверточные нейронные сети, используя АРI-интерфейс
лиотеки

TensorFlow.

Keras

биб­

Первая сеть применялась к уже знакомой задаче рас­

познавания рукописных цифр из набора данных

Затем мы реализовали вторую сеть

CNN

MNJST.

на более сложном наборе дан­

ных, содержащем изображения лиц, и обучили ее для классификации по­
лов. Попутно мы также исследовали дополнение данных и трансформации,
которые можно применять к изображениям лиц с использованием класса

Dataset

из

TensorFlow.

В следующей главе мы перейдем к рассмотрению рекуррентных ней­
р01тых сетей

(recurnmt neuml 11clwm·k --

ЯNN). Сети

RNN

применяются

для выяснения структуры последовательных данных и с ними связано не­

сколько восхитительных приложений, включая перевод с одного языка на

другой и подписание изображений.

------(663]--

16
МОДЕЛИРОВАНИЕ
ПОСЛЕДОВАТЕЛЬНЫХ ДАННЫХ

С ИСПОЛЬЗОВАНИЕМ
РЕКУРРЕНТНЫХ

НЕЙРОННЫХ СЕТЕЙ

предыдущей главе наше внимание было сосредоточено на сверточных
в
_
нейронных сетях (CNN). Мы рассмотрели строительные блоки архи­
тектур

CNN

и выяснили, каким образом реализовывать глубокие сверточные

нейронные сети в

CNN

TensorF\ow.

Наконец, мы показали, как использовать сети

для классификации изображений. В настоящей главе мы будем иссле­

довать рекуррентиые нейронные сети (1·есштенt

neuml

11etн1ork

---- RNN),

а

также их применение в моделировании последовательных данных.

Мы раскроем в главе следующие темы:



понятие последовательных данных;



сети



долгая краткосрочная память

slюгt-tam тетту

--·- LSTM);



укороченное обратное распространение во времени

(tnmcafetl

RNN

для моделирования последовательностей;

pr·opa.~alicm tl11·cщgl1

time ---

(long

Г-НРТJ);

lщck­

Глава



16.

Моделирование последовательнЬtх данных."

реализация многослойной сети

тельностей в



проект номер один

-

ных с рецензиями на



для моделирования последова­

RNN

TensorF\ow;

проект номер два

-

символов с ячейками

сеть

RNN для
фильмы IMDb;

смыслового анализа набора дан­

сеть

RNN для моделирования языка на уровне
LSTM, использующая текстовые данные из рома­

на Жюля Верна "Таинственный остров";



применение отсечеиия градuеитов (дn11/ic11t clippinд) во избежание
проблемы взрывного роста градиентов;



понятие модели преобразователей
мания

(Tml/sfim11c1)

и мехштзлю саwовиu­

(selJ-atte11tion meclranism).

Понятие последовательных данных
Давайте начнем обсуждение сетей

RNN

с выяснения природы последо­

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

их от данных других видов. Затем мы рассмотрим, каким образом можно
представлять последовательные данные, и исследуем разнообразные кате­
гории моделей для последовательных данных, которые основаны на входе и

выходе модели. Такие сведения помогут нам выяснить взаимосвязь между
сетями

RNN

и последовательностями позже в главе.

Модеnирование посnедоватеnьных данных

-

вопросы порядка

Характерная черта последовательностей, которая делает их уникальными,
отличающимися от остальных типов данных, связана с тем, что элементы в

последовательности расположены в определенном порядке и не являются неза­

висимыми друг от друга. Типичные алгоритмы МО с учителем предполагают,
что входные данные 11езавuсu/1сtЫ и одинаково распределены, т.е. обучающие
образцы взаw1то незавuсuttы и имеют одно и то же внутреннее распределе­
ние. В этом отношении, исходя из предположения взаимной независимос­

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

Например, при наличии выборки из п обучающих образцов, х0 >, х(2), "., х,
порядок их использования для обучения алгоритма МО не имеет значения.

Примером сценария подобного рода может служить набор данных

lris,

· - - - - - [666)---------------

с ко-

fлава

16.

Моделирование последовательных данных."

торым мы работали ранее. В наборе данных

Iris

каждый цветок измерял­

ся независимо, а измерения одного цветка не влияют на измерения другого
цветка.

Однако такое допущение перестает быть действительным, когда мы име­
ем дело с последовательностями

-

по определению порядок в них имеет

значение. Примером такого сценария может быть прогнозирование рыноч­
ной стоимости конкретной акции. Скажем, пусть у нас есть выборка из п

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

отсортированном по дате порядке, чтобы вывести тенденцию, а не потреб­
лять имеющиеся обучающие образцы в случайном порядке.

r~ Последовательные данные иnи данные временных рядов

н~ Данные временных рядов являются особым типом последовательных
заметку! данных, где каждый образец ассоциирован с измерением для време­
ни. В данных временных рядов выборки производятся в следующие
друг за другом отметки времени, а потому измерение времени опре­

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

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

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

обработки естестветюю языка (11al1m1l la11g11age

processi11g ·-- :'JI,P)

и моделирования текста, которые не являются данными временных

рядов, но имейте в виду, что сети

RNN

могут применяться также

для данных временных рядов.

Пред(тавnение ПО(nедоватеnьно(тей
Мы признали, что в последовательных данных порядок в точках данных

важен, поэтому далее нам нужно найти способ использования в своих инте­

ресах информации упорядочивания в модели МО. Повсюду в главе мы бу­

дем представлять последовательности как

' .forrnat (t))
: ', xt.numpy())
Вход
prir1t('
priпt( 'Временной шаг

ht = tf.matmul(xt, w_xh) + b_h
: ', ht.numpy())
Скрытый слой

print('
if t>O:

prev_o = out_man[t-1]
else:
prev_o = tf. zeros (shape= (ht. shape))
ot = ht + tf .matmul (prev_o, w_oo)
ot = tf .math. tanh (ot)
out_man.append(ot)
Выход (вручную):', ot .numpy ())
print ('
Выход SimpleRNN:'. format (t),
pri.nt. ('
output[O] [t] .numpy())
pr:i.nt ()
Временной шаг О
Вход

=>

[[1. 1. 1. 1. 1.]]
[[0.41464037 0.96012145]]
(вручную) : [[0.39240566 0.74433106)]
SimpleRNN: [0.39240566 0.74433106]

Скрытый: слой
Выход

Выход

Временной шаг

1 =>

[[2. 2. 2. 2. 2.]]
[ [0. 82928073 1. 9202429 ] ]
Скрытый слой
Выход (вручную) : [[0.80116504 0.9912947 ]]
Выход SimpleRNN: [0.80116504 0.9912947]

Вход

Временной шаг

2 =>

[[3. 3. 3. 3. 3.]]
[ [1. 243921 2. 8803642]]
Скрытьм слой
Выход (вручную) : ((0.95468265 0.9993069 ]]
Выход SimpleRNN: (0. 95468265 о. 9993069 ]
Вход

При ручном расчете прямого прохода мы использовали функцию акти­
вации в виде гиперболического тангенса

SimpleRNN
что

выходы

(tanh),

т.к. она же применяется в

(стандартная активация). По выведенным результатам видно,
из ручного

выходом слоя

расчета прямого

прохода в точности совпадают с

SimpleRNN на каждом временном шаге. Надеемся, рассмот­

ренная практическая задача пролила свет на тайны рекуррентных сетей.

(679]--

Глава

16. Моделирование последовательнь1х данных ."

Сnожности изучения доnrосрочных взаимодействий
Кратко упомянутое выше обратное распространение во времени (ВРТТ)
привносит ряд новых сложностей.
дh(t)

Из-за наличия множителя

дhщ

в расчете градиентов функции потерь

возникает так называемая проблемы исчезновения

(1•1111is/1i11).:) и взрывного

роста (cxpfщliнs) градиентов. Указанные проблемы объясняются с помощью
примеров на рис.

16.8,

где представлена сеть

RNN,

для простоты имеющая

только один скрытый элемент.

Исчез-

Взрывной

новение

градиента: /wпп l

Рис.

16.8.

По существу

рост

1

Жела-

тельно: lwпп l

=1

Проблемы исчешове11ия и взрыв1ю,,о роста градиентов
дh(t)

-(,-)
дh

имеет

t- k

умножений, вследствие чего умножение

t- k

Если lwl < 1, то множитель w1 - k становится очень малым при большом t- k. С другой стороны, если вес рекуррент­
ного ребра lwl > 1, тогда w1 - k становится очень большим при большом t - k.
Следует отметить, что большое t - k относится к долгосрочным зависимос­
раз веса w дает множитель w1 -

k.

тям. Несложно заметить, что наивного решения, позволяющего избежать

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

счет обеспечения

lwl = 1.

В случае заинтересованности в более детальном

исследовании данного вопроса почитайте статью Р. Паскану, Т. Миколова

и Й. Бенджи "On the difficulty of training recurrent neural networks" (О слож­
ности обучения рекуррентных нейронных сетей), которая свободно доступ­
на по ссылке

https: //arxiv. org/pdf/1211. 5063 .pdf.
----~---

(680]

[лава

16.

Моделирование последовательных данных ...

На практике существуют, по меньшей мере, три решения проблем исчез­
новения и взрывного роста градиентов:



отсечение градиентов;



укороченное обратное распространение во времени (Т-ВРГ/);



долгая краткосрочная память

(long :;/mrl>-tam

тет01-у ---

J.ST;W).

При отсечении градиентов мы указываем отсекающее или пороговое зна­
чение для градиентов и присваиваем это отсекающее значение вместо зна­

чений градиентов, которые превышают отсекающее значение. Напротив,

метод ТВРТТ просто ограничивает количество временных шагов, в течение
которых сигнал может обратно распространяться после каждого прямого
прохода. Например, даже если последовательность имеет

l 00

элементов или

шагов, то мы можем обратно распространять лишь самые последние

20

вре­

менных шагов.

Хотя методы отсечения градиентов и ТВРТТ способны решить проблему
взрывного роста градиентов, усечение ограничивает количество шагов, че­

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

1997

LSTM,

разработанный в

году Сеппом Хохрайтером и Юргеном Шмидхубером, был более успе­

шен в решении проблем исчезновения и взрывного роста градиентов наряду
с тем, что моделировал долгосрочные последовательности за счет использо­

вания ячеек памяти. Давайте обсудим метод

LSTM

более подробно.

Ячейки долrой краткосрочной памяти
Первоначально элементы

LSTM

были предложены в качестве спосо­

ба преодоления проблемы с исчезновением градиентов

("Long Short-Term

Memory" (Долгая краткосрочная память), С. Хохрайтер и Ю. Шмидхубер,
Neural Computation, 9(8): с. 1735-1780 (1997 г.)). Строительным блоком эле­
мента LSTM является ячейка памяти, которая по существу представляет
или заменяет скрытый слой стандартных сетей RNN.
В каждой ячейке памяти имеется рекуррентное ребро с упомянутым
ранее желательным весом

w=l

для преодоления проблем исчезновения и

взрывного роста градиентов. Значения, ассоциированные с этим рекуррент­
ным ребром, вместе называются состоянием ячейки. Развернутая структура

современной ячейки

LSTM

показана на рис.

16.9.

[ 6811 --~--------------------·-

Глава

16.

Моделирование последовательных данных. "

f

i

tanh
На следующий

(J

(J

tanh

слой

whf w xf bf whi w xi bi W1,c W xc Ьс
На следующий
временной шаг

(J

e-----•----+~Who W xo Ьо

х

(1)
Рис.

16.9.

Структура совреметтй Я'lейки

LSTM

Обратите внимание, что состояние ячейки из предыдущего временного

шага, c(t-IJ, модифицируется для получения состояния ячейки на текущем
временном шаге, c>>

fron1

tensorflow. keras. layers :i.mport

EmЬedding

>>> model = tf. keras. Sequential ()

- -- ----

- - - - (692] ·---·- -

- - -- --

- ----- - -

Глава

>>>

16.

Моделирование последовательных данных".

model.add(EmЬedding(input_dim=lOO,

output_dim=6,
input _ length=20,
name='emЬed-layer'))

>>> model. summary ()

Model: "sequential"
Param #

Output Shape

Layer (type)

============================================================~~=

emЬed-layer

(EmЬedding)

600

(None, 20, 6)

Total params: 6,00
TrainaЫe params: 6,00
Non-trainaЫe params: О

Вход этой модели (слой вложений) обязан иметь ранг

2

с размерностью

размер пакета х длина входа, где длина входа представляет собой длину
последовательностей (установленную здесь в

20

посредством аргумента

input _ length). Скажем, входной последовательностью в мини-пакете мо­
жет быть , каждый
элемент которой является индексом уникального слова. Выход будет иметь
размерность размер пакета х дли11а входа х размерность вложения, где
размерность вложения

здесь в

6

-

размер признаков вложения (установленный

ou tput _ dim). Еще один аргумент, предоставля­
input _ dirn, соответствует уникальным целочислен­

через аргумент

емый слою вложений,

ным значениям, которые модель будет получать в качестве входа (например,

п

+ 2,

установленное здесь в

100).

Таким образом, матрица вложений в на­

шем случае имеет размер lООхб.

~~ Работа с переменными длинами последовательностей

н~ Обратите внимание, что аргумент input _ length не является обя­
заметку! зательным, и мы можем использовать

None в сценариях, когда дли-

ны входных последовательностей варьируются. Дополнительные

сведения ищите в официальной документации по ссылке

https: / /
www.tensorflow.org/ versions/r2.0/api docs/python/
tf/keras/layers/Embed ding.

Глава

16.

Моделирование последовательных данных."

Построение модеnи на основе рекуррентной нейронной сети
Итак, мы готовы к построению модели на основе

са

Sequential

библиотеки

рекуррентные слои сети

RNN

Keras

RNN.

С помощью клас­

мы можем объединить слой вложений,

и полносвязные нерекуррентные слои. Для ре­

куррентных слоев мы можем применять любую из следующих реализаций:

• SimpleRNN -

обыкновенный слой

RNN,

т.е. полносвязный рекуррен­

тный слой;

• LSTM -

слой

RNN

с долгой краткосрочной памятью, которая полезна

для выявления долгосрочных зависимостей;

рекуррентный слой с управляемым рекуррентным блоком, пред­

• GRU -

LSTM в статье "Learning
Phrase Representations Using RNN Encoder-Decoder for Statistical
Machine Translation" (Изучение представлений фраз с использовани­

ложенный в качестве альтернативы ячейкам

ем рекуррентной нейронной сети "кодировщик-декодировщик" для
статистического машинного перевода), которая свободно доступна по
ссылке

ht tps: / / arxi v. org / abs / 14 О 6. 107 8v3.

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

дель

RNN,

начав со слоя вложений с

мо­

input_dim=lOOO и output_dim=32.
SimpleRNN. В заключение

Затем мы добавим два рекуррентных слоя типа

мы добавим полносвязный нерекуррентный слой в качестве выходного слоя,
который будет возвращать одиночное выходное значение, представляющее
собой прогноз:

>>> f:·!:orrt tensorflow. keras :;.шрссt Sequential
>>> f1·orn tensorflow. keras. layers 1шрогt EmЬedding
>>> froщ tensorflow. keras. layers 11npoi:·t SimpleRNN
>>> froш tensorflow. keras. layers 1mpcн.·t: Dense
>>> model = Sequential ()
>>> model.add(EmЬedding(input_dim=lOOO, output_dim=32))
>>> model.addiSimpleRNN(32, return_sequences=TL·1;e))
>» model.add(SimpleRNN(32))
>>> model.add(Dense(l))
>>> model. sшnmary ()

------------------------- [ 6941 --- ---- -----------------------------

Глава

16.

Моделирование последовательных данных".

Model: "sequential"
Output Shape

Param #

(None, None, 32)

32000

simple rnn (SimpleRNN)

(None, None, 32)

2080

simple_rnn_l (SimpleRNN)

(None, 32)

2080

dense (Dense)

(None, 1)

33

Layer (type)
emЬedding

(EmЬedding)

Total params: 36,193
TrainaЫe params: 36,193
Non-trainaЫe params: О
Как видите, построение модели на основе сети

RNN

с использованием

таких рекуррентных слоев, выглядит достаточно прямолинейно. В следую­
щем подразделе мы вернемся к нашей задаче смыслового анализа и постро­

им модель

RNN

для ее решения.

Построение модели на основе рекуррентной нейронной
сети для решения задачи смысловоrо анализа

Поскольку мы имеем очень длинные последовательности, то собираем­

LSTM для учета долгосрочных эффектов. Вдобавок мы
LSTM внутрь оболочки Bidirectional, которая заставит

ся применять слой
поместим слой

рекуррентные слои проходить по входным последовательностям в обоих на­
правлениях

-

от начала до конца и обратно:

>>> emЬedding_dim = 20
>>> vocab_size = len(token_counts) + 2
>>> tf.random.set_seed(l)
>>> ## построение модели
>>> Ы_lstm_model = tf.keras.Sequential([
tf.keras.layers.EmЬedding(

input_dim=vocab_size,
output_dim=emЬedding_dim,
name='emЬed-layer'),

----------------(695)--~---~--

Глава

16.

Моделирование последовательных данных ...

tf.keras.layers.Bidirectional(
tf.keras.layers.LSTM(64, name='lstm-layer'),
name='bidir-lstm'),
tf.keras.layers.Dense(64, activation='relu'),
tf.keras.layers.Dense(l, activation='sigmoid')

>» ] )
>>>

Ьi_lstm_model.

surnmary ()

>>> ## компиляция и обучение:
>>> Ьi_lstm_model.compile(
optimizer=tf.keras.optimizers.Adam(le-3),
loss=tf.keras.losses.BinaryCrossentropy(from_lo gits=False),
metrics=['accuracy'])

»> history =

Ьi_lstm_model. fit (
train_data,
validation_data=valid_data,
epochs=lO)

>>> ## оценка на испытательных данных
>>> test_results = Ьi_lstm_model.evaluate(test_data)
>>> print( 'Правильность при испытании: {: .2f}%'.
format(test_results[1]*100))
Epoch 1/10
625/625 [==============================] - 96s 154ms/step - loss:
0.4410 - accuracy: 0.7782 - val loss: О.ООООе+ОО - val_accuracy:
О.ООООе+ОО

Epoch 2/10
625/625 [==============================] - 95s 152ms/step - loss:
0.1799 - accuracy: 0.9326 - val loss: 0.4833 - val_accuracy:
0.8414
Правильность при испытании:

85.15%

После обучения модели в течение



эпох оценка на испытательных дан­

ных показывает 85%-ную правильность. (Отметим, что этот результат не яв­
ляется наилучшим в сравнении с современными методами, используемыми

на наборе данных
трировать работу

IMDb. Цель
сети RNN.)

заключалась в том, чтобы просто продемонс­

----------------------------- [ 6 9 6 ] - - - - -

[лава

16. Моделирование последовательных данных ...

\\.~ Допопнитепьные сведения о двунаправленной сети

RNN

н~ Оболочка Bidirectional делает два прохода по каждой входной
Jаметку! последовательности: прямой проход и противоположный или обратный проход (важно не путать их с прямым и обратным проходами
в контексте обратного распространения). Результаты таких прямых

и обратных проходов по умолчанию будут объединяться. Но если
вы хотите изменить это поведение, тогда можете установить аргу­

мент

merge _ mode

в

'sum'

результатов двух проходов),

'concat'

(для сложения),

'ave'

'mul'

(для умножения

(для взятия среднего из двух),

(для объединения; стандартное значение) или

None,

что

приводит к возвращению двух тензоров в списке. Дополнительную

информацию об оболочке

Bidirectional ищите в официаль­
ной документации по ссылке https: / /www. tensorflow. org /
versions/r2.0/api docs/python/tf/keras/layers/
Bidirectional.
Мы также можем опробовать рекуррентные слои других типов, такие

как

SimpleRNN.

Однако оказывается, что модель, построенная с обыкно­

венными рекуррентными слоями, не будет способна достичь хорошей эф­
фективности прогнозирования (даже на обучающих данных). Скажем, если
вы попробуете заменить двунаправленный слой

однонаправленным слоем

SimpleRNN

LSTM

в предыдущем коде

и обучите модель на полноразмерных

последовательностях, то можете заметить, что потеря не уменьшается даже

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

SimpleRNN

не в

состоянии узнать долгосрочные зависимости и может страдать от проблем
исчезновения или взрывного роста градиентов.

Для получения приемлемой эффективности прогнозирования на таком
наборе данных с применением слоя

SimpleRNN

мы можем усекать последо­

вательности. Кроме того, используя наше "знание предметной области", мы

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

мы определим вспомогательную функцию

объединим шаги
дополнительный

preprocess_datasets (),где
2-4. Функция preprocess _ datasets () будет иметь
аргумент max _ seq_ length, определяющий количество

лексем, которые должны быть задействованы из каждой рецензии. Скажем,

[697)----

[лава

16.

Моделирование последовательных данных ...

если мы установим

max seq length

в

100,

а рецензия содержит более

100 лексем, тогда будут применяться только последние 100 лексем. Если
max _ seq_ length устанавливается в None, то будут использоваться пол­
ные последовательности без сокращений. Опробование отличающихся зна­

max _ seq_ length позволит лучше понять возможности
RNN по обработке длинных последовательностей.
приведен код функции preprocess_datasets ():

чений для

моделей
Ниже

>>>

f

разных

rom collections import Counter

>>> def preprocess_datasets (
ds_raw_train,
ds_raw_valid,
ds_raw_test,
max_seq_length=None,
batch_size=32):
## (шаг 1 уже сделан)
## Шаг 2: нахо)JЩение уникальных. лексем
tokenizer = tfds.features.text.Toke nizer()
token_counts = Counter ()
for example in ds_raw_train:
tokens = tokenizer.tokenize(exam ple[O] .numpy() [0])
if max_ seq_length is not None:
tokens = tokens [-max_seq_length:]
token_counts.update(tok ens)
print ('Размер
##

Шаг

словаря:',

len (token_counts))

3: кодирование текста

encoder = tfds.features.text.Token TextEncoder(
token_ counts)
def encode(text_tensor, label):
text = text_tensor.numpy() [О]
encoded_text = encoder.encode(text)
н· max _ seq_length is not None:
encoded_text = encoded_text[-max_seq_ length:]
re~urn encoded_text, label
def encode_map_fn(text, label):
return tf.py_function(encode, inp=[text, label],
Tout=(tf.int64, tf.int64))

(698]---

Глава

16.

Моделирование последовательных данных".

ds_train = ds_raw_train.map(encode_map_fn)
ds_valid = ds_raw_valid.map(encode_map_fn)
ds_test = ds_raw_test.map(encode_map_fn)
##

Шаг

4:

создание пакетов для наборов данных

train_data = ds_train.padded_batch(
batch_size, padded_shapes=([-1), [)))
valid_data = ds_valid.padded_batch(
batch_size, padded_shapes=([-1], []))
test_data = ds_test.padded_batch(
batch_size, padded_shapes=([-1), []))
return (train_data, valid_data,
test_data, len(token_counts))
Далее мы определим еще одну вспомогательную функцию,

model

build_rnn_

(),для более удобного построения моделей с разными архитектурами:

>>> froщ tensorflow .keras. layers
>>> from tensorflow. keras. layers
>>> from tensorflow. keras. layers
>>> from tensorflow. keras. layers
>>> f.rom tensorflow. keras. layers
>>> def·

in\port
im.port
import
i.mport
import

EmЬedding

Bidirectional
SimpleRNN
LSTM
GRU

vocab_size,
recurrent_type='SimpleRNN',
n_recurrent_units=64,
n_recurrent_layers=l,
bidirectional=True):

build_rnn_model(emЬedding_dim,

tf.random.set_seed(l)
#

пос~оение модели

model = tf. keras. Sequential ()
model.add(
EmЬedding(

input dim=vocab_size,
output_dim=emЬedding_dim,
name='emЬed-layer')

(699)-------------

fпава

16. Моделирование последовательных данных."

for i in range (n_recurrent_layers):
return_sequences = (i < n_recurrent_layers-1)
if recurrent_type == 'SimpleRNN':

recurrent_layer = SimpleRNN(
units=n_recurrent_units,
return_sequences=return_sequences,
name='simprnn-layer-{}' .format(i))
elif recurrent_type == 'LSTM':
recurrent_layer = LSTM(
units=n_recurrent_units,
return_sequences=return_sequences,
name='lstm-layer-{}'.format(i))
elif recurrent_type == 'GRU':
recurrent _ layer = GRU (
units=n_recurrent_units,
return_sequences=return_sequences,
name='gru-layer-{}' .format(i))
if bidirectional:
recurrent_layer = Bidirectional(
recurrent_layer, name='bidir-' +
recurrent_layer.name)
model.add(recurrent_layer)
model.add(tf.keras.layers.Dense(64, activation='relu'))
model.add(tf.keras.layers.Dense(l, activation='sigmoid'))
return model
Теперь, располагая этими двумя довольно универсальными, но удобными
вспомогательными функциями, мы можем легко сравнивать разные модели

RNN

с отличающимися длинами входных последовательностей. Например,

в следующем коде мы опробуем модель с единственным рекуррентным сло­
ем типа

100

SimpleRNN,

усекая последовательности до максимальной длины в

лексем:

>>> batch size = 32
emЬedding_dim = 20
>>> max_seq_length = 100

»>

-------------[700)-----·--------~

Глава

16. Моделирование пос11едовате11ьных данных."

>>> train_data, valid_data, test_data, n = preprocess_datasets(
ds_raw_train, ds_raw_valid, ds_raw_test,
max_seq_length=max_seq_length,
batch size=batch size
>>> vocab size = n + 2
>>> rnn model = build rnn_model (
emЬedding_dim, vocab_size,
recurrent_type='SimpleRNN',
n~recurrent_units=64,

n_recurrent_layers=l,
bidirectional=True)
>>> rnn_model. summary ()
Model: "sequential"
Layer (type)

Output Shape

Param #

(None, None, 20)

1161300

bidir-simprnn-layer-0 (Bidir

(None, 128)

10880

Dense

(Dense)

(None, 64)

8256

dense 1

(Dense)

(None, 1)

65

emЬed-layer

(EmЬedding)

========:======================================================
Total params: 1,180,501
TrainaЫe params: 1,180,501
Non-trainaЫe params: О
>>>rnn_model.compile(
optimizer=tf.keras.optimizers.Adam(le-3),
loss=tf.keras.losses.BinaryCrossentropy(
from_logits=False), metrics=['accuracy'])
>>> history = rnn_model.fit(
train_data,
validation data=valid_data,
epochs=lO)
Epoch 1/10
625/625 [==============================] - 73s 118ms/step - loss:
0.6996 - accuracy: 0.5074 - val loss: 0.6880 - val_accura~y: 0.5476

----~--------[701)------------

[лава

16.

Моделирование последовательных данных ...

Epoch 2/10

>>> results = rnn_model.evaluate(test_data)
>>> pr.int ('Правильность при испытании: {:. 2f} %'.
format(results[l]*lOO))
Правильность при испытании:

80.70%

Как выяснилось, усечение последовательностей до

нение двунаправленного слоя

SimpleRNN

100

лексем и приме­

обеспечивает 80%-ную правиль­

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

LSTM

(85.15%-ная правильность на ис­

пытательном наборе данных), эффективность на таких усеченных последо­

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

SimpleRNN

на полных рецензиях. В качестве дополни­

тельного упражнения можете проверить сказанное, используя две

вспомо­

гательные функции, которые мы уже определили. Попробуйте установить

max seq_ length в None и аргумент Ьidirectional внутри вспомога­
тельной функции build_rnn_model () в False. (Полный код доступен в
архиве с примерами для книги.)

Проект номер два

моделирование языка

-

на уровне символов в

TensorFlow

Моделирование языка является восхитительным приложением, которое

наделяет машины способностью исполнять задачи, связанные с челове­
ческим языком, такие как порождение предложений на английском языке.

Одна из интересных научных работ в этой области описана в статье Ильи
Сацкевера, Джеймса Мартинса и Джеффри Хинтона

"Generating Text with

Recurrent Neural Networks" (Генерирование текста с помощью рекуррентных
нейронных сетей), Proceedings of the 28th International Conference оп Machine
Learning (ICML-11), 2011 г" свободно доступная по ссылке https: / /pdfs.
semanticscholar.org/93c2/0e38c85b69fc2d 2eb314b3cl217913f
7dЫ1 .pdf.
В модели, которую мы построим, входом будет текстовый документ, а
наша цель заключается в том, чтобы разработать модель для порождения но­
вого текста, похожего по стилю на текст во входном документе. Примерами
такого входа могут служить книги или компьютерные программы на опреде­
ленном языке программирования.

--(702]---

Глава

Моделирование последовательнЬtх данных ...

16.

При моделировании языка на уровне символов вход разбивается на пос­
ледовательность символов,

которая

подается в нашу сеть по одному сим­

волу за раз. Сеть будет обрабатывать каждый новый символ в сочетании
с

запомненными

ранее

встречавшимися

символами для

прогнозирования

следующего символа. Пример моделирования языка на уровне символов

демонстрируется на рис.

16.11 (EOS

означает

"end of sequence" -

"конец

последовательности").
Разбиение на последо ­

Данные:

" Hello worldl"

i--------

вательность символов

Входная
последова·
тельность:

Прогнозирование
следующего

' е'

символа:

Рис.

'1'

'1'

16.ll. Jlpuwep

'w'

' о'

'о'

'г'

'1'

'd'

'!'

EOS

моделирования языка на уровне символов

Мы можем разделить реализацию на три шага
построение модели на основе сети

RNN

-

подготовка данных,

и вырабатывание прогноза следую­

щего символа вместе с выборкой для порождения нового текста.

Предварительная обработка набора данных
В этом разделе мы подготовим данные для моделирования языка на уров­
не символов.

Чтобы получить входные данные, понадобится зайти на веб-сайт проекта

"Гутенберг"

(https: / /www.gutenberg.org/),

где предлагаются тысячи

бесплатных электронных книг. Для нашего примера мы загрузим книгу

Mysterious lsland"
Верном и вышла в

"The

("Таинственный остров"), которая была написана Жюлем

1874

году, в формате простого текста по ссылке

ht t р : / /

www.gutenberg.org/files/1268/1268-0.txt.
Важно отметить, что указанная ссылка напрямую ведет к странице за­
грузки. В случае работы в среде

macOS

или

Linux

загрузить файл можно

посредством следующей команды:

curl

-О http://www.gutenЬerg.org/files/1268/1268-0 . txt

- - - - - --[703)

fлава

16. Моделирование пос11едовател11н111х данных".

На тот случай, если в будущем данный ресурс станет недоступным, ко­
пия текста также включена в каталог с кодом для настоящей главы, который

находится по ссылке

https: //github. com/rasbt/python-machine-

learning-book-Зrd-edition/tree/master/chlб.
После загрузки набора данных мы можем прочитать его как простой

текст в сеансе

Python.

С помощью показанного ниже кода мы читаем текст

непосредственно из загруженного файла и удаляем части в начале и конце
(они содержат описание проекта "Гутенберг"). Затем мы создаем перемен­
ную

Python

по имени

char _ set,

которая будет представлять набор уникаль­

ных символов, обнаруженных в тексте:

>>> impo:r:t. numpy as np
>>> ## Чтение и обработка текста
>>> 1;ith open('1268-0.txt', 'r') as fp:
text=fp. read ()

>>>
>>>
>>>
>>>
>>>

start_indx = text.find('THE MYSTERIOUS ISLAND')
end_indx = text. find( 'End of the Project Gutenberg')
text = text[start_indx:end_indx]
char_set = set(text)
рr:шt.('Общая длина:', len(text))
Общая длина: 1112350
>>> p:i:·in.t( 'Уникальные символы:', len (char_set))
Уникальные символы: 80
После загрузки и предварительной обработки текста мы имеем последо­
вательность, состоящую в общей сложности из

1 112 350

символов,

80

из

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

RNN

не могут работать с входными данными в

строковом формате, из-за чего мы должны преобразовать текст в числовой
формат. Для такого преобразования мы создадим словарь

Python, char2int,

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

ми, более эффективно использовать массив

NumPy,

с уникальными символами. На рис.

показан пример преобразования

16.12

символов в целые числа и обратно для слов

сопоставляя его индексы

"Hello"

и

"world".

·-----------(704] - - - - - - -

Глава

16.

Модел ирование последовател ьных данных."

Отображение символов

Отображение целых чисел

на целые числа

на символы

char2int

Рис.

16.12.

int2char

Преобразование сш1волов в целые числа и обратно
для сл ов

"Hell o "

и

"world "

Вот как построить словарь для отображения символов на целые числа и
обратного отображения через индексацию массива
на рис.

NumPy,

изображенного

16.12:

>>> chars sorted = sorted(char_set)
>>> char2int = {ch:i f o r i,ch in enurnerate(chars_sorted)}
>>> char_array = np.array(chars_sorted)
>>> text_encoded = np. array (
[char2int [ch] f or ch in text] ,
dtype=np.int32)
>>>

t ext_encoded.shape)
(1112350,)
>>> print (text [: 15] , '== Кодирование ==> ', t ext _ encoded [ : 15] )
>>> print(text_encoded[l5:21], '== Обрат но е преобра зо ва ние ==> ',
'' .join(char_array[text_encoded[l5:21]]))
ТНЕ MYSTERIOUS - - Кодирование==> (44 32 29 1 37 48 43 44 29 42 33
39 45 43 1]
[33 43 36 25 38 28] == Обратное преобразование ==> ISLAND
print('Фopмa з акодиро в а нн о г о т ек с та: ',

Форма закодированного текста:

Массив

NumPy

по имени

text _ encoded

содержит закодированные зна­

чения для всех символов в тексте. А теперь создадим из этого массива на­

бор данных

TensorF\ow:

>>> irnport tensorflow a s tf
>>> ds text encoded = tf.data.Dataset.from_tensor_slices (
text_encoded)
>>> for ех in ds_text_encoded.take(S):
print('{} - > {}' .format(ex.numpy (), char_array[ex.numpy()]))

- - - --- - ----(705) -

---

-

-

-

Глава

16.

Моделирование последовательных данных."

44 ->
32 ->
29 ->

1 ->
37 ->

т
н

Е

м

До сих пор мы создавали итерируемый объект

Dataset

для получения

символов в порядке их появления в тексте. А сейчас давайте отвлечемся и

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

16.13).

Входные последовательности (Х)

[oJ ~ J[ е
r

т

1[

н

Р J l-; lГi JI

][

ir Е 1

1

.

.

1[

мн у

11

а l~ r

ejl

sнт

J1

Е

[ n]

ir R

_1

0W~~~~D0~~l

LS Jl u

1

Р J[ е

il r Jl v ]\

i

l[ s Jl е ll d

]

~Ln~~ L~WL:J[_J~LJ~
Рис.

16.13.

......
......
...

Цели (у)

ш

l

J

I

ы

[" j

L:J

Неполные посл едователыюстu сиАtволов текста

Последовательности в левом прямоугольнике на рис.

16.13

мы можем

рассматривать как вход. При порождении нового текста наша цель заклю­

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

неполный текст. Например, увидев

ровать

"i"

"Deep Leam",

модель должна спрогнози­

в качестве следующего символа. Учитывая наличие

80

уникаль­

ных символов, задача подпадает nод категорию многоклассовой классифи­
кации.

Начиная с последовательности длиной

1

(т.е. одиночной буквы), мы мо­

жем многократно порождать новый текст, основываясь на таком подходе

многоклассовой классификации, как демонстрируется на рис .

- -- - - - - - - - - (706)

16.14.

[лава

Вход (х)

Цель (у)

16. Моделирование последовательных данных ...

г~ , 1, 'J~1~1~~
r е

г е

r-р l [ ' ' :гL-

1

1

r

е

1[

а

lQCQ".
1

rr

=l

Ln ; ГiJ

Вход(х)

Цель(у)

Вход(х)

t
r-

Цель (у)

Рис.

16.14.

J

j

lh ~

'

1

~·--,!

t_, ~

11

о

1

"

1r
М J r· а J_
с ~- • • •

'lr .,
1[

n ) ' '

- Г· r
~ L n~ ~'

•r

1

-=--iг-iг.:-i

~ il ~jc_ с_ L~_J

Многократное поро.ждение нового текста на основе
подхода многоклассовой классификации

Чтобы реализовать задачу nорождения текста в
давайте сократим длину nоследовательности до
дет состоять из

40

TensorFlow, nервым делом
40, т.е. входной тензор х бу­

лексем . На nрактике длина nоследовательности влияет

на качество nорождаемого текста. Более длинные nоследовательности могут

nриводить к более осмысленным nредложениям. Однако в случае более ко­
ротких nоследовательностей модель способна сосредоточиться на коррект­

ном выявлении индивидуальных слов, большей частью игнорируя контекст.
Несмотря на то что, как упоминалось, более длинные последовательности
обычно дают более осмысленные предложения, для длинных последова­
тельностей модель

RNN

будет иметь проблемы с выявлением долгосрочных

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

Здесь мы выбрали длину

40, т. к. она обесnечивает хороший компромисс.
Как вы видели на рис. 16.14, входы х и цели у смещены на один символ.
Следовательно, мы разделим текст на порции с размером 41: первые 40 сим­
волом будут формировать входную последовательность х, а последние 40
элементов образуют целевую последовательность у.

· - - - - - - - · (707) - - -·- "

Глава

16.

Моделирование последовательных данных."

Мы уже сохранили полный закодированный текст в объекте
имени

Dataset

по

Вспомнив приемы трансформирования наборов

ds - text - encoded.

данных, раскрытые ранее в главе (в разделе "Подготовка данных с рецен­

зиями на фильмы"), можете ли вы придумать способ получения входа х и
цели у, как было показано на рис.
применим метод Ьа tch

()

16.14?

Ответ очень прост: мы сначала

для создания порций текста, каждая из которых

batch size в 41.
Далее мы избавимся от последнего пакета, если он короче 41 символа. В ре­
зультате новый разбитый на порции набор данных по имени ds _ chunks
будет содержать последовательности размера 41. Затем 41-символьные пор­
состоит из

41

символа. Таким образом, мы установим

ции будут использоваться для конструирования последовательности х (т.е.
входа) и последовательности у (т.е. цели), которые обе получат по

40

эле­

ментов. Скажем, последовательность х будет состоять из элементов с ин­
дексами [О,

1, .. " 39].

Кроме того, поскольку последовательность у будет

позицию относительно

смещена на одну

последовательности х, ее

индек­

[ 1, 2, "., 40]. Потом мы применим функцию трансформации,
метод map (), чтобы надлежащим образом разделить последова­

сами будут
используя

тельности х и у:

>>> seq_length = 40
>>> chunk_size = seq_length + 1
>>> ds chunks = ds text encoded.batch(chunk_size,
drop_remainder=True)
>>> ## определение функции для разделениях
>>> def split_input_target(chunk):
input_seq = chunk [ :-1]
target _ seq = chunk [ 1: ]
r:E:t-t1rr1 input _ seq, target _ seq

и у

>>> ds_sequences = ds_chunks.map(split_input_target)
Давайте рассмотрим несколько примеров последовательностей из транс­
формированного набора данных:

>>>

Eor.·

example in ds_sequences.take(2):
рг:шt (-'Вход

(х):

',

repr(' '.join(char_array[example[O] .numpy()])))
р1:~пt('Цель

(у):',

repr(' '.join(char_array[example[l] .numpy()])))
[)ГJ.Л t.

()

(708)------

Глава
Вход

(х):

Цель

(у):

Вход

(х):

Цель

(у):

16.

Моделирование последовательных данных."

'ТНЕ МYSTERIOUS ISLAND ***\n\n\n\n\nProduced Ь'
' НЕ MYSTERI OUS I SLAND * * * \n \n \n \n \nProduced Ьу'

' Anthony Matonak, and Trevor Carlson\n\n\n\n'
'Anthony Matonak, and Trevor Carlson\n\n\n\n\n'

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

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

пакет будет содержать множество обучающих образцов:

>>> ВАТСН SIZE = 64
>>> BUFFER SIZE = 10000
>>> ds = ds_sequences.shuffle(BUFFER_SIZE) .batch(BATCH SIZE)
Построение модеnи на основе рекуррентной нейронной
сети дnя модеnирования языка на уровне симвоnов

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

функцию

build_model, которая определит модель RNN с применением
класса Sequential библиотеки Keras. Затем мы можем указать параметры
обучения и вызвать функцию build_ model для получения модели RNN:
>>> dE:1f build_model (vocab_size, emЬedding_dim, rnn_units):
model = tf.keras.Sequential([
tf.keras.layers.EmЬedding(vocab_size,

tf.keras.layers.LSTM(
rnn_units,
return_sequences=Trнe),

tf.keras.layers.Dense(vocab_size)
] )
retu:r.·п

model

>>> ## Установка параметров обучения
>>> charset_size = leп(char_array)
>>> emЬedding_dim = 256
>>> rnn units = 512
>>> tf.random.set_seed(l)
>>> model = build_model (

-··-- [709) ---·--·--------

emЬedding_dim),

fлава

16. Моделирование последовательнЬ1х даннЬlх ...

vocab_size=charset_size ,
emЬedding_dim=emЬedding_dim,

rnn_units=rnn_units)

>>> model. summary ()
Model: "sequential"
Output Shape

Param #

(None, None, 256)

20480

lstm (LSTM)

(None, None, 512)

1574912

dense (Dense)

(None, None, 80)

41040

Layer (type)
emЬedding

(EmЬedding)

Total params: 1,636,432
TrainaЫe params: 1,636,432
Non-trainaЫe params: О

LSTM в этой модели имеет форму выхода
(None, None, 512), т.е. выход слоя LSTM получает ранг 3. Первое изме­
длину последовательнос­
рение обозначает количество пакетов, второе наличия выхода с ран­
Причина
число скрытых элементов.
ти и третье гом 3 из слоя LSTM связана с тем, что при его определении мы указали
return_ sequences=True. Полносвязный слой (Dense) получает выход из
ячейки LSTM и рассчитывает логиты для каждого элемента выходной по­
Обратите внимание, что слой

следовательности. В итоге финальный выход модели также будет тензором
ранга

3.

Вдобавок мы указали

acti vation=None

для последнего полносвязного

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

>>> model.compile(
optimizer='adam',
loss=tf.keras.losses.Spa rseCategoricalCrossent ropy(
from_ logi ts='l'п1e
) )

>>> model.fit(ds, epochs=20)

---[710)--------- --------------

Глава

16.

Моделирование последовательных данных ...

Epoch 1/20
424/424 [==============================] - 80s 189ms/step - loss:

2.3437
Epoch 2/20
424/424 [==============================] - 79s 187rns/step - loss:
1.7654
Epoch 20/20
424/424 [==============================] - 79s 187ms/step - loss:
1.0478
Теперь мы можем оценить модель в плане порождения нового текста, на­
чав с заданной короткой строки. В следующем подразделе мы определим

функцию для оценки обученной модели.

Стадия оценки

-

порождение новых отрывков текста

Модель

RNN, обученная в предыдущем подразделе, возвращает логи­
ты размера 80 для каждого уникального символа. Посредством функции
softmax эти логиты могут быть легко преобразованы в вероятности того,
что определенный символ встретится следующим. Чтобы спрогнозировать

следующий символ в последовательности, мы можем просто выбрать эле­
мент с максимальным значением логита, что эквивалентно выбору символа
с наивысшей вероятностью. Однако вместо постоянного выбора символа с
наивысшей вероятностью мы хотим делать (случайную) выборку из выхо­

дов, иначе модель будет всегда порождать один и тот же текст. Библиотека

TensorFlow

предоставляет функцию

tf. random. categorical (),

кото­

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

1, 2]

с входными логитами

[1, 1, 1]:
>>> tf. random. set_seed (1)
>>> logits = [[1.0, 1.0, 1.0]]
>>> print( 'Вероятности:', tf.math.softmax(logits) .nurnpy() [0])
Вероятности: [0.33333334 0.33333334 0.33333334]
>>> samples = tf. random. categorical (
logits=logits, nurn_samples=lO)
tf.print (samples.nurnpy())
array( [[О, О, 1, 2, О, О, О, О, 1, О]])

»>

--[711)-------- - - - - -

Глава

16.

Моделирование последовательных данных."

Как видите, с заданными логитами категории имеют одинаковые вероят­
ности (т.е. являются равновероятными). Следовательно, если мы применяем
большой размер выборки (количество образцов ~ оо ), то ожидаем, что чис­

ло вхождений каждой категории достигнет
изменим логиты на
ний категории

2

[1, 1, 3),

::::: 1/3

размера выборки. Если мы

тогда будем ожидать встретить больше вхожде­

(когда из этого распределения извлекается очень большое

количество образцов):

>>> tf. random. set_seed (1)
>>> logits = [(1.0, 1.0, 3.0)]
>>> р.гiл t ('Вероятности: ', tf. math. softmax (logi ts) . numpy ()

[О]

)

[0.10650698 0.10650698 0.78698605)
>>> samples = tf.random.categorical(
logits=logits, num_samples=lO)
>>> tf.print(samples.numpy())
array( [ [2, О, 2, 2, 2, О, 1, 2, 2, О]])
Вероятности:

С использованием

tf. random. categorical

мы можем генериро­

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

sample (), которая будет получать короткую стартовую строку
starting_str и порождать новую строку generated_str, первоначаль­
но установленную во входную строку. Затем из конца generated_str
берется строка размером max input length и кодируется в виде после­
довательности целых чисел encoded _ input, которая передается модели
RNN для расчета логитов. Обратите внимание, что выход из модели RNN
функцию

является последовательностью логитов с такой же длиной, как у входной

return_ sequences=True для
последнего рекуррентного слоя модели RNN. Таким образом, каждый эле­
мент в выходе модели RNN представляет логиты (здесь вектор размера 80,
последовательности, поскольку мы указали

равного общему количеству символов) для следующего символа после про­
смотра входной последовательности моделью.

Мы используем только последний элемент выхода logi ts (т.е. o>> 1jef sample(model, starting_str,

len_generated_text=500,
max_input_length=40,
scale_factor=l.0):
encoded_input = [char2int [s] f·or s in starting_str]
encoded_input = tf.reshape(encoded_input, (1, -1))
generated_str = starting_str
model.reset_states()
f:or i in r·aнge (len_generated_text):
logits = model(encoded_input)
logits = tf. squeeze (logits, 0)
scaled_logits = logits * scale_factor
new_char_indx = tf.random.categorical(
scaled_logits, num_samples=l)
new char indx = tf.squeeze(new char_indx) [-1) .numpy()
generated_str += str(char_array[new_char_indx])
new_char_indx = tf .expand_dims ( [new_char_indx], 0)
encoded_input = tf.concat(
[encoded_input, new_char_indx],
axis=l)
encoded_input = encoded_input [:, -max_input_length:]
retur11 generated_str

[713) ------------------------

[лава

16.

Моделирование последовательных данных".

Давайте сгенерируем какой-нибудь новый текст:

>>> tf.random.set_seed(l)
>>> print(sample(model, starting_str='The island'))
The island is рrоЬаЫе that the view of the vegetaЫe discharge on
unexplainst felt, а thore, did not
refrain it existing to the greatest
possing bain and production, for а hundred streamled
estaЫished some branches of the
holizontal direction. It was there is all ready, from one things
from contention of the Pacific
acid, and
according to an occurry so
summ on the rooms. When numЬered the prud Spilett received an
exceppering from their head, and Ьу went inhaЬited.
"What are the most abundance

а

report

Как видите, модель порождает главным образом корректные слова и в
ряде случаев предложения частично осмысленны. Вы можете дополнитель­

но подстраивать параметры обучения, такие как длина входных последова­
тельностей при обучении, архитектура модели и параметры выборки (вроде

max_ input length).
Кроме того, чтобы контролировать предсказуемость сгенерированных

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

лью

RNN логиты допускается
tf. random. categorical ()

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

можно интерпретировать как инверсию температуры в физике. Более вы­
сокая температура дает в результате большую случайность по сравнению с
более предсказуемым поведением при низких температурах. Благодаря мас­
штабированию логитов с коэффициентом а

функцией

softmax,

< 1

вероятности, вычисляемые

становятся более равномерными, как показано в следу­

ющем коде:

»> logits = np.array( [ [1.0, 1.0, 3.0]])
>>>

рrint('ВеRоятности перед масштабированием:

tf.math.softmax(logits) .numpy() [0])

>>> print ('Вероятности после масштабирования с коэффициентом
tf.math.softmax(0.5*logits) .numpy() [0])

[714]

О.

5: ',

Глава 16. Моделирование последовательных даннь1х."

>>> pri11t ('Вероятности

после масштабирования с коэффициентом О .1:

',

tf.math.softmax(O.l*log its) .numpy() [0])
Вероятности перед масштабированием:

[0.10650698 0.10650698 0.78698604]

-

Вероятности после масштабирования с коэффициентом

0.5:

[0.21194156 0.21194156 0.57611688]
Вероятности после масштабирования с коэффициентом

0.1:

[0.31042377 0.31042377 0.37915245]

= 0.1 дает ве­
мы можем
теперь
А
0.38].
0.31,
[0.31,
роятности, близкие к равномерным
сравнить порожденный текст при коэффициентах а.= 2.0 и а.= 0.5:
Как видите, масштабирование логитов с коэффициентом а.

-



а.=

2.0

~больше предсказуемости:

>>> tf.random.set_seed(l)
>>> pr.int (sample (model, starting_str=' The island',
scale_factor=2.0))
The island spoke of heavy torn into the island

froш

the sea.

The noise of the inhabitants of the island was to Ье feared that
the colonists had соте а proj ect wi th а straight Ье put to the
bank of the island was the surface of the lake and sulphuric
acid, and several supply of her animals. The first stranger
carried а sort of accessiЫe to break these screen barrels to
their distance from the palisade.
"The first huntil, " said the reporter, "and his companions the
reporter extended to build а few days а



а.

= 0.5

~ больше случайности:

>>> tf.random.set_seed(l)
>>> pr.·int (sample (model, starting_str=' The island',
scale_factor=0.5))
The island
glissed in
ascercicedly useful? loigeh, Cyrus,
Spileots," henseporvemented
House to а left
the centlic moment. Tonsense craw.
Pencrular ed/ of times," tading had coflently often above anzand?"
"Wat; " then: у. "

(715]

Глава

16.

Моделирование последовательных данных ...

Ardivify he acpearly, howcovered--he hassime; however,
fenquests hen adgents!' .? Let us Neg eqiAl?.
GencNal, my surved thirtyin" ou; is Harding; treuths. Osew
apartarned. "N,
the poltuge of about-but durired with purteg.
Chappes wason !
Fears," returned Spilett; "if
tear 8t trung

уои

Результат показывает, что масштабирование логитов с коэффициентом
а

= 0.5

(увеличение температуры) приводит к порождению более случайно­

го текста. Существует компромисс между новизной порожденного текста и
его корректностью.

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

тельность"

(sequence-to-sequence -

seq2seq).

Хотя рассмотренный пример

не особенно полезен сам по себе, легко придумать несколько практичных
приложений для моделей такого типа; скажем, похожую модель

RNN

можно

обучить в качестве чатбота для оказания помощи пользователям с простыми
запросами.

Понимание языка с помощью модели "Преобразователь"
В главе мы решили две задачи моделирования с применением нейронных

сетей на основе

RNN.

Тем не менее, недавно появилась новая архитектура,

которая превзошла модели

ботки естественного языка

seq2seq,
(NLP).

основанные на

RNN,

в ряде задач обра­

Архитектура называется "Преобразователь" (J'mn.~/i11·111e1} Она способ­
на моделировать глобальные зависимости между входными и выходными
последовательностями и была представлена в

2017

году А шишом Васвани

и др. в статье

"Attention Is All You Need" (Внимание - это все, что нужно),
свободно доступной по ссылке http: / /papers. nips. cc/paper /7181attention-is-all-you-need. Архитектура "Преобразователь" основана
на концепции, которая называется в11и.лю11ием (attentio11), точнее говоря, на
меха11изме са.wов11имания (.se(l~attentim1 meclumism). Давайте возьмем зада­
чу смыслового анализа, которая была раскрыта ранее в этой главе. В таком
случае использование механизма внимания означает, что у нашей модели

появится возможность научиться концентрироваться на частях входной пос­

ледовательности, которые более существенны для отношения.

------------(716)--------

Глава

16.

Моделирование последовательных данных ...

Механизм самовнимания
В настоящем разделе мы объясним механизм самовнимаиия и покажем,
как он помогает модели "Преобразователь" сфокусироваться на важных час­

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

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

Базовая версия самовнимания
Для представления базовой идеи, лежащей в основе самовнимания, да­
вайте предположим, что у нас есть входная последовательность с длиной

т,

х

(0)

, х(1) , ... , х(Т) ,

а также выходная последовательность, о

(0)

,

о

(1)

, ... ,

о

(Т)

.

Каждый элемент в этих последовательностях, x(t) и o(t), является вектором
размера d (т.е. x(t) Е Rd). Цель самовнимания для задачи seq2seq - смоде­
лировать зависимости каждого элемента в выходной последовательности от

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

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

softmax.

В-третьих, мы за­

действуем эти веса в сочетании с соответствующими элементами последо­
вательности для расчета значения внимания.

Выражаясь более формально, выходом самовнимания будет взвешенная
сумма всей входной последовательности. Например, для i-того входного

элемента соответствующее выходное значение вычисляется следующим об­
разом:

т

o(i)

х,

Входная

)DDDD

x<
0

x(l)DDDD

softmax ([х
о

Tools

Help

Run all

X/Ctrl•F9

Run Ьefore

X/Ctrl+F8

Run the focused cell
Run selection

X/Ctrl+Enter
X/Ctrl+Sh1ft+Eriter

Run after

X/Ctrl+F10

Rese1 all runtimes ...

3-

G

ange runtimetyp::::::>
Manage sessions

Рис.

17.6.

Выбор ГП в качестве аппаратного ускорителя

На последнем шаге нужно лишь установить пакеты

Python,

которые по­

надобятся в главе. Среда
тами, куда входят

Colab Notebooks поступает с определенными паке­
NumPy, SciPy и последняя стабильная версия TensorFlow.

Тем не менее, на момент написания главы последней стабильной версией

в

Google Colab

была

TensorFlow 1. 15 .0,

Следовательно, необходимо установить

а нас интересует

TensorFlow 2.0

TensorFlow 2.0.

с поддержкой ГП,

выполнив показанную ниже команду в новой ячейке тетради :

! pip install -q tensorflow-gpu=2. О . О

- · - --- ----- - --

(736)

Глава

(Ячейка тетради

Порождающие состязательные сети для синтеза новых данных

17.

Jupyter Notebook,

начинающаяся с восклицательного знака,

интерпретируется как команда оболочки

Linux.)

Теперь можно протестировать установку и удостовериться в доступности

ГП С ПОМОЩЬЮ такого кода:

>>> irnport tensorflow as tf
>>> print(tf. version )
1

2. о.о 1

>>> print ("Доступность
Доступность ГП:

ГП:

", tf. test. is_gpu_availaЬle ())

True

>» if tf. test. is_gpu_availaЫe ():
device_name

=

tf.test.gpu_device_name()

=

'/CPU:O'

... else:

device_name

>>> print(device_name)

'/device:GPU:O'
Кроме того, если вы хотите сохранить модель на своем

Google

Диске,

переместить или загрузить другие файлы, то должны смонтировать

Google

Диск, для чего выполнить приведенный далее код в новой ячейке тетради:

>>>
google.colab
drive
>>> drive.mount('/content/drive/')
Результатом будет ссылка для аутентификации доступа к вашему

Google

Диску. После следования инструкциям по аутентификации появится код,
который вы должны скопировать и вставить в указанное поле ввода ниже

только что выполненной ячейки. Затем ваш
и станет доступным по ссылке

Google Диск будет
/content/drive/My Drive.

смонтирован

Реаnизация сетей генератора и дискриминатора
Мы начнем реализацию нашей первой модели

GAN

с генератора и диск­

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

17. 7).

Так выглядит исходная версия
на простую сеть

GAN,

на которую мы будем ссылаться как

GAN.

В показанной на рис.

17.7

модели для каждого скрытого слоя мы приме­

няем функцию активации на основе

ReLU

с утечкой.

------------(737)------

Глава

17.

Порождающие состязательны е сети для синтеза новых д анных

Генератор:

784
100 Полносвязный

Полносвязный

20
Вход

z

~

+

ReLU

~

с утечкой

Дискриминатор:

+

Изменение
формы

гиперболический

__..,.

тангенс

28х28

0

784

28х28

Вход

~
или

Полносвязный
Изменение
формы

+
ReLU

__..,.

с утечкой

П сигмо:дальная О

U

0
Рис.

Использование

100 Полносвязный

ReLU

17. 7.

Простая .wодел u

Вероятность

GAN

дает в результате разреженные градиенты , что мо­

жет оказаться неподходящим, когда желательно иметь градиенты для полно­

го диапазона входных значений. В сети дискриминатора за каждым скрытым

слоем следует слой отключения. Вдобавок выходного слой в сети генератора
задействует функцию активации в виде гиперболического тангенса
(Для сети генератора рекомендуется применять функцию

(t a nh).
активации t a n h,

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

[738) - - - - --- - - -

Глава

17.

Порождающие состязательные сети для синтеза новых данных

r~ Функция активации в виде выпрямленного

~ линейного элемента (ReLU) с утечкой

На

заметку! В главе

13 были раскрыты различные нелинейные функции актива-

ции, которые могут применяться в нейросетевой модели. Вспомни­

те, что функция активации

ReLU

определялась как ф(z)

= тах(О, z),

подавляя отрицательные входы (предварительную активацию) за

счет их установки в нули. Как следствие, использование функции
активации

ReLU

может привести к получению разреженных гради­

ентов во время обратного распространения. Разреженные градиенты
не всегда вредны и могут даже приносить пользу моделям для клас­

сификации. Однако в приложениях вроде сетей

GAN

иногда полезно

получать градиенты для полного диапазона входных значений, чего

мы можем достичь, внеся в функцию

ReLU

небольшое изменение,

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

ReLU с утечкой (leaky Яеl,С). Короче
виде ReLU с утечкой также допускает

ReLU

также известна как

говоря, функция активации в
ненулевые градиенты для от­

рицательных входов и в результате делает сети более выразительны­
ми в целом.

Функция активации в виде

ReLU

с утечкой определяется следую­

щим образом:

ф(z)

tp(z)

z,

= {az

если

z ~О

в противном случае

Здесь а задает наклон для отрицательных входов (предварительной
активации).

(739)------

Глава

17.

Порождающие состязательные сети для синтеза новых данных

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

Sequential

библиотеки

Keras

и доба­

вим слои, как было описано ранее. Вот как выглядит код:

>>>
>>>
>>>
>>>

import tensorflow a.s tf

import tensorflow_datasets as tfds
impo.rt numpy as np
impo:r·t matplotlib.pyplot as plt

>>> ## определение функции для генератора:
>>> def" make_generator_network (
num_hidden_layers=l,
num_hidden_units=lOO,
num_output_units=784):
model = tf. keras. Sequential ()
fo:r: i in range (num_hidden_layers):
model.add(
tf.keras.layers.Dense(
units=num_hidden_units, use_Ьias=J''э.1se))
model.add(tf.keras.layer s.LeakyReLU())
model.add(
tf.keras.layers.Dense(
units=num_output_units, activation='tanh'))
ret1"1rn model

>>> ## определение функции для дискриминатора:
>>> def make_discriminator_netw ork (
num_hidden_layers=l,
num_hidden_units=lOO,
num_output_units=l):
model = tf.keras.Sequential()
for i in raнge (num_hidden_layers):
model.add(
tf.keras.layers.Dense(un its=num_hidden units))
model.add(tf.keras.layer s.LeakyReLU())
model.add(tf.keras.laye rs.Dropout(rate=0.5))
model.add(
tf.keras.layers.Dense(
units=num_output_units,
retнrn model
· - - - - - - - - - [740] - -

activation=Noпe))

[лава

17.

Порождающие состязательные сети для синтеза новых данных

Затем мы укажем настройки обучения. Как вы помните из предшеству­

ющих глав, размер изображения в наборе данных

MNIST

составляет 28х28

пикселей. (Имеется лишь один цветовой канал, потому что МNIST содержит

изображения в оттенках серого.) Мы дополнительно укажем размер входно­
го вектора

z,

равный

20,

а для инициализации весов модели будем приме­

нять случайное равномерное распределение. Поскольку мы реализуем очень

простую модель

GAN

только в целях иллюстрации и используем полносвяз­

ные слои, в каждой сети будет присутствовать единственный скрытый слой
с сотней элементов. В следующем коде мы определим и инициализируем

две сети и выведем итоговую информацию о них:

>>> image_size = (28, 28)
>>> z size = 20
# 'uniform'
>>>mode z = 'uniform'
>>> gen_hidden_layers = 1
>>> gen_hidden_size = 100
>>> disc_hidden_layers = 1
>>> disc hidden size = 100

или

'normal'

>>> tf.random.set_seed(l)
>>> gen_model = make_generator_network(
num_hidden_layers=gen_hidden_layers,
num_hidden_units=gen_hidden_size,
num_output_units=np.prod(image_size))
>>> gen_model.build(input_shape=(Noвe, z_size))
>>> gen_model.summary()
Model: "sequential"
Layer (type)

Output Shape

Param #

dense (Dense)

multiple

2000

leaky_re_lu (LeakyReLU)

multiple

о

dense_ 1 ( Dense)

multiple

79184

Total params: 81,184
TrainaЫe params: 81,184
Non-trainaЫe params: О

- - - - - - - - - - - - · · .. [7411

·----- ----------------------

Глава

17.

Порождающие состязательные сети для синтеза новых данных

>>> disc_model = make_discriminator_network(
num_hidden_layers=disc_hidden_layers,
num_hidden_units=disc_hidden_size)
>>> disc_model.build(input_shape=(Norie, np.prod(image_size)))
>>> disc_model. summary ()
Model: "sequential_l"
Layer (type)

Output Shape

Param #

dense _ 2 (Dense)

mul tiple

78500

leaky_re_lu_l (LeakyReLU)

multiple

о

dropout (Dropout)

multiple

о

dense 3 (Dense)

multiple

101

Total params: 78,601
TrainaЫe params: 78,601
Non-trainaЫe params: О

Опредеnение обучающеrо набора данных
Далее мы загрузим набор данных

MNIST

и применим необходимые шаги

предварительной обработки. Так как выходной слой генератора использует

функцию активации

tanh,

будут попадать в диапазон

значения пикселей синтезированных изображений

(-1, 1).

Тем не менее, входные пиксел и изображе­

MNIST находятся внутри диапазона [О, 255) (с типом данных tf. uint8
TensorF\ow). Таким образом, в рамках шагов предварительной подготов­
ки мы будем использовать функцию tf. image. convert image dtype
для преобразования типа d t ур е тензоров входных изображений из
tf. uint8 в tf. float32. В результате помимо изменения типа dtype вы­
ний
из

зов этой функции также изменит диапазон интенсивностей входных точек на
[О, \).Затем мы можем их масштабировать, умножив на

сдвинуть на-1,

так что интенсивности пикселей попадут в диапазон

Кроме того, мы

также создадим случайный вектор

распределении (равномерном

z, основанный

(' uni form')

2, и
[-1, 1].

на желательном случайном

или нормальном

(' normal '),

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

- - - - - [742)----------------------

Глава

17.

Порождающие состязательные сети для синтеза новых данных

>>> mnist_Ыdr = tfds.builder('mnist')
>>> mnist_Ыdr.download_and_prepare()
>>> mnist = mnist_Ыdr.as_dataset(shuffle_files=False)
>>> cleE preprocess

(ех, mode='uniform'):
image = ех [ ' image' ]
image = tf.image.convert_image_dtype(image, tf.float32)
image = tf.reshape(image, [-1))
image = image*2 - 1.0
i..f mode == 'uniform':
input_z = tf.random.uniform(
shape=(z_size,), minval=-1.0, maxval=l.0)
elif' mode == 'normal' :
input_z = tf. random.normal (shape= (z_size,))
retur.n input_z, image

>>> mnist trainset = mnist [ 'train']
>>> mnist trainset = mnist_trainset. map (preprocess)
Обратите внимание, что здесь мы возвращаем входной вектор



изоб­

ражение для удобного извлечения обучающих данных во время подгонки
модели. Однако это не подразумевает, что вектор
ражением
вектор

-

z как-то

связан с изоб­

входное изображение поступает из набора данных, тогда как

z генерируется

случайным образом. На каждой итерации обучения

случайно сгенерированный вектор

z представляет

вход, который генератор

получает для синтеза нового изображения, а изображения (настоящие и син­
тезированные) являются входами дискриминатора.

Давайте проинспектируем созданный объект набора данных. В показан­
ном ниже коде мы будем брать один пакет образцов и выводить формы мас­

сивов этой выборки входных векторов и изображений. Вдобавок для осмыс­
ления общего потока данных модели

GAN

мы будем обрабатывать прямой

проход для генератора и дискриминатора.

Первым делом мы подадим пакет входных векторов

чим его выход

g output.

и полу­

Он будет пакетом фальшивых образцов, который

подается дискриминатору с

d _ 1 og i t s _ f

z генератору

целью получения логитов для данного пакета,

а ke. Кроме того, обработанные изображения, которые мы из­

влекли из объекта набора данных, будут подаваться дискриминатору, что
приведет к получению логитов для настоящих образцов,
Вот необходимый код:

- - - - - - - - - - - - - - - [743]

d _ logi ts real.

Глава

17.

Порождающие состязательные сети для синтеза новых данных

>>>
>>>
>>>
>>>

mnist_trainset = mnist_trainset.batch(32, drop_remainder=T.eue)
input_z, input_real = next(1ter(mnist_trainset))
print('input-z -- форма:
', input_z.shape)
pri11t ( 'input-real -- форма:', input_real. shape)
input-z -- форма:
(32, 20)
input-real -- форма: (32, 784)

>>> g_output = gen_model (input_z)
>>> print:( 'Выход G -- форма:', g_output.shape)
Выход G -- форма: (32, 784)
>>> d_logits_real = disc_model(input_real)
>>> d_logits_fake = disc_model(g_output)

»> print ('Дискриминатор
»> print;( 'Дискриминатор

(настоящие)

Дискриминатор

(настоящие)

Дискриминатор

(фальшивые)

Два логита,

---

(фальшивые)

d_logits_fake

--и

форма:
форма:

форма:',
форма:',

d_logits_real. shape)
d_logits_fake.shape)

(32, 1)
(32, 1)

d_logits_real,

будут применяться для

вычисления функций потерь при обучении модели.

Обучение модели

GAN

В качестве следующего шага мы создадим экземпляр класса

Crossentropy,

Binary

который будет служить функцией потерь, и используем ее

для расчета потерь генератора и дискриминатора, ассоциированных с толь­

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

ванные логиты для сгенерированных изображений,

d _ logi ts _ fake.

В по­

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

d _ logi ts _ fake, и потеря
основанная на d_logits_real.

при обнаружении

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

tf. zeros

()(или

tf. zeros _ like () ).

Аналогично мы можем сгенериро­

вать достоверные метки для настоящих изображений с помощью функции

t f. one s ( ) (или t f . one s _ 1 i ke ( ) ),

которая создает вектор единиц:

>>> loss _ fn = tf. keras. losses. BinaryCrossentropy ( from_logi ts='I'rue)
>>>

##Потеря для генератора

[744)-----------

Глава

17.

Порождающие состязательные сети для синтеза новых данных

>>> g_labels_real = tf.ones_like(d_logits_fake)
>>> g_loss = loss_fn(y_true=g_labels_real, y_pred=d_logits_fake)
>>> print( 'Потеря генератора: {: .4f)' .format(g_loss))
Потеря генератора:

0.7505

>>> ## Потеря для дискриминатора
>>> d_labels_real = tf.ones_like(d_logits real)
>>> d_labels_fake = tf.zeros_like(d_logits_fake)
>>> d_loss_real = loss_fn(y_true=d_labels_real,

y_pred=d_logits_real)
>>> d_loss_fake = loss_fn(y_true=d_labels_fake,
>>>

y_pred=d_logits_fake)
{:. 4f} Фальшивые {:. 4f}'
.format(d_loss_real.numpy(), d_loss_fake.numpy()))
дискриминатора: Настоящие 1.3683 Фальшивые 0.6434

priп

Потеря

t; ('Потеря дискриминатора: Настоящие

В предыдущем примере кода показан пошаговый расчет различных чле­

нов потери в целях понимания общей концепции, лежащей в основе обуче­
ния модели

GAN.

В приведенномниже коде будет настроена модель

реализован цикл обучения, где мы включим эти вычисления в цикл
Вдобавок мы будем применять класс

tf. GradientTape ()

GAN
for.

и

для расче­

та градиентов потерь относительно весов модели, а также оптимизировать
параметры генератора и дискриминатора, используя два отдельных оптими­

затора

Adam.

В коде вы увидите, что для переключения между обучением

генератора и дискриминатора в

TensorFlow

мы явно предоставляем парамет­

ры каждой сети и применяем градиенты каждой сети отдельно к соответ­
ствующему оптимизатору:

>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

i.rr!port time

100
num_epochs
batch size = 64
image_size = (28, 28)
z size = 20
mode z = 'uniforrn'
gen_hidden_layers = 1
gen_hidden_size = 100
disc_hidden_layers = 1
disc hidden size = 100

>>> tf.random.set_seed(l)
>>> np. random. seed ( 1)
>>> if mode z

'uniform':

- - - - - - "----[745]----"--------------

Глава 17. Порождающие состязательные сети для синтеза новых данных

fixed_ z = tf. random. uniform (
shape=(batch_size, z_size),
minval=-1, maxval=l)
>>> e1if mode z == 'normal' :
fixed _z = tf. random. normal (
shape=(batch_size, z_size))

>>> def create_samples (g_model, input_z):
g_output = g_model(input_z, training=False)
images = tf.reshape(g_output, (batch_size, *image_size))
return (images+l)/2.0
>>> ## Настройка набора данных
>>> mnist _ trainset = mnist [ 'train']
>>> mnist_trainset = mnist_trainset.map(
lamЬda ех: preprocess(ex, mode=mode_z))
>>> mnist trainset = mnist_trainset.shuffle(lOOOO)
>>> mnist trainset = mnist_trainset.batch(
batch_size, drop_remainder=True)
>>> ## Настройка модели
>>> with tf.device(device_name):
gen model = make generator_network(
num_hidden_layers=gen_hidden_layers,
num_hidden_units=gen_hidden_size,
num_output_units=np.prod(image_size))
gen_model.build(input_shape=(Norie, z_size))
disc_model = make_discriminator_network(
num_hidden_layers=disc_hidden_layers,
num_hidden_units=disc_hidden_size)
disc_model.build(input_shape=(Norie, np.prod(image_size)))

>>>
>>>
>>>
>>>

## Функция потерь и оптимизаторы:
loss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=Trпe)
g_optimizer = tf.keras.optimizers.Adam()
d_optimizer = tf.keras.optimizers.Adam()

>>> all _ losses = []
>>> all_d_vals = []
>>> epoch_samples = []
>>> start_time-= time. time ()
>>> for epoch in range(l, num_epochs+l):
epoch_losses, epoch_d_vals = [], []

(746)------

Глава

17.

Порождающие состязательные сети для синтеза новых данных

for i, (input_z,input_real) in
##

enшnerate(mnist_trainset):

Расчет потери генератора

wi th tf. GradientTape () as g_tape:
g_output = gen_model(input_z)
d_logits_fake = disc_model(g_output,
training=True)
labels_real = tf.ones_like(d_logits_fake)
g_loss = loss_fn(y_true=labels_real,
y_pred=d_logits_fake)
## Расчет rрадиентов g_loss
g_grads = g_tape.gradient(g_loss,
gen_model.trainaЫe_variaЫes)

##

Оптимизация: применение rрадиентов

g_optimizer.apply_gradients(
grads_and_vars=zip(g_grads,
gen_model.trainaЫe_variaЬles))

##

Расчет потери дискриминатора

with tf.GradientTape() as d_tape:
d_logits_real = disc_model(input_real,
training=True)
d_labels_real = tf.ones_like(d_logits_real)
d_loss_real = loss_fn (
y_true=d_labels_real, y_pred=d_logits_real)
d_logits_fake = disc_model(g_output,
training=True)
d_labels_fake = tf.zeros_like(d_logits_fake)
d_loss_fake = loss_fn (
y_true=d_labels_fake, y_pred=d_logits_fake)
d loss = d loss real + d loss fake
## Расчет rрадиентов d_loss
d_grads = d_tape.gradient(d_loss,
disc_model.trainaЫe_variaЫes)

----------[747)-------------

Глава

17.

Порождающие состязательные сети для синтеза новых данных

##

Оптимизация: применение градиентов

d_optimizer.apply_gradients(
grads_and_vars=zip(d_grads,
disc_model.trainaЫe_variaЫes))

epoch_losses.append(
(g_loss.numpy(), d_loss.numpy(),
d_loss_real.numpy(), d_loss_fake.numpy()))
d_probs_real = tf.reduce_mean(
tf.sigmoid(d_logits_real))
d_probs _ f а ke = t f. reduce _ mean (
tf.sigmoid(d_logits_fake))
epoch_d_vals.append((d_probs_real.numpy(),
d_probs fake.numpy() ))
all_losses.append(epoch_losses)
all_d_vals.append(epoch_d_vals)
print (

{:03d} 1 ЕТ {: .2f} минут 1 Средние потери>>'
'G/D {:.4f}/{:.4f} [D-Real: {:.4f} D-Fake: {:.4f}]'
.format(
epoch, (time.time() - start_time)/60,
*list(np.mean(all_losses[-1], axis=O))))
epoch_samples.append(
create_samples(gen_model, fixed_z) .numpy())
'Эпоха

001
ЕТ 0.88 минут
Средние потери>> G/D 2.9594/0.2843
[D-Real: 0.0306 D-Fake: 0.2537]
Эпоха 002
ЕТ 1.77 минут
Средние потери>> G/D 5.2096/0.3193
[D-Real: 0.1002 D-Fake: 0.2191]
Эпоха ...
Эпоха 100
ЕТ 88.25 минут
Средние потери>> G/D 0.8909/1.3262
[D-Real: 0.6655 D-Fake: 0.6607]
Эпоха

1

1

1

1

1

1

В случае использования ГП процесс обучения, реализованный в преды­
дущем блоке кода, должен завершиться в

Google Colab

менее чем за час.

(При наличии персонального компьютера с современным ЦП и ГП процесс

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

-----------~[748]--

Глава

17.

Порождающие состязательные сети для синтеза новых данных

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

0.5,

т.е. дискриминатор не способен с уверенностью проводить различие

между настоящими и фальшивыми изображениями:

>>> impo.rt i tertools
>>> fig = plt.figure(figsize=(16, 6))
>>> ##

Вычерчивание графика потерь

fig.add_subplot(l, 2, 1)
>>> g_losses = [item[O] for item in itertools.chain(*all_losses)]
>>> d_losses = [item[l)/2.0 for item in itertools.chain(
*all_losses)]
>>> plt.plot(g_losses, lаЬеl='Потеря генератора', alpha=0.95)
>>> plt.plot(d_losses, lаЬеl='Потеря дискриминатора', alpha=0.95)
>>> plt.legend(fontsize=20)
>>> ах. set_xlabel ('Итерация', size=15)
>>> ax.set_ylabel ('Потеря', size=15)
>>>ах=

>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

epochs = np.arange (1, 101)
epoch2iter = lamЬda е: e*len (all_losses [-1))
epoch_ticks = [1, 20, 40, 60, 80, 100)
newpos = [epoch2iter (е) for е in epoch_ticks]
ах2 = ах. twiny ()
ax2.set_xticks(newpos)
ax2.set_xticklabels(epoch_ticks)
ax2.xaxis.set_ticks_position('bottom')
ax2.xaxis.set_label_position('bottom')
ах2. spines [ 'bottom' ] . set _posi tion ( ( 'outward' , 60) )
ax2.set_xlabel('Эпoxa', size=15)
ax2.set_xlim(ax.get_xlim())
ах. tick_params (axis='both', which='major', labelsize=15)
ax2.tick_params(axis='both', which='major', labelsize=15)

>>> ##

Вычерчивание графика выходов дискриминатора

fig.add_subplot(l, 2, 2)
>>> d_vals_real = [item[O] for item in itertools.chain(*all_d_vals)]
»> d_vals_fake = [item[l] for item in itertools.chain(*all_d_vals)]
>>> plt.plot(d_vals_real, alpha=0.75,
label=r'Hacт.: $D(\mathbf{x})$')
>>> plt.plot(d_vals_fake, alpha=0.75,
lаЬеl=r'Фальш.: $D(G(\mathbf{z}))$')

>>>ах=

-----------[749)-----------

Глава

17.

Порождающие состязательные сети для синтеза новых данных

>>> plt.legend(fontsize=20)
>>> ах.sеt_хlаЬеl('Итерация', size=15)
>>> ах. set_ylabel ('Выход дискриминатора', size=15)
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>

ах2 = ах. twiny ()
ax2.set_xticks(newpos)
ax2.set_xticklabels(epoch_ticks)
ax2.xaxis.set_ticks_position('bottom')
ax2.xaxis.set_label__position('bottom')
ах2. spines [ 'bottom'] . set __posi tion ( ( 'outward', 60) )
ax2.set_xlabel('Эпoxa', size=15)
ax2.set_xlim(ax.get_xlim())
ax.tick__params(axis='both', which='major', labelsize=15)
ax2.tick__params(axis='both', which='major', labelsize=15)
plt . show ()

Результаты показаны на рис.

12 .

-

-

17.8.

Потеря дискриминатора

10 1

1.0·

Потеря генератора

..."'g.

Наст.:

D(x)

Фальш . :

D(G(z))

0.8 ·

"'

:z:

~ 0.6 •
:s:
Q.

><

<



а:1

о

20000

40000

60000

80000

0.2 .

о

20000

Итерация

Рис.

17.8.

40000

60000

80000

Итерация

Графики потерь .~енератора и дио.риминатора
и выходов дискри.:иинатора

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

дальную функцию, перед расчетом средних для каждого пакета.
На графике выходов дискриминатора (см. рис.

17.8)

можно заметить, что

на ранних стадиях обучения дискриминатор смог быстро научиться доволь­

но точно отличать настоящие образцы от фальшивых образцов, т.е. фаль­
шивые образцы имеют вероятности, близкие к О, а настоящие образцы

(750]-·--

-

-

Глава

вероятности, близкие к

17.

1.

Порождающие состязательные сети для синтеза новых данных

Причина в том, что фальшивые образцы не имели

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

вых образцов, близких к

0.5.

Кроме того, мы также можем посмотреть, каким образом выходы гене­
ратора (синтезированные изображения) изменяются во время обучения.

После каждой эпохи мы генерировали ряд образцов, вызывая функцию

create samples (),

и сохраняли их в списке

Python.

В следующем коде

мы визуализируем некоторые изображения, выпускаемые генератором, для
избранных эпох:

>>> selected_epochs = [1, 2, 4, 10, 50, 100]
>>> fig = plt.figure(figsize=(lO, 14))
>>> f:'or i, е in enumerate (selected_epochs):
for j in .range (5):
ах= fig.add_subplot(б,

5, i*5+j+l)

ax.set_xticks([])
ax.set_yticks([])
if j == о:
ax.text(
-О.Об, 0.5, 'Эпоха{}' .format(e),
rotation=90, size=18, color='red',
horizontalalignment='right',.
verticalalignment='center',
transform=ax.transAxes)
image = epoch_samples[e-1] (j]
ax.imshow(image, cmap='gray_r')

>>> plt.show()
Синтезированные генератором изображения представлены на рис.

На рис.

17.9

17.9.

несложно заметить, что по мере продвижения процесса обу­

чения сеть генератора производила все более и более реалистичные изоб­

ражения. Тем не менее, даже после

100

эпох выпущенные изображения

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

MNIST.

---[751)-

Глава

17.

Порождающи е состязательные сети для синт еза новых данных

,..
111

)(

о
с

(1)

N
111
)(

о
с

(1)

'o:t
111

)(

о
с

(1)

Q

111

)(
о
с

(1)

о
1()

111

)(
о
с

(1)

о

Q

111
)(
о
с

(1)

Рис.

17.9.

-~-Ьlj
[jJ [1] [f] [1 [j]
:.

.

·•.

.

'

·•.

'

·~

~l2J~[!!Ш

[]] [QJ [_f] ~ [!]
ШJ~[Ж]~~
~ШC0Cl[f]

Подборка изображений, выпущ енных ;>енератором в разных э поха'С

В этом разделе мы спроектировали очень простую модель

GAN

с одиноч­

ным полносвязным скрытым слоем в генераторе и дискриминаторе. После
обучения модели

GAN

на наборе данных

MNIST

мы сумели добиться мно­

гообещающих, хотя пока не удовлетворительных результатов с новыми ру­
кописными цифрами. Как объяснялось в главе

15,

архитектуры нейронных

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

GAN

сверточных слоев для работы с

данными изображений может улучшить результат. В следующем разделе
мы реализуем глубокую сверточную порождающую состязательную сеть
(с/сер c01н1 0/11 ti o11 al с; лN

···-

OC (~ A i\), в которой сверточные слои применя­

ются как для сети генератора, так и для сети дискриминатора.

-

-

- (752) -- -

- - - - - - - - -- - -

[лава

17.

Порождающие состязательные сети для синтеза новых данных

Повышение качества синтезированных

изображений с испоnьзованием сверточной
сети

GAN

и сети

GAN

Вассерштейна

В настоящем разделе мы реализуем сеть

DCGAN,

что позволит нам по­

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

реализуем порождающую состязательную сеть Вассерштейна (H 1asscгstci11

(;1\;\' --

~н;:\,'\).

В разделе будут раскрыты следующие методики:



транспонированная свертка;



пакетная нормализация;

•сеть



WGAN;

штраф градиента.

Сеть

2016 году Алеком Рэдфордом, Люком
Метцом и Сумитом Чинтала в их статье "Unsupervised representation learning
with deep convolutional generative adversarial networks" (Обучение представле­
DCGAN

была представлена в

нию без учителя с помощью глубоких сверточных порождающих состязатель­
ных сетей), которая свободно доступна по ссылке

pdf /1511. 06434 .pdf.

https://arxiv.org/

В указанной статье исследователи предложили

применять сверточные слои в обеих сетях, т.е. генератора и дискриминато­
ра. Начиная со случайного вектора

z,

сеть

DCGAN

зует полносвязный слой для проецирования



первым делом исполь­

новый вектор надлежаще­

го размера, чтобы его форму можно было изменить на пространственное

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

лательного размера выходного изображения.

Транспонированная свертка
В главе

15

вы узнали об операции свертки в одно- и двухмерных про­

странствах. В частности, мы взглянули, каким образом варианты для до­
полнения и страйдов изменяют выходные карты признаков. Наряду с тем,

что операция свертки обычно используется для понижения дискретизации

[753)-----

Глава

17.

Порождающие состязательные сети для синтеза новых данных

пространства признаков (например , путем установки страйда в

2

или за

счет добавления объединяющего слоя после сверточного слоя), операция
транспо11ированной свертки, как правило, применяется для повышения

дискретизации

(11p sampling)

пространства признаков.

Чтобы понять операцию транспонированной свертки, давайте проведем
простой мысленный эксперимент. Предположим, что у нас есть входная кар­

та признаков размера п х п. Затем мы применяем к такому входу п х п двумер­
ную операцию свертки с определенными параметрами дополнения и страй­

да, получая в результате выходную карту признаков размера т х т. Теперь
возникает вопрос: как с помощью другой операции свертки получить из вы­

ходной карты признаков тхт карту признаков первоначальной размерности

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

п х п, но не фактические значения матрицы. Именно это делает транспониро­
ванная свертка, как показано на рис.

17 .1 О.

~~
рованная

DDD D

..--------__,

DDП

-$Совет

ОП[J

17.1 О.

[J

Ll[JCJ··· D
ГJГlD LJ
..
DDLJ lJ

DD:D
D.Q.: D
UD D

DDCJ··· D
DD[l
.• ••• D
Рис.

----------.

свертка

Работа транспонироваююй свертки

Транспонированная свертка или обращение свертки
Транспонированную свертку также называют частичио-страйдин­
говой сверткой

(ji ·uctio ually st1·itletf com·ol11tio11).

В литературе по ГО

еще одним распространенным термином, который используется для

ссылки на транспонированную свертку, является обращение свертки
(tf cc 01н 1 ol 11tio n). Однако следует отметить, что обращение свертки

первоначально было определено как инверсия операции свертки
на карте признаков х с весовыми параметрами

(754) - --

w,

f

производящая кар-

Глава

17.

Порождающие состязательные сети для синтеза новых данных

ту признаков х', fw(x) = х'. Тогда функция обращения свертки /-1 мо­

жет быть определена как/;; 1 (f(х)) =х. Тем не менее, имейте в виду,
что транспонированная

свертка сконцентрирована на восстановле­

нии только размерности пространства признаков, но не фактических
значений.

Повышение дискретизации карт признаков с применением транспониро­

ванной свертки работает путем вставки нулей между элементами входных
карт признаков . На рис .

17 .11

демонстрируется пример применения транс­

понированной свертки к входу размера 4х4 со страйдом 2х2 и размером
ядра 2х2. Матрица размера 9х9 в центре показывает результаты после та­

кой вставки нулей во входную карту признаков. Затем выполнение обыкно­
венной свертки, использующей ядро 2х2 со страйдом

1,

дает в результате

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

2,

результатом которой будет

выходная карта признаков размера 4х4, что совпадает с первоначальным
размером входа .

Ядро
Вставка нулей между
входными элементами :

Вход

UDDD

ULJUU ...
ПDDD

DDDD
4Х4

9х9

Рис.

На рис .

17.11

17.JJ.

Пример работы тра11спо11uрова11ной свертки

показано, как транспонированная свертка работает в целом.

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



Guide to Convolution Arithmetic for Deep Learning"

- - · · - - - - - - -- -

[755) - ··-

(Руководство по ариф-

Глава

17.

Порождающие состязательные сети для синтеза новых данных

метике сверток для глубокого обучения), написанное Венсаном Дюмуленом

и Франческа Визином, которое свободно доступно по ссылке

ht tps: / /

arxiv.org/pdf/1603.07285.pdf.
Пакетная нормализация
Пакетная иор.мализация была представлена в

2015 году Сергеем Иоффе и
Кристианом Сегеди в статье "Batch Normalization: Accelerating Deep Network
Training Ьу Reducing lnternal Covariate Shift" (Пакетная нормализация: ус­
корение обучения глубоких сетей путем сокращения внутреннего ковариа­
ционного сдвига), свободно доступной по ссылке

pdf/1502. 03167 .pdf.

https://arxiv.org/

Одна из главных идей, лежащих в основе пакет­

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

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

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

где т

-

Z

с формой

количество образцов в пакете (т.е. размер пакета),

ность пространства карт признаков и с

-

[mxhxwxc],
hxw - размер­

количество каналов. Пакетную

нормализацию можно подытожить в виде трех шагов.

1.

Рассчитать среднее и стандартное отклонение общих входов для каж­
дого мини-пакета:

_

µв-

i

"

~
mxhxw 1,),
.k

zli.j,k"J

'

где µ 8 и а~ имеют размер с.
2.

Стандартизировать общие входы для всех образцов в пакете:

liJ

Z станд. -где е

-

zliJ_µ
ав

в

+t:

небольшое число для обеспечения численной устойчивости

(т.е. избегания деления на ноль).

3.

Масштабирование и сдвиг нормализованных общих входов с исполь­

зованием двух векторов обучаемых параметров,
[iJ
[i]

чество каналов): Аnредв.

= 7Zстанд. +р.

-- [ 7561 _,.",_

"/

и р, размера с (коли-

fлава

17.

Порождающие состязательные сети для синтеза новых данных

Процесс проиллюстрирован на рис.

17 .12.
Пакетная нормализация

Шаг

1:

Шаг

расчет статистических

2: стандартизация
общих входов

данных по пакету

(среднее и отклонение)

z и -- z[iJ - µ в
ста н д.

Мини - пакет

ив + е

Шаг З : масштабирование
и сдвиг

(i]

••


(i]

А r1рсдв. = /' Z станд. + Р

v

Обучаемые
параметры

w
Рис.

17.12.

Процесс пакетной норwш1изации

На первом шаге пакетной нормализации рассчитываются среднее

µ8

и

стандартное отклонение tJ8 мини-пакета. Оба они представляют собой век­
торы размера с (где с

количество каналов). Далее эти статистические дан­

-

ные будут задействованы на шаге

2

для масштабирования образцов внутри

каждого мини-пакета через нормализацию по z-оценке (стандартизацию),
["]

давая в результате стандартизированные общие входы, z:Танд.· Как следствие, такие общие входы центрированы относительно среднего и имеют

еди11ичную дисперсию, которая обычно является полезной характеристикой
для оптимизации на основе градиентного спуска. С другой стороны, неизмен­

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

N(O, 1),

который после сигмоидальной

активации а(х) дает в результате линейную область для значений, близких к О.
Следовательно, на шаге

3

обучаемые параметры р и

/'.

представляющие со­

бой векторы размера с (количество каналов), позволяют пакетной нормали­

зации управлять смещением и разбросом нормализованных признаков.
---· ~·- ·· (757) · ··· ·--·

Глава

17.

Порождающие состязательные сети для синтеза новых данных

Во время обучения вычисляются скользящее среднее

µ8

и скользящее от-

2

клонение и 8 , которые вместе с подстроенными параметрами р и"/ использу-

ются для нормализации испытательного образца (образцов) при оценке.

\\:~ Почему пакетная нормализация содействует оптимизации?

н~ Первоначально пакетная нормализация разрабатывалась для со­
заметку! кращения так называемого внутреннего ковариационного сдвига
(inlcnщi ипагi1111сс

sbl/f),

который определяется как изменения, ко­

торые происходят в распределении активаций слоя из-за обновлен­

ных параметров сети во время обучения.
Чтобы объяснить все на простом примере, возьмем фиксированный
пакет, который проходит через сеть в течение первой эпохи. Мы за­
писываем активации каждого слоя для этого пакета. После итерации

по всему обучающему набору данных и обновления параметров мо­
дели мы начинаем вторую эпоху, когда ранее зафиксированный па­

кет проходит через сеть. Затем мы сравниваем активации слоев из
первой и второй эпох. Так как параметры сети изменились, мы заме­

чаем, что активации тоже изменились. Описанное явление называ­
ется внутренним ковариационным сдвигом, который, как считалось,

замедляет обучение нейронной сети.

Однако в

2018

году Шибани Сантуркар, Димитрис Ципрас, Эндрю

Ильяс и Александр Мадрый продолжили выяснять, что же делает

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

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

Если вам интересно узнать больше о результатах исследований Сан­
туркара, Ципраса, Ильяса и Мадрого, тогда почитайте исходную ста­

тью

"How Does Batch Normalization Help Optimization?"

(Как пакет­

ная нормализация содействует оптимизации?), свободно доступную
по ссылке http: / /papers. nips. cc/paper /7 515-how-doesbatch-normalization-help-optimization.pdf .

-·---------·-------·-----·--·--------------- [758] -----------·---------·---------

Глава

Порождающие состязательные сети для синтеза новых данных

17.

В АРl-интерфейсе Keras библиотеки TensorF\ow предлагается класс
tf. keras. layers. BatchNormalization, который мы можем приме­
нять в качестве слоя при определении моделей; он будет выполнять все
описанные ранее шаги пакетной нормализации. Обратите внимание, что

поведение обновления обучаемых параметров у и р зависит от установки

training=False

или

training=True.

Это можно использовать для того,

чтобы гарантировать, что указанные параметры будут выясняться только во
время обучения.

Реализация генератора и дискриминатора
раскрыли главные компоненты модели

К настоящему моменту мы

DCGAN,

которую теперь реализуем. Архитектуры сетей генератора и диск­

риминатора показаны на рис.

17.13

и

17.14.

Генератор получает на входе вектор

z размера 20,

применяет к нему пол­

носвязный (плотный) слой, чтобы увеличить его размер до

ему форму тензора ранга
7х7 и

128

6 272,

и придает

с формой 7х7х 128 (размерность пространства

3

каналов). Затем с помощью последовательности транспонирован­

ных сверток, использующих класс

tf. keras. layers. Conv2DTranspose,

карт признаков, пока размерность пространства

повышается дискретизация

результирующих карт признаков не достигнет 28х28. После каждого слоя
транспонированной свертки количество каналов уменьшается вдвое за ис­

ключением последнего, который задействует только один выходной фильтр

для генерации изображения в оттенках серого.
Сеть генератора

6272

/ 7х7х128

20

28х28х32

14Х14Х64

?-(/

28х28х1

r

Фальшивое
(синтезированное)

изображение

z

1

/

Полно-

[

1

\

связный

слой

1

Транспони-

Транспони­

Транспони ­

Транспони­

рованная

рованная

рованная

рованная

свертка

свертка

свертка

свертка

Рис.

17.13.

Архшпектура сети ,'енератора

------ -·------------ -- ---

[759]------ - --

- - - --

Глава

17.

Порождающие со с тязательные сети для синтеза новых данных

Сеть дискриминатора

28Х28Х64

/ /l

17.14.

r-ri
7х7х256

/

Свертка

Свертка

Рис.

14х14х128

lxlxl

[JJ

1

1

Свертка

Свертка

Архит ектура сети дискриминатора

За каждым слоем транспонированной свертки следуют пакетная нормали­

ReLU с утечкой кроме последнего слоя ,
где применяется функция активации tanh (без пакетной нормализации).
Дискриминатор получает изображения размером 28х28 х 1, которые прохо­

зация и функция активации на основе

дят через четыре сверточных слоя. Первые три сверточных слоя понижают
размерность

пространства

в

четыре раза , одновременно увеличивая

коли­

чество каналов карт признаков. За каждым сверточным слоем находится па­
кетная нормализация , функция активации на основе
отключения с

ra te=O. 3

ReLU

с утечкой и слой

(вероятность отключения) . Последний сверточный

слой использует ядро размером 7х7 и единственный фильтр для понижения
размерности пространства выхода до

1 х 1х 1.

-~-

Особенности проектирования архитектуры

Совет

Обратите внимание, что количество карт признаков соответствует

для сверточных порождающих состязательных сетей

различным тенденциям,

генератором

между

возникающим

и диск­

риминатором. В генераторе мы начинаем с крупного числа карт при­
знаков и уменьшаем его по мере продвижения к последнему слою .

С другой стороны , в дискриминаторе мы начинаем с небольшого
количества каналов

и увеличиваем

слою. При проектировании сетей

его,

CNN

направляясь

к

последнему

важный момент связан с ко­

личеством и пространственным размером карт признаков в обратном

порядке. Когда пространственный размер карт признаков увеличива­
ется, количество карт признаков уменьшается

-

и наоборот.

- -- -- - [760)-- -- - - - ·

Глава

17.

Порождающие состязательные сети для синтеза новых данных

Вдобавок имейте в виду, что в слое, следующем за слоем пакетной
нормализации, применять элементы смещения обычно не рекомен­

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

use _ Ьias=False

в

tf. keras. layers. Dense

или

tf.keras.layers.Conv2~

Ниже приведен код двух вспомогательных функций для создания сетей
генератора и дискриминатора:

>>> def make_dcgan_generator (
z_size=20,
output_size=(28, 28, 1),
n_filters=128,
n_Ыocks=2):

size factor = 2**n Ыocks
hidden_size = (
output_size[O]//size_factor,
output_size[l]//size_factor)
model = tf.keras.Sequential([
tf.keras.layers.Input(shape=(z_size,)),
tf.keras.layers.Dense(
units=n_filters*np.prod(hidden_size),
use_ Ьias=Fal se) ,
tf.keras.layers.BatchNormalization(),
tf.keras.layers.LeakyReLU(),
tf.keras.layers.Reshape(
(hidden_size[O], hidden_size[l], n_filters)),
tf.keras.layers.Conv2DTranspose(
filters=n_filters, kernel_size=(5, 5),
strides=(l, 1), padding='same', use_Ьias=False),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.LeakyReLU()
])

nf = n filters
for i in range (n_Ыocks):
nf = nf // 2

[761]---

[лава

17.

Порождающие состязательные сети для синтеза новых данных

model.add(
tf.keras.layers.Conv2DTranspose(
filters=nf, kernel_size=(5, 5),
strides=(2, 2), padding='same',
use_bias=False))
model.add(tf.keras.layers.BatchNormalization( ))
model.add(tf.keras.layers.LeakyReLU())
model.add(
tf.keras.layers.Conv2DTranspose(
filters=output_size[2], kernel_size=(5, 5),
strides=(l, 1), padding='same', use_bias=False,
activation='tanh'))
return model

>>> def make_dcgan_discriminator(
input_size=(28, 28, 1),
n_filters=64,
n _ Ыocks=2) :
model = tf. keras. Sequential ( [
tf.keras.layers.Input(shape=input_size),
tf.keras.layers.Conv2D(
filters=n_filters, kernel_size=5,
strides=(l, 1), padding='same'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.LeakyReLU()
] )

nf = n filters
for i in range (n_Ыocks):
nf = nf*2
model.add(
tf.keras.layers.Conv2D(
filters=nf, kernel_size=(5, 5),
strides=(2, 2),padding='same'))
model.add(tf.keras.layers.BatchNormalization( ))
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Dropout(0.3))
model.add(
tf.keras.layers.Conv2D(
filters=l, kernel_size=(7, 7),
padding='valid'))
model.add(tf.keras.layers.Reshape((l,)))
return model

. [762) - - - - - - - - - · - - - - · - - - · - -

Глава

17.

Порождающие состязательные сети для синтеза новых данных

С помощью этих двух вспомогательных функций можно построить мо­
дель

DCGAN и обучить ее с использованием того же самого объекта набора
данных MNIST, который мы инициализировали в предыдущем сеансе, ког­
да реализовывали простую полносвязную сеть GAN. Кроме того, мы можем
применять те же самые функции потерь и процедуру обучения, что и ранее.
В оставшихся разделах главы мы внесем в модель
ко дополнительных

preprocess

модификаций.

Обратите

DCGAN

внимание,

что

несколь­
функция

(),предназначенная для трансформации набора данных, долж­

на быть изменена, чтобы выдавать тензор изображения, а не сплющивать

изображение в вектор. В следующем коде показаны необходимые модифика­
ции для построения набора данных, а также продемонстрировано создание
новых сетей генератора и дискриминатора:

>>> mnist_Ыdr = tfds.builder ( 'mnist')
>>> mnist_Ьldr.download_and_prepare()
>>> mnist = mnist_Ыdr.as_dataset(shuffle_files=False)
>>>

(jef"

preprocess (ех, mode='uniform'):
image = ех [ ' image ' ]
image = tf.image.convert_image_dtype(image, tf.float32)
image = image*2 - 1.0
if mode == 'uniform':
input_z = tf. random. uniform(
shape=(z_size,), minval=-1.0, maxval=l.0)
e11.f mode == 'normal':
input_z = tf.random.normal(shape=(z_size,))
.гeturr1 input_z, image

Мы можем создать сеть генератора, используя вспомогательную функ­
цию

make _ dcgan _generator (),

и вывести сведения об ее архитектуре,

как показано ниже:

>>> gen_ model = make _ dcgan_generator ()
>>> gen_ model. summary ()
Model: "sequential 2"
Layer (type)

Output Shape

Param #

dense_l (Dense)

(None, 6272)

125440

batch normalization 7 (Batch (None, 6272)

----------------(763)

25088

Глава

17.

Порождающие состязательные сети для синтеза новых данных

leaky_re lu_7 (LeakyReLU)

(None, 6272)

о

reshape 2 (Reshape)

(None, 7, 7, 128)

о

conv2d transpose 4 (Conv2DTr

(None, 7, 7, 128)

409600

batch_normalization_8 (Batch

(None, 7, 7, 128)

512

leaky_re_lu_8 (LeakyReLU)

(None, 7, 7, 128)

о

conv2d_transpose 5 (Conv2DTr

(None, 14, 14, 64)

204800

batch_normalization_9 (Batch

(None, 14, 14, 64)

256

leaky_re lu_9 (LeakyReLU)

(None, 14, 14, 64)

о

conv2d_transpose 6 (Conv2DTr

(None, 28, 28, 32)

51200

batch_normalization_lO (Batc

(None, 28, 28, 32)

128

leaky_re lu_lO (LeakyReLU)

(None, 28, 28, 32)

о

conv2d_transpose 7 (Conv2DTr

(None, 28, 28, 1)

800

Total params: 817,824
TrainaЫe params: 804,832
Non-trainaЫe params: 12,992

Подобным образом мы можем создать сеть дискриминатора и отобразить
информацию о ее архитектуре:

>>> disc_model = make_dcgan discriminator()
>>> disc_model.summary()
Model: "sequential 3"
Layer ( type)

Output Shape

Param #

conv2d 4 (Conv2D)

(None, 28, 28, 64)

1664

batch normalization 11 (Batc

(None, 28, 28, 64)

256

-----------[7641-------

Глава

17.

Порождающие состязательные сети для синтеза новых данных

leaky_re_lu_ll (LeakyReLU)

(None, 28, 28, 64)

о

conv2d 5 (Conv2D)

(None, 14, 14, 128)

204928

batch normalization 12 (Batc

(None, 14, 14, 128)

512

leaky_ re lu - 12 (LeakyReLU)

(None, 14, 14, 128)

о

dropout 2 (Dropout)

(None, 14, 14, 128)

о

conv2d- 6 (Conv2D)

(None, 7, 7, 256)

819456

batch normalization 13 (Batc

(None, 7' 7, 256)

1024

leaky- re - lu 13 (LeakyReLU)

(None, 7, 7, 256)

о

dropout 3 (Dropout)

(None, 7, 7, 256)

о

conv2d- 7 (Conv2D)

(None, 1, 1, 1)

12545

-

о
(None, 1)
reshape 3 (Reshape)
======================= ======================= =================
Total params: 1,040,385
TrainaЬle params: 1,039,489
Non-trainaЫe params: 896

Обратите внимание, что количество параметров для слоев пакетной нор­
мализации на самом деле в четыре раза больше числа каналов. Вспомните,
что параметры пакетной нормализации

и и 8 представляют среднее и

µ8

стандартное отклонение (необучаемые параметры) для значения каждого

признака, выведенные из заданного пакета;

/'

и р являются обучаемыми па­

раметрами пакетной нормализации.

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

WGAN,

которая использует

модифицированную функцию потерь на основе так называемого расстояния
Вассери1тейна-l (или экскаватора) между распределениями настоящих и

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

-------[765)

Глава

17.

Порождающие состязательные сети для синтеза новых данных

Меры несходства между двумя распредеnениями
Сначала мы рассмотрим различные меры для расчета расхождения меж­

ду двумя распределениями. Затем мы выясним , какая из мер уже встроена
в исходную модель

Наконец, переключение этой меры в сетях

GAN.

приведет нас к реализации

GAN

WGAN.

Как упоминалось в начале главы, цель порождающей модели заключает­
ся в том, что научить синтезировать новые образцы, которые имеют такое

же распределение, как в обучающем наборе. Пусть Р(х) и

Q(x)

распределения случайной переменной х, как показано на рис .

Полная

(TV)

TV(P, Q) = supJP(x) - Q(x)I
х

Расстояние

КульбакаЛейблера

KL(PJJQ) =

JP(x)logQ(x/x
Р(х)

(KL)

Дивергенция

ЙенсенаШеннона

17.15.

Формульное выражение

Меры

вариация

представляют

JS(P.Q)

= НкL

(PllP; Q) + KL ( QllP; Q))

(JS)

Расстояние
экскаватора

EM(P,Q) =

(ЕМ)

Рис.

17.15.

inf E(uv)ey(Jlu - vll)

уеП(Р,Q)

'

Меры для распределений случайной перемеююй х

Ниже мы обсудим способы, представленные на рис.

17.15,

которые можно

применять для измерения несходства между двумя распределениями, Р и
Функция супремума (sщпс ти111),

sup(S),

Q.

используемая в мере пошюй

вариации

(total 11(:u-iation -·- 7Т), означает наименьшую величину, которая
больше всех элементов S. Другими словами, sup(S) - точная верхняя грань
для S. Наоборот, функция инфи.мума (i11(imum), inf(S), применяемая в мере
расстояния экскаватора (cm·tli 111m•c1· --- EAI), означает наибольшую вели­
чину, которая меньше всех элементов S (точная нижняя грань).

- - - - --

- - - - (766) -

-

- --

-

Глава

17.

Порождающие состязательные сети для синтеза новых данных

Давайте разберем перечисленные меры, кратко изложив простыми слова­
ми то, чего они пытаются достичь.



Расстояние ТУ измеряет наибольшее различие между двумя распреде­
лениями в каждой точке.



Расстояние экскаватора, ЕМ, может интерпретироваться как минималь­

ный объем работы, необходимой для трансформирования одного рас­
пределения в другое. Функция инфимума в расстоянии ЕМ берется по

П(Р,

Q) -

коллекции всех совместных распределений, маргинальным

распределением которых является Р или

Q.

Тогда у(и, v) представляет

собой план перемещения, который указывает, каким образом мы пере­
распределяем землю с места и в

v,

при условии ряда ограничений для

поддержания допустимых распределений после таких перемещений.

Расчет расстояния ЕМ сам по себе является задачей оптимизации, на­
правленной на нахождение оптимального плана перемещения



Меры расстояния Кульбака-Лейблера (1.tер вычисления

(О.ЗЗ - 0.2) + (О.ЗЗ - 0.З) • 0.1'

.>.tep 11есходства

с двуwя просты.ми

дискрет11ыми распределе11ия.ми

Г:.~ Связь между расстоянием

н~ Расстояние KL, КL(Р

11

Q),

KL

и перекрестной энтропией

измеряет относительную энтропию рас-

3аметку! пределения Р по отношению к эталонному распределению
мульное выражение для расстояния

KL(PllQ)

=

KL

Q.

может быть расширено как

-J Р(х) log( Q(x)) dx - ( -J Р(х) log(P(x)))

Кроме того, для дискретных распределений расстояние

KL

можно

записать так:

KL{PllQ)

=

-7 Р(хд

P(xi)
Q(xi)

что аналогичным образом может быть расширено как

KL(PllQ) = -

L P(xi) log(Q(xi))
i

-

-

- --- -



Фор­

- (- L P(xi) log(P(xi)))
i

-

-

(768] -

fпава

17.

Порождающие состязательные сети для синтеза новых данных

Базируясь на расширенном формульном выражении (дискретном
или непрерывном), расстояние

ная энтропия между Р и

Q

KL

рассматривается как перекрест­

(первый член в предыдущем уравнении),

из которой вычитается (собственная) энтропия Р (второй член), т.е.

KL(P 11 Q) =Н(Р, Q)- Н(Р).
Теперь, возвращаясь к нашему обсуждению сетей

GAN,

давайте пос­

мотрим, как различные меры расстояния связаны с функцией потерь для

сетей

GAN.

Математически можно показать, что функция потерь в перво­

начальной сети

GAN

на самом деле минимизирует дивергенцию

JS

меж­

ду распределением настоящих и фальшивых образцов. Но, как объясня­
ется в статье Мартена Аржовски и др. "Wasserstein Generative Adversarial
Networks" (Порождающие состязательные сети Вассерштейна; h t t р: / /
proceedings. mlr. press /v7 О/ arj ovskyl 7 а/ arj ovs kyl 7 а. pdf),
у дивергенции JS есть проблемы с обучением модели GAN. По указанной
причине для улучшения процесса обучения исследователи предложили ис­
пользовать в качестве меры несходства между распределениями настоящих

и фальшивых образцов расстояние ЕМ.

-r(-

Чтобы ответить на этот вопрос, мы можем рассмотреть пример, ко­

Совет

торый был приведен в упомянутой выше статье Мартена Аржовски

В чем преимущество использования расстояния ЕМ?

и др. Давайте предположим, что у нас имеются два распределения,

Р и

Q,

являющиеся двумя параллельными линиями. Одна линия за­

фиксирована в точке х

= О,

а другая может перемещаться вдоль оси

х, но первоначально находится в точке х

= 0, где 0

> О.

Можно показать, что меры несходства KL, ТУ и JS выглядят как
KL(Pll Q) = +оо, TV(P, Q) = 1 и JS(P, Q) = 1/2 log2. Ни одна из этих
мер несходства не является функцией параметра 0, а потому их не­
льзя дифференцировать относительно 0, чтобы распределения Р и

Q стали похожими друг на друга. С другой стороны, расстояние ЕМ
выражается как ЕМ(Р, Q) = 0
градиент которого относительно 0
существует и способен двигать Q в направлении Р.

l I,

А теперь давайте сосредоточим наше внимание на том, как расстояние

ЕМ можно применять для обучения модели
ние настоящих образцов и

Pg -

GAN.

Пусть Р,

-

распределе­

распределение фальшивых (сгенерирован-

- - - - - - · [769] ------------------··-·---·-·-------

Глава

17.

Порождающие состязательные сети для синтеза новых данных

ных) образцов.

Pr

и

Pg

заменяют Р и



уравнении расстояния ЕМ. Как

упоминалось ранее, расчет расстояния ЕМ сам по себе является задачей

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

GAN.

К счастью расчет расстоя­

ния ЕМ может быть упрощен с использованием теоремы о двойственности
Канторовича-Рубинштейна (Ka11toro\•icl1-RuЬi11stei11

W(Pr,P9 )

= llfllL:s;l
sup EueP [f(u)] т

EveP [f(v)]

Здесь супремум берется по всем

обозначаемым посредством

-~Совет

ll!ll

L

duality):

1-липшицевым
:$; 1.

g

непрерывным функциям,

Липwицева непрерывность
Основываясь на 1-липшицевой 11епрерыв11ости
функция

f

(J-1.ipscbltz umti11uitJ

1) ,

обязана удовлетворять следующему свойству:

Кроме того, вещественная функция

f

R - R,

которая удовлетворяет

свойству

называется К-липшицевой 11епрерывностью.

Практическое использование расстояния Вассерwтейна
для порождающих состязательных сетей
Вопрос теперь в том, как найти такую

1-липшицеву

непрерывную функ­

цию для расчета расстояния Вассерштейна между распределениями на­

стоящих

(Pr)

и фальшивых

(Pg)

выходов в сети

концепции, лежащие в основе подхода

GAN? Хотя теоретические
WGAN, поначалу могут выгля­

деть сложными, ответ на данный вопрос проще, чем может показаться.

Вспомните, что мы считаем глубокие нейронные сети универсальными ап­

проксиматорами функций. Это значит, что мы можем просто обучить ней­
росетевую модель для аппроксимации функции расстояния Вассерштейна.
В предыдущем разделе было показано, что простая сеть
дискриминатор в форме классификатора. Для сети

-----(770)

GAN использует
WGAN дискриминатор

Глава

Порождающие состязательные сети для синтеза новых данных

17.

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

GAN с применением расстояния Вассерштейна,
D и генератора G определяются так, как опи­

потери для дискриминатора

сано ниже. Критик (т.е. сеть дискриминатора) возвращает свои выходы для

пакета настоящих образцов и пакета синтезированных образцов. Мы ис­
пользуем обозначения

и

D(x)

D(G(z))

соответственно. Затем можно опреде­

лить следующие члены потерь:



компонент потери дискриминатора для настоящих образцов:



компонент потери дискриминатора для фальшивых образцов:

L~альш. = ~LD(G(z;))
'
• потеря генератора -

L0

1

=-- LD(G(z;)).

Это все, что нужно для сети

N;

WGAN,

но понадобится еще обеспечить

предохранение 1-липшицева свойства функции критика во время обучения.
Для такой цели в статье о сетях
большой областью, скажем,

WGAN предлагается
[-0.01, 0.01].

ограничивать веса не­

Штраф rрадиента
В статье Аржовски и др. предлагается отсечение весов для поддержа­

ния 1-липшицева свойства дискриминатора (или критика). Тем не менее,
в другой статье под названием
(Усовершенствованное

"lmproved Training of Wasserstein GANs"

обучение

порождающих состязательных сетей

Вассерштейна), которая свободно доступна по ссылке

pdf/1704. 00028.pdf,

ht tps: / / arxi v. org /

Ишаан Гулраджани и др. показали, что отсечение

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

емкости модели, порождая то, что модель критика будет ограничена выявле-

- - - · - -----[771]

fпава

17.

Порождающие состязательные сети для синтеза новых данных

нием только ряда простых, но не более сложных функций. Таким образом,
вместо отсечения весов Ишаан Гулраджани и др. предложили альтернатив­
ное решение

штраф градuе1/та

-

(g1·1111ie11t pomlty ·- (;J>).

Результатом

будет поро.ждающая состязательная сеть Вассерштейна со штрафом
градиента (\Н;ЛN н·it/1

Процедуру для

GP,

gnulicnl pc1111lt,1· ····

Н'с;Л,\'-(~Р).

которая добавляется в каждой итерации, можно поды­

тожить в виде представленной ниже последовательности шагов.

1. Для каждой пары настоящих и фальшивых образцов (x[i], xlil) в задан­
ном пакете выбрать случайное число alil, отобранное из равномерного
распределения, т.е. alil е U(O, 1).
2.

Рассчитать интерполяцию между настоящими и фальшивыми образцами:

_x[i] = axlil + (1 - a)xlil, получив в результате интерполированные об­
разцы.

3.

Рассчитать выход дискриминатора (критика) для всех интерполирован­

ных образцов, D(xlil).

4.

Рассчитать градиенты выхода критика относительно каждого интерпо­

лированного образца, т.е. Y'flil D(xlil).

5.

Рассчитать GP как L~= ~~{llvxlilD(x1 ; 1 )11 2 -1)2 •
l

Тогда общая потеря для дискриминатора будет следующей:

L~бщая = L~аст. + Lфальш. + A.L~p
Здесь А.

-

подстраиваемый гиперпараметр.

Реаnизация порождающей состязатеnьной сети Вассерштейна
со штрафом rрадиента дnя обучения модеnи DCGAN
Мы уже

определили

вспомогательные функции,

сети генератора и дискриминатора для модели

generator ()
построения

>>>
>>>
>>>
>>>

и

make_dcgan discriminator
модели DCGAN.

которые создают

DCGAN (make_dcgan_
()).Ниже показан код для

num_epochs = 100
batch size = 128
image_size = (28, 28)
z size = 20

·-------- [772] -·- · · - - - - - - - - - - - - - - - - -

Глава

>>> mode



х

17.

Порождающие состязательные сети для синтеза новых данных

= 'uniform'
= 10. О

lamЬda _gp

>>> tf.random.set_seed(l)
>>> np.random.seed(l)
>>> ## Настройка набора данных
>>> mnist _ trainset = mnist [ 'train' )
>>> mnist trainset = mnist_trainset.map(preprocess)
>>> mnist trainset = mnist_trainset.shuffle(lOOOO)
>>> mnist trainset = mnist_trainset.batch(
batch_size, drop_remainder=True)
>>> ## Настройка модели
>>> with. tf .device (device_name):
gen_model = make_dcgan_generator()
gen_model.build(input_shape=(None, z_size))
disc_model = make_dcgan_discriminator()
disc_model.build(input_shape=(NorJe, np.prod(image_size)))
Теперь мы можем обучить модель. Обратите внимание, что обычно для
модели

WGAN (без GP) рекомендуется оптимизатор RMSprop, тогда
WGAN-GP применяется оптимизатор Adam. Вот необходимый код:
>>>

iп1port

как для

time

>>> ## Оптимизаторы:
>>> g_optimizer = tf.keras.optimizers.Adam(0.0002)
>>> d_optimizer = tf.keras.optimizers.Adam(0.0002)
>>> if mode z == 'unifonn':
fixed_z = tf. random. uniform (
shape=(batch_size, z_size), minval=-1, maxval=l)
el.i.f mode z == 'normal':
fixed_z = tf.random.normal(shape=(batch_size, z_size))
>>> def create_samples (g_model, input_z):
g_output = g_model(input_z, training=False)
images = tf.reshape(g_output, (batch_size, *image_size))
retщ·n (images+l)/2.0
>>> all_losses = []
>>> epoch_samples = [)
>>> start_time = time. time ()

------ [ 7731 ---·--------------

fлава

17.

Порождающие состязательные сети для синтеза новых данных

>>> for epoch in range(l, num_epochs+l):
epoch_ losses = []
for i, (input_z,input_real) in enumerate(mnist_trainset):
with tf.GradientTape() as d_tape, tf.GradientTape() \
as g_tape:
g_output = gen_rnodel(input_z, training=True)
d_critics_real = disc_rnodel(input_real,
training=True)
d_critics_fake = disc_rnodel(g_output,
training=True)

## Расчет потери генератора:
g_loss = -tf.rnath.reduce_rnean(d_critics_fake)
## Расчет потерь дискриминатора:
d_loss_real = -tf.rnath.reduce_rnean(d_critics real)
d_loss_fake = tf .rnath.reduce_rnean (d_critics_fake)
d loss = d loss real + d loss fake
## Штраф градиента :
th tf. GradientTape () as gp_ tape:
alpha = tf. randorn. uniforrn (
shape=[d_critics_real.shape[O], 1, 1, 1],
rninval=O.O, rnaxval=l.0)
interpolated = (alpha*input_real +
(1-alpha)*g_output)
gp_tape.watch(interpolated)
d_critics_intp = disc_rnodel(interpolated)

1'1i

grads_intp = gp_tape.gradient(
d_critics_intp, [interpolated,J) [О]
grads_ intp_ 12 = tf. sqrt (
tf.reduce_sum(tf.square(grads_intp),
axis=[l, 2, 3]))
grad_penalty = tf.reduce_rnean(tf.square(
grads_intp_l2 - 1.0))
d loss = d loss +

lamЬda_gp*grad_penalty

·----[774)-------

Глава

17.

Порождающие состязательные сети для синтеза новых данных

## Оптимизация: расчет и применение
d_grads = d_tape.gradient(d_loss,

rрадиентов

disc_model.trainaЫe_variaЫes)

d_optimizer.apply_gradients(
grads_and_vars=zip(d_grads,
disc_model.trainaЫe_variaЫes))

g_grads = g_tape.gradient(g_loss,
gen_ model. trai.naЫe_ variaЫes)
g_optimizer.apply_gradients(
grads_and_vars=zip(g_grads,
gen_model.trainaЫe_variaЫes))

epoch_losses.append(
(g_loss.numpy(), d_loss.numpy(),
d_loss_real.numpy(), d_loss_fake.numpy()))
all_losses.append(epoch_losses)
print(
'Эпоха { : ОЗd} 1 ЕТ { : . 2f) минут 1 Средние потери >>'
'G/D {:6.2f)/{:6.2f) [D-Real: {:6.2f}'
' D-Fake: { :6.2f}]'
.format(
epoch, (time. time () - start_time) /60,
*list(np.mean(all_losses[-1], axis=O))))
epoch_samples.append(
create_samples(gen_model, fixed_z) .numpy())
В заключение мы визуализируем сохраненные образцы в некоторых эпо­
хах, чтобы посмотреть, как модель обучается, а качество синтезируемых об­
разцов изменяется в процессе обучения:

>>> selected_epochs = [1, 2, 4, 10, 50, 100]

»> fig = plt. figure ( figsize= ( 10, 14) )
>>>

fо.г

i, е in enнmerate (selected_epochs):
for j in range (5):
ах= fig.add_subplot(6, 5, i*5+j+l)
ax.set_xticks([])
ax.set_yticks([])
ifj=-=0:
ax.text(-0.06, 0.5, 'Эпоха {}'.format(e),
rotation=-90, size=l8, color='red',
horizontalalignment=-'right',
[775)---~-

Глава

17.

Порождающие состязательные сети для синтеза новых данных

verticalaligrunent='cen ter',
transform=ax.transAxes)
image = epoch_samples[e-1) [j)
ax.imshow(image, cmap='gray_r')
>>> plt. show ()
Результаты приведены на рис.

17 .17.

H~~EJ8~
H~[ZiJ0~~
j ~~@l~~
j & ~@]~[QJ
i ~~[l][!]~

i ~ШШL2JlOJ
Рис.

17.17.

Сохра11енные образцы в некоторых эпохах

Для визуализации результатов мы использовали тот же самый код, что и

в разделе "Реализация сетей генератора и дискриминатора" ранее в главе.

Сравнение новых образцов показывает, что модели

DCGAN

(с расстоянием

Вассерштейна и штрафом градиента) способны генерировать изображения
гораздо более высокого качества.

[776) - - - -

Глава 17. Порождающие состязательн1>1е сети для синтеза нов1>1х данн1>1х

Konnanc мод
Из-за состязательной природы моделей

GAN

их крайне нелегко обучать .

Часто случается так, что во время обучения модель

GAN

может застрять

в небольшом подпространстве и учиться генерировать похожие образцы.
Такая ситуация называется коллапсом мод (111 01 /с

collapse )

и на рис .

17.18

приведен пример .

:r

1 "!

'1
,

"

,.

,-

"
'";

"

:1

; 11,

"

J ,.' !

"L



"
"
"

....

•r

"

:1

1,. :·j
"

"'1,.

"

"

...

.

:i,l

"

о

·1

1

"



"

"

"

(

"

'°.

..

"t

,.

/ "I 1 1 "l
'1

Рис.

~1

"

"

"

1
'"

·'

"

1,.

"

10

"

17.18.

,.

1 1

.



:l

1

Возникновение кшиапса .иод

Синтезированные образцы на рис.

17 .18

не являются специально подоб­

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

Помимо упомянутых ранее проблем исчезновения и взрывного роста гра­
диентов существует ряд дополнительных аспектов, которые также могут ус­

ложнить обучение моделей

GAN

(на самом деле это искусство). Ниже пред­

ложено несколько трюков от мастеров в области

GAN.

Один из подходов называется .нини-пакет11ым различением (mi11 i- l1пtcl1
1fiчтi111i1111ti011) и основан на том факте, что пакеты, которые состоят только
из настоящих или фальшивых образцов, подаются дискриминатору по от-

-- (777) - - - --- -- -- - · - - - -

Глава

17.

Порождающие состязательные сети для синтеза новых данных

дельности. При мини-пакетном различении мы позволяем дискриминатору

сравнивать образцы среди таких пакетов, чтобы выяснить, какие образцы
содержит пакет

-

настоящие или фальшивые. Разнообразие в пакете, со­

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

Еще одна методика, часто применяемая для стабилизации обучения мо­
делей

GAN,

называется согласованием приз11аков (.fcatuгe

11111tcl1ing),

когда

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

ными и синтезированными изображениями, основываясь на промежуточных
представлениях (картах признаков) дискриминатора. Мы рекомендуем почи­

тать статью

"High Resolution lmage Synthesis and Semantic Manipulation with
Conditional GANs" (Синтез изображений с высоким разрешением и семанти­
ческое манипулирование с помощью условных порождающих состязатель­

ных сетей), написанную Тинь Чуи Вонгом и др" которая свободно доступна
по ссылке

https: //arxiv. org/pdf/1711.11585 .pdf.
GAN также способна застревать

Во время обучения модель

в нескольких

модах и перепрыгивать между ними. Во избежание такого поведения вы мо­

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

называется воспроизведением опыта

того, вы мо­

жете обучить множество моделей

случайными

(expcrience 1·ерlщ•). Кроме
GAN с разными начальными

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

Друrие приложения порождающих

состязательных сетей
Основное внимание в главе мы уделяли генерированию образцов с ис­

пользованием сетей

GAN

и вдобавок рассмотрели несколько трюков и мето­

дик повышения качества синтезируемого выхода. Спектр приложений сетей

GAN

быстро расширяется, включая компьютерное зрение, машинное обуче­

ние и даже другие предметные области науки и техники. Хороший список
различных моделей

GAN и областей их применения находится
https://github.com/hindupuravinash/the-g an-zoo.
(778)---

по ссылке

fлава

17.

Порождающие состязательные сети для синтеза новых данных

Стоит отметить, что мы обсуждали сети

GAN

в манере без учителя, т.е.

в моделях, раскрытых в главе, информация о метках классов не использова­
лась. Однако подход

GAN

может быть обобщен также на задачи частичного

обучения и обучения с учителем. Например, условные порождающие состя­
зательные сети, предложенные Мехди Мирза и Саймоном Осиндеро в статье

"Conditional Generative Adversarial Nets" (Условные порождающие состяза­
тельные сети; https: / /arxi v. org/pdf /1411. 1784. pdf), задействуют
информацию о метках классов и учатся синтезировать новые изображения,

обусловленные предоставленной меткой х= G(zly) применительно к МNIST.
В итоге у нас появляется возможность избирательного генерирования раз­
личных цифр в диапазоне

Кроме того, условные

0-9.
сети GAN

позволяют выполнять трансляцию изоб­

ражений в изображения, которая предусматривает выяснение способа пре­
образования заданного изображения из одной предметной области в другую.

Интересной работой в этом контексте является алгоритм
в статье

Pix2Pix, описанный
"lmage-to-Image Translation with Conditional Adversarial Networks"

(Трансляция изображений в изображения с помощью условных состяза­
тельных сетей) Филлипа Изоля и др., которая свободно доступна по ссылке

https: / /arxi v. org/pdf /1611. 07004. pdf. Полезно упомянуть о том,
что в алгоритме Pix2Pix вместо единственного прогноза для целого изобра­
жения дискриминатор предоставляет прогнозы "настоящий/фальшивый" для
множества участков изображения.
Еще одной интересной моделью

сети

GAN,

считается

CycleGAN,

GAN,

построенной поверх условной

которая тоже предназначена для трансля­

ции изображений в изображения. Тем не менее, следует отметить, что обуча­

ющие образцы из двух предметных областей в

Cyc\eGAN

не объединяются

в пары, т.е. соответствие типа "один к одному" между входами и выходами
отсутствует.

Например, с использованием модели

Cyc\eGAN

мы могли бы поменять

время года фотографии, сделанной летом, на зиму. В статье

"Unpaired lmageto-1 mage Translation Using Cycle-Consistent Adversarial Networks" (Непарная
трансляция изображений в изображения с использованием циклически

согласованных состязательных сетей) Цзюнь-Янь Чжу и др.

arxi v. org/pdf /1703. 10593. pdf)

(https: / /

демонстрируется впечатляющий при­

мер преобразования лошадей в зебр.

·----------------------- [779) - - - - - - - - - - - - - - - - - - -

f11ава

17.

Порождающие состязательные сети для синтеза новых данных

Резюме
В этой главе вы сначала узнали о порождающих моделях в ГО и их об­

щей цели

-

синтезе новых данных. Затем мы показали, как модели

GAN

применяют сети генератора и дискриминатора, которые соперничают друг с

другом при состязательном обучении, улучшая друг друга с течением вре­
мени. Далее мы реализовали простую модель

GAN,

используя для генерато­

ра и дискриминатора только полносвязные слои.

Мы также рассмотрели способы усовершенствования моделей
увидели архитектуру

DCGAN,

GAN.

Вы

в которой для генератора и дискриминатора

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

WGAN,

которая использует расстояние ЕМ

для измерения несходства между распределениями настоящих и фальшивых

образцов. В заключение мы обсудили сеть

WGAN

со штрафом градиента

для поддержания 1-липшицева свойства вместо отсечения весов.

В следующей главе мы рассмотрим обучение с подкреплением, которое
представляет собой совершенно другую категорию МО в сравнении с тем,
что было раскрыто в книге до сих пор.

------------(780)

18
ОБУЧЕНИЕ с ПОДКРЕПЛЕНИЕМ
ДЛЯ ПРИНЯТИЯ РЕШЕНИЙ
В СЛОЖНЫХ СРЕДАХ

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

.

искусственные нейронные сети и глубокое обучение для решения таких типов

задач МО. Вспомните, что обучение с учителем сфокусировано на прогнози­
ровании метки категории или непрерывной величины из заданного вектора

входных признаков. Обучение без учителя сконцентрировано на выделении

шаблонов в данных, что делает его удобным при сжатии данных (глава
кластеризации (глава

11)

5),

или аппроксимации распределения обучающего на­

бора для генерирования новых данных (глава

17).

В этой главе мы уделим внимание отдельной категории МО
с подкрепление.м (1·еi1фн·сете11t

leaming - JU,),

-

обучению

которая отличается от пред­

шествующих категорий тем, что она фокусируется на изучении последова­

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

Мы раскроем в главе следующие темы:



основы обучения с подкреплением, взаимодействия агента со средой

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

Глава



18.

Обучение с подкреплением для принятия решений в сложных средах

введение в различные категории задач обучения с подкреплением, зада­
чи обучения на основе модели и без модели, а также алгоритмы обуче­
ния методом временных разностей и методом Монте-Карло;



реализация алгоритма Q-обучения в табличной форме;



понятие аппроксимации функций для решения задач обучения с под­
креплением и сочетание обучения с подкреплением с ГО путем реали­
зации алгоритма глубокого Q-обучения.

Обучение с подкреплением

-

сложная и обширная область исследова­

ний, и в главе мы сосредоточимся на его основах. Поскольку настоящая
глава служит введением,

а также для

концентрации

внимания

на

важных

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

Введение

-

обучение на опыте

В этом разделе мы сначала представим концепцию обучения с подкреп­
лением как ветвь МО и выясним его главные отличия от других задач МО.

Затем мы раскроем фундаментальные компоненты системы обучения с под­

креплением, после чего покажем математическую формулировку обучения с
подкреплением, основанную на марковском процессе принятия решений.

Понятие обучения с подкрепnением
Вплоть до текущего места в книге внимание было сосредоточено глав­
ным образом на обучении с учителем и без учителя. Вспомните, что при

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

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

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

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

(782]

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

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

ранее не определены. Взамен их необходимо выяснить через взаимодейс­
твия со средой с целью достижения желаемого исхода вроде выигрыша в

игре. При обучении с подкреплением модель (также называемая агентом)
взаимодействует со своей средой и за счет этого генерирует последователь­

ность взаимодействий, которые вместе называются эпизодом. Посредством
таких взаимодействий агент накапливает ряд наград, определяемых средой.

Награды могут быть положительными или отрицательными, а временами
они не раскрываются агенту вплоть до конца эпизода.

Например, представим, что мы хотим научить компьютер играть в шахма­
ты и выигрывать у игроков-людей. Метки (11аград111) для каждого отдельно
взятого шахматного хода, сделанного компьютером, не известны вплоть до

конца игры, потому что в течение самой игры мы не знаем, приведет ли

конкретный ход в итоге к выигрышу или к проигрышу. Ответная реакция

определяется лишь в конце игры. Вероятно, она будет положительной на­
градой, если компьютер выиграл партию, т.к. агент достиг общей желатель­
ной цели, и наоборот

-

отрицательной, если компьютер проиграл.

Кроме того, в примере с игрой в шахматы входом является текущая
конфигурация, скажем, расположение индивидуальных фигур на доске.
Учитывая большое количество возможных входов (состояний системы), по­
метить каждую конфигурацию или состояние как положительное или отри­
цательное попросту невозможно. Следовательно, для определения процесса

обучения мы предоставляем награды (или штfтфы) в конце каждой игры,
когда знаем, достигнута ли желательная цель

·------[783)

-

выиграли мы игру или нет.

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

В этом и заключается сущность обучения с подкреплением. При обучении
с подкреплением мы не можем или не учим агента, компьютер или робота
тому, как что-то делать;

мы

можем только указать то,

что именно хотим

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

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

-

особенно когда решение задачи

требует последовательности шагов, которые неизвестны, трудны в объясне­
нии или непросты в определении.

Помимо приложений в играх и робототехнике примеры обучения с под­
креплением можно обнаружить в живой природе. Например, дрессировка
собаки включает в себя обучение с подкреплением

-

мы выдаем награды

(лакомство) собаке, когда она выполняет желательные действия. Или возь­

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

посредством которого собака способна выявлять надвигающийся приступ, и
мы, безусловно, не имеем возможности определить последовательность ша­

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

такое поведение!
Хотя обучение с подкреплением предлагает мощную базу для изучения
произвольных последовательностей действий, направленных на достижение

определенной цели, имейте в виду, что обучение с подкреплением являет­
ся все еще новой и активной областью исследований с многочисленными

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

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

t,

может привести к буду­

щей награде, которая появится на какое-то произвольное количество шагов
позже.

~----~------------

(784)--------------.-----

Глава

Обучение с подкреплением для принятия решений в сложных средах

18.

Опредеnение интерфейса "аrент-среда"
системы обучения с подкрепnением
Во всех примерах обучения с подкреплением мы можем обнаружить две
отдельных сущности: агента и среду. Формально агент определяется как
сущность, которая учится принимать решения и взаимодействует с окружа­

ющей ее средой, предпринимая действия . Взамен в виде последовательности

принятия действий агент получает наблюдения и сигнал награды, управля­

емый средой. Среда

-

это все, что находится снаружи агента. Среда обме­

нивается информацией с агентом и определяет сигнал награды для действия
агента, а также его наблюдения.

Сигнш1 награды представляет собой ответную реакцию, которую агент по­
лучает при взаимодействии со средой , обычно принимает форму скалярного
значения и может быть либо положительным, либо отрицательным. Целью
награды является сообщение агенту о том, насколько хорошо он выполнял­
ся . Частота, с которой агент получает награды , зависит от решаемой задачи.
Скажем, в шахматах награда будет определяться после окончания игры на ос­

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

ного шага. Тогда агент в таком лабиринте будет пытаться довести до макси­
мума награды, накопленные за время его существования, где под временем

существования понимается длительность эпизода. Диаграмма на рис.

18.1

ил­

люстрирует взаимодействия и обмен информацией между агентом и средой.

о
Агент

Среда

..

Текущее 'l:....

состояние ~
s, Cl:D

"' '

Наблюдение

О (следующее
состояние)

\

S1+1

''

'

....

_.... _

А

.,,,.-...

Сигнал

V' награды R1+1
Рис.

> ---"_,

__ .,,,. ....

- - - - - -{ +/-

18.1. Взаимодействия и об:иен u11форАtацией .не.жду шеюпо.w и средой

(785) -

-

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

Как показано на рис.

18.1,

состояние агента представляет собой набор из

всех его переменных О. Например, в случае робота-дрона такие перемен­
ные могли бы включать текущее местоположение дрона (долгота, широта

и высота), оставшееся время работы батареи дрона, скорость каждого про­
пеллера и т.д. На каждом временном шаге агент взаимодействует со средой

через набор доступных действий А 1

@.

Но основе предпринятого действия,

обозначенного как А 1 , пока агент находится в состоянии

нал награды

R1+ 1 @,а

его состоянием станет

S1,

он получит сиг­

St+ 1 в.

Во время процесса обучения агент обязан опробовать различные дейс­
твия (исследование), чтобы он мог постепенно узнавать, каким действиям
отдавать предпочтение и выполнять более часто (эксплуатация) для доведе­

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

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

какой-то компании (эксплуатация) либо продолжить обучение в магистрату­
ре или докторантуре с целью обретения дополнительных знаний в области
науки о данных и МО (исследование).
В целом эксплуатация будет приводить к выбору действий с большей
краткосрочной наградой, тогда как исследование потенциально может обес­

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

жения при принятии решений.

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

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

Белллшиа. Итак, займемся марковскими процессами принятия решений.

-----[786]

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

Марковские процессы принятия решений
В общем случае тип задач, с которыми имеет дело обучение с подкрепле­
нием, обычно формулируется как .~tарковские процессы принятия решений

(Jlш·km'

tlecisio11

pгocess

···

М J)P). Стандартный подход к решению задач

МОР предусматривает использование динамического программирования, но

обучение с подкреплением обладает рядом ключевых преимуществ по срав­
нению с динамическим программированием.

Га~ Динамическое программирование

н~ Под динамическим программированием понимается набор компью3i1метку! терных алгоритмов и методик программирования, которые были разработаны Ричардом Беллманом в 1950-х годах. В некотором смысле
динамическое

задач

-

программирование

касается

рекурсивного

решения

решения относительно сложных задач путем их разбиения

на меньшие подзадачи.

Основное различие между рекурсией и динамическим программи­
рованием заключается

что при динамическом

в том,

программиро­

вании сохраняются результаты подзадач (обычно в виде словаря или
другой формы таблицы поиска), поэтому к ним можно получать до­
ступ за постоянное время (вместо их повторного расчета), если они

снова встретятся в будущем.

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

ния, входят выравнивание последовательностей и вычисление крат­

чайшего пути из точки А в точу Б.
Однако динамическое программирование не является осуществимым подхо­

дом, когда размер состояний (т.е. количество возможных конфигураций) отно­
сительно велик. В таких случаях обучение с подкреплением считается гораздо

более эффективной и практичной альтернативой для решения задач МОР.

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

t влияет

на последующие ситуации, математически формализованы как марковские
процессы принятия решений (МОР).
(787)-~---

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

В сценарии взаимодействий агент/среда при обучении с подкреплени­
ем, если мы обозначим начальное состояние агента как

S0,

то взаимодей­

ствия между агентом и средой дадут в результате последовательность такого
вида:

Обратите внимание, что фигурные скобки служат только в качестве ви­
зуальной подсказки. Здесь

S1 и А 1 означают состояние и действие, предпри­
нятое на временном шаге t. Посредством R1+ 1 обозначается награда, полу­
ченная от среды после выполнения действия А 1 • Следует отметить, что S1,
R1+ 1 и А 1 представляют собой независимые от времени переменные, которые
берут значения из предварительно определенных конечных наборов, обоз­

начаемых с помощью s е S, r е R и а е А соответственно. В марковском про­
цессе принятия решений зависимые от времени переменные S1 и R1+ 1 име­
ют распределения вероятностей, которые зависят только от их значений на

предыдущем временном шаге,
и

R1+ 1 = r

состояния

t-1.

Распределение вероятностей для

St+ 1 =s'

может быть записано как условная вероятность от предыдущего

(S1)

и предпринятого действия (А 1 ):

p(s',r 1s,a) ~ P(St+ 1 = s', R,+ 1 = r 1S1 = s, А 1 =а)
Такое распределение вероятностей полностью определяет диншwику

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

методов обучения с подкреплением. Типы методов обучения с подкрепле­
нием, которые требуют модели среды или пытаются изучить модель среды
(т.е. выяснить динамику среды), называются методами на основе модели в
противоположность методам без модели.

\\;~ Обучение с подкреплением без модели и на основе модели

н~ Когда вероятность p(s', r 1 s, а) известна, тогда задачу обучения мож­
заметку! но решить с помощью динамического программирования. Но при
неизвестной динамике среды, как в случае многих реальных задач,

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

[788)

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

Справиться с проблемой помогут два подхода: метод Монте-Кар:ю
без модели и метод временных разностей (lcmpыal 1Щfегепсе ----

TU).

На следующей диаграмме показаны две главных категории и ответ­
вления каждого метода.

[~:=:-i:~:~:--",

[-1-1 _::--==r::.::.:::1.__~§_=J.
м::::о:::~;~j•х

Мон~=~~=рnо

~~=:~~Ч:~~~е ]
рование

·---~-----------

TD(O), TD(A),
SARSA,
О-обучение

Далее в главе мы раскроем эти разные подходы и их ответвления,
начиная с теории и заканчивая практическими алгоритмами.

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

p(s ', r 1s, а) Е {О, 1}. Иначе в более общем случае среда будет обладать сто­
хастическим поведением.

Чтобы понять такое стохастическое поведение, давайте рассмотрим веро­

ятность наблюдения будущего состояния
состоянием является

S1 = s

как p(s' 1 s, а)~ P(S1+1 = s'

S1+ 1 = s'

при условии, что текущим

и предпринято действие А 1

1 S,

= а.

Она обозначается

= s, А 1 =а}.

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

p(s' 1 s,a) ~ Lp(s', r 1 s,a)
reR
Такая вероятность называется вероятностью смены состояния. Исходя
из вероятности смены состояния, если динамика среды детерминирована, то

это значит, что когда агент предпринимает действие А,
переход в следующее состояние,

S1+ 1

=

s',

будет на

= а в состоянии S1 = s,

100%

достоверным, т.е.

p(s' 1s, а)= 1.

-- (789] ---------------------

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

Визуаnизация марковскоrо процесса
Марковский процесс может быть представлен как ориентированный цик­
лический граф, узлы которого соответствуют разным состояниям среды. Ребра
графа (связи между узлами) представляют вероятности переходов между со­
стояниями.

Например, возьмем студента, который делает выбор среди трех состоя­
ний: (А) подготовка к сдаче экзамена дома, (В) игра в видеоигры дома или
(С) подготовка к сдаче экзамена в библиотеке. Кроме того, имеется заклю­
чительное состояние (Т)

-

пойти спать. Решения принимаются каждый час,

и в течение этого конкретного часа студент остается в выбранном состоя­
нии. Далее предположим, что находясь дома (состояние А), студент с веро­

ятностью

50%

будет переключаться с учебы на игру в видеоигры. С другой

стороны, когда студент пребывает в состоянии В (игра в видеоигры) , есть
относительно высокий шанс

(80%)

того , что он продолжит играть в видео­

игру в течение последующих часов.

Динамика поведения студента показана в виде марковского процесса на

рис.

18.2,

включая циклический граф и таблицу переходов.

Значения на ребрах графа представляют вероятности переходов в поведе­
нии студента, и они также приведены в таблице справа. Рассматривая стро­
ки в таблице, имейте в виду, что вероятности переходов из каждого состоя­
ния (узла) всегда в сумме дают

1.

0.5

А: подготовка к сдаче
экзамена дома

В: игра в видеоигры

С: подготовка к сдаче
экзамена в библиотеке
Т: заключительное
состояние (сон)

Рис.

18.2. Диншиика

поведения студента

··--- -- - - - - - - - - - - - - - - (790) --·----·---··---·- ·--·----- -·-·--

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

Эпизодические иnи продоnжающиеся задачи
По мере взаимодействия агента со средой последовательность наблюде­
ний или состояний формирует траекторию. Траектории бывают двух типов.
Если траектория агента может быть разделена на части, так что каждая из
них начинается в момент времени t

состоянии Sт (при t

=

=

О и заканчивается в заключительном

Т), тогда задача называется -:Jnизодической. С другой

стороны, если траектория длится бесконечно, не попадая в заключительное
состояние, то задача называется продолжающейся.

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

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

или траекторию, которую агент проходит от начального состояния
ключительное состояние

S0

в за­

Sr:

So, Ао, R1, S1, А1, R2, .. " S,, А,, R1+1• .. " Sr-1• Ат-1• Rт, Sт
В приведенном на рис.

18.2

марковском процессе, который изображает за­

дачу студента, готовящегося к сдаче экзамена, мы можем встретить эпизоды
вроде следующих трех примеров:



Эпизод

1: ВВССССВАТ---+ экзамен сдан (финальная награда= +1)



Эпизод

2:



Эпизод

3:

АВВВВВВВВВТ---+ экзамен провален (финальная награда=
ВСССССТ---+ экзамен сдан (финальная награда=

-1)

+1)

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

Отдача
Так называемая отдача в момент времени

t

представляет собой накоп­

ленные награды, полученные на всем протяжении эпизода. Вспомните, что

R1+1 = r -

это ие:недлетшя награда, полученная после выполнения действия

А, в момент времени

t;

более поздними наградами являются

Rt+2 , Rt+3

и т.д.

- - - - - - - - - - - - - - - - - - - - (791 ] - - - - - - - - - - - - - -

fпава

18.

Обучение с подкреплением для принятия решений в сложных средах

Тогда отдача в момент времени

t

может быть рассчитана из немедленной

награды и более поздних наград следующим образом:

Gt
Здесь у

-

~ Rt+I + yRt+2

+ ·/R1+2 +". = LiR1+k+I
k=O

коэффициент дисконтирования с диапазоном (О,

1].

Параметр

у указывает, насколько будущие награды "ценны" в текущий момент вре­

мени

(t).

Обратите внимание, что установка у= О подразумевает, что нас не

заботят будущие награды. В таком случае отдача будет равна немедленной
награде, а более поздние награды после момента времени t

+1

игнориру­

ются, и агент окажется "близоруким". С другой стороны, если у=

1,

тогда

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

Gt

= R1+1 + yG1+1 = r + yG1+1

Смысл в том, что отдача в момент времени

r

t

равна немедленной награде

плюс дисконтированная будущая отдача в момент времени

t + 1.

Это очень

важное свойство, которое облегчает вычисления отдачи .

\\;~ Смысл коэффициента дисконтирования

н~ Чтобы получить представление о коэффициенте дисконтирования,
заметку! взгляните на показанный ниже рисунок, который иллюстрирует ценность заработка стодолларовой банкноты сегодня в сравнении с ее
заработком через год. При определенных экономических условиях,

таких как инфляция, заработок стодолларовой банкноты прямо сей­

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

1

..,,-----,,"

,,'
"-'

....

1

' ...."

"'"--- ... ""

....

$100

~
$90

Сегодня

Через год

- - - - - · --

·- -- - -· --[792) -

-- - -·- --

·

fлава

18.

Обучение с подкреплением для принятия решений в сложных средах

Таким образом, мы говорим, что если эта банкнота стоит
мо сейчас, тогда через год она будет стоить
ента дисконтирования у=

$90

$100

пря­

с учетом коэффици­

0.9.

Давайте рассчитаем отдачу на разных временных шагах для эпизодов,

взятых из предыдущего примера со студентом. Пусть у=

0.9, а единственная
(+ 1 за его сдачу и

предоставляемая награда основана на результате экзамена

-1

за провал). Награды на промежуточных временных шагах равны О.



Эпизод

1:

ВВССССВАТ

-

экзамен сдан (финальная награда

= + 1)

t =О : G0 = R1 + yR 2 + y2R3 + ". + y6R7
-



G0 = О + О х у + ". + 1 х у6 = 0.96 ~ 0.531

t= 1

G 1 = 1 х у5 = 0.590

t=2

G2 = 1 х у4 = 0.656

t =6

G6 = 1

t=7

G7 = l = 1

Эпизод

2:

х у=

0.9

АВВВВВВВВВТ- экзамен провален (финальная награда

t =О

G0 = -1 х у8 = -0.430

t= l

G 1 =-1 х у7 =-0.478

t=8

G8 =-1xy=-0.9

t=9

G9 =-l=-l

=-1)

Расчет отдачи для третьего эпизода оставлен в качестве упражнения для

самостоятельной проработки.

Поnитика

Политика, обычно обозначаемая как ж(а 1 s), представляет собой функ­
цию, которая определяет действие, предпринимаемое следующим, и может

быть либо детерминированной, либо стохастической (т.е. давать вероятность
выбора следующего действия). Стохастическая политика имеет распределе­
ние вероятностей по действиям, которые агент может предпринимать в за­
данном состоянии:

lr(a 1s) ~ Р[А, =а 1S1 = s]

----(793)

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

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

агент благополучно научится оптимизировать свою политику в направлении

достижения оптимальной политики. Оптиwаr1ыюй политикой ;r*(a 1 s) счита­
ется такая политика, которая обеспечивает наивысшую отдачу.

Функция ценности
Функция цеююсти

состояния
стояния

-

(1 aluc.f1mction), также называемая футщией центюсти
(statc-valueJu11ctiot1), измеряет доброкачестветюсть каждого со­
1

другими словами, насколько хорошим или плохим должно быть

индивидуальное состояние. Обратите внимание, что критерий доброкачест­

венности базируется на отдаче.
Теперь, основываясь на отдаче
стояния

s

G1,

мы определяем функцию ценности со­

как ожидаемую отдачу (среднюю отдачу по всем возможным эпи­

зодам) после следования политике

v.(s)

;rr:

!О Е, [ G, 1s, =s] = Е,[~ у ыR,+"1 IS, =s]

В реальной реализации мы обычно оцениваем функцию ценности с при­
менением таблиц поиска, а потому у нас нет нужды повторно вычислять ее
много раз. (Это аспект динамического программирования.) Скажем, на прак­

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

V(s).
В реализации на языке Python ею может быть список или массив NumPy, ин­
дексы которого соответствуют различным состояниям, либо словарь Python,
чьи ключи отображают состояния на надлежащие ценности.
Кроме того, мы также можем определить ценность для каждой пары "со­

стояние-действие", что называется функцией ценности действия

11alue /u11ctim1)

и обозначается как

лается на ожидаемую отдачу
предпринимает действие А 1

G1,

= а.

q;r:(s, а).

(11tlio11-

Функция ценности действия ссы­

когда агент находится в состоянии

S1 = s

и

Расширив определение функции ценности

состояния на пары "состояние-действие", вот что мы получим:

[794)---~------

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

Подобно обозначению оптимальной политики как кiа 1s), v.(s) и q.(s, а)
также обозначают оптимальные функции ценности состояния и ценности
действия.

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

\\:~ Отличие между наrрадой, отдачей и функцией ценности

н~ Награда является последствием того, что агент предпринял неко­
з~метку! торое действие при заданном текущем состоянии среды. Другими
словами, награда представляет собой сигнал, который агент полу­
чает, когда выполняет действие для перехода из одного состояния в

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

или отрицательную награду

-

вспомните

о нашем примере с шахматами, где положительная награда получа­
лась только при выигрыше партии, а награды для всех промежуточ­

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

ляется

-

именно здесь в игру вступает функция ценности. Обычно

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

тера нет никакой (положительной) награды. Теперь предположим,

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

он не принимает немедленную награду, делая ход, на котором берет­
ся ферзь соперника. Однако новое состояние (состояние доски после

взятия ферзя) может иметь высокую це1111ость, которая может выдать
награду (если позже партия выиграна). По идее высокая ценность, ас­

социированная с взятием ферзя соперника, связана с тем фактом, что
это часто в итоге обеспечивает выигрыш партии

-

и соответственно

высокую ожидаемую отдачу, или ценность. Тем не менее, обратите

---[795)----------

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

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

Короче говоря, отдача представляет собой взвешенную сумму
наград для полного эпизода, которая будет равна дисконтированной
финальной награде в нашем примере с шахматами (т.к. есть только
одна награда). Функция ценности является математическим ожида­
нием по всем возможным эпизодам, которая по существу рассчиты­

вает то, насколько в среднем "ценно" делать определенный ход.

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

Динамическое проrраммирование
с испоnьзованием уравнения Беnnмана
Уравнение Беллмана

один из центральных элементов многих алго­

-

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

Базируясь на рекурсивном уравнении для общей отдачи,

G, = r+yGt+I•

мы

можем переписать функцию ценности следующим образом:

vir(s)

~ Eir[G,IS,=s]
Eir[r+ yG1+ 1 1s1 = s]
r+yEir[G1+1 IS1 =s]

Обратите внимание, что немедленная награда

r вынесена из математичес­

кого ожидания, поскольку она является константой с известной величиной
в момент времени

t.

Аналогично мы можем записать для функции ценности действия:

qir(s,a)

~

Eir[ G1 1S,

= s, А,= а]

I
+yE11"[G1+1 Ist = s, At =а]

Eir[r+ yG 1+1 S1 = s, А 1 =а]
r

-----------(796]

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

Для расчета математического ожидания мы можем использовать динами­

ку среды, взяв сумму по всем вероятностям следующего состояния
ответствующих наград

vк(s) = L

аеА

s'

и со­

r:

7r(als)

L

s'eS,1·eR

p(s',rls,a)[r+yEк[G 1 + 1 IS1+ 1= s']]

Теперь мы видим, что математическое ожидание отдачи, Eк[Gt+ 1 \ 81+1 = s'],
по существу является функцией ценности состояния, vп:(s'). Таким образом,
мы можем записать vп:(s) как функцию от

vп:(s)=

vn:(s'):

L 7r(als)s'eS,reR
L p(s',r'ls,a)[r'+yvn:(s')]

аеА

Результат называется уравнением Белю.юна, которое связывает функцию
ценности для состояния

ния

s'.

s

с функцией ценности для последующего состоя­

Оно значительно упрощает вычисление функции ценности, потому

что устраняет итерационный цикл по оси времени.

Аnrоритмы обучения с подкреплением
В текущем разделе мы рассмотрим комплект алгоритмов обучения и на­
чнем с динамического программирования, которое предполагает, что дина­

мика переходов (или динамика среды, т.е. p(s', r \ s, а)) известна. Однако в
большинстве задач обучения с подкреплением это не так. Чтобы справить­
ся с проблемой неизвестной динамики среды, были разработаны методики,
предусматривающие обучение через взаимодействие со средой. В их чис­

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

18.3

описан процесс продвижения алгоритмов обучения с подкреплени­

ем от динамического программирования до Q-обучения.
В последующих разделах главы мы пошагово разберем каждый алго­

ритм обучения с подкреплением, указанный на рис.

18.3.

Мы начнем с ди­

намического программирования, затем перейдем к методу Монте-Карло и
в заключение обсудим метод временных разностей, а также его ответвле­

ния

-

метод внутри политики,

SARSA

(slate-acti011-1·eи·m·ti--sl11te-actim1

-

состояние-действие-награда-состояние-действие), и метод вне политики,

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

·-------(797]-----------

Глава

18.

Обучение с подкреплением оля принятия решений в сложных средах

меЮд:•J
SARSA
---политики:

Динамическое

Метод

Метод
Монте-Карло

проrрамми­
рование

временных

разностей

----]
Метод вне
политики:



Предполагает,



что динамика
среды доступна

Динамика среды

•Улучшение

недоступна



Взаимодействует
со средой

_0-обу~!~-~---

по сравнению с

методом Монте-Карло



Обновляет функции
ценности после
каждого шага

Рис. 18.З. Процесс продви:жения а'lгорит.wов обучения с подкрепленuе.J11

Динамиче(кое проrраммирование
В этом разделе мы сосредоточим внимание на решении задач обучения с
подкреплением при следующих допущениях:



мы обладаем полным знанием динамики среды, т.е. вероятности всех

переходов p(s ', r 1s, а) известны;



состояние агента имеет марковское свойство, а потому следующее

действие и награда зависят только от текущего состояния и выбора
действия, которое мы делаем в данный момент или на текущем вре­
менном шаге.

Математическая формулировка для задач обучения с подкреплением, ис­
пользующая марковский процесс принятия решений (МОР), была представ­

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

функции ценности vп:(s), следующей политике 1r, и уравнение Беллмана, ко­
торое выводилось с применением динамики среды.

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

Проблема, связанная с использованием динамического программирования,
заключается в том, что оно предполагает наличие полного знания динамики

среды, которое для большинства реальных приложений обычно необосно­

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

лением в простой манере и служит мотивом к применению более развитых
и сложных алгоритмов.

-------(798]·

[лава

Обучение с подкреплением для принятия решений в сложных средах

18.

В задачах, описанных в последующих подразделах, преследуются две
цели.

Получить точную функцию ценности состояния,

1.

vis);

это также из­

вестно как задача прогнозирования и выполняется с помощью оцеик:и
политики.

Найти оптимальную функцию ценности,

2.

обобщеииой итерации политики

Оценка политики

-

v*(s), что достигается посредством
({?.c11emlizeti policy itcr-ati011 --· (iPI).

проrнозирование функции ценности

с помощью динамическоrо проrраммирования

Основываясь на уравнении Беллмана, мы можем рассчитать функцию
ценности для произвольной политики 7С с помощью динамического програм­

мирования, когда динамика среды известна. При расчете этой функции цен­
ности мы можем адаптировать итеративное решение и начать с ценностей

инициализированных нулями для всех состояний. Затем на каждой
итерации i + 1 мы обновляем ценности всех состояний на базе уравнения

/ 0 >(s),

Беллмана, которое в свою очередь основано на ценностях состояний из пре­
дыдущей итерации

i:

(s)=~
" 7r(ais) "~ p(s',r'ls,a)[r+yv'(s')]
о
v'
а

s'eS,reR

Можно показать, что с увеличением количества итераций до бесконеч­

ности

vо1 (s) сходится в точную функцию ценности состояния vis).

Также обратите здесь внимание, что нам не нужно взаимодействовать со
средой. Причина в том, что мы точно знаем динамику среды. В итоге мы

можем задействовать эту информацию и легко оценить функцию ценности.

После расчета функции ценности возникает очевидный вопрос: какую
пользу она способна нам принести, если наша политика по-прежнему яв­
ляется случайной? Ответ заключается в том, что на самом деле мы можем
использовать рассчитанную функцию ценности

vis)

для улучшения нашей

политики, как будет показано далее.

Улучшение политики с использованием ожидаемой функции ценности
Теперь, когда мы рассчитали функцию ценности
ющей политике

политику

7r.

7r,

имеет смысл применить

vis)

vir(s),

следуя существу­

и улучшить существующую

Это значит, что мы хотим найти новую политику

7r 1,

которая для

-----[799]-----------

fлава

18. Обучение с подкреплением для принятия решений в сложных средах

каждого состояния

s,

следующего

7r',

выдавала бы более высокую или хотя

бы равную ценность, чем при использовании существующей политики

7r.

Математически мы можем выразить такую цель для улучшенной политики

7С' следующим образом:

\1 s

es

Первым делом вспомните, что политика 7С определяет вероятность выбора
каждого действия а, пока агент находится в состоянии
литику

7r',

s.

Чтобы отыскать по­

которая всегда имеет лучшую или равную ценность для каждого

состояния, мы сначала рассчитываем функцию ценности действия,

для каждого состояния

s

и действия а, базируясь на вычисленной ценности

состояния с применением функции ценности
состояниям и для каждого состояния

стояния

s',

q11:(s, а),

s

vis).

Мы проходим по всем

сравниваем ценность следующего со­

куда произошел бы переход в случае выбора действия а.

После получения наивысшей ценности состояния за счет оценки всех

пар "состояние-действие" посредством

qis, а)

мы можем сравнить соответ­

ствующее действие с действием, выбранным текущей политикой. Если дей­

ствие, предлагаемое текущей политикой (т.е. arg max 7r(a 1s)), отличается от
а

действия, предлагаемого функцией ценности действия (т.е.

arg max qis,a)),
а

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

qis,

а). Такая процедура называется алгоритмом улучшения политики.

Итерация поnитики
С использованием алгоритма улучшения политики, описанного в преды­

дущем подразделе, можно показать, что улучшение политики будет опреде­
ленно давать лучшую политику, если только текущая политика уже не явля­

ется оптимальной (т.е. vis)

= v11:.(s) = v*(s) для каждого s е S). Следовательно,

если мы многократно выполним оценку политики, а за ней улучшение поли­
тики, то гарантированно отыщем оптимальную политику.

Обратите внимание, что такая методика называется обобщенной
итерацией политики

((;PJ),

которая распространена во многих ме­

тодах обучения с подкреплением. Мы будем применять
главе для методов Монте-Карло и временных разностей.

----------(800)

GPI

далее в

[лава

18.

Обучение с подкреплением для принятия решений в сложных средах

Итерация ценности
Мы выяснили, что за счет повторения оценки политики (расчета vir> 1.mport gym
>>> env = gym.rnake ( 'CartPole-vl')
>>> env.observation_space
Вох(4,)

>>> env.action_space

Discrete(2)
В предыдущем коде мы создали среду для задачи с
ством наблюдений для этой среды является Вох ( 4 ,

),

CartPole.

Простран­

что представляет че­

тырехмерное пространство, соответствующее четырем вещественным чис­
лам:

местоположение телеги,

скорость телеги,

угол

отклонения дышла

угловая скорость вершины дышла. Пространство действий
пространство

Discrete ( 2)

-

и

дискретное

с двумя вариантами: толкание телеги влево

или вправо.

Объект среды
имеет метод

env, созданный ранее вызовом gym.make ( 'CartPole-vl'),
reset (), который мы можем использовать для повторной ини-- (809) -

- - --- --- - - --- -

[паба

18.

Обучение с подкреплением для принятия решений в сложных средах

циализации среды перед каждым эпизодом. Вызов метода

ществу будет устанавливать начальное состояние дышла

>>> env. reset ()
array([-0.03908273, -0.00837535,
Значения в возвращенном из метода

Когда вызывается метод

0.033

reset (),

по су­

0.03277162, -0.0207195])

env. reset ()

телега имеет начальное местоположение
отклонения дышла составляет

reset ()
(S0):

-0.039

массиве означают, что

-0.008, а угол
скорости -0.021.

при скорости

радиана при угловой

упомянутые значения инициализируют­

ся случайными величинами с равномерным распределением в диапазоне

[-0.05, 0.05].
После сброса среды мы можем взаимодействовать с ней, выбирая дей­
ствие и запуская его за счет передачи методу

>>> env.step(action=O)
(array( [-0.03925023, -0.20395158,
1.0, False, {})
>>> env. step (action=l)
(array([-0.04332927, -0.00930575,
1.0, False, {))
Посредством предыдущих двух

s tep ( ) :

0.03235723,

0.28212046]),

0.03799964, -0.00018409] ),

env. step ( action=O) и
телегу влево (action=O) и затем

команд,

env. step (action=l), мы толкнули
вправо (action=l). Базируясь на выбранном

действии, телега и ее дышло

могут перемещаться в соответствии с законами физики. Каждый раз, когда
мы вызываем метод

env. s tep ( ) ,

он возвращает кортеж, состоящий из че­

тырех элементов:



массив для нового состояния (или наблюдений);



награда (скалярное значение типа



флаг окончания

(True



словарь

содержащий вспомогательную информацию.

Объект

Python,

env

или

float);

False);

также имеет метод

render (),

который мы можем вызывать

после каждого шага (или последовательности шагов) для визуализации сре­
ды и движения телеги с дышлом во времени.

Эпизод заканчивается, когда угол наклона дышла становится больше

12

градусов (в любую сторону) относительно воображаемой вертикальной оси

------------[810)

Глава

18.

Обучение с подкреплением для принятия решений в сложн111х средах

или местоположение телеги сдвигается больше , чем на

единицы , от по­

2.4

зиции центра. Награда, определенная в этом примере, заключается в доведе­

нии до максимума времени, в течение которого телега и дышло стабилизи­

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

Пример

(

миром (етки

После представления среды
комплектом инструментов

CartPole
OpenAI Gym

в качестве разминки для работы с
мы перейдем к другой среде. Мы

рассмотрим пример с миром сетки, который является упрощенческой сре­

дой , имеющей т строк п столбцов . Принимая т
разить среду, как показано на рис .

= 4 и п = 6,

18.6.

Пространство действ=+­

(вверх, вниз, влево, впра во)

Награды :

+1
{ -1

при попадании в "золотое' состояние
при попадании в ловушку

О иначе

Рис.

В среде есть

30

18.6.

Заключительные состояния :
"Золотое'

l О, 15

Ловушки

состояние~

GGG0
Начальное состояние: G

Среда .мира сетки

разных возможных состояний . Четыре состояния явля­

ются заключительными: горшок с золотом в состоянии
состояниях

мы можем изоб­

и

22.

16

и три ловушки в

Попадание в любое из заключительных состояний

заканчивает эпизод, но с отличием между "золотым" состоянием и ловуш­
ками . Попадание в "золотое" состояние выдает положительную награду

+ 1,

тогда как перемещение агента в одну из ловушек выдает отрицательную на­

граду

-1.

Все остальные состояния имею награду О. Агент всегда начинает

с состояния О. Следовательно, всякий раз, когда мы сбрасываем среду, агент
будет возвращаться обратно в состояние О . Пространство состояний состоит
их четырех направлений: перемещения вверх, вниз, влево и вправо. Когда
агент находится на внешней границе сетки , выбор действия, которое приве­
ло бы к покиданию сетки, не изменяет состояние.
· --·---- - - ----- ------ - ----~

[811]

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

Далее мы посмотрим, как реализовать такую среду на языке
применением пакета

Реализация среды мира сетки в

с

OpenAI Gym

При экспериментировании со средой мира сетки посредством

Gym

Python

OpenAI Gym.

OpenAI

настоятельно рекомендуется использовать редактор сценариев или

IDE-cpeдy, а не выполнять код интерактивно.

Первым делом мы создаем новый сценарий

env. ру,

Python

по имени

gridworld

после чего импортируем необходимые пакеты и две вспомогатель­

ных функции, которые определим для построения и визуализации среды.

Для визуализации среды библиотека

Pyglet

OpenAI Gym

применяет библиотеку

и предоставляет удобные классы-оболочки и функции. Мы будем ис­

пользовать эти классы-оболочки для визуализации среды мира сетки в сле­

дующем примере кода. Дополнительные детали о классах-оболочках ищи­
те по ссылке

https: / /gi thub. com/openai/gym/ЬloЬ/master /gym/
envs/classic control/rendering.py.
Ниже приведен пример кода, где задействованы классы-оболочки:

##

Сценарий:

gridworld_env.py

import numpy as np
from gym.envs.toy_text import discrete
frorn collections import defaultdict
import time
irnport pickle
import os
frorn gym.envs.classic_control import rendering

CELL SIZE = 100
= 10

МARGIN

def qet_coords(row, col, loc='center'):

(col+l.5) * CELL SIZE
(row+l.5) * CELL SIZE
if loc == 'center' :

хе=
ус=

return

хе,

ус

elif loc == 'interior corners':
half- size = CELL- SIZE//2 - МARGIN
xl, xr = хе - half_size, хе + half size
yt, уЬ =хе - half_size, хе + half_size
retпrn [ (xl, yt), (xr, yt), (xr, уЬ), (xl,

уЬ)]

-------------(812]--------

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

elif loc == 'interior_ triangle' :
xl, yl =хе, ус+ CELL_SIZE//3
х2, у2 =хе+ CELL_SIZE//3, ус - CELL_SIZE//3
х3, у3 =хе - CELL_SIZE//3, ус - CELL_SIZ~//3
retu:r:·n [ (xl, yl), (х2, у2), (х3, у3)]
def draw_object(coords_list):
i.f len(coords_list) == 1:
#->круг
obj = rendering.make_circle(int(0.45*CELL_SIZE))
obj_transform = rendering.Transform()
obj.add_attr(obj_transform)
obj_transform.set_translation(*coords_list[O])
obj.set_color(0.2, 0.2, 0.2)
#->черный
e1if len (coords_list) == 3:
# -> трепугольник
obj = rendering.FilledPolygon(coords_list)
obj.set_color(0.9, 0.6, 0.2)
#->желтый
>J.lif 1cш(coords_list) > 3:
#->многоугольник
obj = rendering.FilledPolygon(coords_list)
obj.set_color(0.4, 0.4, 0.8)
#->синий
rеtш::л obj
Первая вспомогательная функция,

get coords (),

возвращает коорди­

наты геометрических фигур, которые мы будем применять для аннотиро­

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

draw _ obj ect (),

которая на основе длины входного списка координат

принимает решение о том, что вычерчивать

-

круг, треугольник или мно­

гоугольник.

Теперь мы можем определить среду мира сетки. В том же самом фай­
ле

( gr idwor ld env. ру)

унаследованный

функцией в этом

мы определяем класс по имени

Gr idWor ldEnv,
от класса DiscreteEnv из OpenAI Gym. Самой важной
(),где мы
классе является метод конструктора
init

определяем пространство действий, указываем роль каждого действия и за­

даем заключительные состояния ("золотое" и ловушек):

c1ass GridWorldEnv(discrete.DiscreteEnv):
cJ.ef
init
, num_rows=4, num_cols=б, delay=0.05):
' ' . num rows = num rows
' . num cols = num cols
.delay = delay
move _ up =

lamЬda

row, col: (max (row-1,

О)

, col)

[813]------------

Глава

18. Обучение с подкреплением для принятия решений в сложных средах

move down = lamЬda row, col: (min(row+l, num_rows-1), col)
move_left = lamЬda row, col: (row, max(col-1, 0))
move_right = lamЬda row, col: (
row, min(col+l, num_cols-1))
.action_defs={O: move_up, 1: move_right,
2: move_down, 3: move_left}

##

Количество состояний/действий

nS = num cols*num rows
nA = len(
f.action_defs)
scJ. :'. grid2state_dict={ (s//num_cols, s%num cols): s
.for s in range (nS)}
t 1. state2grid_dict={ s: (s/ /num_cols, s%num_cols)
for s in range (nS)}

##

"Золотое" состояние

gold_cell = (num_rows//2, num_cols-2)

##

Состояния ловушек

trap_cells = [( (gold_cell[OJ+l), gold cell[l]),
(gold_cell[OJ, gold_cell[l]-1),
((gold_cell[0]-1), gold_cell[l])]
gold_state =
11' .grid2state_dict [gold_cell]
trap_states = [ ;,;е1 t. grid2state_dict [ (r, с)]
for (r, с) in trap_cells]
c:".terminal_states = [gold_state] + trap_states
print (sr;: i • terminal_states)
##Построение вероятностей переходов
Р

= defaultdict(dict)

for s in range (nS) :

row, col = scit.state2grid_dict[s]
P[s] = defaultdict(list)
for а in range (nA) :
action =
~· . action_ defs [а]
next_s = s,; U. grid2state_dict [action (row, col)]

##

Заключительное состояние

if se.l.; .is_terminal (next_s):
-.r = (1.0 if next_s == S( if.terminal_states[O]
0

eJ.se -1.0)

else:
r =О.О

- - - - - - - - (814)

Глава

if

18.
!

Обучение с подкреплением для принятия решений в сложных средах
Г.is_terminal(s):

done = True
next s = s
else:
done = False
Р [ s] [а] = [ ( 1 . О, next _ s, r, done) ]

## Начальное распределение
isd = np. zeros (nS)
isd[OJ = 1.0
super(GridWorldEnv,

) t).

состояний

init

.''" +·. viewer = None
·• f ._build_display(gold_cell,

(nS, nA,

isd)

Р,

trap_cells)

def is_terminal (~>0 .! :', state):
return state in :'
.terminal states
def _build_display ( .-''.; i .::·, gold_cell, trap_cells):
screen_width = ( l ' .num_cols+2) * CELL_SIZE
screen_height = (se:,.num_rows+2) * CELL_SIZE
: . viewer = rendering. Viewer ( screen_ width,
screen_height)
all_objects = []

## Список координат
bp_list = [

граничных точек

(CELL_SIZE-МARGIN,

CELL_SIZE-МARGIN),

(screen_width-CELL_SIZE+МARGIN,

CELL_SIZE-МARGIN),

(screen_width-CELL_SIZE+МARGIN,
screen_height-CELL_SIZE+МARGIN),
(CELL_SIZE-МARGIN,

screen_height-CELL_SIZE+МARGIN)

border = rendering.PolyLine(bp_list, True)
border.set_linewidth(S)
all_objects.append(border)

## Вертикальные линии
for col in range(:;·круги

for cell in trap_ cells:
trap_coords = get_coords(*ce1,J.., loc='center')
all_objects.append(draw_object([trap_coords]))

## Золото: --> треугольник
gold_coords = get_coords(*gold_cell,
loc='interior_triangle')
all_objects.append(draw_object(gold_coords))
##Агент:

-->

квадрат или робот

if (os.path.exists('robot-coordinates.pkl') and

CELL_SIZE==lOO):
agent_coords = pickle.load(
open('robot-coordinates.pkl', 'rb'))
starting_coords = get_coords(O, О, loc='center')
agent_coords += np.array(starting_coords)
else:
agent_coords = get_coords(
О, О, loc='interior_corners')
agent = draw_object(agent_coords)
: '. agent_ trans = rendering. Transform ()
agent. add_attr (.с'''· 1 '~. agent _ trans)
all_objects.append(agent)
for obj in all_objects:
i • viewer. add_geom (obj )
, mode=' hwnan', done=Fa1se) :

def render (
it' done:

sleep_ time = 1
else:
'F.delay
J 1 .num cols
-: г. s 11 } т" .nurn cols
(х_coord+O) * CELL SIZE
(у_ coord+O) * CELL SIZE

sleep.-~time

coord
у_coord
х coord
у_ coord

х

=
=
=
=

1T.S

%

--------------(816]---------

[лава

18.

Обучение с подкреплением для принятия решений в сложных средах

.agent_trans.set_translation(x_coord, y_coord)
rend = • ,l : • viewer. render (
return_rgb_array=(mode=='rgb_array'))
time.sleep(sleep_time)
return rend
def close (
' .1 ) :
if •. -. 1,
viewer:
1



!!.viewer.close()
; . viewer = None
В коде реализован класс среды мира сетки, экземпляры которого мы
можем создавать. Затем мы можем взаимодействовать с экземпляром в ма­

нере, похожей на взаимодействие в примере с

CartPole. Класс реализации
GridWorldEnv наследует такие методы, как reset () для сброса состоя­
ния и step () для запуска действия. Ниже описаны детали реализации.


Мы определяем четыре разных действия, используя лямбда-функции:

move_up (), move_down (), move left ()


Массив

NumPy

по имени

isd

и

move right ().

содержит вероятности начальных состо­

яний, так что при вызове метода

reset ()

(из родительского класса)

случайное состояние будет выбираться на основе этого распределения.

Поскольку мы всегда начинаем с состояния О (левый нижний угол мира
сетки), то устанавливаем вероятность состояния О в
всех остальных



29

1.0,

а вероятности

состояний в О.О.

Вероятности переходов, определенные в словаре

Python

по имени Р,

устанавливают вероятности перемещения из одного состояния в другое

при выборе некоторого действия. В итоге мы располагаем вероятност­
ной средой, где выбор действия может иметь отличающиеся исходы в
зависимости от стохастичности среды. Для простоты мы имеем только
один исход, которым является изменение состояния в направлении вы­

бранного действия. Наконец, вероятности переходов будут применять­
ся функцией



env. step ()

Кроме того, функция
ную визуализацию

для определения следующего состояния.

_build_display () будет
среды, а функция render ( ) -

настраивать началь­
показывать переме­

щения агента.

[817) - - · · - - - - ------------·------

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

-~-

Обратите внимание , что во время процесса обучения мы не знаем

Совет

ствие со средой . Таким образом, у нас нет доступа к Р за пределами

вероятности переходов, а наша цель

-

обучение через взаимодей­

определения класса.

Итак, мы можем протестировать реализацию : создать новую среду и
визуализировать случайный эпизод, выбирая в каждом состоянии слу­

чайные действия. Поместите следующий код в конец того же сценария
и запустите сценарий :

(gridworld_env.py)
if

name
== ' main '·
env = GridWorldEnv(S, 6)
for i in ra n ge(l):
s = env. reset ()
env.render(mode='human', done=Fals e )
whi le True :
action = np.random . choice(env.nA)
res = env.step(action)
pr i nt ( 'Action ', env. s, action, ' - > ', res)
env.render(mode='human', done=res[2])
if res [2]:
break
env. close ( )
После выполнения сценария вы



должны увидеть визуализацию сре­
ды сетки мира, как показано на рис.

•:


"

1

18.7.
Решение задачи с миром сетки
с помощью Q-обучения



После
процесса

рассмотрения
разработки

настройки

OpenAI Gym
18. 7.

и

алгоритмов

обучения с подкреплением, а так­
же

Рис.

теории

Визуал изация среды

сетки мира

среды

посредством

мы займемся реализа­

цией самого популярного в настоя­

щее время алгоритма из данной об-

- --

(818) - --

---

[лава

ласти

-

18. Обучение с подкреплением для принятия решений в сложных средах

Q-обучения. Для этого мы будем использовать пример с сеткой

мира, который уже реализован в сценарии

gridworld_ env. ру.

Реаnизация аnrоритма Q-обучения
Далее мы создадим новый сценарий и назовем его
сценария

##

agent. ру

Сценарий:

agent. ру.

Внутри

мы определяем агент для взаимодействия со средой:

agent.py

f'rom collections import defaultdict
import num.py as np
class Agent (object):
def
init (
с:(.: •. :: ,
env,
learning_rate=0.01,
discount_factor=0.9,
epsilon_greedy=0.9,
epsilon_min=0.1,
epsilon_decay=0.95):
'." .env = env
:' '·: : : . lr = learning_rate
'"'~ • ! • gamma = discount _ factor
о :· .:. i • epsilon = epsilon_greedy
:'.'·.~С. epsilon_min = epsilon_min
''':·l.: .epsilon_decay = epsilon_decay

## Определение q_ tаЫе
:...... q_tаЫе = defaul tdict ( lamЬda: np. zeros С· . : : • env. nA) )

def choose_action (':·:· '. ~., state) :
if np.random.uniform() < ·.. , :~ .epsilon:
action = np. random. choice ( '"'; :•·. env. nA)
else:

q_vals = '"' lt.q_taЫe[state]
perm_actions = np.random.permutation (:с' 1. .: .env.nA)
q_vals = [q_vals[a] for а in perm_actions]
perm_ q_argmax = np. argmax (q_vals)
action = perm_actions[perm_q_argmax]
ret\Jrn action
def _learn (:•;:· 1 i', transition):
s, а, r, next_s, done = transition
q_val = :•;; · .q_taЫe[s] [а]

---[819)

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

if cione:

q_target = r
;:й";е:

##

Обновление

q_ tаЫе
+=

.q_taЫe[s] [а]

##

. :

.gamma*np.max(

q_target = r +

.q_taЫe[next_s])

.lr * (q_target - q_val)

Корректировка эпсилон

'._adjust_epsilon ()
.) :

def' _adjust_epsilon (
• epsilon >
i:f~·
· : . epsilon *=
Конструктор

_ini t _ ( )

· . epsilon_ min:
; .epsilon_decay
настраивает разнообразные гиперпараметры,

такие как скорость обучения, коэффициент дисконтирования (у), а также па­
раметры для в-жадной политики. В исходном положении мы начинаем с вы­
сокого значения в, но метод

adjust_epsilon ()

понижает его до тех пор,

пока не достигнет минимального значения, вмин· Метод

choose _action ()

выбирает действие на основе в-жадной политики следующим образом. Для
определения, должно ли действие выбираться случайно или же иным спосо­

бом, на основе функции ценности действия, отбирается случайное число с
равномерным распределением. Метод

_learn ()

реализует правило обнов­

ления для алгоритма Q-обучения. Для каждого перехода он получает кортеж,

(s), выбранное
состояние (s') и флаг,

содержащий текущее состояние
награду

(r),

следующее

действие (а), наблюдаемую
который устанавливает, до­

стигнут ли конец эпизода. Целевая ценность равна наблюдаемой награде

(r),

если переход помечен как конец эпизода; в противном случае целевая цен­

ность вычисляется как

r

+у max

Q((s', а)).

а

На следующем шаге мы создаем новый сценарий

qlearning. ру,

чтобы

собрать все вместе и обучить агент с применением алгоритма Q-обучения.
В показанном ниже коде мы определяем функцию

run _ qlearning (),

которая реализует алгоритм Q-обучения, имитируя эпизод с помощью вы­

() агента и запуска среды. Затем кортеж пе­
_learn () агента для обновления функции цен­

зова метода_ choose _ action

рехода передается методу

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

+ 1)

-\

и длины эпизодов (количество перемещений, предпринятых агентом

с начала до конца эпизода).

-----------·-·-- [820] - .

--------~------

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

В заключение посредством функции

plot _ learning_ history ()

вы-

черчиваются графики для финальных наград и количества перемещений.

##

Сценарий:

qlearning.py

froru gridworld_env import GridWorldEnv
Erom agent import Agent
f rom collections i.mport namedtuple
i:mport~ ma tplotlib. pyplot as pl t
import numpy as np
np.random.seed(l)
Transition = namedtuple(
'Transition', ('state', 'action', 'reward',
'next state', 'done'))
cit;!f

run_qlearning(agent, env, num_episodes=SO):
history = []
t·or episode in range (num_episodes):
state = env. reset ()
env.render(mode='human')
final_reward, n_moves = О. О, О
while True:
action = agent.choose_action(state)
= env.step(action)
next_s, reward, done,
agent._learn(Transition(state, action, reward,
next_s, done))
env.render(mode='human', done=done)
state = next s
n moves += 1
if done:

t)reak
final reward = reward
history.append( (n_moves, final_reward))
print ( 'Episode %d: Reward %• lf #Moves %d'
% (episode, final_reward, n_moves))
i-etu:r:n

history

def plot_learning_history (history):
fig = plt.figure(l, figsize=(l4, 10))
ах= fig.add_subplot(2, 1, 1)
episodes = np. arange ( lен (history) )
moves = np.array( [h[O] for h in history])
plt.plot(episodes, moves, lw=4,
marker='o', markersize=lO)

- [821 )--·-----·------------------

Глава

18.

Обучение с подкреплением для принятия решений в сложнЬlх средах

ax.tick_params(axis='both', which='major', labelsize=lS)
plt.xlabel ('Эпизоды', size=20)
рlt.уlаЬеl('Количество перемещений', size=20)
ах= fig.add_subplot(2, 1, 2)
rewards = np.array( [h[l] f'or h in history))
plt.step(episodes, rewards, lw=4)
ax.tick_params(axis='both', which='major', labelsize=15)
pl t. xlabel ( 'Эпизоды' , si ze=20)
plt.ylabel ('Финальные награды', size=20)
plt.savefig('q-learning-history.png', dpi=ЗOO)
plt. show ()
if
name
== ' main ' ·
env = GridWorldEnv(num_rows=S, num_cols=б)
agent = Agent(env)
history = run_qlearning(agent, env)
env.close()
plot_learning_history(history)
Запуск этого сценария приведет к выполнению программы Q-обучения
для

50

эпизодов. Поведение агента будет визуализировано, и вы сможете

увидеть, что в начале процесса обучения агент по большей части оказыва­
ется в состояниях с ловушками. Но с течением времени он извлекает уроки
из своих неудач и в конечном итоге находит "золотое" состояние (скажем,

первый раз в эпизоде
ставлены на рис.

Количество перемещений и награды агента пред­

7).

18.8.

10

-

1.0

~
е"'"
~
"

20

Эпизоды

30

40

50

30

40

50

0.5

о.о

JJ

с;

~ -0.S
s

е

-1 .О

-

-

10

20
Эпизоды

Рис.

18.8.

Количество пере.wещепuй и награды агента

· - - - (8221 - --- - - --

- - - -- --

fлава

18.

Обучение с подкреплением для принятия решений в сложных средах

Вычерченные графики хронологии обучения, приведенные на рис.
указывают на то, что после

20

18.8,

эпизодов агент выяснил кратчайший маршрут

к "золотому" состоянию. В результате длины эпизодов после 30-го эпизода
оказываются более или менее одинаковыми с небольшими отклонениями
из-за е-жадной политики.

Обзор rnyбoкoro Q·обучения
В предыдущем коде была представлена реализация популярного алго­
ритма Q-обучения для примера с миром сетки. Пример включал дискрет­
ное пространство состояний с размером
Q-ценности в словаре

30,

где было достаточно хранить

Python.

Тем не менее, мы обязаны отметить, что временами количество состоя­

ний может стать очень большим

чуть ли не бесконечно большим. Кроме

-

того, вместо работы с дискретными состояниями мы можем иметь дело с не­

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

Чтобы решить указанные проблемы, вместо представления функции цен­
ности в табличном формате вида

V(S1)

или

Q(S,,

А,) для функции ценности

действия мы используем подход с аппрокси.иацией фуикции. Здесь мы опре­
деляем параметрическую функцию

vw(xs), которая способна научиться ап­

проксимировать настоящую функцию ценности, т.е. vw(xs) ~

virCs),

где xs -

набор входных признаков (или "снабженных признаками" состояний).

Когда функция аппроксиматора qw(xs, а) является глубокой нейрон­
ной сетью, результирующая модель называется г.'1убокой Q-сетью

(tfi:cp

()- нсlн'огk -- - IH)i\! ). Для обучения модели DQN веса обновляются в соот­
ветствии с алгоритмом Q-обучения. Пример модели

DQN

показан на рис.

18.9,

где состояния представлены как признаки, переданные первому слою.

А теперь давайте посмотрим, как обучить модель

DQN

с применением

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

----------------------------- [823) ------------------

[лава

18.

Обучение с подкреплением для принятия решений в сложных средах

Скрытый
слой

Скрытый

СЛОЙ

1

2



признаками •

Ожидаемые

состояние х 5

qw(xs,a)

Рис.

Обучение модеnи

ценности

действий

DQN

18. 9.

Приwер люде.7u

DQN

в соответствии с аnrоритмом О-обучения

В этом разделе мы рассмотрим процедуру обучения модели

DQN

с ис­

пользованием алгоритма Q-обучения. Глубокое Q-обучение требует вне­
сения ряда модификаций в ранее реализованный стандартный алгоритм

Q-обучения.
Одной такой модификацией является метод

choose action ()

агента,

где в предыдущем разделе просто производился доступ к ценностям дейс­

твий, хранящимся в словаре. Теперь данный метод нужно изменить, чтобы
выполнять прямой проход нейросетевой модели для вычисления ценностей
действий.

Другие модификации, необходимые для алгоритма глубокого Q-обучения,
описаны в последующих двух разделах.

Память воспроизведения

С применением табличного метода при Q-обучении мы могли обновлять
ценности для индивидуальных пар "состояние-действие", не влияя на цен­

ности остальных. Однако теперь, когда мы аппроксимируем

q(s, а) с помо­

щью нейросетевой модели, обновление весов для какой-то пары "состоя­
ние-действие" вполне вероятно повлияет на выход остальных состояний.

При обучении нейронных сетей с использованием стохастического градиен­
тного спуска для задачи с учителем (например, задачи классификации) мы

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

· -- - ~------- ---- -----

(824)

Глава

18.

Обучение с подкреплением для принятия решений в сложнЬ1х средах

В Q-обучении это неосуществимо, поскольку эпизоды будут изменяться
во время обучения и в результате снизится вероятность будущего посеще­
ния ряда состояний, которые посещались на ранних стадиях обучения.
Кроме того, еще одна проблема заключается в том, что при обучении
нейронной сети мы предполагаем, что обучающие образцы независи:иы и
uдеитuчно распределены. Тем не менее, образцы, взятые из некоторого эпи­
зода агента, не являются независимыми и идентично распределенными, т.к.

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

действует со средой и генерирует пятерку перехода

qw(xs, а), мы сохраня­

ем большое (но конечное) количество переходов в буфере памяти, который
часто называется паwятью воспроизведения

(n!play

т е тогу). После каждого

нового взаимодействия (т.е. агент выбирает действие и запускает его в сре­

де) результирующая пятерка перехода добавляется в эту память .
Для ограничения размера памяти воспроизведения самый старый переход

будет удаляться (скажем, если память реализована в виде списка

Python,

то

мы можем использовать метод рор (О) с целью удаления первого элемента

из списка). Затем из буфера памяти случайным образом выбирается мини­
пакет образцов, который применяется для расчета потери и обновления па­
раметров сети . Процесс проиллюстрирован на рис .

18. 1О .

рор(О)

append(O)

'\

max_ len

/

(~\-.~~~~~~~~~~__,л.___-~~~~~~~~~~-J..
~
Память

Т

воспроизведения

Выбрать
случайным
образом

т

т

'

1 /

-Tiiie
Совет

т

18.10.

т

•••

т

~\
Пакет:

Рис.

т

т

т

т

!/

•••

т

т

Использова11ие памяти воспроизведения

Реализация памяти воспроизведения
Память воспроизведения можно реализовать с использованием списка

Python. При

добавлении к нему нового элемента нам нужно проверять

размер списка и в случае необходимости вызывать метод рор (О) .

(825) -

-

-

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

В качестве альтернативы мы можем применять структуру данных

deque

из Руthоn-библиотеки

вать дополнительный

collections, позволяющую указы­
параметр max _ len, с помощью которого мы

получим ограниченную очередь с двусторонним доступом. Таким об­

разом, когда объект

deque

полон, добавление к нему нового элемента

приводит к автоматическому удалению из него старого элемента.

Обратите внимание, что структура данных
на, чем список

deque

более эффектив­

т.к. удаление первого элемента из списка

Python,

O(n), тогда как сложность
времени выполнения deque составляет 0(1). Исчерпывающие све­
дения о реализации структуры данных deque доступны в официаль­
ной документации по ссылке ht tps: / / docs. python. org / З. 7 /
library/collections.html#collections.deque.
посредством рор (О)

имеет сложность

Определение цеnевых ценностей дnя расчета потери
Еще одно обязательное изменение метода табличного Q-обучения касает­
ся адаптации правила обновления для узнавания параметров модели

DQN.

Вспомните, что пятерка транзакции Т, хранящаяся в пакете образцов, содер­

жит (xs, а, r, xs'• done).
Как показано на рис.

18.11,

мы выполняем два прямых прохода модели

DQN. На первом прямом проходе используются признаки текущего состоя­
ния (xs). Затем на втором прямом проходе применяются признаки следующе­
го состояния (Xs•). В результате мы получим ожидаемые ценности действий
из первого и второго прямых проходов, qw(xs,:) и qw(xs.,:). (Здесь qw(xs,:)

обозначает вектор Q-ценностей для всех действий в А.) Из пятерки перехода
нам известно, что агент выбрал действие а.

Следовательно, согласно алгоритму Q-обучения нам необходимо обновить
ценность действия, соответствующего паре "состояние-действие"

скалярной целевой ценностью

(xs, а),

r+yma,_xqw(xs'•a'). Вместо формированияскаа'ЕА

лярной целевой ценности мы будем создавать целевой вектор пар "состояние-действие", который хранит ценности для остальных действий, а'* а
(см. рис

18.11 ).

-----------(826]--

Глава

18.

Обучение с подкреплением для принятия решений 6 сложных средах

1

1 Текущее состояние :

s

Следующее состояние :

: Выбранное действие : а

'-~:п~~ме_р~~=~ -

Текущее

s'

1

:

- -:

Потери

Цель

"8

Xs . . .

состояние

+ ymaxqw(x5

r
Следующее

1, : )

состояние Xs' . . .

Рис.

18.11.

Два прямых прохода модели

DQN

Мы трактуем это как задачу регрессии, используя следующие три вели­
чины:



ценности, спрогнозированные в текущий момент,



целевой вектор ценностей, описанный выше;



функция издержек в виде стандартной среднеквадратической ошибки

qw(xs,:);

(MSE).
В результате потери будут нулевыми для всех действий кроме а. Наконец,

выполняется обратное распространение рассчитанной потери для обновле­
ния параметров сети.

Реаnизация аnгоритма rnyбoкoro Q-обучения
В заключение мы применим все рассмотренные методики для реализации

алгоритма глубокого Q-обучения. Мы будем использовать представленную

CartPole из комплекта инструментов OpenAI Gym. Вспомните,
CartPole имеет непрерывное пространство состояний размера 4.

ранее среду

что среда

В приведенном далее коде мы определяем класс DQNAgent, который строит
модель и задает различные гиперпараметры.

·-- -- -

------·------- --- - --

----- (827) ----

Глава

18.

Обучение с подкреплением для принятия решений в сложных средах

В сравнении с предыдущим классом агента, который базировался на таб­
личном Q-обучении, класс

DQNAgent

имеет два дополнительных метода.

Метод rememЬer

() будет добавлять новую пятерку перехода в буфер памя­
ти, а метод replay () создавать мини-пакет переходов и передавать его
методу learn () для обновления весовых параметров сети:
import. gym
import. numpy as np
irnport tensorflow as tf
import random
irnport ma tplotlib. pyplot as pl t
from collections irnport namedtuple
frorn collections irnport deque

np.random.seed(l)
tf.random.set_seed(l)
Transition = namedtuple(
'Transition', ('state', 'action', 'reward',
'next state', 'done'))
class DQNAgent:
def
init
env, discount_factor=O. 95,
epsilon_greedy=l.O, epsilon_min=0.01,
epsilon_decay=0.995, learning_rate=le-3,
max_memory_size=2000):
~''" .: . enf = env
! .state size = env.observation_space.shape(O]
, - .action_size = env.action_space.n
:'с,

l;,

1

1i.memory = deque(maxlen=max_memory_size)
gamma = discount _ factor
epsilon = epsilon_greedy
l ' • epsilon_ min = epsilon_ min
: .f .epsilon_decay = epsilon_decay
s· l f:. lr = learning_rate

-"" l



_f.

t J:.

:::·: