/
Author: Банерджи А.
Tags: искусственный интеллект программирование программное обеспечение язык программирования python
ISBN: 978-5-04-211474-8
Year: 2025
Text
Путеводитель
по GPT и AI
Arindam Banerjee
Pythonic AI:
A Beginner’s Guide to Building AI Applications
in Python
Ариндам Банерджи
Pythonic AI:
руководство для начинающих по созданию
приложений ИИ на Python
УДК 004.8
ББК 32.813
Б23
Arindam Banerjee
PYTHONIC AI:
A BEGINNER’S GUIDE TO BUILDING AI APPLICATIONS IN PYTHON
© 2024 BPB Publications, 20 Ansari Road, Darya Ganj, New Delhi 110002
Б23
Банерджи, Ариндам.
Pythonic AI: руководство для начинающих по созданию приложений искусственного интеллекта на Python / Ариндам Банерджи ;
[перевод с английского О. И. Перфильева]. — Москва : Эксмо,
2025. — 528 с. — (Путеводитель по GPT и AI).
ISBN 978-5-04-211474-8
Книга предлагает читателям глубокое погружение в мир искусственного
интеллекта с использованием популярного языка программирования Python.
Руководство идеально подходит тем, кто хочет освоить ключевые инструменты и методологии, включая CNN для компьютерного зрения и NLP-модели
с TensorFlow 2. Предлагаются полезные проекты и продвинутые концепции,
такие как механизмы внимания, трансформеры и GAN. Автор делится опытом
создания сложных моделей и разработки современных приложений ИИ, делая
акцент на практическое освоение технологий.
УДК 004.8
ББК 32.813
ISBN 978-5-04-211474-8
© Перфильев О. И., перевод на русский язык, 2025
© Оформление. ООО «Издательство «Эксмо», 2025
Посвящается
моим вдохновителям, опоре всей моей жизни — моим родителям:
Ашиму и Сипре Банерджи.
Об авторе
Ариндам Банерджи вот уже более тринадцати лет работает в области разработки программного обеспечения в качестве технического руководителя
и инженера-программиста. За это время он создал и внедрил множество решений для обработки данных на основе искусственного интеллекта, которые
успешно решают бизнес-задачи и помогают организациям достичь значительных результатов.
В настоящее время Ариндам занимает должность старшего консультанта по
ИИ в компании Ernst & Young GDS. Он получил степень магистра в области компьютерных наук и инженерии в Технологическом институте Веллуру
в Индии. Его опыт и знания в области ИИ подтверждаются многочисленными сертификатами.
Ариндам активно участвует в международных конференциях в качестве
докладчика и публикует научные статьи на темы, связанные с искусственным интеллектом. На сегодняшний день он является обладателем девяти
патентов.
О рецензенте
Шреяс Кулкарни, родом из индийского штата Махараштра, является истинным поклонником технологий и настоящим энтузиастом в области Data
Science. За годы работы он накопил обширный опыт в решении сложных задач, связанных с данными и технологиями.
Его главная черта — умение создавать оригинальные решения, используя
разнообразные навыки, включая анализ данных, построение моделей, эксперименты с передовыми решениями в области искусственного интеллекта
и управление всем жизненным циклом данных. Он обладает способностью
упрощать сложные задачи, что позволяет эффективно автоматизировать трудоемкие процессы. Его опыт оптимизации кода обеспечивает бесперебойную
реализацию проектов.
Помимо своей профессиональной деятельности, Шреяс увлекается ведением личного блога, где делится своими мыслями, знаниями и достижениями
в области науки о данных. Его блог представляет собой вдохновляющую
платформу для тех, кто хочет углубить свои знания и не потеряться в быстро
меняющемся мире технологий.
Шреяс активно взаимодействует с технологическим сообществом, щедро делясь своими идеями и знаниями. В своей работе и увлечениях он руководствуется принципами ответственного отношения к регулированию искусственного интеллекта и этическим аспектам технологической практики. Это
делает его убежденным сторонником этичных и эффективных инноваций.
Уникальное сочетание навыков и искренней увлеченности технологиями делает Шреяса ценным помощником в любом начинании.
Благодарности
Создание книги — это настоящее путешествие, требующее не только творческого подхода, но и неуклонного следования к цели. Невозможно представить
себе это путешествие без поддержки, помощи и участия множества людей
и организаций, которые сыграли ключевую роль в воплощении первоначального замысла в реальность.
Прежде всего, я выражаю искреннюю благодарность членам моей семьи за их
постоянную поддержку на протяжении всего процесса. Моя жена Арисмита
всегда была рядом, и это мотивировало и заставляло меня продолжать трудиться над книгой. Любовь моего сына Мимо поддерживала меня на протяжении всего пути.
Также я благодарен издательству BPB Publications за профессиональную помощь в подготовке этой книги к публикации. Я выражаю признательность
самоотверженным рецензентам, техническим экспертам и редакторам, посвятившим свое время и мастерство проверке и пересмотру рукописи. Ваше
скрупулезное внимание к деталям и продуманные предложения сыграли решающую роль в повышении качества ее содержания.
Также я хотел бы поблагодарить моих коллег и соратников за годы совместной работы в технологической отрасли. Они внесли неоценимый вклад в мое
развитие, многому меня научили и дали полезные отзывы о моей работе.
Наконец, я хочу выразить глубокую признательность читателям этой книги.
Ваша любознательность и увлеченность вдохновили меня на получение знаний и обмен идеями, и именно для вас я проделал всю эту работу.
Предисловие
В наше время, когда технологии обработки данных стремительно развиваются, искусственный интеллект (ИИ) открывает перед нами поистине безграничные горизонты. Эта книга станет вашим надежным гидом на пути
к новым возможностям. Она предназначена специально для тех, кто только
начинает изучать язык Python и делает первые шаги в сфере ИИ.
Книга поможет вам использовать огромный потенциал ИИ, даже если вы новичок и не обладаете каким-либо опытом в этой области. Независимо от того,
кто вы — студент, пожелавший изучить передовые технологии ИИ; профессионал, осваивающий новые территории; или энтузиаст, увлеченный неизведанным, — эта книга создана для вас.
Мы вместе с вами отправимся в захватывающее путешествие. Это практическое руководство будет сопровождать вас на каждом шагу, от изучения
основ программирования на Python до создания сложных ИИ-решений и обработки естественного языка. Мы понимаем, что идея изучать Python и искусственный интеллект одновременно может показаться немного пугающей,
поэтому мы постарались сделать процесс максимально структурированным,
чтобы облегчить вам освоение этих захватывающих областей.
По мере изучения глав вы сами удостоверитесь в том, что язык Python, славящийся своей простотой и универсальностью, идеально подходит для работы
с технологией искусственного интеллекта, которая определяет развитие целых
отраслей и открывает новые возможности. Книга познакомит вас с ключевыми особенностями сверточных нейросетей, моделей преобразования многословных последовательностей, моделей с механизмом внимания, трансформеров, генеративно-состязательных сетей и других моделей, а также покажет, как
с их помощью создавать эффективные, надежные и простые в обслуживании
корпоративные приложения. Вы также познакомитесь с лучшими практическими методами, а множество примеров помогут вам лучше понять описываемые концепции. Книга не предполагает наличия каких-либо предварительных
знаний; напротив, она даст вам четкое представление об основополагающих
концепциях и укрепит вашу уверенность в том, что вам под силу создавать
приложения на основе искусственного интеллекта с помощью языка Python.
Мы рады познакомить вас с увлекательным миром искусственного интеллекта, начиная с фундаментальных понятий и заканчивая практическими проектами в виде примеров создания реальных приложений. Попутно вы приобретете технические навыки для разработки и внедрения решений на основе ИИ
и научитесь мыслить как специалист в области инновационных решений.
10
■
Эта книга станет вашим верным спутником в замечательном путешествии,
совершив которое вы вооружитесь инструментами для создания технологий будущего. Так что приготовьтесь отправиться по дороге, на которой
любопытство создает возможности, а Python — приложения искусственного интеллекта. Приготовьтесь стать свидетелем слияния этих двух могущественных сил, которые сделают вас творцами и первопроходцами в области
технологий. Давайте же вместе окунемся в эти захватывающие темы и раскроем огромный потенциал ИИ на Python!
Глава 1. Основы Python. Концепции, библиотеки и написание кода. Здесь
мы рассмотрим основы языка Python, структуры данных и объектно-ориентированное программирование. В первой главе мы на примерах объясним
принципы использования популярных библиотек Python, таких как NumPy,
Pandas, Matplotlib и т. д., благодаря чему читателю будет легко усвоить последующие главы. Эти библиотеки широко используются в приложениях ИИ
и машинного обучения и подробно рассматриваются в последующих главах
вместе с библиотекой TensorFlow 2.
Глава 2. Настройка лаборатории искусственного интеллекта. В этой главе
мы расскажем о платформе Google Colab для написания кода на Python —
весьма ценном инструменте, позволяющем пользоваться облачными возможностями и дополнительными мощностями графических ускорителей (GPU)
без необходимости развертывания собственной инфраструктуры. Также
в этой главе мы дадим пошаговое руководство по созданию локальной среды
Anaconda, обеспечивающей гибкость разработки. Кроме того, мы раскроем
потенциал Google Colab и расскажем о том, как легко интегрировать его с репозиториями Google Диска и GitHub для оптимизации рабочего процесса.
Глава 3. Создание нашей первой модели нейронной сети. Эта глава посвящена использованию возможностей API TensorFlow 2 для создания моделей
глубокого обучения с нуля с сохранением и загрузкой моделей TensorFlow.
Также в ней рассматриваются возможности визуализации, предоставляемые
инструментом TensorBoard. В этой главе мы раскроем фундаментальные концепции искусственных нейронных сетей (ИНС) и рассмотрим создание моделей ИНС в API TensorFlow 2 и Keras, их обучение, тонкую настройку архитектуры для оптимальной производительности и оценку моделей.
Глава 4. Проектирование CNN с помощью TensorFlow. Глава познакомит
читателя с увлекательным миром классификации изображений, одним из
основных приложений ИИ. Мы расскажем о тонкостях работы сверточных
нейронных сетей (CNN) и практическом опыте построения моделей CNN
с помощью TensorFlow 2, а также рассмотрим реализацию различных архитектур CNN и поговорим об использовании предварительно обученных моделей CNN.
■
11
Глава 5. Разработка приложений для классификации изображений на основе CNN. Мы поговорим о практическом построении приложений на основе сверточных нейронных сетей (CNN). Эта глава посвящена созданию
ИИ-приложения, способного точно идентифицировать изображения благодаря обучению на наборе данных CIFAR-10. В главе рассматривается как создание модели CNN с нуля, так и использование предварительно обученных
моделей для классификации изображений.
Глава 6. Обучение и развертывание моделей обнаружения объектов. В этой
главе мы поговорим о важнейшей сфере обнаружения объектов, фундаментальной задаче в области искусственного интеллекта. Глава закладывает прочный фундамент в этой области и помогает обрести интуитивное понимание,
необходимое для постижения тонкостей этой темы. Также мы рассмотрим
внутреннюю работу популярных алгоритмов обнаружения объектов, таких
как SSD, RCNN и YOLO и опишем различия между ними. Кроме того, мы
представим реализацию концепции обнаружения объектов на примере предварительно обученных моделей в API TensorFlow.
Глава 7. Создание приложения для чтения текста и изображений. В этой
главе мы расскажем о распознавании текста с помощью искусственного интеллекта и о приложениях для преобразования изображений в текст. Глава
начинается с практического руководства по использованию инструмента
Tesseract для приложений оптического распознавания символов (OCR). Благодаря ему вы получите знания и навыки, которые помогут вам создать собственное приложение для чтения текста. Далее мы перейдем в сферу глубокого обучения и используем возможности TensorFlow 2 для создания передовых
приложений, позволяющих преобразовывать изображения в текст.
Глава 8. NLP как средство расширенного анализа текста. Именно с этой главы начинается путешествие в увлекательный мир обработки естественного
языка (NLP), важнейшего прикладного аспекта ИИ. Мы расскажем о практическом использовании широко распространенных библиотек Python, таких
как Spacy и NLTK, которые позволяют с легкостью обрабатывать «сырой»,
неструктурированный текст. Кроме того, здесь мы раскроем силу векторного представления слов, а также опишем концепцию использования модели
GloVe для представления слов в виде векторов. В этой главе мы расскажем
о модели Word2Vec и о ее реализации в TensorFlow.
Глава 9. Запускаем модели последовательностей. Из этой главы вы узнаете
о тонкостях работы с рекуррентными нейронными сетями (РНС), двунаправленными РНС, моделями долговременной краткосрочной памяти (LSTM)
и управляемыми рекуррентными блоками (GRU) на платформе TensorFlow 2.
Также мы расскажем о языковом моделировании и дадим руководство по его
реализации.
12
■
Глава 10. Модели последовательностей для автоматической классификации текста. Здесь мы рассмотрим использование данных моделей, в частности сетей LSTM (долгой краткосрочной памяти) для создания мощного приложения для автоматической классификации текста. В этой главе мы расскажем
о данных, об их необходимой очистке и предварительной обработке и о преобразовании их в подходящий для классификации формат. На практическом
примере мы объясним принципы построения и обучения LSTM-модели с помощью TensorFlow 2, а также рассмотрим реализацию одномерной модели
CNN (сверточной нейронной сети).
Глава 11. Модели внимания и трансформеры. Глава описывает модели
с механизмом внимания, ключевым понятием в области обработки естественного языка (NLP). Здесь мы рассмотрим различные варианты реализации внимания, включая «самовнимание» (self-attention), «двунаправленное» (bi-directional) и «многоголовое» (multi-head) внимание; объясним их
уникальные роли и опишем создание пользовательского слоя внимания
в TensorFlow 2. Затем мы раскроем трансформационный потенциал сети
Transformer, использующий концепцию внимания для повышения скорости
обучения моделей. В этой главе мы поговорим о применении предварительно
обученных моделей Transformer в TensorFlow 2 для обработки естественного
языка и предложим практические рекомендации по их использованию.
Глава 12. Генерация подписей к изображениям. В этой главе мы рассмотрим
ИИ систему «изображение-текст» для автоматического создания описательного и контекстуально релевантного текста к заданным входным изображениям. Мы опишем принципы построения модели создания подписей к изображениям, состоящей из кодера, декодера последовательности, механизма
внимания и генератора подписей.
Глава 13. Учимся строить модели GAN. Глава посвящена увлекательному
миру генеративного моделирования и содержит краткий обзор как дискриминативных, так и генеративных моделей. Мы представим описание вариационного кодировщика — ключевой концепции в области генеративных моделей. Также из этой главы вы узнаете о том, как построить мощную модель
генеративно-состязательной сети (GAN) с помощью TensorFlow 2.
Глава 14. Генерация искусственных лиц с помощью GAN. Из этой главы
вы узнаете об условных генеративно-состязательных сетях (cGAN) и их необычайной способности генерировать синтетические изображения лиц определенных возрастных категорий. Мы опишем архитектуру условной GAN
и приведем пример разработки модели с использованием API TensorFlow 2.
Пакет кода и цветные изображения
Скачать пакет кода и цветные изображения к книге можно по следующему
адресу:
https://addons.eksmo.ru/it/Pythonic-AI-main.zip
На сайте https://github.com/bpbpublications доступны пакеты кода из нашего
богатого каталога книг и видео. Ознакомьтесь с ними!
Возможные ошибки и опечатки
Мы гордимся своей работой в издательстве BPB Publications и стараемся
обеспечить точность контента, чтобы гарантировать нашим подписчикам
удовольствие от чтения. Наши читатели — это наше зеркало, и мы с удовольствием воспользуемся их отзывами, чтобы исправить возможные ошибки
и опечатки, если таковые возникнут в процессе публикации. Вы можете помочь нам поддерживать качество и сообщить о возможных ошибках и опечатках по следующему адресу:
errata@bpbonline.com.
Ваша поддержка и все ваши предложения и отзывы будут удостоены высокой
оценки всеми членами семьи BPB Publications.
Знаете ли вы, что BPB предлагает электронные версии всех изданных
книг в форматах PDF и ePub? Электронную версию любой книги можно найти на сайте www.bpbonline.com, а покупатели печатного издания
получат право на скидку для электронной копии. Свяжитесь с нами по
следующему адресу:
business@bpbonline.com
На сайте www.bpbonline.com можно ознакомиться с коллекцией бесплатных технических статей, подписаться на ряд бесплатных информационных бюллетеней, а также получить эксклюзивные скидки
и предложения на книги и электронные книги BPB.
14
■
Пиратство
Если вы обнаружите в Интернете нелегальные копии наших работ
в любой форме, мы будем благодарны, если вы сообщите нам адрес или
название сайта. Свяжитесь с нами по адресу: business@bpbonline.com
и укажите ссылку на материал.
Потенциальным авторам
Если вы хорошо разбираетесь в какой-либо теме и заинтересованы
в написании книги или соавторстве, посетите сайт www.bpbonline.com.
Мы сотрудничаем с тысячами разработчиков и технических специалистов, таких же, как вы, помогая им делиться своими знаниями с мировым технологическим сообществом. На этом сайте можно подать общую заявку или заявку на конкретную актуальную тему, для которой
мы набираем авторов, а также предложить свою собственную идею.
Отзывы
Если вас не затруднит, то после прочтения и использования этой книги оставьте отзыв на сайте, на котором вы ее приобрели. С вашим непредвзятым мнением смогут ознакомиться потенциальные читатели,
которым оно поможет принять решение о возможной покупке. Мы
в BPB узнаем, что вы думаете о нашей продукции, а наши авторы увидят ваш отзыв о своей книге. Заранее благодарим вас за отзыв!
Дополнительную информацию о BPB можно получить на сайте
www.bpbonline.com.
Оглавление
Глава 1. Основы Python. Концепции, библиотеки и написание кода . . . . . 25
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Введение в Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Как пользоваться инструментом Colab . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Переменные Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Отступы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Операторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Арифметические операторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Операторы сравнения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Логические операторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Операторы идентичности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Операторы принадлежности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Условные выражения в Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Циклы в Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Основные структуры данных в Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Списки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Кортеж . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Словарь . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Множество . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Подробнее о строковых переменных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Функции в Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Объектно-ориентированное программирование в Python . . . . . . . . . . . 52
Наследование классов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
NumPy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Изменение формы (размерности) массивов . . . . . . . . . . . . . . . . . . . . . . . 58
Транспонирование массивов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Векторизация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
16
■
Умножение матриц . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Генерация чисел . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Matplotlib . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Глава 2. Настройка лаборатории искусственного интеллекта . . . . . . . . . . . 70
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Локальная среда или облако . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Настройка локальной лабораторной среды . . . . . . . . . . . . . . . . . . . . . . . . 72
Google Colab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Использование возможностей GPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Подключение Google Диска . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Использование Google Colab с GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Глава 3. Создание нашей первой модели нейронной сети . . . . . . . . . . . . . . . 93
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Основы ИНС . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Принципы ИНС . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Функция активации . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Глубокая нейронная сеть . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Обратное распространение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Функция потерь . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Оптимизатор . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
TensorFlow и Keras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Построение нашей первой модели ИНС . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Предварительная обработка изображений . . . . . . . . . . . . . . . . . . . . . . 107
■
17
Построение модели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Первый способ. Использование последовательного API . . . . . . . . 109
Второй способ. Использование функционального API . . . . . . . . . 113
Третий способ. Создание подкласса . . . . . . . . . . . . . . . . . . . . . . . . . 115
Обучение и оценка модели ИНС . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Компиляция модели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Настройка модели на обучение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Оценка модели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Построение графика значений потерь и метрик . . . . . . . . . . . . . . . . . 121
TensorBoard. Инструментарий визуализации TensorFlow . . . . . . . . . . . 123
Настройка гиперпараметров . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Сохранение и загрузка моделей Tensor Flow . . . . . . . . . . . . . . . . . . . . . . . 130
Сохранение и загрузка модели во время обучения . . . . . . . . . . . . . . . . . 130
Сохранение и загрузка модели после обучения . . . . . . . . . . . . . . . . . . . . 132
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Глава 4. Проектирование CNN с помощью TensorFlow . . . . . . . . . . . . . . . . 135
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
Введение в концепцию сверточных нейронных сетей . . . . . . . . . . . . . . 136
Основные понятия CNN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Архитектура CNN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Сверточный слой . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
Фильтр, или ядро . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
Дополнение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
Сдвиг . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Слой ReLu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
Слой «пулинга» (субдискретизации) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
Полносвязный слой . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Методы обобщения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Что делать в случае переобучения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
18
■
Переопределение архитектуры модели . . . . . . . . . . . . . . . . . . . . . 146
Регуляризация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Дропаут (отключение) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Аугментация данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Аугментация во время обучения модели . . . . . . . . . . . . . . . . . . . . 150
Аугментация до обучения модели . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Пакетная нормализация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Что делать в случае недообучения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Построение CNN с помощью TensorFlow . . . . . . . . . . . . . . . . . . . . . . . . . 153
Набор данных TensorFlow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
Использование GPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Использование TPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Стандартные архитектуры CNN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
LeNet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
AlexNet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
VGGNet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
ResNet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Сеть Inception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
Глава 5. Разработка приложений для классификации изображений
на основе CNN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
Введение в классификацию изображений . . . . . . . . . . . . . . . . . . . . . . . . . 169
Набор данных и их структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Скачивание данных и их предварительная обработка . . . . . . . . . . . . . 173
Создание, обучение и оценка CNN-моделей . . . . . . . . . . . . . . . . . . . . . . 176
Устранение переобучения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
Дропаут . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
Регуляризация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
■
19
Классификация изображений с помощью предварительно
обученных моделей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Предварительно обученная модель VGG16 . . . . . . . . . . . . . . . . . . . . . . 187
Предварительно обученная модель ResNet50 . . . . . . . . . . . . . . . . . . . . . 192
Использование пользовательских изображений
для классификации . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
Глава 6. Обучение и развертывание моделей обнаружения
объектов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
Что такое обнаружение объектов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
Основные принципы обнаружения объектов . . . . . . . . . . . . . . . . . . . . . 204
Функции потерь в задачах по обнаружению объектов . . . . . . . . . . . . 206
Метрики оценки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
Средняя точность . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Усредненная точность . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Точность и полнота . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
F1-мера . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Кривая точности-полноты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
ROC-кривая . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Подавление немаксимумов (NMS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Якорные рамки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
Сеть пирамиды признаков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
Модели обнаружения объектов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
Модель SSD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
Использование моделей SSD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
Региональные сверточные нейронные сети . . . . . . . . . . . . . . . . . . . . . . . 230
Использование моделей R-CNN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
Модель YOLO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Использование моделей YOLO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
20
■
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
Глава 7. Создание приложения для чтения текста и изображений . . . . . . 247
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
Концепция преобразования изображений в текст . . . . . . . . . . . . . . . . . 248
Принципы OCR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
Области применения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Создание OCR-приложения с помощью Tesseract . . . . . . . . . . . . . . . . . . 251
Создание приложений для преобразования изображений
в текст с помощью TensorFlow 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
Глава 8. NLP как средство расширенного анализа текста . . . . . . . . . . . . . . 274
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
Введение в обработку естественного языка . . . . . . . . . . . . . . . . . . . . . . . 275
Обработка естественного языка с помощью NLTK . . . . . . . . . . . . . . . . 276
Токенизация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
Удаление стоп-слов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
Стемминг . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
Лемматизация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
POS-теггинг, или разметка частей речи . . . . . . . . . . . . . . . . . . . . . . . . 286
Обработка естественного языка с помощью spaСy . . . . . . . . . . . . . . . . 288
Токенизация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
POS-теггинг, или разметка частей речи . . . . . . . . . . . . . . . . . . . . . . . . 293
Распознавание именованных сущностей . . . . . . . . . . . . . . . . . . . . . . . . . 294
Разбор зависимостей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
■
21
Лемматизация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
Сходство . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
Векторное представление слов (эмбеддинг) . . . . . . . . . . . . . . . . . . . . . . 300
Эмбеддинг в TensorFlow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Эмбеддинг с помощью GloVe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
Глава 9. Запускаем модели последовательностей . . . . . . . . . . . . . . . . . . . . . . 318
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
Введение в модели последовательностей . . . . . . . . . . . . . . . . . . . . . . . . . . 319
Построение модели рекуррентной нейронной сети . . . . . . . . . . . . . . . . 321
Основы моделей RNN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
Различные архитектуры RNN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
Построение RNN с помощью TensorFlow . . . . . . . . . . . . . . . . . . . . . . . . . 327
Построение модели долгой краткосрочной памяти (LSTM) . . . . . . . . 331
Основы LSTM моделей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
Построение LSTM с помощью TensorFlow . . . . . . . . . . . . . . . . . . . . . . . . 335
Построение модели управляемого рекуррентного блока . . . . . . . . . . . 336
Двунаправленная RNN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
Языковая модель и генерация последовательности . . . . . . . . . . . . . . . . 343
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
Глава 10. Модели последовательностей для автоматической
классификации текста . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354
Введение в классификацию текстов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
22
■
Набор данных и их структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
Загрузка набора данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
Обработка данных с помощью библиотеки Pandas языка
Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361
Очистка и предварительная обработка данных . . . . . . . . . . . . . . . . . . . . 362
Построение и обучение моделей последовательностей . . . . . . . . . . . . . 370
Модель LSTM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
Двунаправленная модель GRU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373
Использование предварительно обученных эмбеддингов . . . . . . . . . . 374
Использование эмбеддингов GloVe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376
Построение и обучение одномерной модели CNN . . . . . . . . . . . . . . . . . 379
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
Глава 11. Модели внимания и трансформеры . . . . . . . . . . . . . . . . . . . . . . . . 385
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
Механизм внимания в RNN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
Архитектура трансформера . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
Векторы запроса, ключа и значения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
Механизм самовнимания . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
Энкодер и декодер . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
Многоголовое внимание . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391
Модель BERT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392
Реализация слоя внимания . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
Слой внимания библиотеки TensorFlow . . . . . . . . . . . . . . . . . . . . . . . . . . 394
Пользовательский слой внимания . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
Реализация блока трансформера . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
Использование слоев из официальных моделей
TensorFlow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
Создание пользовательского слоя-трансформера . . . . . . . . . . . . . . . . . 403
Использование предварительно обученных
трансформеров . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
■
23
Генеративные предварительно обученные трансформеры . . . . . . . . . .
Модели GPT от Hugging Face . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Модели GPT от OpenAI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
412
414
416
418
419
419
Глава 12. Генерация подписей к изображениям . . . . . . . . . . . . . . . . . . . . . . .
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Методология и подход . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Набор данных и их структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Подготовка данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Построение модели для обработки изображений . . . . . . . . . . . . . . . . .
Построение модели для генерации подписей . . . . . . . . . . . . . . . . . . . . .
Обучение и оценка модели генерации подписей . . . . . . . . . . . . . . . . . . .
Создание модели на основе LSTM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Создание модели, основанной на механизме внимания . . . . . . . . . . . .
Создание модели на основе трансформера . . . . . . . . . . . . . . . . . . . . . . .
Использование предварительно обученных моделей
генерации подписей от Hugging Face . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
421
421
422
422
422
425
427
433
435
439
439
447
450
Глава 13. Учимся строить модели GAN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Генеративные модели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Концепция GAN. Обзор . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Архитектура GAN. Генератор и дискриминатор . . . . . . . . . . . . . . . .
Генератор . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Дискриминатор . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Обучение GAN. Состязательное обучение . . . . . . . . . . . . . . . . . . . . . . .
457
457
458
458
458
460
461
462
462
463
452
455
455
456
24
■
Построение GAN-модели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
Вариационный автокодировщик . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
Архитектура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479
Обучение вариационного автокодировщика . . . . . . . . . . . . . . . . . . . . . 480
Построение модели вариационного автокодировщика . . . . . . . . . . . . . 480
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489
Глава 14. Генерация искусственных лиц с помощью GAN . . . . . . . . . . . . . 490
Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490
Структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491
Цели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491
Условные генеративно состязательные сети . . . . . . . . . . . . . . . . . . . . . . . 492
Архитектура и обучение сGAN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 492
Области применения cGANs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493
Набор данных и их структура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496
Предварительная обработка метаданных . . . . . . . . . . . . . . . . . . . . . . 497
Построение модели . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501
Дискриминатор . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501
Генератор . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503
Итоговая модель cGAN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
Загрузка набора данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506
Создание латентных точек и фальшивых данных . . . . . . . . . . . . . . . 507
Обучение модели cGAN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508
Генерация и вывод полученных данных . . . . . . . . . . . . . . . . . . . . . . . . . . 510
Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
Основные выводы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
Ссылки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
Указатель . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516
Глава 1
Основы Python.
Концепции, библиотеки
и написание кода
Введение
Благодаря своим обширным библиотекам и фреймворкам язык программирования Python играет ключевую роль в разработке приложений в области
искусственного интеллекта (ИИ). Чтобы получить максимальную пользу
от этой книги, нужно обладать базовыми представлениями о языке Python.
В этой главе мы на примерах рассмотрим как основы Python, так и популярные библиотеки, широко используемые в ИИ-приложениях. На протяжении
всей главы для написания кода на языке Python мы будем пользоваться блокнотом Google Colab, позволяющим запускать примеры в облаке без применения дополнительной инфраструктуры. Также мы рассмотрим такие популярные библиотеки Python, как NumPy и Matplotlib и, конечно же, TensorFlow,
которые будем активно использовать в последующих главах.
Структура
В этой главе мы рассмотрим следующие темы:
• Введение в Python.
• Основные структуры данных Python.
• Объектно-ориентированное проектирование в Python.
• Пакет NumPy.
• Библиотека Matplotlib.
26
■
Pythonic AI
Цели
К концу этой главы вы научитесь писать код на языке программирования
Python, а также получите хорошее представление о структурах данных Python
и об объектно-ориентированном программировании на Python. В этой главе
мы также рассмотрим особенности применения таких важных библиотек,
как NumPy и Matplotlib.
Введение в Python
Эта глава представляет собой краткий курс программирования на языке
Python. Технология искусственного интеллекта (ИИ) позволяет компьютерам эффективно справляться с задачами на уровне человека. Компьютеры
могут выполнять работу гораздо быстрее, чем люди, но для того, чтобы передавать им инструкции, нужен особый язык. Функцию средства коммуникации между человеком и компьютером выполняет язык программирования.
При этом алгоритм решения задачи записывается в виде кода, доступного
для восприятия человеком, а затем специальная программа преобразует его
в понятный компьютеру двоичный код (состоящий из 1 и 0). Среди широко используемых языков программирования можно назвать Python, Java, C,
C++, Javascript, R, Ruby, PHP и т. п.
Python — это язык программирования общего назначения, который используется для создания различных приложений в сферах машинного обучения,
веб-разработки, разработки игр, прикладного программного обеспечения
и т. д. Python — бесплатный язык программирования с открытым исходным
кодом, доступным в Интернете. Это высокоуровневый язык программирования, то есть сильно абстрагированный от низкоуровневых вычислительных
подробностей.
Python представляет собой независимую платформу, то есть его поддерживают все основные операционные системы. Основной философский принцип
Python — это повышение удобочитаемости кода за счет правильного использования пробелов, называемых отступами. Этот язык приобрел широкую популярность в сфере разработки приложений для искусственного интеллекта
благодаря наличию обширных библиотек, позволяющих обрабатывать данные и производить вычисления масштабируемым образом. Итак, прежде чем
переходить к разработке реальных ИИ-приложений, давайте освоим основы
языка Python!
Основы Python. Концепции, библиотеки и написание кода
■
27
Как пользоваться инструментом Colab
В этой главе для написания и выполнения кода на языке Python мы будем
пользоваться инструментом Google Colab. Более подробно мы познакомимся
с ним в главе 2 «Настройка лаборатории искусственного интеллекта», поэтому пока не стоит беспокоиться, если что-то покажется вам не совсем понятным. Мы расскажем о деталях в последующих главах.
Сервис Colaboratory, или Colab, разработан исследовательской группой
Google и позволяет любому пользователю писать и выполнять код Python, не
устанавливая каких-либо приложений на локальном компьютере. Достаточно открыть браузер и перейти на сайт https://colab.research.google.com/. Там
вы увидите следующую страницу:
Иллюстрация 1.1. Начальная страница Colab
Открыв страницу Google Colab, перейдите к меню Файл (File) в левом верхнем углу и выберите в выпадающем списке пункт Создать блокнот (New
notebook) (показан красной стрелкой на иллюстрации 1.2). Для этого вы
должны войти в Google под своей учетной записью. Если в вашем браузере
уже выполнен вход в аккаунт Google, будет сразу создан новый блокнот.
28
■
Pythonic AI
Иллюстрация 1.2. Создание нового блокнота
Как показано на иллюстрации 1.3, вы увидите открытый блокнот с мигающим
на панели курсором. Эта панель называется ячейкой кода, куда мы и будем
вводить фрагмент программы. После запуска ячейки результат выполнения
кода отобразится прямо под ней. Пришло время приступить к программированию.
Иллюстрация 1.3. Ячейка кода в блокноте Colab
Основы Python. Концепции, библиотеки и написание кода
■
29
Переменные Python
В языке программирования переменные нужны для хранения значений. Эти
значения могут меняться в зависимости от команд или данных, передаваемых
программе. В отличие от Java, C/C++ и некоторых других языков, в Python не
нужно указывать тип переменной (например, integer, float, string и т. д.). Он
определяется автоматически, когда мы присваиваем переменной значение.
Для начала попробуем создать переменные и вывести их содержимое с помощью функции print(). Посмотрите на следующую иллюстрацию:
Иллюстрация 1.4. Переменные
Переменная a — целое число (integer), pi — число с плавающей точкой (float),
а st — строка (string). Введите этот код в одну ячейку блокнота и нажмите на
клавиатуре клавиши Shift+Enter. Код будет выполнен и отобразится его результат, после чего появится новая ячейка кода с мигающим курсором внутри — это признак готовности принять следующий набор инструкций. Выполнение ячейки может занять некоторое время. Число [1], показанное в левом
верхнем углу на иллюстрации 1.4, — это порядковый номер, под которым
была исполнена ячейка. Он необязательно будет совпадать с вашим.
При написании кода на любом языке очень важно оставлять комментарии,
повышающие читабельность кода и помогающие его тестировать. В языке
Python однострочный комментарий создается с помощью добавления знака
# перед любой строкой в коде. Многострочные комментарии оформляются
тройными кавычками (""") с обеих сторон. Закомментированная часть кода
остается в нем, но не выполняется интерпретатором Python, как показано на
иллюстрации 1.5.
30
■
Pythonic AI
Иллюстрация 1.5. Комментарии в Python
Примечание. Имя переменной в языке Python может содержать только буквы, цифры и символ подчеркивания и не должно начинаться
с цифры.
Совет. При написании кода лучше всего присваивать переменным содержательные и легкие для понимания имена. Например, если нужно
создать переменную для хранения данных о температуре, используйте
не одну лишь букву “t”, а полное слово “temperature”.
В Python имеются встроенные типы данных для хранения числовых значений: int, float и complex.
Переменные типа int хранят целые числа, переменные типа float — числа
с плавающей точкой (формат записи вещественных чисел), а переменные типа complex — комплексные числа. Проверить тип переменной можно с помощью функции type(). Строковые переменные (string) в Python используются для хранения текстовых данных. Переменную строкового типа можно
создать, присвоив ей значение с помощью одинарных или двойных кавычек,
как показано на иллюстрации 1.6.
Основы Python. Концепции, библиотеки и написание кода
■
31
Иллюстрация 1.6. Функция type()
Python поддерживает работу с переменными логического или «булева» типа
(boolean). Булевы переменные могут хранить только два возможных значения: True («истина») и False («ложь»), как показано на иллюстрации 1.7.
Иллюстрация 1.7. Булевы переменные
Можно также явным образом переопределить тип переменной с помощью
соответствующей функции. Этот процесс называется «приведением типов»
и выполняется так, как показано на иллюстрации 1.8.
Иллюстрация 1.8. Приведение типов
32
■
Pythonic AI
Примечание. Имена переменных в Python чувствительны к регистру
(строчные или прописные буквы различаются). После создания переменной нельзя изменить регистр в любой части ее имени.
Отступы
Важную роль при написании кода на Python играют отступы (пробелы в начале строки). Создание читабельного кода с помощью отступов — это один
из философских принципов языка Python. Отступы не только служат для визуального оформления, но и определяют структуру кода, и если они заданы
неправильно, то программа может работать некорректно, или не работать
вообще.
Операторы
Операторы Python — это различные символы и ключевые слова, которые
используются для выполнения какой-либо операции над заданными переменными или данными. Оператор либо изменяет значение какой-либо переменной, либо выдает новый результат. Рассмотрим некоторые используемые
в Python операторы.
Арифметические операторы
Арифметические операторы — это символы, обозначающие выполнение различных арифметических действий над заданными переменными или данными. Эти символы не требуют особых пояснений. Попробуем выполнить
несколько распространенных арифметических операций с помощью языка
Python, как показано на следующей иллюстрации:
Основы Python. Концепции, библиотеки и написание кода
■
33
Иллюстрация 1.9. Арифметические операторы
Операторы сравнения
В качестве операторов сравнения используются соответствующие математические символы, как показано на следующей иллюстрации:
Иллюстрация 1.10. Операторы сравнения
34
■
Pythonic AI
Логические операторы
В Python имеются три логических оператора: and (и), or (или) и not (не).
Они комбинируют значения двух условных выражений. Логическое and истинно, если оба выражения истинны; в противном случае оно ложно. Логическое or истинно, если истинно одно из выражений; в противном случае оно
ложно. Результат применения логического not будет истинным, если условие
ложно, и наоборот (см. иллюстрацию 1.11).
Иллюстрация 1.11. Логические операторы
Операторы идентичности
В Python имеются два оператора идентичности: is и is not. Они используются для проверки того, являются ли два объекта одним и тем же (то есть
находятся по одному адресу в памяти) или нет, как показано на следующей
иллюстрации:
Иллюстрация 1.12. Операторы идентичности
Основы Python. Концепции, библиотеки и написание кода
■
35
Операторы принадлежности
Эти операторы используются для проверки принадлежности определенных
значений или переменных к заданной последовательности. В Python последовательности могут быть представлены в виде списка, кортежа, словаря, множества и т. д. Мы еще поговорим об этих типах подробнее. Пока заметим, что
строки можно считать последовательностями символов, поэтому к ним тоже
применимы операторы принадлежности (см. иллюстрацию 1.13).
Иллюстрация 1.13. Операторы принадлежности
Условные выражения в Python
При составлении программ на Python часто приходится расписывать варианты действий в зависимости от выполнения какого-либо условия. Условные
выражения в Python применяются для перехода к нужному блоку программы. Для их записи используются такие ключевые слова, как if, elif и else.
Оператор if («если») в Python используется совместно с выражением, значение которого необходимо вычислить и оценить. Внутри блока if записываются
операторы, которые выполняются в случае, если условное выражение истинно.
Оператор elif — это краткая форма else if («иначе, если»). Оператору elif
всегда предшествует оператор if. Если условие, записанное после ключевого
слова if, не выполняется, программа переходит внутрь блока elif и оценивает условие, заданное в нем. Не забывайте о том, что блок elif также всегда
должен содержать выражение, значение которого предстоит оценить.
Оператор else («иначе») не сопровождается никаким условием. Если ни одно
из предыдущих условий не выполняется, программа переходит в блок else.
В примере на иллюстрации 1.14 видно, что b больше a. Следовательно, будет
выполнена только команда вывода на экран внутри блока elif.
36
■
Pythonic AI
Условные операторы могут быть вложенными, то есть находиться один внутри другого.
Иллюстрация 1.14. Условный блок
Циклы в Python
Циклы используются для выполнения повторяющихся операций, или «итераций». В Python предусмотрены два типа циклов: с командами while и for.
Цикл while проверяет условие и продолжает выполнять инструкции, записанные внутри блока while, до тех пор, пока условие истинно. В примере ниже
инициируется переменная i со значением 0. Цикл while сравнивает значение
i с числом 5. Внутри цикла текущее значение i выводится на экран и увеличивается на единицу. После пятого выполнения цикла значение i становится
равным 5. В результате условие while (значение i меньше 5) не выполняется,
и цикл останавливается, как показано на следующей иллюстрации:
Иллюстрация 1.15. Цикл while
Цикл for чаще всего используется, когда нужно повторить одни и те же инструкции для каждого члена какой-либо последовательности: списка, кортежа,
Основы Python. Концепции, библиотеки и написание кода
■
37
словаря, строки (последовательности символов) и т. д. В примере на иллюстрации 1.16 с помощью цикла for действия внутри этого цикла выполняются
для каждого элемента списка nums. Подробнее о последовательностях мы расскажем в следующем разделе. Подобно условным блокам, циклы тоже могут
быть вложенными.
Иллюстрация 1.16. Цикл for
Оператор break используется для досрочного выхода из цикла при выполнении заданного условия. Если оператор break срабатывает до перебора всех
элементов последовательности, цикл пропускает оставшиеся итерации. Оператор continue используется для пропуска инструкций, стоящих после него
внутри блока цикла, и переходу к новой итерации со следующим элементом
последовательности. Рассмотрим следующие примеры:
Иллюстрация 1.17. Операторы break и continue
38
■
Pythonic AI
В первом примере на иллюстрации 1.17 программа перебирает значения строковой переменной msg и выводит на экран по очереди каждый символ. По достижении символа o срабатывает оператор break, поэтому печатаются только символы h, e, l и l, после чего цикл прекращает свою работу. Символ o не выводится.
Во втором примере на иллюстрации 1.17 цикл выполняет аналогичные действия,
но для символа e срабатывает оператор continue. При выполнении заданного
условия команда print пропускается, и цикл переходит к следующему значению
в строке. В результате на экран выводятся только символы h, l, l и o, но не e.
Вместе с циклами в Python часто используется функция range(). Она создает
последовательность, для которой и выполняется цикл. Основной синтаксис
функции range() таков:
Range (start, stop, step):
• start: значение, с которого начинается последовательность; по умолчанию это 0;
• stop: значение, на котором последовательность заканчивается (само
это значение не включается);
• step: шаг перехода к новому значению в пределах start и stop; по умолчанию это 1.
Рассмотрим следующие примеры:
Иллюстрация 1.18. функция range()
В первом примере на иллюстрации 1.18 используется функция range с одним
значением. По умолчанию последовательность начинается с 0 и продолжается
Основы Python. Концепции, библиотеки и написание кода
■
39
до 5 с шагом, равным 1. Во втором примере на иллюстрации 1.18 для функции range заданы начало, конец и шаг в виде значений 1, 10 и 2, поэтому выбирается каждое второе значение от 1 до 10.
Основные структуры данных в Python
Структуры данных помогают обеспечить эффективное хранение и извлечение информации. Выбор конкретного способа хранения информации в программе зависит от задачи и предполагаемого алгоритма ее решения. Основные структуры данных в Python — это список, кортеж, словарь и множество.
Их также называют коллекциями.
Списки
Списки в Python используются для хранения группы элементов в одной переменной. Список задается перечислением значений внутри квадратных скобок. Для создания списка можно также использовать функцию list() (конструктор списков). Создадим для примера список городов с названием city:
city = ["Париж", "Мумбаи", "Нью-Йорк", "Лондон", "Токио"]
В одном списке Python можно хранить данные разных типов, например, целые числа, числа с плавающей точкой и строки (int, float и string). Также элементом списка может быть другой список, например:
items = ["Париж", 2, 5.98765, [3, 7, 9]]
К элементам списка можно обращаться по их индексу. Индексы в списках
Python начинаются с нуля. Рассмотрим следующий пример:
Иллюстрация 1.19. Индексация списков
40
■
Pythonic AI
Как показано на иллюстрации 1.19, для доступа к первому элементу списка
city нужно воспользоваться индексом со значением 0 в квадратной скобке:
city[0]. Аналогично для доступа ко второму элементу используется индекс
1: city[1], и т. д. В языке Python можно также указывать индекс элементов
списка с конца с помощью отрицательного числа. Так команда city[-1] вернет последний элемент списка, city[-2] — предпоследний элемент списка и т. д.
Довольно часто возникает необходимость вырезать небольшую часть списка. Сделать это можно с помощью индексов. Для этого в Python не используются какие-либо функции или операторы, достаточно после имени списка
(list_name) указать в квадратных скобках диапазон среза. Основной синтаксис срезов следующий:
list_name[start : stop : step]:
start: значение индекса, с которого начинается срез;
stop: значение индекса, на котором заканчивается срез (элемент с индексом
stop не включается);
step: шаг индекса в пределах первого и последнего индексов (по умолчанию 1).
Ниже показаны примеры с различными срезами:
Иллюстрация 1.20. Срезы
Основы Python. Концепции, библиотеки и написание кода
■
41
Списки Python упорядочены. После создания списка его элементы можно изменять, добавлять и удалять. К спискам применимы некоторые встроенные
функции Python.
Одна из важных функций — len(), с ее помощью можно вернуть длину
списка. Функция append()используется для добавления элемента в конец
списка. Подсчитать количество вхождений значения в список позволяет функция count(). В качестве аргумента для функции следует указать значение,
которое необходимо найти. Примеры показаны на иллюстрации 1.21:
Иллюстрация 1.21. Полезные функции
Чтобы вставить некоторое значение в определенное место списка, нам нужно использовать функцию insert(). В качестве аргументов следует указать
индекс в списке и значение. Функция pop() удаляет элемент из списка1. По
умолчанию удаляется последний элемент списка. При указании аргумента
для функции pop()удаляется элемент с этим индексом. Примеры показаны
на иллюстрации 1.22.
Функция reverse() переставляет элементы в обратном порядке. Также для
списков часто используется функция sort(). Нередко бывает нужно отсортировать список по возрастанию или убыванию. Функция sort может принимать аргумент reverse с двоичным значением. Если значение reverse равно true, то сортировка выполняется по убыванию.
1
И возвращает указанный элемент в качестве своего значения. Таким образом, эту функцию можно использовать в правой части оператора присваивания. — Прим. науч. ред.
42
■
Pythonic AI
Иллюстрация 1.22. Полезные функции
По умолчанию это значение равно false, и сортировка выполняется по возрастанию. Примеры показаны на иллюстрации 1.23:
Иллюстрация 1.23. Полезные функции
Основы Python. Концепции, библиотеки и написание кода
■
43
Кортеж
Для хранения группы значений в одной переменной в языке Python также
используются кортежи. Кортеж задается перечислением значений внутри
круглых скобок. Также можно воспользоваться ключевым словом tuple()
(конструктором кортежей). Основное различие между кортежем и списком
заключается в том, что список можно изменять, а кортеж — нет. После создания списка в него разрешается добавлять новые элементы, удалять существующие, переставлять их местами, сортировать и т. д. Но над кортежами
такие действия невозможны. Кортежи используются для хранения коллекции значений, которые не будут меняться во время выполнения программы.
Кортежами, например, можно представить координаты (широту и долготу)
города, семь дней недели, двенадцать месяцев года и т. д. По этой причине для
кортежа недоступны такие функции, как append(), pop(), reverse(), sort(),
insert(), remove() и т. д.
На иллюстрации 1.24 показано создание кортежа weekdays из названий дней
недели. Функции len() и count() для кортежей работают точно так же, как
и для списков. Как и в списках, в кортежах допускается дублирование значений и хранение данных разных типов. Обращение к элементам может осуществляться по их номерам (индексация начинается с нуля). Также допускается создание срезов. Примеры показаны на следующей иллюстрации:
Иллюстрация 1.24. Кортеж
44
■
Pythonic AI
Словарь
Словарь — это важная структура данных в Python. Как мы уже говорили,
к хранящимся в списках и кортежах значениям можно обращаться по индексу. Индекс начинается с нуля и увеличивается автоматически. Часто, однако,
бывает нужно хранить данные в формате пары «ключ-значение», для которого получить значение можно с помощью правильного ключа. В этом смысле
ключ походит на индекс, который полностью настраивается пользователем.
Формат «ключ-значение» делает словарь подходящей структурой данных для
многих приложений.
Словарь Python задается с помощью фигурных скобок, внутри которых записываются пары «ключ-значение», разделенные запятыми. Все ключи в словаре должны быть уникальными, но значения могут дублироваться. Типы значений могут быть любыми, но ключи должны быть неизменяемыми. Из-за
того, что списки в Python изменяемы, их нельзя использовать в качестве ключей, но можно использовать кортежи. Как и в случае со списками и кортежами, для создания словаря можно воспользоваться ключевым словом dict()
(конструктором словаря).
Для получения конкретного значения нужно воспользоваться ключом — подобно тому, как мы пользовались индексом для списков и кортежей. Как показано на иллюстрации 1.25, ключ указывается внутри квадратных скобок.
Также можно вставлять и обновлять значение, указывая соответствующий
ключ. Чтобы получить список всех ключей в словаре, нужно воспользоваться
функцией keys(). Функция values() возвращает список всех значений, как
показано на следующей иллюстрации.
Итерирование (перебор всех значений по очереди) по словарю выполняется
с помощью цикла for. По умолчанию для этого используются ключи словаря.
Но можно явно указать функции keys() и values() для итерирования по
ключам или значениям.
Основы Python. Концепции, библиотеки и написание кода
■
45
Иллюстрация 1.25. Словарь
Можно даже воспользоваться функцией items() для возвращения ключей
и значений попарно, как показано на следующей иллюстрации:
Иллюстрация 1.26. Итерация по словарю
46
■
Pythonic AI
Множество
Множество — это еще один тип структуры данных Python для хранения
коллекции значений. Множество задается перечислением значений через
запятую внутри фигурных скобок. Элементы множества не упорядочиваются, поэтому данные из множества нельзя получить с помощью индексов.
После создания множества также нельзя изменить ни один из его элементов,
но можно добавлять или удалять элементы. Множество не может содержать
дублирующие элементы.
Функция add() позволяет добавлять элементы в множество. Удалить элементы из множества можно с помощью метода remove(), как показано на
следующей иллюстрации:
Иллюстрация 1.27. Множество
К структурам данных типа «множество» в Python применимы операции из теории множеств, например, объединение (union), пересечение (intersection),
разность (difference) и т. д., как показано на иллюстрации 1.28:
Основы Python. Концепции, библиотеки и написание кода
■
47
Иллюстрация 1.28. Операции с множествами
Подробнее о строковых переменных
Строковые переменные (типа string) хранят необработанные текстовые данные. Работа с текстом очень важна в сфере искусственного интеллекта, особенно для задач, связанных с обработкой естественного языка (NLP, Natural
language processing). Давайте ознакомимся с различными способами работы
со строками в Python.
Строки можно рассматривать как список символов, и они итерируемы, как
списки. В отличие от языков программирования C/C++ или Java, в Python
нет отдельного «символьного» типа данных (character). Одиночный символ
в Python считается строкой, длина которой равна 1.
Как и к элементам в списках Python, к символам в строке можно обращаться
по индексу. Функция len() также работает со строкой, возвращая ее длину.
К строкам, так же как и к спискам, применимы срезы. Для проверки вхождения подстроки в строку можно воспользоваться ключевым словом in (оператором принадлежности), как показано на следующей иллюстрации:
48
■
Pythonic AI
Иллюстрация 1.29. Строки
С помощью базовых функций можно менять регистр строки. Функция
upper() переводит всю строку в верхний регистр. Функция lower() производит противоположное действие. Иногда требуется удалить из строки
предшествующие и последующие пробелы. Для этого используются функции
rstrip(), lstrip() и strip(), как показано на следующей иллюстрации:
Иллюстрация 1.30. Функции строк
Основы Python. Концепции, библиотеки и написание кода
■
49
При работе со строками часто используется функция split(). Она разбивает
строку, ориентируясь на заданный разделитель, и возвращает список отдельных строк. При вызове функции без параметров разделителем считается
пробел. Еще одна широко используемая функция — join(). Она объединяет все элементы итерируемой последовательности (списка, кортежа, словаря
и т. д.) в одну строку, помещая между ними заданный разделитель, как показано на следующей иллюстрации:
Иллюстрация 1.31. Строковые функции
Как показано на иллюстрации 1.32, с помощью знака «+» можно складывать
строки (операция «конкатенации»). Однако данные других типов со строками объединять или комбинировать нельзя. Если попытаться это сделать,
Python выдаст ошибку. Динамически вставить данные других типов в строки
можно с помощью операции форматирования. Для этого нужно поставить
фигурные скобки в тех местах, где должны появиться значения переменных
или выражений. Внутри скобок можно использовать индексные номера для
указания, в каком порядке вставлять данные. Форматирование осуществляет
функция format(). В качестве аргументов функции format() можно вставлять любые данные без их приведения к другому типу. Рассмотрим следующий пример.
50
■
Pythonic AI
Иллюстрация 1.32. Конкатенация и форматирование строк
Функции в Python
При написании программы для выполнения какой-то большой задачи иногда возникает необходимость в составлении нескольких маленьких подпрограмм, которые должны выполняться многократно. Вместо того, чтобы всякий раз заново расписывать всю логику (все операции и процедуры), можно
сохранить фрагмент кода и при необходимости его вызывать. Такой вариант
сэкономит время работы и уменьшит длину файла программы. Python обеспечивает гибкость и удобство повторного использования кода с помощью
функций. Функция в Python — это просто блок кода, который программа запускает при обращении к нему по имени. Не забывайте о том, что команды
и процедуры внутри блока должны записываться с отступами, определяющими тело функции.
Для создания функции в Python нужно воспользоваться ключевым словом
def. Функция может принимать входные аргументы — по сути, это переменные с их значениями, которые можно использовать в коде этой функции, — а также возвращать некоторое значение. В целом синтаксис функции таков:
1. def имя_функции (входные аргументы):
2.
# код функции
3.
# оператор возврата (return)
Основы Python. Концепции, библиотеки и написание кода
■
51
Типы данных входного и возвращаемого аргументов можно определить явным образом:
1. def имя_функции (аргумент: тип данных)-> тип возвращаемых данных
2.
# код функции
3.
# оператор возврата (return)
Как показано на иллюстрации 1.33, для входного аргумента функции можно
определить значение по умолчанию. Если при вызове функции значение аргумента не задано, то будет использовано значение по умолчанию. В противном случае аргумент примет указанное программистом значение. Рассмотрим
следующий пример, где функция area_of_rectangle рассчитывает площадь
прямоугольника по его длине (length) и ширине (width):
Иллюстрация 1.33. Аргументы функции
Если мы не знаем, сколько аргументов понадобится передать функции, то перед именем аргумента можно поставить символ «*». В общем случае аргумент
здесь записывается как «*args». Таким образом, функция в качестве входных
данных ожидает кортеж аргументов, а внутри кода этой функции отдельные
аргументы извлекаются по индексу. Аналогично можно передавать входные
аргументы в функцию по принципу «ключ = значение». Так функция получает
корректные значения даже при несоблюдении порядка аргументов. В общем
случае аргумент здесь записывается как «**kwargs». Рассмотрим следующие
примеры:
52
■
Pythonic AI
Иллюстрация 1.34. *args и **kwargs в Python
Объектно-ориентированное
программирование в Python
ООП — это методика программирования с ориентацией на компоненты, соответствующие сущностям реального мира, которые имеют определенные
свойства и поведение. Эти компоненты называются объектами. Такая модель
весьма эффективна при обработке, масштабировании и организации больших и сложных программ. Кроме того, она обеспечивает легкость сопровождения и читаемость кода.
Python — объектно-ориентированный язык программирования. Он позволяет представить любой объект реального мира в виде объекта на Python.
Объектами, например, можно считать мяч, дерево, дом, лампочку, автомобиль и т. д. Каждый объект имеет свои уникальные атрибуты (такие свойства,
как форма, цвет и т. д.) и функциональные возможности (то, что он может
делать, или что можно с ним делать). В объектно-ориентированном программировании выделяют следующие важные понятия:
• Класс. Коллекция объектов со схожими атрибутами и функциональными возможностями. Класс — это определяемый пользователем тип
данных, который можно рассматривать как «шаблон» объектов.
• Объект. Отдельный экземпляр класса. При создании объект получает доступ к переменным и функциям, принадлежащим его классу.
Основы Python. Концепции, библиотеки и написание кода
■
53
Например, можно создать класс Animal («животное») и после этого
создавать отдельные объекты или экземпляры этого класса, такие как
«корова», «утка», «сова», «собака» и т. д.
• Метод. Это функция, записанная для класса и определяющая поведение объектов, созданных на основе этого класса. Например, класс
Animal может содержать в себе такие функции, как make_sound() («издавать звук»).
Как показано на иллюстрации 1.35, класс в Python задается с помощью ключевого слова class. Затем имя нового класса используется для создания отдельных объектов. Первый аргумент конструктора класса позволяет получить
доступ к переменным или атрибутам класса. Как правило, в качестве первого
входного аргумента используется параметр self. У каждого класса в Python
имеется функция __init__. Она представляет собой то же самое, что конструктор в других объектно-ориентированных языках (таких как Java, C++
и т. д.) и выполняется всегда при создании объекта класса. С ее помощью
мы будем присваивать значения атрибутов объекта и задавать некоторые его
функции. Так, например, при создании объектов класса «животные» можно
задавать количество ног, цвет, и прочие признаки конкретного животного
(коровы, собаки, совы и т. д.). Рассмотрим следующие примеры, где num_of_
legs — это количество ног, color — цвет, а sound — издаваемый звук.
Иллюстрация 1.35. Классы в Python
54
■
Pythonic AI
Наследование классов
Для создания новых классов в ООП часто используется принцип наследования. На основе одного класса можно сформировать другой, «дочерний» по
отношению к «родительскому», дополнительно задействуя возможность повторного использования кода.
В приведенном ниже примере на основе родительского класса Animal («животное») создается дочерний класс Bird («птица»). Дочерний класс наследует
все методы и атрибуты родительского класса, включая функцию __init__.
Однако если для дочернего класса определить свою функцию __init__, она
заместит функцию __init__ родительского класса. Иногда бывает нужно сохранить родительскую функцию __init__ и в то же время добавить некоторые дополнительные команды для инициализации объекта дочернего класса.
В этом случае к методам, унаследованным от родительского класса, следует
дописать новые функции. Рассмотрим следующий код:
1. # родительский класс
2. class Animal:
3. def __init__(self, num_of_legs, color):
4.
self.num_of_legs = num_of_legs
5.
self.color = color
6.
7. def make_sound(self, sound):
8. print("Это животное {0}, и оно {1}.".format(self.color, sound))
9.
10. # дочерний класс Bird, наследующий классу Animal
11. class Bird(Animal):
12.
13.
def __init__(self, num_of_legs, color, can_fly=True):
self.can_fly = can_fly
14.
15.
# вызов функции __init__ родительского класса
16.
Animal.__init__(self, num_of_legs, color)
17.
18.
# перезаписывание метода родительского класса
19.
def make_sound(self, sound):
20.
if self.can_fly == True:
Основы Python. Концепции, библиотеки и написание кода
21.
22.
23.
■
55
print("Это летающая {0} птица, и она {1}.".format(self.
color,sound))
else:
print("Это нелетающая {0} птица, и она {1}.".format(self.
color,sound))
Создание объекта дочернего класса показано на иллюстрации 1.36:
Иллюстрация 1.36. Создание объекта duck («утка»)
В данном случае мы создали объект под названием duck («утка»). Функция
make_sound() наследуется от родительского класса Animal и переопределяется. В новой версии этой функции мы использовали дополнительную переменную can_fly («умеет летать»). Можно создать другой объект под названием ostrich («страус») и задать переменной can_fly значение False, как
показано на следующей иллюстрации:
Иллюстрация 1.37. Создание объекта ostrich («страус»)
При наличии нескольких дочерних классов, наследующих свойства от родительского класса, каждый дочерний класс имеет общие свойства с родительским, но может реализовывать их по-своему. Такая черта называется полиморфизмом.
56
■
Pythonic AI
NumPy
За последнее десятилетие популярность языка Python как средства разработки систем искусственного интеллекта стремительно возросла. Это произошло во многом благодаря наличию высокоуровневых пакетов для численных
расчетов и обработки данных. NumPy — это библиотека Python, которая широко используется для работы с большими многомерными массивами и матрицами. Она включает в себя обширный набор математических функций для
работы с этими массивами. Чтобы воспользоваться библиотекой NumPy,
нужно для начала импортировать ее в код программы следующим образом:
1. import numpy as np
Здесь np — это сокращенное имя библиотеки numpy, и в дальнейшем мы будем использовать его для вызова любой функции NumPy.
Основная структура данных, предоставляемая NumPy, — это однородный
многомерный массив. Список Python позволяет хранить в себе несколько элементов, принадлежащих к разным типам, но в многомерном массиве
NumPy можно хранить данные только одного типа.
Создадим для примера n-мерный массив NumPy чисел с плавающей точкой
(ndarray) под именем nums, как показано на иллюстрации 1.38. Для создания
массива типа ndarray нужно вызвать функцию np.array() и указать в скобках его элементы. Далее можно проверить тип созданного массива nums, а также его форму, размер и количество измерений. Функция size возвращает количество элементов в массиве, а ndim — число измерений. В данном примере
ndim возвращает 1, то есть массив nums — это по сути одномерный список.
То же самое можно увидеть и в выводе функции shape, которая возвращает
одноэлементный кортеж (3,). Отсутствие значения после запятой означает,
что массив типа ndarray в данном случае одномерный и содержит 3 элемента.
Такой массив также называется массивом первого ранга.
Если вместо массива нескольких чисел с плавающей точкой (float) мы создадим массив ndarray только с одним значением, он будет считаться скаляром.
В математике скаляром называется величина, каждое значение которой описывается одним числом.
Основы Python. Концепции, библиотеки и написание кода
■
57
Иллюстрация 1.38. NumPy
В NumPy скаляры ndarray считаются нульмерными; для них функция shape
возвращает пустой кортеж, как показано на следующей иллюстрации:
Иллюстрация 1.39. Скаляр
58
■
Pythonic AI
Если создать массив типа ndarray в виде списка списков, то он будет считаться двумерным массивом или матрицей. На иллюстрации 1.40 в качестве
аргумента функции np.array() передан список списков (или массив массивов). Функция shape в данном случае возвращает кортеж (2, 3); это говорит
о том, что массив состоит из двух строк и трех столбцов (или двух строк,
имеющих по три элемента в каждой). Двумерный массив называется матрицей, а трехмерный — тензором. При разработке ИИ-приложений мы будем
стараться избегать массивов первого ранга путем преобразования их в соответствующие вектор-столбцы (n-строк и 1 столбец) или вектор-строки
(1 строка и n-столбцов).
Иллюстрация 1.40. Ndarray из списка списков
Изменение формы (размерности) массивов
Функция reshape() в NumPy часто используется для изменения формы массива перед применением к нему какой-либо математической операции. В качестве аргумента функции reshape нужно указать новый размер в виде целого числа (в этом случае результатом станет одномерный массив указанной
длины) или кортежа целых чисел. При этом новая форма обязательно должна
быть совместима с исходной. См. иллюстрацию 1.41.
Основы Python. Концепции, библиотеки и написание кода
■
59
Иллюстрация 1.41. Изменение формы массива
Как показано на иллюстрации 1.42, в качестве одного из значений новой формы можно указать -1. В таком случае значение размерности будет автоматически вычислено исходя из длины массива и остальных размерностей.
Иллюстрация 1.42. Автоматический выбор формы
60
■
Pythonic AI
Транспонирование массивов
Транспонирование — важная математическая операция, которая широко
применяется во многих алгоритмах искусственного интеллекта. С точки
зрения векторов транспонирование меняет ориентацию элементов. Векторстрока становится вектором-столбцом и наоборот. В матрице транспонирование меняет расположение элементов относительно главной диагонали,
в результате чего строки становятся столбцами, а столбцы — строками, как
показано на следующей иллюстрации:
Иллюстрация 1.43. Транспонирование массива
Основы Python. Концепции, библиотеки и написание кода
■
61
Векторизация
Векторизация — мощный инструмент NumPy, позволяющий приложению
применять какую-либо функцию ко всему массиву сразу, а не перебирать его
элементы по отдельности. Такие функции позволяют сильно оптимизировать программы и значительно сократить время вычислений. При разработке приложений для искусственного интеллекта следует пользоваться векторизацией NumPy везде, где это возможно. Пример векторизации показан на
иллюстрации 1.44:
Иллюстрация 1.44. Векторизация
Умножение матриц
NumPy позволяет получить произведение (результат умножения) двух матриц, если они совместимы, то есть если количество столбцов первой матрицы
равно количеству строк второй матрицы. Для этого в библиотеке NumPy используется функция dot(). В Python 3.5 для этой же функции появился оператор @, как показано на следующей иллюстрации:
62
■
Pythonic AI
Иллюстрация 1.45. Умножение матриц
Генерация чисел
Для некоторых математических операций бывает необходимо сгенерировать
определенный массив чисел. В NumPy это можно сделать с помощью следующих функций:
• arange(start, stop, step). Эта функция создает одномерный массив чисел, равномерно расположенных в интервале заданных значений
start (начало) и stop (конец); этот интервал включает значение start,
но не включает значение stop. Промежутки между числами определяются значением параметра step (шаг).
• linspace(start, stop, num). Эта функция создает одномерный массив из num равноотстоящих друг от друга чисел в диапазоне, заданном
значениями start и stop.
Примеры выполнения этих функций показаны на следующей иллюстрации:
Иллюстрация 1.46. Функции arange и linspace в NumPy
Основы Python. Концепции, библиотеки и написание кода
■
63
• random.rand(shape). Эта функция заполняет массив заданной формы
случайными числами из равномерного распределения на интервале от
0 до 1;
• random.randn(shape). Эта функция заполняет массив заданной формы
случайными числами из стандартного нормального распределения;
• zeros(shape). Эта функция создает массив нулей заданной формы, где
shape — целое число или кортеж. Также можно указать нужный тип
данных;
• ones(shape). Эта функция создает массив единиц заданной формы,
где форма — это целое число или кортеж. Также можно указать нужный тип данных.
Примеры этих функций показаны на следующей иллюстрации:
Иллюстрация 1.47. Случайные числа, нули и единицы в NumPy
Matplotlib
Matplotlib — это библиотека Python, используемая для создания двумерных визуализаций на основе данных, и весьма полезная утилита для
64
■
Pythonic AI
программистов на Python, позволяющая преобразовывать данные в понятные изображения. Визуальное представление помогает лучше анализировать
данные, заниматься отладкой ИИ-моделей, измерять производительность
моделей, демонстрировать результаты прогнозирования и т. д. Для Python
есть и другие библиотеки визуализации, позволяющие создавать эстетически более привлекательные иллюстрации, но преимущество Matplotlib в том,
что она легко настраивается, и результаты ее работы можно экспортировать
во множество форматов файлов, поэтому большинство разработчиков ИИприложений выбирают именно ее. Мы тоже будем пользоваться этой библиотекой в последующих главах.
Для подключения Matplotlib к программе, написанной на языке Python, ее тоже нужно импортировать как и NymPy. Большинство утилит визуализации,
предоставляемых библиотекой Matplotlib, находятся в ее подмодуле pyplot.
Поэтому для визуального отображения данных мы будем импортировать
подмодуль pyplot:
1. import matplotlib.pyplot as plt
Одна из самых широко используемых функций подмодуля pyplot — это метод
plot(), который позволяет строить графики по координатам точек на осях
x и y, задаваемых в виде массива. Построим график из линий, соединяющих
точки с координатами (0, 0), (4, 86), (6, 77) и (8, 100). Для начала нужно создать
массив NumPy, содержащий только значения координат x, и массив NumPy,
содержащий только значения координат y. Затем эти массивы необходимо
передать в качестве аргументов функции plot(). В итоге мы получим график,
показанный на следующей иллюстрации:
Основы Python. Концепции, библиотеки и написание кода
■
65
Иллюстрация 1.48. Функция plot() библиотеки Matplotlib
С помощью форматирующей строки можно указать дополнительные параметры, такие как цвет, маркер и тип линии. Например, если мы захотим отобразить только точки («маркеры») в виде кругов без линий, нам нужно будет
указать в форматирующей строке букву o (нижнего регистра). Цвет линии
и маркеров задается первой буквой названия цвета. Так синяя линия с круглыми маркерами задается строкой bo-. Как показано на иллюстрации 1.49,
зеленая пунктирная линия с маркером в виде знака «плюс» задается строкой g+--. Ширина линии и размер маркера задаются с помощью свойств
linewidth и marker size.
66
■
Pythonic AI
Иллюстрация 1.49. Форматирование графика
Можно также построить график из нескольких наборов данных. Самый простой способ — использовать функцию plot() несколько раз, как показано на
иллюстрации 1.50. Подписи к осям X и Y добавляются с помощью функций
xlabel() и ylabel(). Также можно задать заголовок графика с помощью
функции title() и легенду с помощью функции legend(). Функция grid()
добавляет на график сетку, облегчающую его чтение. Рассмотрим следующую
иллюстрацию, на которой выводится график летних и зимних средних температур (summer_tmp и winter_tmp) в разных городах.
Основы Python. Концепции, библиотеки и написание кода
■
67
Иллюстрация 1.50. Отображение нескольких наборов данных
Если мы хотим показать рядом несколько графиков по горизонтали или
вертикали, можно воспользоваться очень полезной функцией для сравнения графиков — subplot(). Синтаксис ее следующий: subplot(количество
строк, количество столбцов, индекс изображения). Номера строк и столбцов указывают на местоположение отдельных графиков. Если нужно нарисовать два графика вертикально, один под другим, то оба они будут находиться
в одном столбце, но в двух разных строках. При горизонтальном построении
они окажутся в одной строке и двух столбцах, как показано на следующей
иллюстрации:
68
■
Pythonic AI
Иллюстрация 1.51. Подграфики
Заключение
В этой главе мы начали наше путешествие по миру языка Python и познакомились с его базовыми концепциями, основными библиотеками и практическими приемами написания кода. Эти концепции и приемы служат строительными блоками любой программы, написанной на Python. Теперь вы
знаете, какие действия можно производить с данными, и как управлять ходом выполнения программы. В следующих главах мы изучим еще несколько
библиотек и освоим еще несколько приемов, полезных для разработки ИИприложений. В следующей главе мы настроим свою собственную ИИ-лабораторию в Google Colab.
Основы Python. Концепции, библиотеки и написание кода
■
69
Основные выводы
• Python поддерживает различные типы данных (такие как целые числа,
числа с плавающей точкой, строки, булевы переменные и т. д.) с условными операторами и операторами цикла. Он также поддерживает широкий спектр других операторов, включая арифметические, операторы
сравнения, логические операторы, операторы тождества и операторы
принадлежности.
• Python поддерживает базовые структуры данных, такие как список,
кортеж, множество, словарь и прочие, необходимые для эффективной
организации данных и управления ими.
• Python — это объектно-ориентированный язык, в котором можно определять классы для создания объектов с заданными свойствами и поведением. Наследование позволяет создавать новые классы на основе
существующих и тем самым поддерживает многократное использование кода.
• NumPy — это мощная библиотека, которая расширяет возможности
языка по работе с массивами и позволяет эффективно производить
вычисления. Библиотека Matplotlib служит для отображения информации в виде диаграмм и графиков, упрощающих анализ данных и составление презентаций.
• Google Colab — это удобная онлайн-среда для написания и выполнения кода на языке Python.
Ссылки
• Официальная документация по Python: https://www.python.org/doc/
• Официальная документация по NumPy: https://numpy.org/doc/stable/
• Официальная документация по Matplotlib: https://matplotlib.org/stable/
tutorials/index
Глава 2
Настройка лаборатории
искусственного
интеллекта
Введение
Для проведения любого научного эксперимента требуется лаборатория, и нам
для проведения своих экспериментов тоже понадобится своеобразная лаборатория — точнее, среда, в которой мы сможем экспериментировать и разрабатывать реальные ИИ-приложения. В этой главе мы рассмотрим основы работы в среде Google Colab, которая позволит нам запускать ИИ-приложения
в облаке с помощью аппаратных ускорителей и освободит нас от трудностей,
связанных с созданием дорогостоящей инфраструктуры.
Структура
В этой главе мы рассмотрим следующие темы:
• Локальная среда или облако.
• Настройка локальной лабораторной среды.
• Google Colab.
• Использование возможностей GPU.
• Подключение Google Диска.
• Использование Google Colab с GitHub.
Настройка лаборатории искусственного интеллекта
■
71
Цели
Цель этой главы — предоставить исчерпывающее руководство по созданию
функциональной и эффективной среды ИИ-разработки. Мы вооружим читателей основными теоретическими и практическими знаниями для создания
ИИ-лаборатории на локальной машине с помощью Anaconda, а также в облаке с помощью таких платформ, как Google Colab. К концу этой главы вы поймете, как писать код на Google Colab и получите представление о лабораторных условиях, необходимых для разработки ИИ-приложений и эффективного
использования Google Colab. Эта глава обеспечит отдельных пользователей
и членов команд, а также новичков в области ИИ-разработки, необходимыми
инструментами и знаниями для достижения успеха в их начинаниях.
Локальная среда или облако
В главе 1 под названием «Основы Python. Концепции, библиотеки и написание кода» мы изучали основы написания программного кода на языке Python
и пользовались при этом средой Google Colab. Но помимо этой облачной среды, позволяющей работать в браузере, существуют и другие инструменты для
написания и выполнения программ на языке Python. Можно также организовать необходимую инфраструктуру в локальной сети или на персональном
компьютере. Хотя мы настоятельно рекомендуем писать код для разработки
описываемых в этой книге ИИ-приложений на Colab, стоит вкратце обсудить
настройку локальной среды и сравнить ее с облачной.
Главное преимущество облачной среды, такой как Colab, заключается в том,
что для написания кода на языке Python не нужно устанавливать все с нуля.
Нет необходимости в установке интерпретатора Python и отдельно его библиотек. В Colab уже предустановлены многие важные пакеты, такие как NumPy,
Matplotlib, Tensorflow, Keras, PyTorch и т. д.). Благодаря тому, что Colab — это
облачная среда, блокноты, используемые в ней для написания кода, доступны
из браузера на любом устройстве. Риск потери наработок из-за сбоев оборудования практически отсутствует. Файлы ваших блокнотов также можно использовать для совместной работы, как и любые другие документы в Google
Docs. Кроме того, среда Colab предоставляет пользователям ограниченное
количество бесплатных ускорителей, таких как графический процессор
(GPU) и тензорный процессор (TPU). Они играют важную роль при работе
с большими моделями нейронных сетей или наборами данных. Установка подобного оборудования в локальной системе обходится довольно дорого.
Google Colab можно легко интегрировать с Google Диском, что обеспечит
ИИ-проекты хранилищем данных требуемого размера.
72
■
Pythonic AI
С другой стороны, если вы готовы взять на себя хлопоты по настройке среды
разработки с нуля, то можно создать лабораторию ИИ и на локальной системе. Для этого мы рекомендуем использовать дистрибутив Python Anaconda —
бесплатное программное обеспечение с открытым исходным кодом. Anaconda
поставляется с предустановленными важными библиотеками и упрощает
процессы развертывания программных продуктов, написанных на Python.
Настройка локальной
лабораторной среды
Чтобы установить программное обеспечение Anaconda локально, перейдите на сайт https://www.anaconda.com/products/ и загрузите дистрибутив. Вебстраница (см. иллюстрацию 2.1), скорее всего, сама определит вашу операционную систему и отобразит соответствующие исполняемые файлы для
загрузки. Тем не менее стоит удостовериться в том, что загружаемый файл
действительно соответствует вашей операционной системе и разрядности
процессора (в нашем случае 64-битной).
Иллюстрация 2.1. Веб-страница загрузки Anaconda
После загрузки исполняемого файла нужно дважды щелкнуть по нему и установить программное обеспечение, следуя инструкциям на экране. Затем
необходимо открыть программу под названием Anaconda Navigator. В операционной системе Windows ее можно найти через меню «Пуск». Сразу после
запуска Anaconda Navigator появится окно примерно такого вида:
Настройка лаборатории искусственного интеллекта
■
73
Иллюстрация 2.2. Anaconda Navigator
В нем отображается список программ, поставляемых вместе с Anaconda. Для
начала работы нужно запустить Jupyter Notebook (показан красной стрелкой на предыдущем изображении). В браузере откроется домашняя страница
Jupyter Notebook. Нажмите на кнопку New, как показано на иллюстрации 2.3,
и выберите в выпадающем списке пункт Python3, чтобы открыть блокнот
Python (Python notebook).
Иллюстрация 2.3. Jupiter Notebook
В отдельной вкладке браузера откроется новый блокнот Python, после чего
вы сможете писать код — точно так же, как и в блокноте Colab.
74
■
Pythonic AI
Иллюстрация 2.4. Написание кода в Jupiter Notebook
Notebook (блокнот) — это интерактивная командная оболочка для Python,
работающая через веб-интерфейс. Файл блокнота имеет расширение .ipynb.
Заметим, что запустить его непосредственно в виде сценария или кода Python
для выполнения какой-либо задачи нельзя. Для этого нужно сначала написать код в ячейке блокнота в виде скрипта, открыть меню File, выбрать пункт
Download и загрузить свой код в виде файла с расширением .py, как показано
на следующей иллюстрации:
Иллюстрация 2.5. Загрузка файла из Jupiter Notebook
Настройка лаборатории искусственного интеллекта
■
75
После загрузки у вас на компьютере появится файл с расширением.py —
не что иное, как сценарий, написанный на языке Python. Теперь можно открыть Anaconda Prompt и выполнить этот сценарий. В операционной системе
Windows нужно нажать кнопку «Пуск» и найти «Anaconda Prompt» в списке
установленных программ. Anaconda Prompt — это просто командная строка
или оболочка, в которой можно запустить Python-скрипт без настройки переменной среды PATH. Чтобы запустить Python-скрипт из этой строки, достаточно написать команду со следующим синтаксисом:
python путь_к_скрипту_python
Например:
1. python /Downloads/my_script.py
Таким образом можно создать локальную лабораторную среду для написания и выполнения кода на Python и разработки ИИ-приложений.
Примечание. Пользовательский интерфейс сайта www.anaconda.com
может время от времени меняться и не совпадать с показанным на иллюстрациях выше. Но принципы его использования будут аналогичны
описанным выше и такими же простыми.
Google Colab
В главе 1 «Основы Python. Концепции, библиотеки и написание кода» мы
уже пользовались Google Colab для написания и выполнения кода на Python.
В этой главе мы рассмотрим Google Colab подробнее и узнаем, как создать
свою лабораторию. Google Colab — это облачное приложение от Google, то
есть оно работает на внутреннем оборудовании Google, к которому мы получаем доступ через браузер. Мы уже обсуждали, как открыть блокнот в Colab.
Для работы с приложением Google Colab потребуется учетная запись Google
(Gmail ID). Новый блокнот, который создается в Colab, сохраняется на Google
Диске, связанном с указанной учетной записью. Убедиться в этом можно, открыв меню Файл (File) и выбрав пункт Показать на диске (Locate in
Drive):
76
■
Pythonic AI
Иллюстрация 2.6. Как посмотреть блокнот на Google Диске
В отдельной вкладке откроется страница с расположением файла блокнота.
По умолчанию на Google Диске создается каталог Colab Notebooks, внутри
которого и хранятся блокноты Colab. В открывшейся вкладке отображаются
все хранящиеся на Google Диске блокноты, как показано на следующей иллюстрации:
Иллюстрация 2.7. Блокноты Colab на Google Диске
На крайней левой панели блокнота имеется значок папки (показанный
стрелкой на иллюстрации 2.8). При нажатии на эту иконку откроется панель
Настройка лаборатории искусственного интеллекта
■
77
проводника, в которой будут отображены все доступные файлы, такие как
загруженные файлы, файлы с подключенного Google Диска и т. д., см. иллюстрацию 2.8.
Иллюстрация 2.8. Файловый менеджер в Google Colab
Новый блокнот при создании получает имя Untitled с определенным номером. Если вы хотите переименовать блокнот, нужно дважды щелкнуть по его
имени и отредактировать текст, как показано на иллюстрации 2.9.
Иллюстрация 2.9. Переименование блокнота
78
■
Pythonic AI
Google Colab позволяет создавать ячейки двух типов: кодовые и текстовые.
Как показано на иллюстрации 2.10, в левом верхнем углу блокнота содержатся две кнопки ("+ Код" и «+ Текст") для добавления ячейки соответствующего типа. Мы уже создавали кодовую ячейку, а теперь создадим текстовую
ячейку и посмотрим, как она поможет сделать блокнот более читабельным.
Иллюстрация 2.10. Вставка кодовой или текстовой ячейки
Текстовая ячейка в блокноте Colab может содержать объемные тексты, изображения, гиперссылки и прочие материалы, предназначенные для пояснения
кода. В них можно записывать свои наблюдения и размышления по поводу
методов или их реализации. Разработчики могут оставлять в них комментарии по поводу данных и записывать предсказания модели. Код, записанный
в текстовой ячейке, не будет исполнен. Такие ячейки допускают оформление
текста с помощью языка разметки Markdown — простого и удобного инструмента форматирования документов в Интернете.
Для текстовых ячеек доступны такие варианты форматирования, как вставки
заголовков разных уровней, жирный шрифт, курсив, отображение текста как
фрагмента кода, вставка гиперссылки, вставка изображения, отступ, добавление нумерованного списка, добавление маркированного списка и т. д. Эти
варианты отображаются в виде кнопок на панели. Кроме того, текст можно
форматировать непосредственно при наборе с помощью синтаксиса языка
Markdown. Окно набора текста поделено на две части: слева от разделителя содержится поле для ввода, а справа набранный текст отображается для
предварительного просмотра в уже отформатированном виде. Чтобы выйти
из режима редактирования, нужно, как и в случае с кодовой ячейкой, нажать
сочетание клавиш Shift+Enter.
Настройка лаборатории искусственного интеллекта
■
79
Попробуем написать заголовок в формате Markdown. Для этого в начале
строки нужно поставить символ #, как показано на иллюстрации 2.11. По мере увеличения количества символов # (всего их можно вставить шесть) размеры заголовков будут уменьшаться.
Иллюстрация 2.11. Заголовки в формате Markdown
После того как мы нажмем на клавиши Shift+Enter и выйдем из режима редактирования, текст отобразится следующим образом:
Иллюстрация 2.12. Итоговый вид текстовой ячейки
На иллюстрации 2.13 показаны различные варианты форматирования с использованием синтаксиса Markdown для более красивого и понятного оформления текста.
80
■
Pythonic AI
Иллюстрация 2.13. Возможности языка разметки Markdown
Для выполнения (запуска кода) одной ячейки блокнота мы пользовались клавишами Shift+Enter. Также существуют способы запустить сразу несколько
ячеек одной командой. В меню Среда выполнения (Runtime) можно выбрать
такие варианты как «выполнить все ячейки», «выполнить все ячейки перед
текущей», «выполнить все выбранные ячейки» и т. д. Если что-то пошло не
так, есть возможность прервать выполнение программы или перезапустить
сеанс. По окончании работы с блокнотом, вы можете отключиться от среды
разработки и удалить результаты выполнения программы. Эти варианты показаны на следующей иллюстрации:
Иллюстрация 2.14. Варианты выполнения
Настройка лаборатории искусственного интеллекта
■
81
Примечание. Google Colab — это интерактивная среда. При длительном бездействии соединение с сервером прекращается. В бесплатной
версии блокнот Colab может работать не более двенадцати часов, в зависимости от доступности и режима использования.
Использование возможностей GPU
Центральный процессор (CPU, Central Processing Unit) — это мозг компьютера. Любая программа по своей сути — это набор инструкций, которые мы
посылаем процессору, выполняющему различные вычислительные операции.
Для более быстрого параллельного выполнения заданий центральный процессор может содержать несколько вычислительных ядер, но для многих задач в области ИИ мощностей одного процессора часто бывает недостаточно.
В следующих главах мы увидим, что при разработке ИИ-приложений приходится обучать сложные, многоуровневые и глубокие нейронные сети, обрабатывающие большие массивы неструктурированных данных. С обучением
ИИ-моделей лучше справляются графические процессоры (GPU, Graphics
Processing Unit), состоящие из специализированных ядер. Они обеспечивают значительный прирост производительности благодаря тому, что задачи по
вычислениям разделяются и выполняются на нескольких ядрах параллельно.
Существуют также специализированные интегральные схемы под названием
Тензорные процессоры (TPU, Tensor Processing Unit), разработанные корпорацией Google для создания ИИ-приложений с использованием программного обеспечения TensorFlow от Google. Эти GPU и TPU называют аппаратными ускорителями, так как при сочетании с центральными процессорами
они обеспечивают значительный прирост производительности моделей глубокого обучения или искусственного интеллекта.
Google Colab позволяет для обучении моделей в блокнотах Colab бесплатно
пользоваться ускорителями Nvidia GPU и Google TPU. Чтобы запустить блокнот на GPU или TPU, нужно перейти в меню Среда выполнения (Runtime)
и выбрать пункт Сменить среду выполнения (Change runtime type), как
показано на иллюстрации 2.15.
82
■
Pythonic AI
Иллюстрация 2.15. Смена среды исполнения
После этого появится всплывающее окно, в котором значение аппаратного
ускорителя по умолчанию равно None (Не выбран). Можно выбрать GPU
или TPU, нажав на соответствующий пункт выпадающего списка и сохранив
настройки, как показано на иллюстрации 2.16.
Иллюстрация 2.16. Выбор GPU или TPU
Совет. Также аппаратный ускоритель можно также включить, перейдя
в меню Правка | Настройки блокнота (Edit | Notebook Settings)
в блокноте.
Настройка лаборатории искусственного интеллекта
■
83
В Google Colab кнопка монитора использования оперативной памяти и дисков расположена в правом верхнем углу блокнота, как показано на иллюстрации 2.17. При нажатии на нее в окне справа отобразится подробный отчет
о загруженности оперативной памяти системы, оперативной памяти GPU (при
добавлении GPU) и диска. При повторном нажатии на кнопку это окно исчезает.
Иллюстрация 2.17. Более подробные сведения об использовании
оперативной памяти и диска
Подключение Google Диска
Для разработки ИИ-приложений в среде Google Colab часто возникает потребность в хранилище — например, для наборов исходных данных, обученных моделей, результатов и т. д., к которому можно легко получить доступ
из блокнота Colab. Поскольку Colab — это продукт Google, для работы с ним
потребуется учетная запись Google (Gmail ID). Каждому зарегистрированному пользователю Google предоставляет облачное хранилище файлов определенного объема под названием Google Диск (Google Drive). Его можно легко
интегрировать с Google Colab и хранить рабочие данные на нем. Интеграция
осуществляется следующим образом:
1. Для начала нужно подключить сервис Google Диск. Откройте блокнот Google
Colab и выполните следующие команды, запустив их в кодовой ячейке:
1. from google.colab import drive
2. drive.mount('/content/drive')
84
■
Pythonic AI
2. После выполнения этого кода появится всплывающее окно с запросом разрешения на доступ к файлам Google Диска. Выберите вариант Подключиться к Google Диску (Connect to Google Drive), как показано на следующей
иллюстрации:
Иллюстрация 2.18. Подключение к Google Диску
3. После этого в другом всплывающем окне появится форма входа в учетную
запись Google с идентификатором Gmail (Gmail ID). Нужно щелкнуть мышью по своей учетной записи, как показано на следующей иллюстрации:
Иллюстрация 2.19. Вход в учетную запись Google
Настройка лаборатории искусственного интеллекта
■
85
4. После входа в учетную запись Google появится еще одно окно с сообщением
о том, что Google Диск для настольных компьютеров хочет получить доступ
к нашей учетной записи Google. Нужно прокрутить страницу вниз и нажать
на кнопку Разрешить (Allow), как показано на следующей иллюстрации:
Иллюстрация 2.20. Предоставление доступа
5. После предоставления доступа фокус вновь переместится на блокнот
Google Colab, и вы увидите, что ячейка с кодом выполнена. Это означает, что
Google Диск смонтирован, то есть подключен для использования в блокноте
Colab, как показано на следующей иллюстрации:
Иллюстрация 2.21. Google Диск смонтирован
86
■
Pythonic AI
6. Теперь вы можете выполнить показанную ниже команду в кодовой ячейке блокнота Colab и увидеть доступные файлы и каталоги нашего Google
Диска:
1. !ls /drive/MyDrive/
Восклицательный знак (!) перед командой ls обязателен. Он говорит о том,
что мы выполняем команду Unix, а не какой-либо Python-код из блокнота
Colab. Команда ls в Unix выводит список всех файлов и каталогов по заданному пути.
Теперь мы можем получить доступ к любому файлу, расположенному по
адресу: /drive/MyDrive/ или любому подкаталогу /drive/MyDrive/ из нашего
Python-кода в блокноте Google Colab.
Примечание. Пользовательский интерфейс сайта Google Colab и некоторые его функциональные возможности могут время от времени
меняться и отличаться от того, что показано на приведенных здесь иллюстрациях. Но даже если интерфейс и не совпадет с иллюстрациями,
в целом он останется таким же простым и будет соответствовать описанным здесь процедурам.
Использование Google Colab с GitHub
GitHub (www.github.com) — это сервис репозиториев (хранилищ) кода, широко используемый для разработки программного обеспечения и контроля
версий. В Google Colab из GitHub можно импортировать любой общедоступный файл. Можно также отправить файл блокнота в репозиторий GitHub.
Чтобы открыть файл (формата .ipynb) из репозитория GitHub в среде Colab,
для начала нужно перейти в меню Файл (File) и в выпадающем списке вы-
Настройка лаборатории искусственного интеллекта
■
87
брать пункт Открыть блокнот (Open notebook), как показано на следующей
иллюстрации:
Иллюстрация 2.22. Открытие блокнота
После этого откроется окно с несколькими вкладками, такими как Примеры, Недавние, Google Диск, GitHub и Загрузить (Examples, Recent, Google
Drive, Upload). Нужно перейти на вкладку GitHub, как показано на иллюстрации 2.23. Вы увидите результаты только в том случае, если уже открывали это
окно и что-то искали в нем. Тут же имеется строка поиска, в которую можно
ввести адрес GitHub, имя пользователя или название организации. Если вы
хотите организовать поиск по личным хранилищам, поставьте галочку в пункте Показывать личные хранилища (Include private repos).
88
■
Pythonic AI
Иллюстрация 2.23. Вкладка GitHub
Давайте найдем блокнот под названием beginner.ipynb из официального репозитория Tensorflow на GitHub. Этот блокнот находится по следующему адресу : https://github.com/tensorflow/docs/blob/master/site/en/tutorials/quickstart/
beginner.ipynb. Введите этот адрес в строку поиска и нажмите Enter. Ниже
в этом же окне появятся результаты, как показано на иллюстрации 2.24.
Иллюстрация 2.24. Поиск по адресу GitHub
Настройка лаборатории искусственного интеллекта
■
89
Щелкните на первый результат, и в Colab откроется блокнот beginner.ipynb.
Если вы хотите сохранить файл блокнота в репозитории GitHub, нужно снова
зайти в меню Файл (File) и выбрать опцию Создать копию в GitHub (Save
a copy in GitHub), как показано на следующей иллюстрации:
Иллюстрация 2.25. Сохранение в GitHub
Если вы делаете это в первый раз, сначала откроется окно с надписью «Colab
ожидает авторизации от GitHub», а потом другое окно с предложением войти
в учетную запись GitHub, как показано на иллюстрации 2.26.
Иллюстрация 2.26. Вход в GitHub
90
■
Pythonic AI
После входа в учетную запись GitHub откроется окно Копировать в GitHub
(Copy to GitHub), как показано на иллюстрации 2.27. Оно содержит список
репозиториев (хранилищ), доступных для указанной учетной записи. Выберите нужное Хранилище (Repository) и нужную Ветвь (Branch) из выпадающих списков. При необходимости можно добавить «Сообщение для отправки», то есть комментарий к этой версии файла. После этого нажмите OК,
и ваш блокнот будет скопирован в хранилище, как показано на следующей
иллюстрации.
Иллюстрация 2.27. Копирование в GitHub
Теперь можно перейти в репозиторий GitHub и проверить, сохранился ли
блокнот Colab.
Совет. Выполнение кода сложных задач (например, обучения глубокой нейронной сети с большим набором данных) может потребовать
большого количества времени. Во время выполнения длительного кода вы можете переключиться на другую вкладку, окно или приложение и заняться другими делами. В меню Инструменты | Настройки |
Сайт (Tools | Settings | Site) можно поставить галочку напротив пункта
«Показывать уведомления на рабочем столе для выполненных операций», и тогда Colab будет отправлять вам уведомления о законченной
работе.
Настройка лаборатории искусственного интеллекта
■
91
Заключение
В этой главе мы рассмотрели различные аспекты организации лаборатории для разработки ИИ-приложений. Вы узнали, что можно развернуть локальную среду разработки с помощью дистрибутива Python под названием
Anaconda. Другой вариант — воспользоваться онлайн-сервисом Google Colab,
для чего даже не нужно устанавливать Python и наиболее распространенные
библиотеки на свой компьютер. Кроме того, Google Colab позволяет бесплатно воспользоваться аппаратными ускорителями (GPU и TPU). Теперь, имея
в своем арсенале такие средства, как дистрибутив Anaconda, сервис Google
Colab, аппаратные ускорители, хранилище данных Google Диск и репозиторий кода GitHub, мы вполне готовы продолжить путешествие в захватывающий мир искусственного интеллекта.
В следующей главе мы познакомимся с искусственными нейронными сетями (ИНС) и займемся практическим заданием — разработкой нашей первой ИНС.
Основные выводы
• Anaconda — мощный инструмент, позволяющий легко управлять окружением и компонентами Python, устанавливать новые пакеты и переключаться между различными версиями того или иного проекта.
Он не только упрощает рабочий процесс, но и помогает поддерживать
чистую и организованную локальную среду разработки на персональных компьютерах.
• Google Colab обладает замечательным преимуществом — предоставляет доступ к высокопроизводительным вычислительным ресурсам без
необходимости приобретения специализированного оборудования.
• Google Colab позволяет легко подключать мощные аппаратные ускорители (например, графические процессоры), которые делают процесс
обучения моделей и проведения экспериментов гораздо более эффективным.
• Благодаря удобному подключению (монтированию) Google Диска
к Google Colab можно легко хранить данные и файлы проекта и получать к ним доступ из любого места без необходимости заниматься
громоздкой передачей больших массивов данных.
92
■
Pythonic AI
• В Google Colab можно подключаться к своей учетной записи на GitHub,
чтобы сохранять или импортировать файлы блокнотов. Такая интеграция делает совместную работу и контроль версий более удобным.
Ссылки
• Платформа Anaconda: https://www.anaconda.com/
• Google Colab: https://colab.research.google.com/
• GitHub: https://github.com/
Глава 3
Создание нашей первой
модели нейронной сети
Введение
Благодаря быстрому развитию искусственного интеллекта в современном
мире мы все чаще слышим такие интригующие слова, как «глубокое обучение», «нейронные сети» и т. д. Почти каждая организация, каждая отрасль
и каждый сектор промышленности пытаются воспользоваться достижениями в области искусственного интеллекта и внедрить его в свои продукты
и услуги. Это связано с тем, что искусственный интеллект (ИИ) при взаимодействии с миром в какой-то степени подражает человеку. ИИ становится незаменимым помощником в автоматизации повторяющихся задач, позволяя
людям сосредоточиться на более важной и сложной деятельности. В настоящее время ИИ разрабатывается на основе различных видов искусственных
нейронных сетей (ИНС). Однако не стоит волноваться, если вы раньше не
работали с ИНС. В этой главе мы познакомимся с принципами, лежащими
в их основе, и построим свою первую полностью функционирующую модель
ИНС, которая сможет самостоятельно принимать решения, как это делают
люди.
Структура
В этой главе мы рассмотрим следующие темы:
• Основы ИНС.
• Библиотеки TensorFlow и Keras.
• Построение нашей первой модели ИНС.
94
■
Pythonic AI
• Обучение и оценка модели ИНС.
• TensorBoard: Инструментарий визуализации.
• Настройка гиперпараметров.
• Сохранение и загрузка моделей TensorFlow.
Цели
Глава поможет вам разобраться с основными понятиями и освоить базовые
практические навыки, необходимые для решения различных задач с использованием искусственных нейронных сетей. К концу этой главы вы узнаете,
как создать работающую ИНС с помощью TensorFlow 2 API на Python. Вы
получите представление о различных компонентах нейронной сети, таких
как слои, нейроны или узлы сети, функции потерь, оптимизаторы, метрики
и т. д. Мы изучим различные способы проектирования модели нейронной
сети в TensorFlow 2, ее обучения, настройки гиперпараметров и оценки результатов. И наконец, вы узнаете, как сохранять и загружать модель.
Основы ИНС
Скорее всего, вам уже известно, что машинное обучение подразумевает процесс обучения компьютеров на основе каких-либо данных. Мы разрабатываем модель или математическую функцию, которая сама настраивается на
принятие правильных решений при вводе в нее корректно размеченных данных. Система может принимать разные типы решений. В частности, регрессионный анализ проводится с целью предсказания некоего числа, например,
цены на дом, на основании заданных признаков (количества спален, количества санузлов, общей площади, возраста здания и т. д.). А в задачах классификации необходимо определить принадлежность объекта к дискретному классу, например, отсортировать картинки по признаку наличия или отсутствия
кошки на изображении на основе информации о цвете пикселей. ИНС — это
модель машинного обучения, которую можно использовать для решения задач как регрессии, так и классификации.
Создание нашей первой модели нейронной сети
■
95
Принципы ИНС
ИНС — это модель, построенная по принципу работы человеческого мозга,
обучающегося в процессе взаимодействия с окружающей средой, потому она
и называется «нейронной» (нейроны — это биологические нервные клетки).
Основная единица обучения в ИНС также называется «нейроном». В данном
случае — это узел, который выполняет некую математическую функцию, то
есть принимает входные данные, выполняет вычисления и формирует выходной сигнал. Нейрон можно рассматривать как линейный классификатор,
способный разграничить некоторую линейно разделяемую часть пространства входных данных. Например, он может задавать направление прямой линии, отделяющей круги от треугольников на точечной диаграмме, как показано на иллюстрации 3.1. Нейрон учится выполнять эту функцию в процессе
тренировки, когда модели нейронной сети подают на вход обучающий набор
данных.
Иллюстрация 3.1. Линейный классификатор
Представим себе набор данных, элементы которого характеризуются двумя
параметрами: x1 и x2. Один нейрон может обучиться выполнять функцию
линейного классификатора, вычисляющего функцию f(x) = w0 + w1.x1 + w2x2,
с помощью которой плоскость делится на две части. Можно нарисовать схему работы этого нейрона со входом и выходом — это будет схема так называемой однослойной ИНС, показанная на следующей иллюстрации:
96
■
Pythonic AI
Иллюстрация 3.2. Схема работы одного нейрона
Архитектура ИНС позволяет объединять в сеть множество нейронов в виде
отдельных слоев. В реальных наборах данных часто встречаются сложные,
нелинейные взаимосвязи, и изучить эти связи с помощью всего лишь одного
нейрона невозможно. Несколько нейронов связывают в один слой для того,
чтобы они учились обрабатывать различные участки распределения данных.
Для изучения сложных взаимосвязей добавляют несколько слоев. Как показано на иллюстрации 3.3, ИНС с одним скрытым слоем уже может научиться
классифицировать точки с помощью нелинейного классификатора (кривой).
Но в данном примере видно, что он работает не совсем корректно. Два круга
находятся на стороне треугольников, один треугольник — на стороне кругов,
и по одному кругу с треугольником — на самой кривой.
Иллюстрация 3.3. Классификация с использованием ИНС с одним скрытым слоем
Создание нашей первой модели нейронной сети
■
97
В более крупных ИНС нейроны взаимодействуют между собой и обучаются сложным функциям, которые позволяют лучше классифицировать точки
данных. Как показано на иллюстрации 3.4, ИНС с тремя скрытыми слоями
способна обработать сложное распределение (отражающее нелинейные взаимосвязи) точек, отделив различные области друг от друга с помощью извилистой кривой.
Иллюстрация 3.4. Классификация с использованием ИНС с тремя скрытыми слоями
Функция активации
При объединении нейронов и слоев с одиними лишь линейными классификаторами внутри каждого нейрона на выходе нейронной сети получается
просто взвешенная сумма входных сигналов. Такая обработка не содержит
нелинейных эффектов, которые отражали бы сложные взаимосвязи. Чтобы
ввести в ИНС нелинейность, нужно пропустить линейный выходной сигнал
каждого нейрона (то есть взвешенную сумму) через некоторое нелинейное
преобразование — так называемую функцию активации. К широко используемым функциям активации принадлежат сигмоида, tanh (гиперболический тангенс), ReLu (Rectified Linear unit, «усеченное линейное преобразование») и пр. Итак, если нейрон обучится выполнять линейную функцию
f(x) = w0 + w1.x1 + w2.x2, то к этой функции f(x) также нужно будет применить
функцию активации g(x). Конечный выход будет таким: y = g(f(x)).
Ниже описаны некоторые из широко используемых функций активации.
Сигмоидальная функция (Сигмоида). Это гладкая S-образная функция,
вносящая нелинейность в обучение нейронных сетей. Давайте реализуем эту
98
■
Pythonic AI
функцию в Python и построим ее график. В главе 1 «Основы Python. Концепции, библиотеки и написание кода» мы научились использовать библиотеку
NumPy и ее функцию linspace. Для начала с ее помощью создадим массив из
пятидесяти чисел, равномерно расположенных в диапазоне от –10 до 10. Зададим сигмоидальную функцию и применим ее к этому массиву из пятидесяти чисел, как показано на иллюстрации 3.5.
Иллюстрация 3.5. Сигмоидальная функция
Мы видим, что переменная f — это массив, содержащий значения от –10 до
10. Представим, что этот массив f — результат работы линейной функции,
или f(x), как уже говорилось ранее. Задать сигмоидальную функцию в NumPy
Создание нашей первой модели нейронной сети
■
99
можно с помощью функции exp. Значения на выходе сигмоидальной функции записываются в переменную g, которая также представляет собой массив. Для удобства чтения мы округлили числа до трех знаков после запятой
средствами NumPy. Выведя на экран массив g, мы убедились, что сигмоида
«сжимает» значения, поданные ей на вход, и преобразует их в числа в диапазоне от 0 до 1, как показано на иллюстрации 3.5.
Отобразив на графике массивы g и f, мы увидим характерную S-образную
кривую сигмоиды, как показано на иллюстрации 3.6.
Иллюстрация 3.6. График сигмоидальной функции
Гиперболический тангенс (функция tanh). Это элементарная функция,
выражающаяся через экспоненту. Она встроена в библиотеку NumPy и также используется в качестве функции активации в нейронных сетях. Воспользуемся ею для построения графика, как показано на следующей иллюстрации:
100
■
Pythonic AI
Иллюстрация 3.7. Функция tanh
Видно, что график похож на сигмоиду, но область значений функции tanh лежит в диапазоне от -1 до 1.
Функция ReLu. Функция ReLu (Rectified Linear unit, усеченное линейное
преобразование) — это функция активации, возвращающая входное значение, если оно положительное, и нулевое в противном случае. ReLu широко
используется в нейронных сетях. Мы реализуем эту функцию с помощью
функции maximum NumPy, подав ей на вход массив чисел. В данном случае
функция будет сравнивать каждый элемент массива f с нулем, в результате
чего на выходе получим либо этот элемент, либо нуль, как показано на следующей иллюстрации:
Создание нашей первой модели нейронной сети
■
101
Иллюстрация 3.8. Функция ReLu
Помимо описанных функций активации существует множество других, но
наиболее широко при обучении нейронных сетей используются эти три. Позже мы увидим, что все эти функции активации доступны в TensorFlow, и их
легко можно использовать в программном коде.
Глубокая нейронная сеть
Полносвязную ИНС с несколькими скрытыми слоями и нейронами можно
визуально представить в виде схемы на иллюстрации 3.9. В показанной сети имеются три скрытых слоя. Первые два скрытых слоя содержат по пять
нейронов, а третий — три. Каждая стрелочка содержит некоторые веса (аналогично иллюстрации 3.2), которые умножаются на соответствующие входные значения. Такой тип ИНС также называется нейронной сетью прямого
распространения (нейронной сетью с прямой связью). Как видно из схемы,
в нейронной сети этого типа данные идут только в одном направлении — слева (входы) направо (выход).
102
■
Pythonic AI
Иллюстрация 3.9. Полносвязная нейронная сеть прямого распространения
ИНС с несколькими скрытыми слоями называется глубокой нейронной сетью. Глубокие нейронные сети весьма эффективны при изучении сложных
взаимосвязей на основе таких неструктурированных данных, как изображения, тексты на естественном языке и т. д., поэтому они широко используются
при разработке ИИ-приложений.
Обратное распространение
Нейронные сети чаще всего обучают с помощью алгоритма, который называется алгоритмом обратного распространения ошибки. Он позволяет эффективно обучать многослойные нейронные сети с помощью градиентных
методов. Его цель — уменьшить потери, или разницу между фактическими
и ожидаемыми значениями. Алгоритм обратного распространения использует цепное правило для вычисления градиента функции потерь относительно
каждого веса нейронной сети. Слой за слоем градиент распространяется от
последнего слоя к входному. Процесс обратного распространения ошибки
повторяется несколько раз до тех пор, пока потери не будут минимизированы и не перестанут меняться. В процессе обратного распространения алгоритмы оптимизации находят идеальные значения весов для получения минимальных потерь на выходе. Мы не будем здесь рассматривать математические
расчеты, лежащие в основе этого алгоритма, а исследуем его поведение в ходе
реализации кода.
Создание нашей первой модели нейронной сети
■
103
Функцияп отерь
Поскольку алгоритм обратного распространения ошибки предназначен для
минимизации потерь на выходе нейронной сети, эффективность его работы
измеряют с помощью функции потерь, вычисляющей разницу между фактическими и ожидаемыми показаниями на выходе. Эту разницу нужно минимизировать. Для задач регрессии самые широко используемые функции
потерь — это среднеквадратичная ошибка (MSE, Mean Square Error), средняя абсолютная ошибка (MAE, Mean Absolute Error), средняя абсолютная
процентная ошибка (MAPE, Mean Absolute Percentage Error) и т. п. Для задач классификации часто используются такие функции потерь, как функция
кросс-энтропии (перекрестной энтропии), кусочно-линейная функция потерь (hinge loss) и т. д.
Оптимизатор
Алгоритмы оптимизации минимизируют потери в нейронных сетях посредством настройки весов. Оптимизаторы используются вместе с методом обратного распространения ошибки для обучения нейронной сети. При этом веса
изменяются пошагово для того, чтобы достичь точки, в которой потери по
сравнению с ожидаемыми показаниями на тренировочной выборке минимальны. Значение шага называется коэффициентом скорости обучения оптимизатора Некоторые широко используемые оптимизаторы для обучения
нейронных сетей — это стохастический градиентный спуск (SGD, Stochastic
Gradient Descent), RMSProp, Adadelta, Adagrad, Adam и др.
TensorFlow и Keras
TensorFlow — это бесплатная программная библиотека с открытым исходным
кодом, ориентированная в первую очередь на разработку ИИ-приложений
для решения различных задач. TensorFlow — не единственная библиотека
для разработки ИИ-приложений ИИ. Существуют и другие пакеты, такие как
PyTorch, Theano, Caffe, широко используемые разработчиками ИИ, но в этой
главе мы будем пользоваться API TensorFlow для Python.
Мы научимся разрабатывать ИИ-приложения с помощью TensorFlow 2. Между версиями TensorFlow 1 и TensorFlow 2 имеются некоторые фундаментальные различия, но мы не будем в них углубляться и воспользуемся последней
версией, то есть TensorFlow 2. В Google Colab библиотека уже установлена,
104
■
Pythonic AI
так что нам ничего не потребуется настраивать с нуля. Удостовериться в этом
и узнать текущую версию tensorflow можно с помощью следующего фрагмента кода, выполняемого в блокноте Colab:
1. import tensorflow as tf
2. print(tf.__version__)
Keras — это также бесплатная библиотека Python с открытым исходным кодом для разработки ИИ-приложений. Эту библиотеку создал инженер Google
Франсуа Шолле в 2015 году. В то время процесс разработки ИИ-моделей с помощью TensorFlow 1 был довольно трудоемким, и разработчикам для выполнения нескольких простых задач приходилось писать множество строк кода.
Keras разрабатывалась как средство высокоуровневой абстракции с чистым
и простым в использовании интерфейсом для того, чтобы разработчики могли быстро создавать и тестировать модели искусственного интеллекта при
меньшем объеме работ. Из-за популярности и простоты использования Keras
разработчики TensorFlow интегрировали ее в качестве одного из интерфейсов во вторую версию своей библиотеки. Доступ к Keras в TensorFlow 2 можно получить с помощью интерфейса tf.keras.
Библиотека TensorFlow предлагает средства работы со слоями, функциями
активации, функциями потерь, оптимизаторами, метриками и прочими элементами, необходимыми для разработки ИИ-приложений. Также TensorFlow
позволяет создавать свои собственные модели, слои, функции потерь и т. д.
Построение нашей первой
модели ИНС
Итак, мы получили базовое представление об ИНС и подготовили к работе свою ИИ-лабораторию. Теперь мы можем приступить к созданию нашей
первой модели ИНС. В этом эксперименте мы воспользуемся библиотекой
TensorFlow 2 для построения модели ИНС, распознающей рукописные цифры. Так как модель будет распознавать только десять цифр (от 0 до 9), можно
считать, что мы имеем дело с задачей многоклассовой классификации.
Прежде чем приступать к построению модели, разберемся для начала с данными. API Keras в TensorFlow предлагает несколько готовых наборов данных,
которыми мы можем воспользоваться для построения своих моделей. Для нашей первой задачи возьмем базу данных MNIST (Modified National Institute
Создание нашей первой модели нейронной сети
■
105
of Standards and Technology), составленную Национальным институтом
стандартов и технологий США. Это коллекция черно-белых изображений
рукописных цифр, которая широко используется для обучения моделей компьютерного зрения. Всего в ней содержится 60 000 обучающих и 10 000 тестовых изображений.
Каждое изображение в наборе данных MNIST имеет высоту и ширину, равные 28 пикселям, и представлено в виде двумерной сетки или матрицы размером 28×28, в которой хранятся значения яркости пикселей в диапазоне от
0 до 255. Функция load_data() загружает кортежи обучающих и тестовых
данных в виде массивов NumPy. Для начала мы импортируем необходимые
библиотеки и воспользуемся функцией load_data(), как показано на иллюстрации 3.10.
Иллюстрация 3.10. Загрузка данных MINST
Массивы NumPy x_train и x_test содержат входные данные изображения.
y_train и y_ test — это целевые переменные, которые содержат фактические значения цифр, представленных на соответствующих изображениях. Для
данной задачи целевая переменная принимает только десять значений (от 0
до 9). Визуализировать, то есть показать на экране загруженные изображения можно двумя способами. Можно воспользоваться функцией библиотеки
matplotlib imshow(), которая отображает данные, организованные в формате массива, в виде изображения. Поскольку база данных MNIST содержит
только черно-белые картинки, параметр cmap (цветовой карты) зададим
как gray (серый). Установленный по умолчанию параметр cmap отображает
106
■
Pythonic AI
изображение в оттенках серого как псевдоцветное с цветовыми значениями
пикселей. Выведем на экран первое изображение (индекс 0) из x_train и фактическое значение ("actual value") цифры из целевой переменной y_train,
как показано на иллюстрации 3.11.
Иллюстрация 3.11. Вывод изображения
Итак, мы увидели, что отображенная на предыдущей иллюстрации цифра — это 5. Прежде чем познакомиться со вторым способом отображения
изображения, давайте разберемся, какой формы загруженные нами массивы
NumPy. Для этого воспользуемся функцией NumPy shape, как показано на
иллюстрации 3.12.
Иллюстрация 3.12. Формы массивов данных
Создание нашей первой модели нейронной сети
■
107
Мы видим, что x_train — это трехмерный массив, имеющий размеры (60 000,
28, 28). Это значит, что всего в нем 60 000 изображений, и каждое из них представляет собой массив размером 28×28. Аналогично x_test — это трехмерный массив размером (10 000, 28, 28). Массив y_train — это одномерный
массив размером (60 000,), содержащий только целевые значения для 60 000
соответствующих изображений. Аналогично y_test — это массив размером
(10 000,). API Keras в TensorFlow предлагает функцию array_to_img(), преобразующую трехмерный массив NumPy в картинку. Каждое изображение
в наших наборах данных представляет собой двумерный массив размером
28×28. Воспользуемся функцией NumPy reshape() и преобразуем его в трехмерный массив размером 28×28×1. Затем передадим этот трехмерный массив
в функцию array_to_img(), как показано на иллюстрации 3.13. Выводимое
на экран изображение в этом случае будет меньше, чем при использовании
предыдущего метода.
Иллюстрация 3.13. Вывод изображения с помощью утилиты Keras
Примечание. Последнее измерение получившегося трехмерного массива размером 28×28×1 соответствует номеру цветового канала. Для
черно-белых изображений канал только один — оттенок серого. Как
мы позже увидим, цветные изображения имеют три канала: R (красный), G (зеленый) и B (синий).
Предварительная обработка
изображений
Перед тем как строить модель и подавать в нее данные, эти данные нужно
предварительно обработать так, чтобы на их основе модель обучалась и строила эффективные прогнозы. Так как мы пытаемся разработать полносвязную
нейронную сеть прямого распространения, все значения пикселей изображения будут входными параметрами модели ИНС. Матрицу изображения
108
■
Pythonic AI
следует рассматривать как развернутый вектор, который будет подаваться на
вход нейросети. В первом слое нейронной сети будет столько же нейронов,
сколько элементов в векторе. На иллюстрации 3.14 показана развернутая
матрица 3×3, содержащая значения пикселей. В результате мы получили вектор из девяти элементов. Аналогичным образом мы развернем изображение
размером 28×28 и получим вектор из 784 элементов.
Иллюстрация 3.14. Разворачивание матрицы
Развернуть матрицу можно двумя способами:
• с помощью функции NumPy reshape();
• с помощью слоя Flatten в Keras.
Мы рассмотрим оба варианта.
На предыдущей иллюстрации были показаны формы (размерности) обучающего и тестового наборов данных. Воспользуемся функцией reshape()
и преобразуем второе и третье измерения массива NumPy в одно измерение,
перемножив их размеры. Новые значения размерностей обучающего и тестового наборов данных будут (60 000, 784) и (10 000, 784), как показано на
иллюстрации 3.15.
Создание нашей первой модели нейронной сети
■
109
Иллюстрация 3.15. Изменение размерности массива
Второй способ разворачивания матрицы — использование в модели слоя
Flatten, с которым мы познакомимся в одном из следующих разделов.
Выполним теперь минимально-максимальную нормализацию данных изображения. Значения пикселей находятся в диапазоне от 0 до 255. Согласно
формуле минимально-максимальной нормализации из каждого значения
пикселя нужно вычесть минимальное значение и поделить его на диапазон
(разность максимального и минимального значений). Поскольку в данном
случае минимальное значение — это 0, то достаточно разделить значение
пикселя на 255. Код нормализации будет следующим:
1. x_train=x_train/255.0
2. x_test=x_test/255.0
Построение модели
Теперь построим модель полносвязной ИНС прямого распространения для
распознавания рукописных цифр. В TensorFlow это можно сделать тремя способами: с помощью последовательного API, функционального API и создания подклассов. Мы познакомимся с каждым из них.
Первый способ. Использование
последовательного API
Keras для Tensorflow 2 предоставляет так называемый «последовательный
API» для разработки ИИ-моделей. Он широко используется благодаря простоте реализации. Последовательный API группирует стек слоев в модель
tf.keras. Добавлять слои в соответствии с предполагаемой архитектурой
модели можно без трудоемкого программирования. В качестве первой модели создадим трехслойную нейронную сеть с помощью последовательного
(Sequential) API, воспользовавшись следующим кодом:
110
■
Pythonic AI
1. model = tf.keras.models.Sequential()
2. model.add(tf.keras.layers.Dense(128,input_shape=((784,)),
activation='relu'))
3. model.add(tf.keras.layers.Dense(128,activation='relu'))
4. model.add(tf.keras.layers.Dense(10))
Сначала мы создали переменную model как экземпляр класса sequential. Теперь с помощью функции add() можно добавлять в модель нужные нам слои.
Первым мы добавили плотный слой, состоящий из 128 нейронов, к каждому из которых применили функцию активации ReLu. При создании первого
слоя мы также указали форму входных данных, которые будут передаваться
в нейронную сеть. Поскольку до этого мы развернули матрицу изображений
размером 28×28 в вектор из 784 элементов, входная форма первого слоя будет
выглядеть как кортеж (784,) внутри другого кортежа.
Вторым слоем мы добавили также плотный слой из 128 нейронов с функцией
активации ReLu. Так как в наборе данных MNIST нам нужно классифицировать десять классов, третий слой состоит из десяти узлов, или нейронов
и является выходным слоем.
После того как модель задана, можно просмотреть ее характеристики с помощью функции summary(), как показано на следующей иллюстрации:
Иллюстрация 3.16. Характеристики модели
Создание нашей первой модели нейронной сети
■
111
На иллюстрации видно, что общее количество параметров для первого плотного слоя равно 100 480. Так как в этом слое 128 элементов, каждый из них
содержит (100 480/128) = 785 параметров. Размер вектора изображения равен
784 плюс еще один параметр — это смещение (bias). В процессе обучения необходимо определить оптимальные значения этих 785 параметров для каждого
нейрона. Всего у первого слоя 128 выходов (по количеству нейронов), и информация из них подается на входы второго слоя из 128 нейронов. Так как второй
слой имеет 16 512 параметров для обучения, то в каждом нейроне должно быть
(16 512/128) = 129 параметров: 128 с выходов предыдущего слоя плюс смещение. Третий слой имеет 1290 параметров и 10 нейронов. На каждый нейрон
приходится (1290/10) = 129 параметров: 128 с выходов предыдущего слоя плюс
смещение. Всего этой модели нужно настроить 118 282 параметра.
Для получения визуальной схемы модели в Keras API есть служебная функция plot_model, которая позволяет сохранить визуальную схему в виде графического файла. Для этого нужно указать имя файла в качестве значения
параметра to_file. Файл изображения будет создан в том же разделе Google
Диска, где находится блокнот Colab. Если к блокноту подключен Google Диск,
то для сохранения файла допускается также указывать другой каталог. В параметрах функции можно задать разрешение изображения в точках на дюйм.
Поля show_shapes и show_dtype — это двоичные параметры, позволяющие
включить в схему информацию о форме и типах данных. Пример схемы показан на следующей иллюстрации:
Иллюстрация 3.17. Схема модели
112
■
Pythonic AI
Обратите внимание, что на этой иллюстрации в архитектуру модели добавляется входной слой. При передаче параметра input_shape Keras создает
входной слой перед текущим. Входной слой можно задать и как InputLayer,
добавленный к архитектуре модели. Вот фрагмент кода, задающий модель
с InputLayer:
1. model = tf.keras.models.Sequential()
2. model.add(tf.keras.layers.InputLayer((784,)))
3. model.add(tf.keras.layers.Dense(128, activation='relu'))
4. model.add(tf.keras.layers.Dense(128, activation='relu'))
5. model.add(tf.keras.layers.Dense(10))
Ранее в разделе «Предварительная обработка изображений» мы узнали, что
существуют два способа развертывания матрицы в вектор. Мы уже познакомились с первым способом — с использованием функции Nympy reshape().
Теперь попробуем проделать то же самое с помощью слоя Flatten в Keras.
В данном случае мы не будем разворачивать матрицу на этапе предварительной обработки изображения. Мы только проведем нормализацию и начнем
строить модель. Форма входного изображения останется прежней: 28×28,
а не 784. Первым слоем нейронной сети будет слой Flatten, а формой входного изображения будет 28×28. Остальная часть архитектуры останется прежней — два скрытых слоя с 128 нейронами в каждом и один выходной слой
с десятью нейронами. Но в этот раз мы не будем указывать параметр input_
shape для первого скрытого слоя. Код построения сети будет следующим:
1. model = tf.keras.models.Sequential([
2. tf.keras.layers.Flatten(input_shape=(28, 28)),
3. tf.keras.layers.Dense(128, activation='relu'),
4. tf.keras.layers.Dense(128, activation='relu'),
5. tf.keras.layers.Dense(10, activation='softmax')
6. ])
В данном случае мы не использовали функцию model.add() для добавления
слоев в последовательный стек. Вместо этого мы определили все слои в списке
и передали этот список в качестве входа в функцию Sequential. Это еще один
способ задания модели с помощью последовательного API. Можно либо добавлять слои с помощью функции model.add(), либо задавать все слои в виде
списка. В этом случае функция summary модели покажет слой flatten. Обратите
Создание нашей первой модели нейронной сети
■
113
внимание, что у слоя flatten нет параметров для обучения. Этот слой просто
разворачивает двумерный массив размером 28×28 в одномерный массив размером 784. Характеристики такой модели показаны на иллюстрации 3.18.
Иллюстрация 3.18. Характеристики модели
Второй способ. Использование
функционального API
Познакомимся теперь со вторым способом построения моделей с помощью
функционального API TensorFlow. Функциональный API более гибок по сравнению с последовательным. Последовательного API будет достаточно для создания простой линейной архитектуры модели с одним входом и одним выходом. Функциональный же API позволяет создавать сложные архитектуры
моделей с нелинейной топологией, ветвями, общими слоями, несколькими
входами и несколькими выходами.
В случае последовательного API, как мы видели, задавать входной слой не
обязательно. Достаточно просто указать форму входа для первого слоя,
и API создаст входной слой автоматически. В функциональном API задать
вход очень важно. Заметим, что для функциональной модели TensorFlow рекомендует пользоваться функцией Input без прямого использования функции InputLayer. Разница между функциями Input и InputLayer заключается в том, что Input создает тензорный объект заданной формы. InputLayer
же — это тип слоя, который можно использовать в последовательном стеке.
Код для задания нашей модели с помощью функционального API выглядит
следующим образом:
114
■
Pythonic AI
1. inputs = tf.keras.Input((784,))
2. hidden_1 = tf.keras.layers.Dense(128, activation='relu')(inputs)
3. hidden_2 = tf.keras.layers.Dense(128, activation='relu')(hidden_1)
4. outputs = tf.keras.layers.Dense(10)(hidden_2)
5. model = tf.keras.Model(inputs=inputs, outputs=outputs)
Согласно этому коду сначала задается тензор Input (не InputLayer) размером (784,). Важно помнить, что размер входных данных (784,) актуален, только если входные изображения были развернуты на этапе предварительной
обработки с помощью функции reshape(). В противном случае форма будет
(28, 28). Далее мы создаем первый скрытый слой hidden_1 в виде плотного
слоя со 128 нейронами и передаем ему объект inputs. Точно так же мы создаем
второй скрытый слой hidden_2 и передаем ему hidden_1. Затем мы создаем выходной слой — плотный слой с десятью нейронами — и передаем ему
hidden_2. При этом важно понимать, что мы создаем каждый слой как объект плотного класса (Dense) и передаем ему предыдущий слой (другой объект) в качестве входного параметра. В итоге мы должны создать модель в виде
объекта с уже созданными входами и выходами.
Как и в случае с последовательной моделью, есть возможность посмотреть
характеристики или вывести их в файл. Характеристики модели показаны на
иллюстрации 3.19.
Иллюстрация 3.19. Характеристики модели
Создание нашей первой модели нейронной сети
■
115
Третий способ. Создание подкласса
Третий способ построения моделей TensorFlow предназначен для опытных
пользователей. Он обеспечивает еще большую гибкость, так как позволяет
настраивать прямой проход модели. В этом случае мы создаем свой класс модели, наследующий классу tf.keras.Model. Для этой пользовательской модели нужно переопределить функцию call() ее родительской модели Keras.
Как показано в следующем коде, в функции __init__() пользовательского
класса определяются слои, необходимые для нашей целевой архитектуры модели. Затем мы определяем прямой проход модели в функции call(). Код для
этого будет следующий:
1. class MyModel(tf.keras.Model):
2. def __init__(self, input_shape, target_classes):
3.
# вызов родительского конструктора
4.
super(MyModel, self).__init__()
5.
6.
# набор слоев
7.
self.inputs = tf.keras.layers.InputLayer(input_shape)
8.
self.hidden_1 = tf.keras.layers.Dense(128, activation='relu')
9.
self.hidden_2 = tf.keras.layers.Dense(128, activation='relu')
10.
self.outputs = tf.keras.layers.Dense(target_classes)
11.
12.
def call(self, x):
13.
x = self.inputs(x)
14.
x = self.hidden_1(x)
15.
x = self.hidden_2(x)
16.
x = self.outputs(x)
17.
return x
18.
19. model = MyModel((784,),10)
В этом коде мы определили класс MyModel, который наследует классу
tf.keras.Model. Конструктор __init__() принимает в качестве входных
данных input_shape и target_classes. Вызов функции super() внутри __
init__ заставляет дочерний класс MyModel наследовать все функции и свойства от родительского класса. Далее задается набор слоев, необходимый для
116
■
Pythonic AI
построения нашей модели. Затем в функции call() определяется порядок
прямого прохода. Наконец, создается экземпляр модели в виде объекта пользовательского класса, при этом определяется форма входа и количество целевых классов на выходе, на которые необходимо разбить данные из набора.
Обучение и оценка модели ИНС
До сих пор мы изучали различные способы задания архитектуры модели
ИНС. Чтобы получить рабочую модель, нужно ее скомпилировать, обучить на
тренировочной выборке и оценить ее работу с помощью тестовых данных.
Компиляция модели
После определения и задания архитектуры нужно скомпилировать модель.
На этом этапе задаются оптимизатор, функция потерь, метрики и т. д., которые будут использоваться для обучения модели. В классе tf.keras.Model
есть метод compile(). Нам нужно вызвать этот метод, указав выбранные нами параметры, как показано в следующем примере кода:
1. model.compile(
2.
optimizer=tf.keras.optimizers.Adam(0.001),
3.
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_
logits=True),
4.
metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],
5. )
Здесь мы воспользовались оптимизатором Adam, доступным в модуле
tf.keras.optimizers. Коэффициент скорости обучения оптимизатора Adam
взят равным 0,001. В качестве функции потерь мы использовали функцию
разреженной категориальной перекрестной энтропии (SparseCategorical
Crossentropy), доступную в модуле tf.keras.losses. Значение параметра
from_logits этой функции установлено как «истинное». Это означает, что
предсказанное значение будет представлять собой тензор логитов, а не вероятностное распределение. Подробнее о значении параметра from_logits мы
узнаем позже в этой главе.
Создание нашей первой модели нейронной сети
■
117
Примечание. Распознавание рукописных цифр — это задача многоклассовой классификации. Следовательно, нужно использовать
тип функции потерь «категориальная перекрестная энтропия». Так
как целевые переменные (y_train и y_test) в этом наборе данных
представлены в виде целых чисел, нужно воспользоваться функцией SparseCategoricalCrossentropy. Если целевые переменные представлены в прямом унитарном коде, нужно использовать функцию
CategoricalCrossentropy. Для задач бинарной классификации используют функцию потерь BinaryCrossentropy.
Также внутри функции compile() мы определили метрики metrics. Функции
метрик используются для оценки производительности модели в процессе
обучения. Производительность указывает на предсказательную способность
модели. Здесь мы использовали функцию SparseCategoricalAccuracy, доступную в модуле tf.keras.metrics. Она измеряет точность (accuracy) предсказания, то есть частоту совпадения предсказанного значения с целевым. Мы
используем функцию «разреженной категориальной точности», так как целевое значение задано в целочисленной форме. Параметр metrics принимает
список используемых метрик, то есть можно указывать более одной релевантной метрики.
Настройка модели на обучение
После настройки модели с помощью функции compile() нужно применить
обучающие данные к модели («подогнать») с помощью функции fit(). Функция fit() принимает входные данные для обучения, целевые данные, значение размера пакета данных, количество эпох для обучения, проверочные
данные, шаги проверки, количество шагов в эпохе и т. д.
Ранее мы говорили о том, что в методе обратного распространения для обучения нейронной сети используются оптимизаторы. Они пошагово изменяют веса, чтобы добиться минимального значения функции потерь. Если
оптимизатор для расчета потерь будет рассматривать все входные обучающие данные, и только потом обновлять веса, то при большом наборе данных это займет много времени. Поэтому вместо того, чтобы использовать все
входные данные, оптимизатор может ограничиться только определенным
пакетом, или выборкой данных. Размер этой выборки задается параметром
batch_size функции fit().
118
■
Pythonic AI
Значение параметра epochs задает количество обучающих итераций, которые
будут выполняться для всего обучающего набора данных. Предполагается,
что результативность модели после многократных итераций с обучающим
набором данных должна улучшиться.
Как упоминалось ранее, в используемом нами наборе данных MNIST содержится 60 000 обучающих изображений. Если установить значение batch_size
равным 60, то для завершения одной эпохи, то есть одного полного прохода
по всему набору данных, потребуется (60 000/60) = 1000 шагов. Можно сократить количество шагов одной эпохи, задав значение параметра steps_per_
epoch. Если значение параметра steps_per_epoch не задано, то одна эпоха,
то есть однократное прохождение всего набора данных, займет 1000 шагов.
Проверочный набор данных (validation dataset) используется для оценки потерь и производительности модели в конце каждой эпохи, но для обучения
эти данные не задействуются. Параметр validation_split задает процент
обучающих данных, которые будут использоваться для проверки. Параметр
validation_steps задает количество выборок данных из проверочного набора. Если значение параметра validation_steps не задано, то для проверки
производительности модели после каждой эпохи будет использоваться весь
проверочный набор данных.
Для вывода информации о ходе обучения можно установить параметр
verbose. Можно также задать параметр обратных вызовов (callbacks) со списком экземпляров tf.keras.callbacks. Таким образом мы можем определять
действия, которые будут выполняться на разных этапах процесса обучения.
Существует возможность создавать свои собственные функции обратного
вызова.
Функция fit() возвращает список значений функции потерь и метрик для
обучающего и проверочного наборов.
На следующей иллюстрации показан результат выполнения функции fit()
для обучения модели. Мы установили значение verbose равным 2. При запуске ячейки на исполнение выводится информация о ходе обучения по
эпохам.
В данном случае, как показано на иллюстрации выше, для параметра epochs
мы задали значение 50, для batch_size — 32, для steps_per_epoch — 200
и для validation_steps — 50. С этими значениями можно экспериментировать. Выход функции fit() мы перенаправили в переменную history, которой
позже воспользуемся для построения графиков значений потерь и метрик.
Создание нашей первой модели нейронной сети
■
119
Иллюстрация 3.20. Обучение модели
Оценка модели
Так как модель теперь полностью обучена, оценим ее на тестовом наборе данных. Для этого воспользуемся функцией evaluate() нашей модели. Эта функция возвращает значения потерь и метрик для заданных входных данных.
Ее выполнение показано на следующей иллюстрации:
Иллюстрация 3.21. Оценка модели
Мы передали тестовый набор данных (x_test и y_test) функции evaluate(),
и она вернула значения потерь и метрики (точности). Получается, что наша
модель может распознавать рукописные цифры с точностью примерно 97%.
Функция модели predict() просто возвращает значения, предсказанные
нейросетью, как показано на иллюстрации 3.22.
Иллюстрация 3.22. Генерация предсказанных значений
120
■
Pythonic AI
Предсказанные значения присваиваются переменной y_pred. Распечатаем
первые элементы массивов y_test и y_pred, чтобы проверить, верно ли прошло распознавание, как показано на иллюстрации 3.23.
Иллюстрация 3.23. Сравнение фактических и предсказанных значений
Первый элемент y_test содержит значение 7. Первый элемент y_pred — это
массив, в котором седьмой элемент (начиная с нулевого) имеет значение
17.269983 — наибольшее из всех. Это говорит о том, что нейронная сеть сочла
первую цифру семеркой — в данном случае истинное и распознанное значения совпадают.
Теперь давайте копнем немного глубже и постараемся понять, каким образом
значение y_pred получилось именно таким. Вспомним архитектуру модели,
которую мы использовали для решения этой задачи. Последним слоем модели был плотный слой из десяти нейронов. В первых двух слоях мы использовали функцию активации ReLu, но в последнем — нет. Теперь вспомним
раздел о функции активации. Мы узнали, что если нейрон обучается линейной функции f(x) = w0 + w1x1 + w2x2, а затем применяет функцию активации
g(x), то конечным выходом будет y = g(f(x)). Поскольку в архитектуре нашей
модели последний слой не содержит никакой функции активации, на его выходе получается просто взвешенная сумма f(x), то есть массив необработанных предсказаний, значение которых может варьироваться от отрицательной
бесконечности до бесконечности. На предсказанный класс указывает индекс
максимального элемента. Такие необработанные предсказания называются
«логитами». Из-за того, что последний слой возвращает логиты, при компиляции модели мы и задали в функции compile() параметр потерь from_
logits равным «истине» (True).
Создание нашей первой модели нейронной сети
■
121
Примечание. К последнему слою можно применить функцию активации softmax (многопеременную логистическую функцию). Функция
softmax преобразует массив логитов в распределение вероятностей. Если бы мы решили применить ее к последнему слою, то должны были бы
присвоить параметру потерь from_logits значение «ложь» (False). В таком случае предсказания y_pred представляли бы собой распределение
вероятностей со значениями от 0 до 1. Предсказанным классом считался бы индекс элемента массива с наибольшим значением вероятности.
Также стоит отметить, что при решении задачи регрессии, то есть когда
результатом работы нейронной сети является одно вещественное число
для каждого элемента входных данных, выходной слой должен содержать единственный узел без какой-либо функции активации.
Построение графика значений потерь
и метрик
Мы сохранили вывод функции fit() в переменную history. Этот объект содержит словарь, также названный history. В этом словаре доступны такие
ключи, как loss, sparse_categorical_accuracy, val_loss и val_sparse_
categorical_accuracy. Их значения можно использовать для построения
графика потерь и метрик (точности) по эпохам. Рассмотрим следующий код:
1. # График точности модели
2. plt.plot(history.history["sparse_categorical_accuracy"])
3. plt.plot(history.history["val_sparse_categorical_accuracy"])
4. plt.title("Model accuracy plot")
5. plt.xlabel("Epoch")
6. plt.ylabel("Accuracy")
7. plt.legend(["Train", "Test"], loc="upper left")
8. plt.show()
9.
10. # График потерь модели
11. plt.plot(history.history["loss"])
12. plt.plot(history.history["val_loss"])
122
■
Pythonic AI
13. plt.title("Model Loss Plot")
14. plt.xlabel("Epoch")
15. plt.ylabel("Loss")
16. plt. legend(["Train", "Test"], loc="upper left")
17. plt.show()
Первый график — это график точности («accuracy»), показывающий, как значения точности нейросети на обучающем и проверочном наборах менялись
по эпохам в процессе обучения. Он показан на следующей иллюстрации, где
"train" — обучающие данные, а "test" — проверочные данные.
Иллюстрация 3.24. Точность по эпохам
Видно, что точность предсказаний как для обучающих, так и для проверочных данных постепенно улучшалась. Ближе к концу обучения показатели
почти не менялись. На втором графике показано изменение значений потерь
как для обучающего, так и для проверочного наборов данных. Сначала значение потерь резко снизилось, а затем, по мере увеличения количества эпох,
стабилизировалось.
Создание нашей первой модели нейронной сети
■
123
Иллюстрация 3.25. Значения потерь по эпохам
Такого рода визуализации важны для проверки качества обучения модели.
Если метрики для обучающего набора данных продолжают улучшаться, но
для проверочного набора данных значительного улучшения не происходит,
то можно сделать вывод, что модель подвержена «переобучению» (overfitting),
то есть хорошо выдает прогнозы только для обучающих данных, но не для
проверочных.
TensorBoard. Инструментарий
визуализации TensorFlow
Вместо того чтобы строить графики значений потерь и точности с помощью библиотеки matplotlib, можно делать это в библиотеке TensorBoard.
TensorBoard — это набор инструментов для визуализации и проведения различных измерений, предоставляемый TensorFlow и предназначенный для
разработки ИИ-приложений. TensorBoard позволяет отображать на графиках метрики потерь и точности, изменения весов и смещений нейронных сетей в зависимости от количества прошедших эпох, схемы моделей, наборы
данных и т. д.
124
■
Pythonic AI
Чтобы воспользоваться tensorboard, нужно сначала загрузить это расширение в блокноте, как показано ниже:
1. %load_ext tensorboard
Для хранения результатов визуализации и измерений TensorBoard создает
файлы журналов (logs). Перед началом работы необходимо удалить все журналы, созданные во время предыдущих запусков, как показано ниже:
1. !rm -rf ./logs/
Далее нужно задать функцию обратного вызова TensorBoard с именем
tf.keras.callbacks.TensorBoard для генерирования и хранения журналов
дальнейших измерений и визуализации. При создании функции укажем путь
к каталогу журналов и частоту в эпохах (histogram_freq), с которой будут вычисляться гистограммы весов для визуализации. Эту функцию вызова нужно
указать как элемент списка для параметра callbacks функции fit() модели.
Выполнение этой функции показано на следующей иллюстрации:
Иллюстрация 3.26. Обучение модели с использованием функции обратного вызова
TensorBoard
Теперь можно запустить TensorBoard из блокнота с помощью команды
%tensorboard, показанной ниже:
1. %tensorboard --logdir logs
На информационной панели TensorBoard имеются разные вкладки для различных визуализаций.
Создание нашей первой модели нейронной сети
■
125
Вкладка TIME SERIES показывает, как потери, метрики (точность), веса и смещения слоев нейронной сети изменяются в зависимости от эпох и итераций.
Она показана на следующей иллюстрации:
Иллюстрация 3.27. Вкладка TIME SERIES
На вкладке SCALARS отображаются графики потерь и любой метрики (в данном случае sparse categorial accuracy — «разреженной категориальной точности») по эпохам и итерациям. Они похожи на те графики, которые мы ранее
создавали с помощью matplotlib, а также наблюдали на вкладке Time Series.
Вкладка SCALARS приведена на следующей иллюстрации:
Иллюстрация 3.28. Вкладка SCALAR
126
■
Pythonic AI
На вкладке GRAPHS отображается граф модели нейросети. Помимо базовой
структуры модели здесь можно посмотреть, как в модели используются различные метрики и оптимизаторы. Эта вкладка показана на следующей иллюстрации:
Иллюстрация 3.29. Вкладка GRAPHS
Примечание. Обратите внимание, на иллюстрации 3.26 значение
verbose для функции fit() установлено равным единице. Если значение
verbose будет иным, вкладка GRAPHS в панели TensorBoard не сможет
отобразить детали графа. В данный момент это известная, но еще не
исправленная ошибка TensorFlow, поэтому пока рекомендуется использовать параметр verbose = 1.
Вкладки DISTRIBUTIONS и HISTOGRAMS отображают довольно схожее содержание, но с разными видами визуализации. Они показывают распределение весов и смещений слоев нейронной сети по эпохам. Здесь можно удостовериться в том, что веса и смещения изменяются ожидаемым образом. Эти вкладки
показаны на следующих иллюстрациях:
Создание нашей первой модели нейронной сети
■
127
Иллюстрация 3.30. Вкладка DISTRIBUTIONS
Иллюстрация 3.31. Вкладка HISTOGRAMS
Настройка гиперпараметров
Итак, мы построили полнофункциональную модель искусственного интеллекта, способную распознавать рукописные цифры с удовлетворительной точностью. Архитектура построенной нами модели состоит из двух скрытых слоев
и одного выходного слоя. Скрытые слои содержат по 128 нейронов, или узлов.
Для обучения мы использовали оптимизатор Adam с коэффициентом обучения 0,001. Однако именно такая архитектура модели и такой оптимизатор могут
оказаться далеко не лучшим выбором. Возможно, большее количество слоев,
нейронов или другой оптимизатор позволили бы получить лучшую результативность. Прежде чем окончательно определиться с архитектурой модели, стоит испробовать различные варианты. Такой процесс называется настройкой
128
■
Pythonic AI
гиперпараметров. Они бывают двух типов: гиперпараметры уровня модели (например, количество слоев, нейронов и т. д.) и гиперпараметры уровня алгоритма
(например, скорость обучения, алгоритм оптимизатора и т. д.). В TensorFlow настраивать гиперпараметры можно с помощью библиотеки Keras Tuner. В Google
Colab она по умолчанию не установлена, поэтому сначала нам нужно установить ее, выполнив следующую команду pip в ячейке блокнота:
1. !pip install keras-tuner
После установки библиотеки импортируем ее следующей командой:
1. import keras_tuner as kt
Для настройки модели нужно определить пространство поиска гиперпараметров. Создадим функцию для построения архитектуры модели путем перебора в заданном пространстве гиперпараметров, как показано ниже:
1. def build_model(hp):
2.
model = tf.keras.models.Sequential()
3.
model.add(tf.keras.layers.InputLayer((784,)))
4.
5.
hp_layers = hp.Int('layers', min_value=2, max_value=4, step=1)
6.
hp_units = hp.Int('units', min_value=128, max_value=512,
step=128)
7.
hp_activations = hp.Choice('activation', ['relu', 'tanh'])
8.
9.
10.
for i in range(hp_layers):
model.add(tf.keras.layers.Dense(units=hp_units,
activation=hp_ activations))
11.
12.
model.add(tf.keras.layers.Dense(10))
13.
14.
hp_learning_rate = hp.Choice('learning_rate',
values=[0.01,0.001, 0.0001])
15.
16. model.compile(optimizer=tf.keras.optimizers.Adam(learning_
rate=hp_ learning_rate),
17.
loss=tf.keras.losses.SparseCategoricalCrossentropy
(-from_logits=True),
Создание нашей первой модели нейронной сети
18.
■
129
metrics=['accuracy'])
19.
20.
return model
Как показано в предыдущем фрагменте кода, мы задали последовательную
модель и добавили входной слой, такой же, как и раньше. Потом мы создали пространство поиска гиперпараметров для количества слоев, узлов (units)
и функций активации (activation). Метод Int() формирует выборку значений
гиперпараметров от заданного min_value до max_value с увеличением шага
step. Метод Choice() выбирает значения из заданного списка. Далее к последовательной модели мы добавили слои в нужном количестве. Количество
слоев, узлов, функция активации для каждого слоя и скорость обучения также выбираются из заданного пространства вариантов.
Затем мы запустили поиск наилучшей комбинации гиперпараметров
с помощью имеющегося в библиотеке Keras Tuner настройщика (tuner)
Hyperband. Помимо него, в библиотеке есть такие тюнеры, как RandomSearch,
BayesianOptimization и Sklearn. Код для определения процедуры подбора гиперпараметров представлен ниже:
1. tuner = kt.Hyperband(build_model,
2.
objective='val_accuracy',
3.
max_epochs=10,
4.
directory='proj_dir',
5.
project_name='mnist')
В параметре max_epochs мы задаем значение максимального количества эпох
для обучения одной модели. Подробные сведения о процессе настройки гиперпараметров будут сохраняться в указанном каталоге (directory).
Теперь можно начать попытки процесса обучения для поиска гиперпараметров с помощью кода, показанного ниже. В результате мы получим наиболее
подходящие значения гиперпараметров, воспользовавшись функцией get_
best_hyperparameters():
1. tuner.search(x_train, y_train, epochs=50, batch_size=32, steps_
per_ epoch=200,
2.
validation_split=0.2)
3.
4. best_hps = tuner.get_best_hyperparameters(num_trials=1)[0]
130
■
Pythonic AI
Далее выведем лучшее значение каждого гиперпараметра, как показано на
иллюстрации 3.32.
Иллюстрация 3.32. Лучшие значения гиперпараметров
После этого можно снова создать свою модель уже с этими оптимальными
значениями, чтобы получить наилучший результат.
Сохранение и загрузка моделей
Tensor Flow
Обучение нейронной сети на большом количестве данных занимает много времени. Поэтому полезной будет возможность сохранения модели во время или
после обучения, чтобы при необходимости продолжить работу с ней позже.
Сохранение и загрузка модели
во время обучения
Если мы захотим сохранить модель в процессе обучения, чтобы впоследствии
ее загрузить, то должны будем воспользоваться функцией обратного вызова ModelCheckpoint из модуля tf.keras.callbacks. В параметре filepath
этой функции указывается путь для сохранения. Можно записать в файл как
всю модель, так и только веса, в зависимости от значения параметра save_
weights_only (False или True). При сохранении всей модели будет записана ее
архитектура, веса, конфигурация обучения, функция потерь, состояние оптимизатора и т. д. В данном примере мы сохраним только контрольные точки
лучшей модели (save_best_only).
После задания архитектуры модели и ее компиляции укажем функцию обратного вызова ModelCheckpoint внутри функции fit() для обучения модели. Как показано на иллюстрации 3.33, после завершения процесса обучения
Создание нашей первой модели нейронной сети
■
131
можно нажать на расположенную в левой части блокнота иконку Файлы
(Files) (показана красной стрелкой), чтобы убедиться в том, что файлы контрольных точек созданы.
Иллюстрация 3.33. Вызов функции ModelCheckpoint
После сохранения объекта модели его можно снова загрузить в память. Как
показано на иллюстрации 3.33, мы сохранили только веса модели, установив
в функции обратного вызова ModelCheckpoint значение параметра save_
weights_only как True. В случае сохранения только весов нужно задать архитектуру и скомпилировать модель, и только потом загружать веса. Процесс
обучения при этом пропускается. Но если мы сохраним всю модель внутри
каталога контрольной точки (установив для параметра save_weights_only
значение False), нам не придется заново прописывать ее архитектуру выполнять команды компиляции. Мы сможем загрузить модель напрямую. На следующей иллюстрации показаны оба способа:
Иллюстрация 3.34. Загрузка весов и всей модели
132
■
Pythonic AI
Сохранение и загрузка модели после
обучения
После процесса обучения можно в любой момент сохранить модель с помощью функции save():
1. model.save("mnist_model")
После выполнения соответствующей ячейки можно нажать иконку Файлы
(Files) в блокноте (как показано на иллюстрации 3.33) и убедиться в том, что
каталог mnist_model создан.
Загружается модель с помощью функции load_model(), как показано на иллюстрации 3.34.
Совет. Чтобы не потерять сохраненные контрольные точки модели,
стоит сохранять их на Google Диске. Для этого необходимо подключить Google Диск к Colab, как было описано в главе 2, и указать путь
к файлу контрольной точки на Google Диске в функции обратного вызова. В противном случае после завершения работы блокнота файлы
контрольных точек будут удалены.
Заключение
В этой главе мы занимались таким увлекательным делом, как создание нашей
первой модели нейронной сети. Мы научились создавать нейронную сеть с нуля с помощью библиотеки TensorFlow 2, познакомились с различными способами определения архитектуры модели, ее обучения сохранения для использования в будущем. Полученные в этой главе знания и навыки станут прочной
основой для дальнейшего погружения в мир искусственных нейронных сетей.
Путешествие продолжается, и с каждым шагом мы становимся все увереннее
в своих навыках в сфере искусственного интеллекта и глубокого обучения.
В следующей главе мы познакомимся с концепцией сверточных нейронных
сетей (CNN) и займемся разработкой нашей первой CNN.
Создание нашей первой модели нейронной сети
■
133
Основные выводы
• При создании ИНС задаются слои модели, функции активации и функции потерь. Функции активации играют решающую роль, поскольку
привносят в модель нелинейность. Для эффективного обучения нейронной сети очень важен выбор подходящей функции потерь и подходящего оптимизатора.
• После создания модели следует обратить внимание на важнейшие
аспекты — обучение и оценку производительности нейронной сети.
Улучшить работу модели можно с помощью таких методов, как обратное распространение ошибки и градиентный спуск.
• TensorFlow и Keras — мощные инструменты для построения и обучения нейронных сетей. TensorFlow допускает три различных способа
создания моделей нейронных сетей: последовательный API, функциональный API и создание подклассов базовой модели. Каждый метод
позволяет адаптировать наши модели к конкретным требованиям
и уровню сложности задачи.
• Строить, обучать и оценивать работу модели можно с использованием
различных метрик и функций потерь, что позволяет управлять ее производительностью.
• Настройка гиперпараметров модели помогает найти их наилучшую
комбинацию. Благодаря систематическим экспериментам и подбору
параметров можно повысить точность и эффективность модели.
• TensorBoard — набор инструментов визуализации библиотеки
TensorFlow — помогает нам визуализировать и анализировать процесс
обучения, что облегчает выявление и устранение любых возникающих
проблем.
• Объект обученной модели можно сохранить в файл для последующей
загрузки и дальнейшего использования.
Ссылки
• Официальная документация TensorFlow: https://www.tensorflow.org/
• Функции потерь TensorFlow:
python/tf/keras/losses
https://www.tensorflow.org/api_docs/
• Функции активации TensorFlow: https://www.tensorflow.org/api_docs/
python/tf/keras/activations
134
■
Pythonic AI
• Оптимизаторы TensorFlow: https://www.tensorflow.org/api_docs/python/
tf/keras/optimizers
• Метрики TensorFlow:
keras/metrics
https://www.tensorflow.org/api_docs/python/tf/
• Вызовы функций TensorFlow:
python/tf/keras/callbacks
https://www.tensorflow.org/api_docs/
• Официальная документация TensorBoard: https://www.tensorflow.org/
tensorboard
• TensorFlow Keras tuner: https://www.tensorflow.org/tutorials/keras/keras_
tuner
• Домашняя страница набора данных MNIST: http://yann.lecun.com/exdb/
mnist/
Глава 4
Проектирование CNN
с помощью TensorFlow
Введение
Технология компьютерного зрения позволяет вычислительным системам
извлекать значимую информацию из оцифрованных визуальных данных
(изображений, видео и т. д.) и на основе их анализа принимать те или
иные решения. В последнее время технология компьютерного зрения присутствует во всех сферах нашей жизни. Она используется в социальных
сетях, которые предлагают вам отметить своих друзей на фотографиях,
при разблокировке мобильных телефонов по лицу владельца, в сканерах
штрихкодов, при разработке и производстве машин на автопилоте, анализе снимков МРТ и рентгеновских снимков в здравоохранении, а также
при наблюдении за территорией с помощью беспилотников. При этом для
обработки получаемых данных используются так называемые сверточные
нейронные сети (CNN, Convolutional Neural Networks), выполняющие задачи по классификации изображений, разделению изображений на составные части, обнаружению объектов и т. д. CNN могут обнаруживать закономерности в сложных изображениях и обучаться на их основе. Модели CNN
допускают очень гибкую настройку, благодаря чему они хорошо подходят
для различных приложений по обработке изображений. CNN полностью
изменили сферу компьютерного зрения, которая продолжает совершенствоваться. Работа в этой увлекательной и быстро развивающейся области
искусственного интеллекта требует понимания основных принципов CNN,
будь вы студент, исследователь, разработчик или специалист по изучению
данных.
136
■
Pythonic AI
Структура
В этой главе мы рассмотрим следующие темы:
• Введение в концепцию сверточных нейронных сетей.
• Архитектура CNN.
• Методы обобщения.
• Построение CNN с помощью TensorFlow.
• Стандартные архитектуры CNN.
Цели
В этой главе мы начнем исследовать такую важную прикладную задачу ИИ,
как классификация изображений. Мы познакомимся со сверточными нейронными сетями (CNN), широко используемыми для этой работы. Мы узнаем об архитектурах модели CNN, различных методах обобщения моделей
CNN и о том, как строить CNN с помощью библиотеки TensorFlow 2. Также
мы рассмотрим некоторые стандартные архитектуры CNN и реализуем их
с помощью готовых и предварительно обученных моделей CNN, доступных
в TensorFlow.
Введение в концепцию сверточных
нейронных сетей
В главе 2 «Создание нашей первой модели нейронной сети» мы построили
полносвязную искусственную нейронную сеть. Сверточная нейронная сеть,
или CNN (Convolutional Neural Network), — это еще один тип искусственных
нейронных сетей, который особенно хорошо подходит для технологий компьютерного зрения.
Прообразом архитектуры CNN послужила организация зрительной коры человеческого мозга. Исследования в области компьютерного зрения проводились еще в 1950-х годах, но новый импульс они получили в 1980-х годах. Одним из крестных отцов концепции глубокого обучения считается Ян Лекун,
вице-президент и главный специалист по искусственному интеллекту в компании Meta (по состоянию на 2023 год), разработавший сверточную нейронную сеть для распознавания рукописного текста. С тех пор CNN прошли
Проектирование CNN с помощью TensorFlow
■
137
долгий путь, и исследователи предложили несколько усовершенствованных
архитектур, значительно улучшивших технологии компьютерного зрения.
Основные понятия CNN
В главе 3 «Создание нашей первой модели нейронной сети» мы научились
строить полносвязную ИНС с прямой связью для классификации рукописных цифр. При этом следует отметить одну фундаментальную проблему использования полносвязной ИНС для классификации изображений. Если вы
помните, мы разворачивали наше входное изображение, преобразуя матричную структуру в вектор, и подавали на вход полносвязной ИНН именно его.
Поскольку каждое черно-белое изображение представляло собой матрицу
размером 28×28, развернутый вектор имел всего 28×28 = 784 элемента. В реальной жизни изображения не всегда имеют такие маленькие размеры. Для
черно-белого изображения размером 128×128 длина входного вектора будет
равна 16 384. Размер вектора цветного изображения (RGB-изображения) составит 128×128×3 = 49 152. Если для первого слоя выделить 1000 нейронов,
то понадобится обучить 49 миллионов весов. Высокая размерность входных
данных приводит к тому, что модель становится слишком громоздкой. Обучение такой модели занимает больше времени и делает ее непригодной для
масштабирования.
Что касается CNN, то для нее не нужно преобразовывать матрицу в вектор,
кроме того, CNN лучше всего работает с матричной (похожей на таблицу или
сетку) структурой входных данных. Она сканирует входное изображение,
идентифицирует его и извлекает самые полезные признаки без ручного вмешательства. Извлечение значимых признаков из данных пространственного
типа (например, изображений) — это самая сильная сторона CNN.
Архитектура CNN
Типичная архитектура CNN состоит из четырех важных слоев:
• Сверточный слой.
• Слой ReLu.
• Слой «пулинга» (субдискретизации).
• Полносвязный слой.
138
■
Pythonic AI
Сверточный слой
Сверточный слой принимает на вход изображение в виде матрицы. Значения
в матрице соответствуют цветам пикселей изображения. Можно рассматривать ее как сетку и представлять в виде двумерного массива NumPy, что показано на следующем изображении.
Иллюстрация 4.1. Входное изображение в виде матрицы
Как показано на иллюстрации 4.1, входное изображение содержит цифру 4.
Это изображение можно представить в виде матрицы размером 5×5. Каждому элементу этой матрицы, или пикселю, присвоено значение цвета, где 0 —
это белый цвет, а 255 — черный.
Для извлечения из изображения элементарных визуальных признаков (таких
как вертикальные края, горизонтальные края, углы, конечные точки, текстуры и т. д.) сверточный слой использует фильтры, иначе именуемые «ядрами»
или сверточными матрицами.
Фильтр, или ядро
Фильтр — это матрица или сетка, размер которой намного меньше фактического размера матрицы входного изображения. Матрица-фильтр сканирует
матрицу изображения слева направо и сверху вниз, извлекая важные признаки по мере обнаружения изменений в характеристиках пикселей. Во время сканирования фильтр накладывается на часть изображения и значения
в матрице фильтра поэлементно умножаются на значения, присутствующие
в этой части матрицы изображения. Полученные результаты суммируются
и сохраняются в выходной матрице. Полный процесс поэлементного умножения и суммирования матриц при сканировании называется «сверткой»
и обозначается символом (*). Рассмотрим следующую иллюстрацию:
Проектирование CNN с помощью TensorFlow
■
139
Иллюстрация 4.2. Операция свертки
Иллюстрация 4.2 показывает процесс свертки входного изображения размером 5×5 с помощью фильтра размером 3×3. Он начинается с левого верхнего угла изображения; фильтр накладывается на изображение в этой части,
попарно умножает соответствующие элементы и вычисляет сумму произведений. В результате получается число 255, которое записывается в качестве
первого элемента выходной матрицы. Затем фильтр перемещается вправо на
один пиксель и снова выполняет поэлементное произведение и сумму. В итоге получается нуль, который записывается как значение второго элемента выходной матрицы. Далее фильтр продолжает двигаться слева направо и сверху
вниз, выполняя операцию свертки, или поэлементного произведения и суммы. Итоговая выходная матрица размером 3×3 выглядит так, как показано на
следующей иллюстрации:
Иллюстрация 4.3. Итоговая выходная матрица
140
■
Pythonic AI
Но что, если изображение будет цветным? В отличие от изображения в оттенках серого, цветное изображение имеет три канала: красный, зеленый
и синий. Это означает, что для каждого изображения используются три матрицы. Цветное изображение представлено в виде трехмерного тензора, где
три измерения — это высота, ширина и количество цветовых каналов.
Фильтр для свертки также представляет собой трехмерный тензор. Если для
какого-то цветного отображения определяют фильтр 5×5 пикселей, это значит, что на самом деле для трех каналов он будет размером 5×5×3. В процессе
свертки каждый канал фильтра выполняет поэлементное умножение и вычисляет сумму произведений. Далее результаты суммирования по всем каналам складываются.
Так как результаты работу всех трех фильтров суммируются, то в итоге получается двумерный тензор. Если для свертки используются несколько фильтров, то получившиеся двумерные тензоры добавляются вдоль третьего измерения итогового трехмерного тензора. В таком случае размер последнего
измерения итогового трехмерного тензора для определенного сверточного
слоя будет соответствовать количеству фильтров в этом слое.
Примечание. Для всех примеров кода в этой главе предполагается, что
все необходимые библиотеки уже импортированы. Например:
1. import tensorflow as tf
2. import numpy as np
3. import matplotlib.pyplot as plt
В TensorFlow 2 можно создать модель CNN с помощью сверточного слоя
Conv2D, доступного в модуле tf.keras.layers. При этом можно задать количество фильтров и размер ядра, как показано в следующем примере кода:
1. inputs = tf.keras.Input((28,28,3))
2. conv_1 = tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3))
(inputs)
Следует учесть, что после операции свертки размер выходных данных всегда
уменьшается. Если в сети глубокого обучения имеется несколько сверточных
слоев, размер изображения после каждого этапа будет уменьшаться, и может
Проектирование CNN с помощью TensorFlow
■
141
даже сократиться до матрицы 1×1. Очень маленькое изображение не содержит ценных признаков для дальнейшего анализа. Для компенсации уменьшения размера входной матрицы в CNN используется специальный прием
под названием дополнение (padding).
Дополнение
Дополнение (padding, также «обрамление») — это окружение матрицы фиктивными значениями. В CNN дополнение используется для сохранения пространственных размеров (размерности изображения). Операция свертки выполняется
в ходе продвижения фильтра/ядра по входному изображению, и без дополнения
пространственные размеры матрицы признаков будут уменьшаться. В результате мы можем столкнуться с ситуацией, когда в более глубоких слоях будут
получаться очень маленькие матрицы признаков, что затруднит распознавание
важных образов. Дополнительные пиксели вокруг изображения помогают сохранить пространственные размеры входных данных, избегая обрезки матрицы.
Кроме того, без дополнения сверточный фильтр сканирует пограничные пиксели
меньшее количество раз, чем центральные, что приводит к потере информации.
В большинстве случаев дополнительным пикселям присваивается нулевое
значение (zero-padding). В процессе свертки эти нулевые значения поэлементно умножаются на значения матрицы фильтра и не влияют на конечную
сумму. Дополним нулевыми значениями матрицу изображения, показанную
на иллюстрации 4.1. Получится следующая матрица.
Иллюстрация 4.4. Свертка с дополнением нулями
Как показано на иллюстрации 4.4, мы окружили матрицу изображения
размером 5×5 рамкой из нулевых значений и получили входную матрицу
142
■
Pythonic AI
размером 7×7. Применив тот же фильтр к этой входной матрице, получим
выходную матрицу размером 5×5, то есть она будет такого же размера, что
и изначальное изображение. Итак, с помощью добавления можно контролировать размер выходных данных. В TensorFlow 2 задать опцию padding можно
при добавлении слоя Conv2D в модель. Значение valid означает, что добавления рамки не будет, а значение same говорит о том, что добавится рамка из
нулей, как указано в следующем фрагменте кода:
1. inputs = tf.keras.Input((28,28,3))
2. conv_1 = tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3),
padding='same')(inputs)
Сдвиг
Сдвигом (striding) называется перемещение фильтра по входному изображению. Фильтр сканирует изображение слева направо и сверху вниз. По умолчанию он перемещается на один пиксель по горизонтали и, дойдя до края строки,
смещается на один пиксель по вертикали, после чего возвращается к началу следующего ряда. Но и этот параметр можно изменить и задать количество пикселей, на которое будет смещаться фильтр. При значении параметра stride («шаг»)
равном 2 фильтр будет смещаться на два пикселя по горизонтали и вертикали.
Более высокие показатели позволят ускорить процесс свертки с сокращением
размера входного изображения. Рассмотрим следующую иллюстрацию:
Иллюстрация 4.5. Сдвиг
Проектирование CNN с помощью TensorFlow
■
143
На иллюстрации 4.5 показано перемещение фильтра при разных значениях
сдвига. При значении 2 размер выходных данных становится меньше, чем
при значении 1.
В TensorFlow 2 значение параметра stride для слоя Conv2D задается в виде кортежа из двух целых чисел, определяющих сдвиг фильтра свертки в вертикальном
и горизонтальном направлениях, как показано в следующем фрагменте кода:
1. inputs = tf.keras.Input((28,28,3))
2. conv_1 = tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3),
padding='same', stride=(1, 1))(inputs)
Наиболее выгодное количество фильтров в сверточном слое, как и наилучшие значения показателей stride и padding, можно вычислить с помощью оптимизации гиперпараметров, о которой говорилось в главе 3 «Создание нашей первой модели нейронной сети».
СлойR eLu
В CNN за сверточным слоем следует слой активации ReLu (Rectified Linear
Unit), добавляющий нелинейность. На нем к полученным со сверточного
слоя данным поэлементно применяется функция активации ReLu. Слой ReLu
не меняет размер изображения.
В TensorFlow 2 слой активации добавляется при определении функции, как
показано в следующем фрагменте кода:
1. inputs = tf.keras.Input((28,28,3))
2. conv_1 = tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3),
padding='same')(inputs)
3. act_1 = tf.keras.layers.Activation("relu")(conv_1)
В этом фрагменте мы добавили слой активации с функцией ReLu.
Слой «пулинга» (субдискретизации)
Слой «пулинга» (также называемый слоем субдискретизации, слоем «подвыборки» или «объединяющим слоем») подобно сверточному слою сканирует
144
■
Pythonic AI
входное изображение слева направо и сверху вниз. Но в отличие от сверточного слоя он не выполняет поэлементное произведение с суммированием,
а вычисляет некое общее значение для какой-то области пикселей. Результат
вычисляется одним из двух способов:
• Определением максимального значения — «пулинг по максимальному значению» (max pooling).
• Определением среднего значения — «пулинг по среднему значению»
(average pooling).
Таким образом, объединяющий слой уменьшает размер входной матрицы
с сохранением наиболее важных признаков для дальнейшей обработки. Как
и в случае со сверточным слоем, для слоя пулинга можно задавать параметры
padding и stride. Рассмотрим следующую иллюстрацию:
Иллюстрация 4.6. Выбор максимального значения
На иллюстрации 4.6 показано, как ядро пулинга размером 2×2 сканирует изображение и на его основе создает матрицу с максимальными значениями. На
первом шаге он рассматривает четыре значения (255, 0, 255, 255), определяет
максимальное (255) и присваивает его первому элементу выходной матрицы.
Такая операция делает местоположение признаков инвариантным по отношению к субдискретизации, или понижению разрешения. Это означает, что
даже если один и тот же объект на разных изображениях изменит свое местоположение, операция «пулинга» («объединения») все равно поможет сохранить наиболее важные признаки для классификации объекта.
Проектирование CNN с помощью TensorFlow
■
145
В TensorFlow 2 операция пулинга добавляется к модели в виде слоя. В модуле
tf.keras.layers для пулинга по максимальному значению определен слой
MaxPooling2D, а для пулинга по среднему значению — слой AveragePooling2D.
Слой пулинга добавляется к модели после слоя Conv2D, как показано в следующем фрагменте кода:
1. inputs = tf.keras.Input((28,28,1))
2. conv_1 = tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3),
padding='same')(inputs)
3. act_1 = tf.keras.layers.Activation("relu")(conv_1)
4. pool_1 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2),
strides= (1, 1), padding='valid',)(act_1)
Как показано выше, мы добавили слой пулинга с указанными параметрами
pool_size, stride и padding. Если параметр сдвига (stride) не задать явно, он
примет такое же значение, как и pool_size.
С помощью нескольких сверточных слоев с последующими слоями активации и слоями субдискретизации можно последовательно извлекать из изображения признаки от низкого до более высокого уровня абстракции.
Полносвязныйс лой
Этот слой — такой же, как и в полносвязной ИНС, изученной нами в главе 3
«Создание нашей первой модели нейронной сети». После того как сверточные
слои и слои пулинга уменьшат размер входных данных и извлекут из изображения матрицу значимых признаков, для окончательного анализа используются полносвязные слои. Перед передачей данных в первый полносвязный
слой нужно выполнить операцию разворачивания.
Методы обобщения
После тренировки глубокой нейронной сети на обучающем наборе данных
модель должна показывать удовлетворительные результаты на неизвестных
ей тестовых выборках. Предполагается, что обучающий и тестовый наборы
данных взяты из одного и того же распределения. Обобщение — это способность модели делать хорошие предсказания на новых данных, которых она
не видела. При создании любой модели нейросети нужно ставить себе целью
добиться как можно более эффективного обобщения навыка.
146
■
Pythonic AI
На способность к обобщению отрицательно влияют два явления — переобучение (overfitting) и недообучение (underfitting). Хорошая модель должна
успешно работать на тестовой выборке, а не только на обучающей. Переобученная модель отлично усваивает обучающие данные, но на наборах неизвестных ей данных выдает плохие результаты. Такую модель называют моделью
с высокой дисперсией и низким смещением. Недообученная модель — это
модель, которая не научилась как следует распознавать данные, и ее производительность можно улучшить с помощью дополнительного обучения и тестовых данных. Такую модель называют моделью с высоким смещением и низкой
дисперсией. В хорошей модели должен наблюдаться баланс между смещением
и дисперсией. Рассмотрим, как добиться такого компромисса между смещением и дисперсией в наших моделях нейронных сетей глубокого обучения.
Что делать в случае переобучения
Переобучение — распространенное явление для моделей нейронных сетей,
и особенно ему подвержены глубокие нейронные сети из-за сложности своей
архитектуры. Для борьбы с переобучением используются различные методы, в том числе переопределение архитектуры модели, отключение нейронов
(дропаут), аугментация данных, регуляризация, ранний останов и пр.
Переопределение архитектуры модели
Если модель переобучается, стоит пересмотреть ее архитектуру. Слишком
глубокие нейронные сети легко переобучаются. Один из распространенных
способов решения этой проблемы — упрощение архитектуры с уменьшением количества слоев или количества нейронов в каждом слое. Более простая
модель менее склонна к переобучению, потому что она настраивает меньшее
количество параметров и менее подвержена влиянию шума в обучающих
данных. Для выбора оптимального количества слоев и нейронов можно воспользоваться оптимизацией гиперпараметров.
Не стоит также использовать одно разбиение на тренировочную и тестовую
выборки, вместо этого лучше провести кросс-валидацию для оценки эффективности модели на нескольких подвыборках. Кросс-валидация позволит получить более надежную оценку способности модели к обобщению и выявить
переобучение.
Важно найти баланс между сложностью модели и объемом доступных данных. Упрощение модели уменьшает вероятность переобучения, но чрезмерная
Проектирование CNN с помощью TensorFlow
■
147
простота грозит недообучением. Нужно экспериментировать с различными архитектурными настройками, следить за производительностью модели
и выбирать конфигурацию, обеспечивающую наилучший компромисс между
смещением и дисперсией.
Регуляризация
Методы регуляризации позволяют контролировать сложность путем введения штрафов для очень сложных моделей. В главе 3 «Создание нашей первой модели нейронной сети» мы узнали, что каждый нейрон в ИНС пытается
подогнать линейный классификатор к данным, вычисляя взвешенную сумму
входных признаков. Методы регуляризации уменьшают, или штрафуют, веса,
имеющие большую величину в линейной модели. Ниже рассматриваются два
типа регуляризации: L1-регуляризация и L2-регуляризация:
• Регуляризация L1 или регрессия Lasso: метод уменьшения абсолютного значения весов. С ее помощью можно даже полностью удалить несущественные признаки, обнулив их веса или коэффициенты. Для этого
задается значение параметра, который называется коэффициентом регуляризации, и это значение определяет размер вычисляемого штрафа.
• Регуляризация L2 или Ridge-регрессия: в этом методе штраф пропорционален квадрату значений весов. При этом также задается коэффициент регуляризации.
В одной модели можно объединять регуляризацию L1 и L2. При необходимости во время построения модели нейронной сети регуляризацию можно
задавать на каждом слое. В TensorFlow при задании слоя можно указывать
настройки регуляризации с помощью опций kernel_regularizer и bias_
regularizer, как показано в следующем фрагменте кода:
1. inputs = tf.keras.Input((28,28,1))
2. conv_1 = tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3),
padding='same', kernel_regularizer="L1L2")(inputs)
В этом фрагменте в слой Conv2D мы добавили регуляризатор L1L2 (комбинацию
регуляризаторов L1 и L2) как значение параметра kernel_regularizer. Точно
так же можно добавлять регуляризаторы и в полносвязные плотные слои.
Ниже показан фрагмент кода, в котором добавляются регуляризаторы с указанием точного коэффициента регуляризации для tf.keras.regularizers:
148
■
Pythonic AI
1. inputs = tf.keras.Input((28,28,1))
2. conv_1 = tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3),
padding='same', kernel_regularizer=tf.keras.regularizers.
L1L2(0.01, 0.01))(inputs)
В данном случае мы добавили регуляризатор L1L2 c коэффициентами регуляризации равными 0,01 как для L1, так и для L2.
Дропаут (отключение)
Дропаут (dropout), или «отключение» («случайное исключение нейронов»), —
еще один метод регуляризации, предотвращающий переобучение модели за
счет случайного отключения некоторых нейронов во время обучения. Благодаря этому сложность архитектуры модели временно снижается, и в каждом проходе при прямом распространении активность сохраняют меньше нейронов.
В TensorFlow дропаут добавляется как отдельный слой после того слоя, к которому мы желаем применить этот метод. Например, если мы желаем применить этот метод к сверточному слою, то нужно просто добавить слой дропаута после сверточного слоя. Для него задается параметр коэффициента
отключения, представляющий собой вероятность того, что конкретный нейрон, или узел сети, не будет задействован в очередном проходе при обучении.
Во время инференса (применения уже обученной модели к новым данным)
нейроны не отключаются. Рассмотрим следующий фрагмент кода:
1. inputs = tf.keras.Input((28,28,1))
2. conv_1 = tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3),
padding='same', kernel_regularizer="L1L2")(inputs)
3. act_1 = tf.keras.layers.Activation("relu")(conv_1)
4. pool_1 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(1, 1), padding='valid',)(act_1)
5. do_1 = tf.keras.layers.Dropout(0.6)(pool_1)
Здесь мы добавили слой исключения с коэффициентом 0,6. Точно так же
можно добавлять слой исключения и после плотных слоев.
Аугментация данных
Уменьшить переобучение модели можно с помощью дополнительных обучающих данных. Аугментация («расширение», «дополнение») данных — это метод искусственного увеличения количества данных. Новые образцы данных
Проектирование CNN с помощью TensorFlow
■
149
создаются посредством различных преобразований уже имеющихся данных — переворачиванием, масштабированием, вращением, срезами и прочими манипуляциями.
В библиотеке TensorFlow в виде слоев представлено несколько методов
аугментации и предварительной обработки данных. Это такие слои модуля tf.keras.layers, как RandomCrop, RandomFlip, RandomTranslation,
RandomRotation, RandomZoom, CenterCrop, RandomHeight, RandomWidth,
RandomContrast и т. д. Использовать их можно двумя способами: во время
обучения модели и перед обучением модели.
На следующей иллюстрации показан простой пример использования слоя
RandomFlip для переворачивания изображения. Изображение взято из набора данных CIFAR-10.
Иллюстрация 4.7. Пример использования слоя RandomFlip
Как показано на иллюстрации 4.7, мы создали последовательную модель
и добавили в нее слой RandomFlip. Затем мы подали на вход модели одно
изображение, после чего вывели исходное и измененное изображения рядом
друг с другом на экран.
150
■
Pythonic AI
Аугментация во время обучения модели
В этом случае различные слои аугментации последовательно добавляются
к модели ИНС, точно так же, как и обычные. Эти слои работают во время
обучения модели. При сохранении модели слои дополнения также сохраняются вместе с основными. Рассмотрим следующий пример кода:
1. model = tf.keras.Sequential()
2. model.add(augmentation)
3. model.add(tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3),
padding='same'))
4. model.add(tf.keras.layers.Activation("relu"))
5. model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(1, 1), padding='valid',))
6.
7. model.add(tf.keras.layers.Conv2D(16, (3, 3), padding='same'))
8. model.add(tf.keras.layers.Activation("relu"))
9. model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
10.
11. model.add(tf.keras.layers.Flatten())
12. model.add(tf.keras.layers.Dense(512, activation='relu'))
13. model.add(tf.keras.layers.Dense(512, activation='relu'))
14. model.add(tf.keras.layers.Dense(10, activation='softmax'))
Здесь мы добавили слой аугментации как часть основной модели.
Аугментация до обучения модели
Аугментацию можно применять отдельно к набору данных во время предварительной обработки изображений. В таком случае она может выполняться
на CPU, в то время как обучение основной модели асинхронно проводится
на GPU.
Чтобы применить такой способ, нужно иметь представление о концепции
набора данных в TensorFlow, и подробно этот вопрос мы рассмотрим в следующем разделе. Сейчас же попробуем создать наборы данных TensorFlow из
обучающих и тестовых данных в следующем примере кода:
1. train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train))
2. test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test))
Проектирование CNN с помощью TensorFlow
■
151
3.
4. train_ds = train_ds.batch(32).map(lambda x,y:(augmentation(x), y),
5.
num_parallel_calls=tf.data.AUTOTUNE)
6.
7. test_ds = test_ds.batch(32)
Как показано выше, мы использовали функцию from_tensor_slices для работы с наборами данных в TensorFlow. С ее помощью мы создали набор данных из массивов NumPy признаков и меток. Затем мы сформировали пакет
из 32 образцов данных для обучения и тестирования, и применили к матрице
признаков метод аугментации. Параметр num_parallel_calls был установлен в значение tf.data.AUTOTUNE. Это означает, что пакеты обрабатываются
параллельно и асинхронно, а количество параллельных вызовов устанавливается динамически в зависимости от доступных вычислительных ресурсов.
Важно отметить, что аугментация применяется только к обучающему набору
данных, но не к тестирующему. В этом случае добавлять к модели слой аугментации не нужно.
Пакетнаян ормализация
Пакетная нормализация1 используется не только для борьбы с переобучением, хотя в некоторой степени и способствует регуляризации. При обучении
нейронной сети входные данные обычно передают в нее по пакетам (batches).
Перед этим на стадии предварительной обработки также выполняют нормализацию данных. Пакетная нормализация — один из способов нормализации данных для нейронных сетей, и она особенно полезна для ускорения
обучения глубоких нейронных сетей.
В нейронной сети над входными данными выполняется ряд математических
операций. Веса, найденные с помощью процедуры оптимизации, используются для расчета взвешенной суммы входных признаков. Затем для добавления нелинейных эффектов к данным применяется функция активации.
В достаточно глубокой нейросети из-за всех этих математических операций
в более поздних слоях меняется распределение входных данных, в результате
чего первые и более глубокие слои нейронной сети получают данные из разных распределений. Эта проблема называется внутренним ковариационным
1
Sergey Ioffe, Christian Szegedy. “Batch normalization: Accelerating deep network training by
reducing internal covariate shift”. Международная конференция по машинному обучению, стр. 448–456, 2015.
152
■
Pythonic AI
сдвигом. Пакетная нормализация устраняет проблему внутреннего ковариационного сдвига, преобразовывая мини-пакеты данных так, чтобы их средние значения стремились к нулю, а стандартное отклонение приближалось
к единице, что способствует ускорению обучения.
В TensorFlow пакетная нормализация доступна в виде слоя BatchNormalization
из tf.keras.layers, который, как правило, добавляют к модели нейронной
сети перед слоем активации. Рассмотрим следующий пример кода:
1. inputs = tf.keras.Input((28,28,1))
2. conv_1 = tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3),
padding='same', kernel_regularizer='L1L2')(inputs)
3. bn_1 = tf.keras.layers.BatchNormalization()(conv_1)
4. act_1 = tf.keras.layers.Activation("relu")(bn_1)
5. pool_1 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(1, 1), padding='valid',)(act_1)
6. do_1 = tf.keras.layers.Dropout(0.6)(pool_1)
Как показано выше, мы добавили слой BatchNormalization после слоя
Conv2D и перед слоем активации.
Что делать в случае недообучения
Если модель нейросети не усваивает основные закономерности и не соответствует сложности обучающих данных, говорят, что она «недообучена».
Это происходит, когда модель слишком проста или не эффективна для имеющихся данных. Недообучение столь же нежелательно, как и переобучение.
Недообучение может происходить по нескольким причинам:
• Недостаточная сложность модели. Модель нейросети не способна отразить взаимосвязи, лежащие в основе данных, и потому не справляется с обучением и обобщением. Например, эффективность модели ограничивает небольшое количество слоев в неглубокой сети или малое
количество нейронов в слое.
• Недостаточное обучение. Модель обучалась слишком малое количество итераций, или эпох. Для того чтобы вычислить нужные веса и смещение, нейронным сетям требуется определенное количество циклов
обучения. Если модель прекращает обучаться слишком рано, то она не
успевает найти оптимальное для своей архитектуры решение.
Проектирование CNN с помощью TensorFlow
■
153
• Недостаточный набор обучающих данных. Если набор обучающих
данных слишком мал или недостаточно разнообразен, модель будет
анализировать небольшое количество вариаций и не сможет найти все
закономерности. В таких случаях модель плохо обрабатывает незнакомые данные.
Для выявления проблем, связанных с недообучением, нужно рассмотреть
следующие показатели:
• Эффективность на обучающей выборке. Если эффективность модели
на обучающем наборе существенно не улучшается, это говорит о том,
что модель не справляется с обучением на имеющихся данных.
• Эффективность на проверочной выборке. Если модель плохо работает на проверочном наборе или отдельном тестовом наборе, это свидетельствует о недостаточной способности к обобщению. Недообученные модели часто демонстрируют низкую производительность или
высокий уровень ошибок на незнакомых данных.
Для решения проблемы недообученности применяются следующие методы:
• Усложнение архитектуры модели за счет добавления дополнительных
слоев и нейронов. Одна из основных причин недообучения — это то,
что модель с простой архитектурой не способна отразить сложные
взаимосвязи в данных. Можно также увеличить количество фильтров
в сверточных слоях CNN.
• Увеличение количества эпох для более длительного обучения.
• Добавление образцов данных для обучения и настойка регуляризации.
Построение CNN с помощью
TensorFlow
Как мы знаем, библиотека TensorFlow позволяет создавать и настраивать различные слои сверточных нейронных сетей (CNN), пользоваться методами
обобщения и т. д. Помимо архитектуры моделей стоит рассмотреть несколько дополнительных аспектов, полезных для эффективного построения моделей с помощью библиотеки TensorFlow. Обсудим их в следующих темах.
154
■
Pythonic AI
Набор данных TensorFlow
Нейронная сеть учится на данных. Чем больше данных, тем лучше обучение.
При работе с системами ИИ мы часто сталкиваемся с огромными наборами
данных, которые могут не поместиться в оперативную память (RAM) из-за
своего большого размера. Для работы с большими выборками библиотека
TensorFlow предоставляет API под названием dataset, который позволяет читать данные и обучать модель в потоковом режиме. Кроме того, что при этом
не требуется предварительно загружать в память весь набор данных, мы также можем на ходу выполнить их преобразование.
При наличии файлов с данными можно создать из них набор данных TensorFlow
по определенному шаблону. Ниже показан фрагмент кода для создания набора данных из всех CSV-файлов, хранящихся по некоторому пути:
1. ds = tf.data.Dataset.list_files("/path/to/csv/files/*.csv")
Мы уже рассматривали создание наборов данных из массивов NumPy с помощью функции from_tensor_slices. Наборы обучающих и проверочных/
тестовых данных для изображений можно создавать из каталога изображений с помощью служебной функции image_dataset_from_directory. Рассмотрим следующий фрагмент кода:
1. from tensorflow.keras.utils import image_dataset_from_directory
2.
3. image_directory = "./input_data/images/"
4.
5. train_dataset = image_dataset_from_directory(
6. directory = image_directory,
7. label_mode = "int",
8. validation_split = 0.3,
9. subset = "training",
10.
image_size = (28, 28),
11.
batch_size = 64)
12.
13.
validation_dataset = image_dataset_from_directory(
14.
directory = image_directory,
15.
label_mode = "int",
16.
validation_split = 0.3,
17.
subset = "validation",
Проектирование CNN с помощью TensorFlow
18.
image_size = (28, 28),
19.
batch_size = 64)
■
155
Как показано выше, функция image_dataset_from_directory принимает на
вход путь к каталогу изображений. Все изображения хранятся в этом каталоге. Также нужно упомянуть о режиме label_mode. Когда параметр label_mode
равен int, это значит, что метки задаются в виде целых чисел, и в данном случае
для функции потерь нужно использовать SparseCategoricalCrossentropy.
Если же label_mode определен как categorical, то метки задаются в виде вектора с прямым унитарным кодом, и в таком случае для функции потери нужно использовать CategoricalCrossentropy. Если label_mode определен как
binary, то метка задается как одно из всего двух возможных значений, и тогда
для функции потери нужно использовать BinaryCrossentropy.
Использование GPU
В главе 2 «Настройка лаборатории искусственного интеллекта» мы узнали,
что Google Colab предлагает бесплатно воспользоваться ускорителем Nvidia
GPU при обучении моделей в блокноте Colab. Чтобы запустить блокнот с использованием GPU, нужно перейти в меню «Среда выполнения» (Runime),
выбрать пункт «Сменить среду управления» (Change runtime option) и выбрать «Аппаратный ускоритель» (Hardware Accelerator) GPU. После этого код,
запускаемый в блокноте, будет выполняться на GPU, и специально менять
код для этого не нужно.
Если перед запуском глубокой нейронной сети выбрать в качестве ускорителя GPU, процесс обучения будет происходить быстрее. Более подробные
сведения о ресурсах GPU в Colab, об их доступности, ограничениях на использование, о типах GPU и о других аспектах можно получить по адресу:
https://research.google.com/colaboratory/faq.html.
Использование TPU
Среда Colab предлагает еще один вариант для обучения моделей — TPU. Этот
вариант также выбирается как тип «Аппаратного ускорителя» (Hardware
Accelerator) пункта «Сменить среду управления» (Change runtime option) в меню «Среда выполнения» (Runtime).
После активации TPU можно узнать IP-адрес TPU Colab с помощью библиотеки os. Он понадобится нам для создания рабочего TPU с адресом gRPC.
Рассмотрим следующие фрагменты кода:
156
■
Pythonic AI
Иллюстрация 4.8. Получение адреса TPU
После запуска рабочего TPU создадим кластерный резольвер для службы
Google Cloud TPU. После подключения к TPU выведем на экран список доступных устройств, как показано в следующем фрагменте кода:
1. resolver = tf.distribute.cluster_resolver.TPUClusterResolver(TPU_
WORKER)
2. tf.config.experimental_connect_to_cluster(resolver)
3. tf.tpu.experimental.initialize_tpu_system(resolver)
4. print(tf.config.list_logical_devices('TPU'))
Далее нужно создать стратегию (strategy) TPU для синхронного обучения на
TPU. Под «стратегией TPU» в Google Colab подразумевается использование
TPU для ускорения обучения и инференса (практического использования)
моделей машинного обучения. Код для этого следующий:
1. strategy = tf.distribute.TPUStrategy(resolver)
После создания стратегии создадим модель и скомпилируем ее в рамках стратегии, как показано ниже:
1. with strategy.scope():
2. inputs = tf.keras.Input((28,28,1))
3. conv_1 = tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3),
padding='same')(inputs)
Проектирование CNN с помощью TensorFlow
■
157
4. act_1 = tf.keras.layers.Activation("relu")(conv_1)
5. pool_1 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2),
strides=(1, 1), padding='valid',)(act_1)
6. do_1 = tf.keras.layers.Dropout(0.6)(pool_1)
7.
8. conv_2 = tf.keras.layers.Conv2D(16, (3, 3), padding='same')(do_1)
9. act_2 = tf.keras.layers.Activation("relu")(conv_2)
10.
pool_2 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(act_2)
11.
do_2 = tf.keras.layers.Dropout(0.6)(pool_2)
12.
13.
conv_3 = tf.keras.layers.Conv2D(32, (3, 3), padding='same')
(do_2)
14.
act_3 = tf.keras.layers.Activation("relu")(conv_3)
15.
pool_3 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(act_3)
16.
do_3 = tf.keras.layers.Dropout(0.6)(pool_3)
17.
18.
flatten = tf.keras.layers.Flatten()(do_3)
19.
dense_1 = tf.keras.layers.Dense(512, activation='relu',
kernel_regularizer="L1L2")(flatten)
20.
dense_2 = tf.keras.layers.Dense(512, activation='relu',
kernel_regularizer="L1L2")(dense_1)
21.
outputs = tf.keras.layers.Dense(10, activation='softmax')
(dense_2)
22.
model = tf.keras.Model(inputs=inputs, outputs=outputs)
23.
24.
model.compile(
25.
optimizer=tf.keras.optimizers.Adam(0.001),
26.
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
27.
metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],
28. )
Таким образом, после вызова функции model.fit() начнется обучение с использованием ускорителя TPU.
158
■
Pythonic AI
Примечание. В бесплатной подписке Google Colab ускорители TPU бывают недоступны, особенно в часы пик. В такое время лучше пользоваться бесплатным GPU, и, разумеется, для этого не нужно будет писать стратегию TPU и настраивать его. Стоит также отметить, что TPU
более эффективны для масштабных моделей и наборов данных. Для
небольших моделей или наборов данных прирост производительности может оказаться незначительным по сравнению с использованием
других аппаратных ускорителей, таких как GPU.
Стандартные архитектуры CNN
Как мы уже говорили, архитектура CNN была впервые разработана
в 1980-х годах. С тех пор концепция CNN прошла долгий путь развития, и исследовательское сообщество предложило множество эффективных и сложных архитектур для ее реализации. При разработки модели CNN для анализа
изображений поначалу можно растеряться, не зная, какую архитектуру модели выбрать. Поэтому давайте познакомимся с некоторыми стандартными
архитектурами CNN, которые широко используются на протяжении длительного времени и хорошо зарекомендовали себя в научных задачах и коммерческих приложениях. Выбрав одну из этих архитектур, мы можем перейти к решению задачи анализа изображений.
LeNet
LeNet — одна из ранних архитектур CNN, названная в честь ее изобретателя
Яна Лекуна. Это довольно простая нейросеть, и впервые ее использовали для
распознавания рукописных цифр. Обычно под архитектурой LeNet подразумевают ее разновидность LeNet-5, состоящую из семи слоев: двух сверточных,
двух слоев пулинга и трех плотных или полносвязных слоев. В оригинальной
статье1 Яна Лекуна и его коллег описано использование архитектуры LeNet-5
для распознавания черно-белых изображений размером 32×32 пикселя. Первый сверточный слой состоит из шести фильтров/ядер размером 5×5. За этим
сверточным слоем следует слой пулинга с фильтрами размера 2×2, вычисляющий среднее значение с шагом 2. Второй сверточный слой имеет 16 фильтров
размером 5×5, и за ним следует слой пулинга (вычисляющий среднее значение)
1
Yann LeCun, Léon Bottou, Yoshua Bengio, Patrick Haffner. “Gradient-based learning applied
to document recognition”. Статья в сборнике Proceedings of the IEEE 86, no. 11 (1998),
стр. 2278–2324.
Проектирование CNN с помощью TensorFlow
■
159
с фильтрами размером 2×2. Далее идут полносвязные слои из 120 и 84 узлов,
а затем последний (полносвязный) слой из десяти узлов. В этой архитектуре
к нейронам применяется сигмоидальная функция активации. Следующая иллюстрация взята из оригинальной статьи Яна Лекуна и его коллег.
Иллюстрация 4.9. Архитектура LeNet
Оригинальная архитектура LeNet довольно проста. Эту архитектуру допускается использовать в качестве базовой для простых задач анализа изображений. Улучшить ее производительность можно посредством добавления
дополнительных фильтров, слоев исключения (дропаута), пакетной нормализации, активации ReLu, пулинга максимального значения и т. д.
AlexNet
AlexNet — еще одна классическая архитектура CNN, названная в честь ее
изобретателя Алекса Крижевского. Впервые эту архитектуру использовали
в ходе проведения конкурса LSVRC-2010, а новый вариант этой модели был
представлен на конкурсе ILSVRC-2012. Архитектура состоит из пяти сверточных слоев, слоев пулинга по максимальному значению и трех полносвязных слоев. В оригинальной статье1 перечисляются некоторые уникальные
особенности этой архитектуры:
• В модели использована функция активации ReLu.
• Модель обучалась на нескольких графических процессорах для ускорения обучения.
• Исследователи использовали метод локальной нормализации выходного сигнала в сверточных слоях. В настоящее время широко используется альтернативный метод пакетной нормализации.
1
Alex Krizhevsky, Ilya Sutskever, Geoffrey E. Hinton. “Imagenet classification with deep
convolutional neural networks”. Communications of the ACM 60, no. 6 (2017), стр. 84–90.
160
■
Pythonic AI
• Для предотвращения переобучения исследователи использовали методы
аугментации данных и случайного отключения (дропаута) нейронов.
Эксперимент проводился на наборе данных ImageNet, содержащем цветные
изображения 1000 различных видов в целях их классификации. Размер каждого входного изображения составляет 224×224×3. Первый сверточный слой
содержит 96 фильтров размером 11×11 с шагом 4 пикселя. Второй сверточный слой содержит 256 фильтров размером 5×5. За первым и вторым сверточными слоями следуют слои локальной нормализации сигнала. За этими
двумя слоями нормализации и пятым сверточным слоем следуют слои пулинга по максимальному значению с фильтрами 3×3. После третьего и четвертого сверточных слоев слой пулинга отсутствует. Третий и четвертый
сверточные слои имеют по 384 фильтра размером 3×3, а пятый сверточный
слой — 256 фильтров размером 3×3. Полносвязные слои имеют по 4096 нейронов, а последний полносвязный слой представляет собой 1000-полосный
слой SoftMax. Архитектура этой сети показана на следующей схеме, взятой из
оригинальной статьи:
Иллюстрация 4.10. Архитектура AlexNet
VGGNet
VGGNet — это архитектура глубокой CNN, предложенная Кареном Симоняном и Эндрю Зиссерманом из Оксфордского университета. Она названа
в честь исследовательской группы изобретателей “Visual Geometry Group”.
Наиболее распространенные варианты VGGNet — это VGG-16 и VGG19.
Первый содержит в общей сложности 16 слоев, а второй — 19. Базовая архитектура VGGNet представляет собой стек сверточных слоев с фильтрами
размером 3×3 и шагом равным 1. Для линейной трансформации в архитектуре используется сверточный фильтр размером 1×1. За сверточными слоями следуют слои пулинга по максимальному значению с фильтрами размера
2×2 с шагом равным 2. Полносвязные слои такие же, как и в AlexNet. Как
Проектирование CNN с помощью TensorFlow
■
161
и в архитектуре AlexNet, в VGGNet используются слои активации ReLu и локальной нормализации сигнала. Ниже приведена схема архитектуры VGGNet
из оригинальной статьи1:
Иллюстрация 4.11. Архитектура VGGNet
На иллюстрации 4.11 в столбцах с A по E показаны различные конфигурации (варианты) архитектуры VGGNet. В столбцах C и D показаны варианты
VGG16, а в столбце E — конфигурация VGG19.
Если мы захотим воспользоваться моделью VGGNet для какой-либо задачи анализа изображений, нам не придется писать ее с нуля. Библиотека
TensorFlow имеет модуль tf.keras.applications, включающий в себя некоторые из широко используемых архитектур с предварительно обученными
весами. С его помощью можно быстро создать архитектуру VGG.
1
Karen Simonyan, and Andrew Zisserman. “Very deep convolutional networks for large-scale
image recognition”. arXiv preprint arXiv:1409.1556 (2014).
162
■
Pythonic AI
На иллюстрации 4.12 показано, как создаются готовые архитектуры VGG16
и VGG19 в приложениях TensorFlow. Установив значение include_top равным True, мы сохранили в модели три полносвязных слоя. Переменная input_
shape хранит размер входного изображения. Всего нужно классифицировать
1000 классов (classes), и в качестве функции активации на выходе в модели
используется softmax. Теперь остается только скомпилировать модель и обучить ее на данных, как мы делали это раньше.
Иллюстрация 4.12. VGGNet в TensorFlow
ResNet
Residual Network (ResNet) («Остаточная сеть») — это архитектура глубокой
CNN, предложенная Каймингом Хе и его коллегами из компании Microsoft.
В целом такие глубокие архитектуры, как VGGNet, обучаются трудно.
В ResNet было представлено очередное архитектурное усовершенствование — так называемые соединения быстрого доступа, или «обходные соединения» (shortcut connections).
На следующей иллюстрации показано, как эти соединения позволяют пропускать один или несколько слоев в архитектуре CNN. Схема взята из оригинальной статьи с описанием ResNet1:
Обходные соединения переносят данные на несколько слоев вперед без дополнительных параметров. Как показано на иллюстрации 4.13, F(x) + x — это
поэлементное сложение. В глубоких нейронных сетях ошибки обучения и тестирования могут достигать очень высоких значений. Из-за регуляризации
1
Kaiming He, Xiangyu Zhang, Shaoqing Ren, and Jian Sun. “Deep residual learning for image
recognition”. Статья в сборнике Proceedings of the IEEE conference on computer vision and
pattern recognition, стр. 770–778. 2016.
Проектирование CNN с помощью TensorFlow
■
163
многие веса и фильтры стремятся к нулю, и даже градиенты становятся
чрезвычайно малыми, что приводит к проблеме исчезающего градиента. Все
это влияет на производительность глубокой CNN. Обходные связи в ResNet
позволяют пропускать слои, влияющие на производительность модели, и тем
самым упрощать процесс обучения. ResNet содержит один-единственный
полносвязный слой в качестве выходного слоя.
Иллюстрация 4.13. Обходное соединение
В TensorFlow доступно несколько готовых вариантов архитектуры ResNet.
В следующем фрагменте кода показана инициация архитектуры ResNet101
в TensorFlow:
Иллюстрация 4.14. ResNet в TensorFlow
СетьI nception
Для обучения глубоких нейронных сетей требуются огромные вычислительные ресурсы. Кристиан Сегеди и его коллеги из компании Google предложили для обучения глубоких нейронных сетей на ограниченных вычислительных ресурсах архитектуру Inception. В модели Inception используются три
типа сверток с фильтрами 5×5, 3×3 и 1×1. Сверточный слой 1×1 используется
164
■
Pythonic AI
для уменьшения размерности и, благодаря этому, для устранения узких мест
(bottlenecks, «бутылочных горлышек»). Модуль Inception показан на следующей схеме, взятой из оригинальной статьи1:
Иллюстрация 4.15. Модуль Inception
1
Christian Szegedy, Wei Liu, Yangqing Jia, Pierre Sermanet, Scott Reed, Dragomir Anguelov,
Dumitru Erhan, Vincent Vanhoucke, Andrew Rabinovich. “Going deeper with convolutions”.
Статья в сборнике Proceedings of the IEEE conference on computer vision and pattern
recognition, стр. 1–9. 2015.
Проектирование CNN с помощью TensorFlow
■
165
В TensorFlow доступны различные версии готовых архитектур Inception. Ниже показан фрагмент кода с созданием одной из них:
1. inception_model = tf.keras.applications.inception_v3.InceptionV3(
2. include_top=True,
3. input_shape=(299, 299, 3),
4. classes=1000,
5. classifier_activation='softmax'
6. )
Среди других известных современных сверточных сетей можно выделить
EfficientNet, DenseNet, Xception и т. д.
Архитектуру EfficientNet1 предложила команда Минсин Тань, она охватывает семейство моделей от EfficientNet B0 до B7. В ней используется метод
составного коэффициента для оптимизации компонентов модели (количества слоев, ширины, глубины сети и т. д.) при ограниченных вычислительных
ресурсах. В отличие от традиционных архитектур, использующих функцию
активации ReLu, в этой применяется функция активации Swish (SiLU).
Архитектуру DenseNet2 предложила команда Гао Хуан. На каждый слой в архитектуре DenseNet подается конкатенация выходов всех предыдущих слоев.
Она обучается распознавать сложные признаки без увеличения числа настраиваемых параметров и эффективно решает проблему исчезающего градиента
в сетях глубокого обучения.
Франсуа Шолле, создатель библиотеки Keras, предложил модель Xception3.
Xception — это вариант архитектуры Inception, с которой мы познакомились
ранее. В ней используется линейный стек глубинно-разделенных сверток
с остаточными связями для уменьшения количества параметров и, соответственно, вычислительных затрат. По сравнению с предыдущими архитектурами CNN архитектура Xception показала большую эффективность и точность
на различных эталонных наборах данных для распознавания изображений.
Она широко используется в научных исследованиях и в промышленности,
1
2
3
Mingxing Tan, and Quoc Le. “Efficientnet: Rethinking model scaling for convolutional neural
networks”. В International conference on machine learning, стр. 6105–6114. 2019.
Gao Huang, Zhuang Liu, Laurens Van Der Maaten, and Kilian Q. Weinberger. “Densely
connected convolutional networks”. Статья в сборнике Proceedings of the IEEE conference
on computer vision and pattern recognition, стр. 4700–4708. 2017.
François Chollet. “Xception: Deep learning with depthwise separable convolutions”. Статья
в сборнике Proceedings of the IEEE conference on computer vision and pattern recognition,
стр. 1251–1258. 2017.
166
■
Pythonic AI
и является предпочтительным вариантом для многих приложений компьютерного зрения.
В библиотеке TensorFlow все эти современные архитектуры доступны в виде
предварительно обученных моделей. В главе 5 «Разработка приложений для
классификации изображений на основе CNN» мы подробно рассмотрим, как
реализовать переносное обучение (обучение с переносом знаний) с помощью
этих готовых, предварительно обученных моделей, доступных в виде приложений TensorFlow.
Заключение
В этой главе описаны методы эффективного использования сверточных сетей
для решения задач анализа изображений. Как мы уже говорили, сверточные
нейронные сети (CNN, Convolutional Neural Networks) стали популярным
и эффективным инструментом для разработки приложений компьютерного
зрения. CNN способны эффективно обрабатывать и анализировать изображения с помощью сверточных фильтров, что делает их полезными в различных
областях применения. За прошедшие годы были разработаны и протестированы различные архитектуры CNN, каждая из которых имеет свои сильные и слабые стороны. Мы познакомились с классическими архитектурами, такими как
LeNet, AlexNet и VGGNet, а также с современными архитектурами, такими как
ResNet, Inception, EfficientNet, DenseNet и Xception, демонстрирующими впечатляющие результаты в различных тестах на распознавание изображений. По
мере развития области компьютерного зрения ожидается дальнейшее совершенствование архитектур CNN и разработка новых методов. Будущие достижения в области аппаратного и программного обеспечения, а также обработки
данных еще больше повысят эффективность CNN как инструмента компьютерного зрения. В следующей главе мы познакомимся с задачами распознавания изображений и разработаем модели CNN для их выполнения.
Основные выводы
• CNN могут обнаруживать закономерности в сложных изображениях
и обучаться на их основе. Главный элемент CNN — это сверточные
слои. Во время операции свертки используются фильтры, сканирующие входные данные и извлекающие из них признаки.
• Для контроля за пространственными размерами выходных матриц
признаков используются методы дополнения и сдвига. С их помощью
Проектирование CNN с помощью TensorFlow
■
167
модели CNN способны улавливать мелкие детали. Слои активации (например, ReLu) вносят в модель нелинейность, а слои субдискретизации
(«пулинга») повышают способность модели распознавать важные шаблоны посредством уменьшения размеров матриц признаков.
• На последних этапах архитектуры CNN встречаются полносвязные
слои, которые отвечают за прогнозирование на основе высокоуровневых признаков, извлеченных предыдущими слоями.
• Методы обобщения, включая регуляризацию, отключение нейронов
(«дропаут»), пакетную нормализацию, аугментацию данных и т. д., помогают избежать переобучения и повышают надежность моделей.
• В главе рассмотрено несколько известных архитектур CNN, включая
LeNet, AlexNet, VGGNet, ResNet, InceptionNet и др. Знакомство с этими
архитектурами служит надежной отправной точкой для построения
и настройки CNN-моделей под конкретные задачи.
Ссылки
• Сверточный слой TensorFlow: https://www.tensorflow.org/api_docs/
python/tf/keras/layers/Conv2D
• Активационный слой TensorFlow: https://www.tensorflow.org/api_docs/
python/tf/keras/layers/Activation
• Слой Max-pooling TensorFlow: https://www.tensorflow.org/api_docs/
python/tf/keras/layers/MaxPool2D
• Регуляризаторы TensorFlow: https://www.tensorflow.org/api_docs/python/
tf/keras/regularizers
• Слой Dropout TensorFlow: https://www.tensorflow.org/api_docs/python/
tf/keras/layers/Dropout
• Слой Batch Normalization TensorFlow: https://www.tensorflow.org/api_
docs/python/tf/keras/layers/BatchNormalization
• Набор данных TensorFlow: https://www.tensorflow.org/api_docs/python/
tf/data/Dataset
• Приложения TensorFlow: https://www.tensorflow.org/api_docs/python/tf/
keras/applications
Глава 5
Разработка
приложений
для классификации
изображений
на основе CNN
Введение
В обширной области компьютерного зрения одной из самых фундаментальных и одновременно самых увлекательных задач является задача классификация изображений. Она заключается в распределении изображений по заранее определенным классам или категориям на основе их содержания. Этот
процесс подразумевает анализ и извлечение информации из изображений
с помощью алгоритмов искусственного интеллекта с последующим предсказанием, к какому классу должно принадлежать то или иное изображение. Это жизненно важная задача в области компьютерного зрения, которая
направлена на то, чтобы машины могли воспринимать и интерпретировать
окружающий мир схожим с человеком образом. По мере расширения массивов данных и развития методов машинного обучения задачи распознавания
и классификации изображений приобретают все большее значение в различных отраслях промышленности.
Разработка приложений для классификации изображений на основе CNN
■
169
Структура
В этой главе мы рассмотрим следующие темы:
• Введение в классификацию изображений.
• Набор данных и их структура.
• Скачивание данных и их предварительная обработка.
• Создание, обучение и оценка моделей CNN.
• Классификация изображений с помощью предварительно обученных
моделей.
• Использование пользовательских изображений для классификации.
Цели
К концу этой главы мы узнаем, как создать работающую сверточную нейронную сеть (CNN) с помощью библиотеки TensorFlow 2 для Python. Мы
получим достаточно хорошее представление о реализации различных компонентов архитектуры CNN. Мы узнаем о том, как скачивать, предварительно обрабатывать и загружать входные изображения в модели CNN. Мы научимся реализовывать на практике различные обученные модели, доступные
в библиотеке TensorFlow 2, а также обучать и тестировать их. Наконец, мы
узнаем, как использовать обученную модель CNN для классификации пользовательских изображений.
Введение в классификацию
изображений
Из главы 4 «Проектирование CNN с помощью TensorFlow» мы узнали о различных видах сверточных нейронных сетей и об их компонентах. Теперь
нужно понять, как построить работающую модель CNN с нуля, и как соединить между собой все изученные в предыдущей главе компоненты. Для обучения модели CNN нельзя непосредственно воспользоваться любым набором
изображений. Сначала нужно освоить различные способы сбора данных, их
анализа, загрузки и предварительной обработки, и только потом создавать
модель CNN для этих данных. В библиотеке TensorFlow также имеется множество предварительно обученных моделей CNN. Нам нужно узнать, как
пользоваться их функциями применительно к нашему набору данных с помощью технологии переноса обучения.
170
■
Pythonic AI
Набор данных и их структура
Мы будем использовать набор данных CIFAR-10, доступный по адресу: https://
www.cs.toronto.edu/~kriz/cifar.html, и строить сверточную нейронную сеть
для распознавания объектов на изображениях. Набор данных CIFAR-101 содержит 60 000 изображений, каждое имеет размеры 32×32 пикселя. Всего они
принадлежат к 10 целевым классам по 6000 изображений в каждом. Из этих
60 000 изображений 50 000 являются обучающими, а 10 000 — тестовыми.
Официальный сайт набора данных CIFAR-10 расположен по адресу: https://
www.cs.toronto.edu/~kriz/cifar.html. Там же можно получить прямую ссылку
для загрузки набора данных: https://www.cs.toronto.edu/~kriz/cifar-10-python.
tar.gz. Набор данных CIFAR собран Алексом Крижевским, Винодом Наиром
и Джеффри Хинтоном, и изображения в нем делятся на десять целевых
классов:
• Самолет.
• Автомобиль.
• Птица.
• Кошка.
• Олень.
• Собака.
• Лягушка.
• Лошадь.
• Корабль.
• Грузовик.
Для начала импортируем несколько необходимых библиотек и с помощью
команды wget загрузим набор данных CIFAR-10 по указанной ссылке. wget —
это команда Linux. Мы добавляем перед ней восклицательный знак (!), чтобы
она выполнилась в ячейке блокнота. Эта команда скачивает файлы из сети.
Команды показаны в следующем фрагменте кода:
1
Технический доклад (глава 3) с описанием набора данных CIFAR-10 и методов его сбора
см. в издании: Alex Krizhevsky, Geoffrey Hinton. “Learning multiple layers of features from
tiny images”. (2009): 7.
Разработка приложений для классификации изображений на основе CNN
■
171
Иллюстрация 5.1. Скачивание набора данных
Примечание. Кроме официального репозитория набор CIFAR-10 также доступен в виде набора данных TensorFlow. Подключить его можно
с помощью следующих команд:
1. cifar = tf.keras.datasets.cifar10
2. (x_train, y_train), (x_test, y_test) = cifar.load_data()
После завершения загрузки нажмите на иконку Файлы (Files) (показана красной стрелкой на следующей иллюстрации) на левой боковой панели блокнота
Colab и удостоверьтесь в том, что файл с именем cifar-10-python.tar.gz
загружен, как показано на следующей иллюстрации:
Иллюстрация 5.2. Загруженные данные
172
■
Pythonic AI
Помните, что файл tar.gz в настоящее время хранится в хранилище сеанса.
Мы не подключали Google Диск для постоянного хранения файлов. Любой
файл или каталог, хранящийся в хранилище сеанса, будет удален по завершении сеанса выполнения Colab. Так как загруженный файл представляет собой
архив tar.gz, перед работой с ним его нужно распаковать. Для этого воспользуемся командой tar с восклицательным знаком перед ней, поскольку tar —
это команда Linux. Ее выполнение показано на следующей иллюстрации:
Иллюстрация 5.3. Команда tar
После выполнения команды разархивирования в «Файлах» появится новый
каталог cifar-10-batchespy. Он содержит несколько файлов, в которых хранятся обучающие и тестовые данные. Файлы data_batch_1, data_batch_2,
data_batch_3, data_batch_4 и data_batch_5 содержат обучающие данные,
а файлы test_batch — тестовые данные. Каждый из этих файлов содержит
10 000 изображений и представляет собой сериализованный объект Python,
который можно прочитать и загрузить с помощью библиотеки Python pickle.
Функция для распаковки файлов приведена на домашней странице CIFAR-10
по адресу: https://www.cs.toronto.edu/~kriz/cifar.html. Мы будем пользоваться
этой функцией для чтения или десериализации объектов из распакованных
файлов, как показано в следующем фрагменте кода:
1. def unpickle(file):
2. import pickle
3. with open(file, 'rb') as fo:
4. dict = pickle.load(fo, encoding='bytes')
5. return dict
Разработка приложений для классификации изображений на основе CNN
■
173
Скачивание данных
и их предварительная обработка
Теперь запишем функцию load_data() для загрузки обучающих и тестовых данных из файлов формата pickle, как показано в следующем фрагменте
кода:
1. def load_data(file_location):
2.
x_train = None
3.
y_train = None
4.
5.
# Обучающие данные
6.
for i in [1, 2, 3, 4, 5]:
7.
db_dict = unpickle(file_location+"data_batch_"+str(i))
8.
data_points = len(db_dict[b'data'])
9.
data = db_dict[b'data']\
10.
.reshape((data_points, 3, 32, 32))\
11.
.transpose((0, 2, 3, 1))
12.
if x_train is None:
13.
x_train = data
14.
y_train = db_dict[b'labels']
15.
else:
16.
x_train = np.concatenate((x_train, data))
17.
y_train = np.concatenate((y_train, db_dict[b'labels']))
18.
19.
# Тестовые данные
20.
db_dict = unpickle(file_location+"test_batch")
21.
data_points = len(db_dict[b'data'])
22.
x_test = db_dict[b'data']\
23.
.reshape((data_points, 3, 32, 32))\
24.
.transpose((0, 2, 3, 1))
25.
y_test = np.array(db_dict[b'labels'])
26.
27.
return (x_train, y_train), (x_test, y_test)
174
■
Pythonic AI
Как показано выше, функция load_data() запускает цикл for для чтения обучающих данных. Пакетные файлы data_batch с префиксом от 1 до 5 десериализируются и загружаются с помощью функции unpickle. Десериализованный объект представляет собой словарь Python, в котором ключевые данные
содержат фактические графические данные (массив NumPy), а ключевые метки — целевые метки изображений. Как объясняется на домашней странице
CIFAR-10, каждая строка массива NumPy хранит цветное изображение размером 32×32 (RGB-изображение). Графические данные представлены в виде
вектора размером 3072, первые 1024 элемента которого содержат информацию о пикселях красного канала. Следующие 1024 элемента содержат значения пикселей зеленого канала, а последние 1024 элемента передают значения
синего канала. Чтобы изменить форму вектора из 3072 элементов, мы последовательно выбираем каждый из 1024 элементов и составляем матрицу размером 32×32. Таким образом, нам нужно составить три матрицы размером
32×32. После этого функция NumPy transpose() меняет порядок элементов
так, чтобы номер канала оказался на последнем месте, и каждое изображение
представляется в виде матрицы NumPy (ndarray) размера 28×28×3. Аналогичным образом мы извлекаем тестовые данные из pickle-файла под названием test_batch. В архиве содержится еще один pickle-файл под названием
batches.meta. Мы десериализируем его содержимое в переменную meta_
dict и получаем словарь. Ключ этого словаря под названием label_names содержит имена всех 10 классов для набора данных CIFAR-10, как показано на
следующей иллюстрации:
Иллюстрация 5.4. Имена label_names из мета-файла
Теперь загрузим обучающие и тестовые данные с помощью функции load_
data(). Загруженные данные нормализуются посредством деления значения
каждого пикселя на 255, как показано в следующем фрагменте кода:
Разработка приложений для классификации изображений на основе CNN
■
175
Иллюстрация 5.5. Загрузка и нормализация данных
Вывести на экран (визуализировать) любое изображение можно с помощью служебной функции Keras array_to_image(). На следующей иллюстрации показано, как вывести изображение с указанием значения метки из
y_train и названия метки из мета-словаря.
Иллюстрация 5.6. Визуализация данных
На официальной странице CIFAR-10, расположенной по адресу: https://www.
cs.toronto.edu/~kriz/cifar.html, приведено по десять случайных изображений из
каждых десяти целевых классов, как показано на следующей иллюстрации:
Иллюстрация 5.7. Образцы изображений из десяти классов
176
■
Pythonic AI
Теперь перейдем к созданию модели сверточной нейронной сети с помощью
TensorFlow 2.
Создание, обучение и оценка
CNN-моделей
Теперь определим архитектуру модели CNN и обучим ее на данных CIFAR-10.
Для ускорения обучения воспользуемся аппаратным ускорителем. Для обучения модели применим тензорный процессор (TPU).
Как говорилось в главе 4 «Проектирование CNN с помощью TensorFlow», для
этого нужно в меню «Среда выполнения» (Runime) выбрать пункт «Сменить
среду управления» (Change runtime option) и выбрать «Аппаратный ускоритель» (Hardware Accelerator) TPU. Имейте в виду, что если на этом шаге вы
смените среду выполнения, то вам придется выполнить загрузку и предварительную обработку данных заново. После этого нужно создать кластерный
резольвер TPU и задать стратегию TPU, как показано на следующей иллюстрации:
Иллюстрация 5.8. Стратегия TPU
Примечание. В бесплатной подписке Google Colab ускоритель TPU может быть временно недоступен, особенно в часы пик.
Разработка приложений для классификации изображений на основе CNN
■
177
После того как стратегия TPU задана, можно создавать фактическую модель
CNN. Для построения архитектуры модели определим функцию create_
model(), как показано в следующем блоке кода:
1. def create_model():
2.
inputs = tf.keras.layers.Input(shape=(32,32,3))
3.
x=tf.keras.layers.Conv2D(filters=32,kernel_
size=(3,3),padding='same') (inputs)
4.
x = tf.keras.layers.Activation("relu")(x)
5.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
6.
7.
x=tf.keras.layers.Conv2D(filters=32,kernel_
size=(3,3),padding='same') (x)
8.
x = tf.keras.layers.Activation("relu")(x)
9.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
10.
11.
x = tf.keras.layers.Conv2D(filters=64, kernel_size=(3, 3),
padding='same')(x)
12.
x = tf.keras.layers.Activation("relu")(x)
13.
x = tf.keras.layers.Conv2D(filters=64, kernel_size=(3, 3),
padding='same')(x)
14.
x = tf.keras.layers.Activation("relu")(x)
15.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
16.
17.
x = tf.keras.layers.Conv2D(filters=128, kernel_size=(3, 3),
padding='same')(x)
18.
x = tf.keras.layers.Activation("relu")(x)
19.
x = tf.keras.layers.Conv2D(filters=128, kernel_size=(3, 3),
padding='same')(x)
20.
x = tf.keras.layers.Activation("relu")(x)
21.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
22.
23.
x = tf.keras.layers.Flatten()(x)
24.
x = tf.keras.layers.Dense(128, activation='relu')(x)
25.
x = tf.keras.layers.Dense(128, activation='relu')(x)
26.
predictions = tf.keras.layers.Dense(10, activation='softmax')(x)
27.
28.
model = tf.keras.models.Model(inputs=inputs,
outputs=predictions)
29.
return model
178
■
Pythonic AI
В приведенном выше примере мы создали архитектуру с шестью сверточными слоями. Первые два сверточных слоя имеют по 32 фильтра, за ними следуют слои пулинга по максимальному значению. Третий и четвертый сверточные слои имеют по 64 фильтра, и за четвертым следует слой пулинга по
максимальному значению. Далее идут пятый и шестой сверточные слои, но
каждый из них имеет по 128 фильтров. У всех сверточных слоев одинаковый
параметр дополнения (padding), размер фильтра (ядра) — 3×3 — и функция
активации ReLu.
Под конец архитектура разворачивается (с преобразованием данных в векторы), и появляются два полносвязных слоя, каждый из которых имеет по 128
фильтров и функцию активации ReLu. Последний выходной слой состоит из
10 узлов, поскольку в наборе данных CIFAR-10 данные поделены на десять
классов. Функция активации Softmax для этого слоя позволяет получать вероятностные оценки для каждого класса. Теперь мы можем создать модель,
вызвав функцию create_model(), и скомпилировать ее в области действия
стратегии TPU, как показано в следующем блоке кода:
1. with strategy.scope():
2.
model = create_model()
3.
model.compile(
4.
optimizer=tf.keras.optimizers.Adam(0.001),126
5.
loss=tf.keras.losses.SparseCategoricalCrossentropy(),
6.
metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],7. )
Мы использовали оптимизатор Adam с коэффициентом обучения 0,001, в качестве функции потерь взята «разреженная категориальная перекрестная энтропия». Теперь можно перейти к процессу обучения с помощью функции
model.fit(), как показано на следующей иллюстрации:
Иллюстрация 5.9. Обучение модели CNN
Разработка приложений для классификации изображений на основе CNN
■
179
После завершения процесса обучения модели ее можно оценить на тестовом
наборе данных.
Иллюстрация 5.10. Оценка модели CNN
Как показано на иллюстрации 5.10, точность на обучающей выборке составила 79,78 %, а на тестовой — 69,17 %. Теперь можно построить графики точности (accuracy) и потерь (loss) обучающего и проверочного наборов. Рассмотрим следующий блок кода для построения графиков:
1. # График точности модели
2. plt.plot(history.history["sparse_categorical_accuracy"])
3. plt.plot(history.history["val_sparse_categorical_accuracy"])
4. plt.title("Model Accuracy Plot")
5. plt.xlabel("Epoch")
6. plt.ylabel("Accuracy")
7. plt.legend(["Train", "Validation"], loc="upper left")
8. plt.show()
9.
10. # График потерь модели
11. plt.plot(history.history["loss"])
12. plt.plot(history.history["val_loss"])
13. plt.title("Model Loss Plot")
14. plt.xlabel("Epoch")
15. plt.ylabel("Loss")
16. plt.legend(["Train", "Validation"], loc="upper left")
17. plt.show()
Получившиеся графики точности и потерь приведены на следующей иллюстрации:
180
■
Pythonic AI
Иллюстрация 5.11. Графики точности и потерь модели CNN
Как показано на иллюстрации 5.11, между значениями точности и потерь
для обучающего и проверочного наборов наблюдается значительный разрыв. Точность на тренировочной выборке достигла 90%, но на проверочной
составила около 60 %. Это явно свидетельствует о переобучении модели.
Нейросеть работает достаточно хорошо на обучающем наборе данных, но не
способна к обобщению и не может показывать аналогичные результаты на
тестовом наборе данных.
Разработка приложений для классификации изображений на основе CNN
■
181
Устранение переобучения
Применим некоторые из известных нам способов уменьшения переобученности модели. Начнем с метода дропаута (случайного отключения нейронов).
Дропаут
Добавим в архитектуру нашей модели отдельные дропаут-слои после слоев
пулинга и плотных слоев, как показано в следующем блоке кода:
1. def create_model():
2.
inputs = tf.keras.layers.Input(shape=(32,32,3))
3.
x = tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3),
padding='same')(inputs)
4.
x = tf.keras.layers.Activation("relu")(x)
5.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
6.
x = tf.keras.layers.Dropout(0.2)(x)
7.
8.
x = tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3),
padding='same')(x)
9.
x = tf.keras.layers.Activation("relu")(x)
10.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
11.
x = tf.keras.layers.Dropout(0.2)(x)
12.
13.
x = tf.keras.layers.Conv2D(filters=64, kernel_size=(3, 3),
padding='same')(x)
14.
x = tf.keras.layers.Activation("relu")(x)
15.
x = tf.keras.layers.Conv2D(filters=64, kernel_size=(3, 3),
padding='same')(x)
16.
x = tf.keras.layers.Activation("relu")(x)
17.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
18.
x = tf.keras.layers.Dropout(0.2)(x)
19.
20.
x = tf.keras.layers.Conv2D(filters=128, kernel_size=(3, 3),
padding='same')(x)
21.
x = tf.keras.layers.Activation("relu")(x)
182
■
Pythonic AI
22.
x = tf.keras.layers.Conv2D(filters=128, kernel_size=(3, 3),
padding='same')(x)
23.
x = tf.keras.layers.Activation("relu")(x)
24.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
25.
x = tf.keras.layers.Dropout(0.2)(x)
26.
27.
x = tf.keras.layers.Flatten()(x)
28.
x = tf.keras.layers.Dense(128, activation='relu')(x)
29.
x = tf.keras.layers.Dropout(0.2)(x)
30.
x = tf.keras.layers.Dense(128, activation='relu')(x)
31.
x = tf.keras.layers.Dropout(0.2)(x)
32.
predictions = tf.keras.layers.Dense(10, activation='softmax')(x)
33.
34.
model = tf.keras.models.Model(inputs=inputs, outputs=predictions)
35.
return model
Как показано выше, мы добавили дропаут-слои с коэффициентом 0,2. После
проектирования модели скомпилируем и обучим ее, как мы делали это раньше. По окончании процесса обучения оценим модель, как показано на следующей иллюстрации.
Иллюстрация 5.12. Оценка модели с дропаутом
На иллюстрации 5.12 показаны результаты оценки модели на обучающем
и тестовом наборах данных. Видно, что значения точности для обоих наборов данных не сильно отличаются друг от друга; точность при обучении составила 67,99%, а при тестировании — 65,5%.
Построим графики точности и потерь с помощью блока кода, использованного выше.
Разработка приложений для классификации изображений на основе CNN
■
183
Иллюстрация 5.13. Графики точности и потерь модели с дропаутом
Графики точности и потерь на иллюстрации 5.13 ясно показывают, что разрыв
между обучающим и проверочным наборами не очень заметен. По сравнению
с предыдущей архитектурой ситуация улучшилась, и можно утверждать, что
благодаря методу дропаута модель больше не подвержена переобучению.
184
■
Pythonic AI
Регуляризация
Можно также попробовать снизить эффект переобучения с помощью регуляризации, описанной в главе 4 «Проектирование CNN с помощью TensorFlow».
При построении модели с помощью слоев TensorFlow укажем регуляризаторы L1 и L2, добавив их к прочим параметрам плотных слоев в определении
функции create_model(), как это сделано в следующем блоке кода:
1. def create_model():
2.
inputs = tf.keras.layers.Input(shape=(32,32,3))
3.
x = tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3),
padding='same')(inputs)
4.
x = tf.keras.layers.Activation("relu")(x)
5.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
6.
7.
x = tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3),
padding='same')(x)
8.
x = tf.keras.layers.Activation("relu")(x)
9.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
10.
11.
x = tf.keras.layers.Conv2D(filters=64, kernel_size=(3, 3),
padding='same')(x)
12.
x = tf.keras.layers.Activation("relu")(x)
13.
x = tf.keras.layers.Conv2D(filters=64, kernel_size=(3, 3),
padding='same')(x)
14.
x = tf.keras.layers.Activation("relu")(x)
15.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
16.
17.
x = tf.keras.layers.Conv2D(filters=128, kernel_size=(3, 3),
padding='same')(x)
18.
x = tf.keras.layers.Activation("relu")(x)
19.
x = tf.keras.layers.Conv2D(filters=128, kernel_size=(3, 3),
padding='same')(x)
20.
x = tf.keras.layers.Activation("relu")(x)
21.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
22.
Разработка приложений для классификации изображений на основе CNN
■
185
23.
x = tf.keras.layers.Flatten()(x)
24.
x = tf.keras.layers.Dense(128, activation='relu', kernel_
regularizer=tf.keras.regularizers.l2(0.1))(x)
25.
x = tf.keras.layers.BatchNormalization()(x)
26.
x = tf.keras.layers.Dense(128, activation='relu', kernel_
regularizer=tf.keras.regularizers.l2(0.1))(x)
27.
x = tf.keras.layers.BatchNormalization()(x)
28.
predictions = tf.keras.layers.Dense(10, activation='softmax')(x)
29.
30.
model = tf.keras.models.Model(inputs=inputs,
outputs=predictions)
31.
return model
Выше показано, как в определении функции create_model() мы добавили
к плотным (Dense) слоям регуляризатор L2 с помощью параметра kernel_
regularizer с коэффициентом регуляризации 0,1.
Также после каждого плотного (Dense) слоя в модели мы добавили слой пакетной нормализации BatchNormalization. Пакетная нормализация ускоряет обучение и в какой-то мере способствует регуляризации.
Теперь, когда модель создана, можно скомпилировать и обучить ее на TPU,
как мы делали это раньше. Результаты выполнения оценки для обучающих
и тестовых данных показаны на следующей иллюстрации:
Иллюстрация 5.14. Оценка модели с регуляризацией
На иллюстрации видно, что точность на обучающем наборе составляет
71,53%, а на тестовом — 66,68%. Производительность модели для обучающих
и тестовых данных не сильно отличается.
Построим графики точности и потерь с помощью использованного ранее
блока кода:
186
■
Pythonic AI
Иллюстрация 5.15. Графики точности и потерь модели с регуляризацией
На графиках, показанных на иллюстрации 5.15, разрыв между обучающим
и проверочным наборами данных не очень заметен. Следовательно, благодаря регуляризации модель больше не склонна к переобучению.
Классификация изображений
с помощью предварительно
обученныхм оделей
Из главы 4 «Проектирование CNN с помощью TensorFlow» мы узнали, что библиотека TensorFlow предоставляет некоторые готовые архитектуры моделей
Разработка приложений для классификации изображений на основе CNN
■
187
CNN, называемые «предварительно обученными моделями». Предварительно обученные модели — это модели с заданными алгоритмами машинного
обучения, которые можно использовать в качестве отправной точки для решения различных задач, связанных с искусственным интеллектом, в частности классификации изображений. Эти модели уже обучены на больших наборах данных, благодаря чему они умеют распознавать важные признаки.
В библиотеке TensorFlow имеется несколько широко используемых предварительно обученных моделей классификации изображений, таких как VGGNet,
ResNet, Inception и т. д. Поскольку эти модели уже обучены на больших выборках изображений, таких как ImageNet, усвоенными ими «навыками» можно воспользоваться для классификации новых изображений.
Использование предварительно обученной модели для решения другой, но
смежной задачи, называется «переносом обучения». Преимущество этого метода заключается в том, что он позволяет значительно сократить объем данных и время для обучения модели, и весь процесс не нужно начинать с нуля,
что очень важно в условиях ограниченных данных или вычислительных ресурсов. С помощью уже усвоенных признаков предварительно обученную модель можно точнее настроить на меньший набор данных и при этом добиться
большей производительности при выполнении некоторых конкретных задач.
Далее мы рассмотрим, как пользоваться некоторыми из предварительно обученных моделей, доступных в модуле tf.keras.applications библиотеки
TensorFlow 2. Начнем с готовой модели VGG16 для нашего набора данных
CIFAR-10.
Предварительно обученная модель VGG16
Предварительно обученная модель VGG16 в TensorFlow 2 доступна в модуле
tf.keras.applications.vgg16. По умолчанию эта модель принимает входные данные размером 224×224. Но в наборе данных CIFAR-10 изображения
имеют размеры 32×32. Поэтому для начала необходимо выполнить операцию
под названием «апсемплинг» (или «повышение/увеличение дискретизации»,
от англ. upsampling).
Эту операцию можно рассматривать как действие, обратное к пулингу по
максимальному значению. В результате изображение увеличивается до заданного размера посредством повторения строк и столбцов изображения.
В TensorFlow 2 «апсемплинг» задается как отдельный слой. Для нашего набора данных мы воспользуемся слоем tf.keras.layers.UpSampling2D, для
которого доступны различные методы масштабирования (интерполяции)
188
■
Pythonic AI
изображений; по умолчанию используется метод «ближайшего соседа»
(nearest-neighbor). Этот метод позволит нам преобразовать маленькое входное изображение в большое.
В качестве базовой модели мы также воспользуемся моделью, которая включает в себя все слои VGG16, кроме трех верхних полносвязных слоев. Поскольку модель VGG16 предварительно обучена на наборе данных ImageNet,
три верхних полносвязных слоя в итоге дадут на выходе классы ImageNet, но
нам это не нужно. Мы воспользуемся полностью изученными признаками
архитектуры VGG16, но настроим полносвязные слои в соответствии с нашей задачей — классификацией изображений из набора данных CIFAR-10.
Также зададим параметр weights модели VGG16, загрузив тем самым список
весов всех слоев, полученных в результате предварительного обучения модели на наборе данных ImageNet. Рассмотрим следующий блок кода:
1. def create_model():
2.
inputs = tf.keras.layers.Input(shape=(32,32,3))
3.
resize = tf.keras.layers.UpSampling2D(size=(7,7))(inputs)
4.
5.
base_model_output = tf.keras.applications.vgg16.VGG16(
6.
include_top=False,
7.
input_shape=(224, 224, 3),
8.
weights='imagenet'
9.
)(resize)
10.
11.
x = tf.keras.layers.GlobalAveragePooling2D()(base_model_output)
12.
x = tf.keras.layers.Dense(1024, activation="relu")(x)
13.
x = tf.keras.layers.Dense(512, activation="relu")(x)
14.
pred = tf.keras.layers.Dense(10, activation='softmax')(x)
15.
16.
model = tf.keras.models.Model(inputs=inputs, outputs=pred)
17.
return model
Как показано выше, сначала в функции create_model()мы задали входной
слой для приема изображений размером 32×32×3. Это цветные изображения
из набора данных CIFAR-10, поэтому у них имеются 3 канала. Затем мы увеличили размер изображений в 7 раз. Модель VGG16 принимает изображения размером 224×224, а наш набор данных (CIFAR-10) имеет размеры 32×32.
Разработка приложений для классификации изображений на основе CNN
■
189
Слой UpSampling2D с коэффициентом апсэмплинга 7×7 увеличивает входные
изображения с размера 32×32 до размера (32×7)×(32×7), то есть 224×224.
Затем подаем измененные данные на вход базовой модели VGG16. Выход базовой модели мы подаем на слой GlobalAveragePooling2D.
Из главы 4 «Проектирование CNN с помощью TensorFlow» мы узнали, что
выходной сигнал любого сверточного слоя является трехмерным. Первые два
измерения — это высота и ширина матрицы после применения каждого фильтра, а третье — количество фильтров. Слой GlobalAveragePooling2D выполняет пулинг (дискретизацию) по среднему значению на каждом выходе каждого
фильтра, то есть выдает одно значение (среднее) от каждого фильтра. Выход
слоя GlobalAveragePooling2D равен количеству фильтров в последнем сверточным слое, или, точнее, имеет форму 1×1×f, где f — количество фильтров.
Чтобы разобраться подробнее, рассмотрим следующую иллюстрацию:
Иллюстрация 5.16. Характеристики модели VGG16
190
■
Pythonic AI
Как показано на иллюстрации 5.16, размер выходного тензора VGG16 равен
7×7×512, а слоя GlobalAveragePooling2D — 512 (во время обучения «вместо
None будет автоматически подставлен размер пакета»). Видно, что на выходе конечного слоя VGG16 имеются 512 фильтров, и каждый фильтр дает на
выходе матрицу, или «карту» 7×7. Слой GlobalAveragePooling2D берет среднее
значение для каждой матрицы размера 7×7 всех 512 фильтров. Таким образом, выходной сигнал слоя GlobalAveragePooling2D представляет собой тензор
размера 512. Таким образом, слой GlobalAveragePooling2D резко сокращает количество входных признаков с 7×7×512 = 25 088 до 512. Общее количество
обучаемых параметров, соответственно, также уменьшается.
Можно ли вместо слоя GlobalAveragePooling2D использовать слой flatten?
Да, можно, но, как показано в предыдущем примере, слой flatten не выполняет никакой понижающей дискретизации, а просто преобразует трехмерный
тензор предыдущего слоя в одномерный вектор. Если бы в нашей модели использовался слой flatten, то входная размерность первого плотного слоя осталась бы равной 25 088.
В чем здесь разница, кроме уменьшения размерности? Глобальное усреднение (average pooling) выводит из пространственной информации некое значение, более устойчивое по отношению к пространственным преобразованиям
входных данных. Кроме того, уменьшение числа обучаемых параметров снижает вероятность переобучения.
Теперь можно создать модель, вызвав функцию create_model(), и скомпилировать ее в рамках стратегии TPU, как показано на следующей иллюстрации:
Иллюстрация 5.17. Компиляция и обучение модели VGG16
Разработка приложений для классификации изображений на основе CNN
■
191
После обучения модели можно оценить ее производительность на проверочных и тестовых данных, как показано на следующей иллюстрации:
Иллюстрация 5.18. Оценка модели VGG16
На иллюстрации 5.18 видно, что точность модели для обучающих данных составляет 98,67%, а для тестовых — 93,16%. Это значительное улучшение по
сравнению с предыдущей моделью, где точность на тестовых данных составляла 66,68%. Построим график точности и потерь, полученных в процессе
обучения модели.
Иллюстрация 5.19. Графики точности и потерь модели VGG16
192
■
Pythonic AI
Как видно на иллюстрации 5.19, модель не сильно переобучена. Разница между точностью на обучающей выборке и на тестовой не очень велика.
Предварительно обученная
модель ResNet50
В главе 4 «Проектирование CNN с помощью TensorFlow» мы упомянули модель ResNet, в которой предусмотрены соединения в обход нескольких слоев,
помогающие решать такие проблемы нейронных сетей, как исчезающий градиент, переобучение и пр. Обученная модель ResNet50, так же как и рассмотренная нами выше модель VGG16, представлена в приложениях TensorFlow
для классификации изображений. Ниже показан блок кода функции create_
model(), создающей глубокую нейронную сеть на основе предварительно
обученной модели ResNet50 в качестве базовой.
1. def create_model():
2.
inputs = tf.keras.layers.Input(shape=(32,32,3))
3.
resize = tf.keras.layers.UpSampling2D(size=(7,7))(inputs)
4.
5.
base_model_output = tf.keras.applications.resnet.ResNet50(
6.
include_top=False,
7.
input_shape=(224, 224, 3),
8.
weights='imagenet'
9.
)(resize)
10.
11.
x = tf.keras.layers.GlobalAveragePooling2D()(base_model_output)
12.
x = tf.keras.layers.Dense(1024, activation="relu")(x)
13.
x = tf.keras.layers.Dense(512, activation="relu")(x)
14.
predictions = tf.keras.layers.Dense(10, activation='softmax')(x)
15.
16.
17.
model = tf.keras.models.Model(inputs=inputs, outputs=predictions)
return model
В данном случае при определении функции create_model() мы задали
слой UpSampling2D для масштабирования изображений CIFAR-10 с размера
Разработка приложений для классификации изображений на основе CNN
■
193
32×32 до размера 224×224. Кроме того, здесь использованы такие же слои
GlobalAveragePooling2D и Dense, как и в рассмотренной ранее модели на основе VGG16.
Теперь, когда определена архитектура модели, можно выполнить функцию
create_model() для создания модели и проверки ее характеристик. Обратите
внимание на то, что общее количество обучаемых параметров в модели на
основе ResNet50 больше, чем в модели на основе VGG16. Заметим также, что
для сети на основе ResNet50 входящие данные в слой глобального пулинга по
среднему значению имеют другие размеры. Глубина входящих данных в слой
GlobalAveragePooling2D модели VGG16 составляла 512, а для модели ResNet50
значение глубины равно 2048. Слой GlobalAveragePooling2D получает по одному среднему значению от каждого из 2048 фильтров. Характеристики модели
показаны на следующей иллюстрации:
Иллюстрация 5.20. Характеристики модели ResNet50
Теперь можно скомпилировать и обучить модель, как мы делали это ранее на примере модели VGG16. После обучения модели можно проверить
194
■
Pythonic AI
ее производительность на тестовых данных, как показано на следующей
иллюстрации:
Иллюстрация 5.21. Оценка модели ResNet50
Точность модели приближается к 95,35% для тестовых данных, что лучше,
чем у прежней модели на основе VGG16.
Примечание. Можно также поэкспериментировать с другими предварительно обученными моделями, доступными в модуле tf.keras.
applications, и найти модель с наилучшими показателями для нашего
конкретного случая (с использованием набора данных CIFAR-10).
Использование пользовательских
изображений для классификации
Итак, теперь у нас есть модель с удовлетворительной производительностью.
Мы обучили эту модель на наборе данных CIFAR-10, следовательно, она умеет различать имеющиеся в этом наборе десять целевых классов. Можно протестировать модель не только на данных CIFAR-10, но и на наших собственных изображениях. Загрузим картинку из Интернета и подадим его на вход
обученной модели, чтобы проверить, сможет ли она правильно классифицировать это изображение.
Наша модель может распознавать десять целевых классов — картинки, на которых представлены: самолет, автомобиль, птица, кошка, олень, собака, лягушка, лошадь, корабль и грузовик. Поэтому новое изображение должно относиться только к этим темам. Загрузим бесплатную стоковую фотографию
самолета с сайта pexels.com, как показано на следующей иллюстрации:
Разработка приложений для классификации изображений на основе CNN
■
195
Иллюстрация 5.22. Изображения самолетов на сайте pexels.com
При желании можно выбрать любое изображение, на котором представлен
объект, относящийся к одному из десяти целевых классов. Мы выбрали фото
пользователя Pixabay, расположенное на сайте по адресу: https://www.pexels.
com/photo/air-air-travel-airbus-aircraft-358319/. Сайт Pexels позволяет настраивать размер скачиваемого изображения. Поскольку наша модель нейросети
принимает только изображения размером 32×32, для загрузки с сайта зададим именно его, как показано на следующей иллюстрации:
Иллюстрация 5.23. Загрузка изображения с сайта
196
■
Pythonic AI
Переименуем файл изображения, полученного с сайта, в test.jpg. Теперь
нужно загрузить его в нашу лабораторию искусственного интеллекта, среду
Google Colab. Нажмите значок Файлы(Files) в левой части панели и выберите пункт «Загрузить в сессионное хранилище», как показано на следующей
иллюстрации:
Иллюстрация 5.24. Загрузка изображения в среду Colab
Итак, мы загрузили файл test.jpg в сессионное хранилище. Помните, что файлы остаются в нем до тех пор, пока не закончится «сессия», или сеанс работы.
По окончании сеанса файлы из этого хранилища удаляются.
Теперь прочитаем (и выведем на экран) это изображение в нашем блокноте
с помощью библиотеки Python Image Library или PIL. Это библиотека Python,
используемая для работы с файлами изображений. Соответствующий код
приведен на следующей иллюстрации:
Разработка приложений для классификации изображений на основе CNN
■
197
Иллюстрация 5.25. Считывание изображения и его предварительная обработка
Как показано на иллюстрации 5.25, сначала мы считали файл изображения test.jpg с помощью PIL и преобразовали его в массив NumPy. Далее
мы выполнили базовую нормализацию, разделив элементы массива на 255,
как делали это для изображений CIFAR-10. Размер тензора с данными изображения — 32×32×3. Картинка выводится на экран с помощью функции
imshow().
Преобразуем формат данных так, чтобы модель воспринимала изображение
как один экземпляр из целого пакета, хотя в нашем случае предсказание делается только для одного образца. Возьмем заданную ранее модель — например,
198
■
Pythonic AI
вторую, со слоями дропаута. Затем воспользуемся функцией predict() модели, предсказывающей класс изображения, как показано в следующем фрагменте кода:
1. im = im.reshape(1,32,32,3)
2. pred = model.predict(im)
Результат предсказания записывается в переменную pred. Значение этой переменной можно проверить, как показано на следующей иллюстрации:
Иллюстрация 5.26. Предсказанные значения
Как показано на иллюстрации 5.26, в массиве NumPy имеются десять значений. Эти десять значений — вероятности того, что изображение принадлежит к каждому из десяти возможных классов. Из этого списка нужно выбрать наибольшее значение, которое и будет считаться ответом нейросети
для данного объекта. Для этого выберем индекс максимального значения
с помощью функции NumPy argmax() и подставим его в качестве аргумента
в словарь meta_dict, чтобы получить название класса.
Иллюстрация 5.27. Вывод предсказанного класса
Как показано на иллюстрации 5.27, индекс предсказанного класса равен 0,
а его название в виде метки — это ‘airplane’, то есть «самолет». Получается, что
модель правильно классифицировала наше пользовательское изображение.
Разработка приложений для классификации изображений на основе CNN
■
199
Примечание. Теперь, когда мы узнали, как применять модели CNN к
набору данных CIFAR-10, можно применить эти же модели к набору
данных CIFAR-100. Набор данных CIFAR-100 такой же, как и CIFAR-10,
только в нем 100 классов по 600 изображений в каждом. Загрузить его
можно на том же официальном сайте, что и набор данных CIFAR-10;
там же содержатся более подробные сведения о нем.
Заключение
Из этой главы мы получили знания и навыки, которые помогут нам эффективно разрабатывать приложения для классификации изображений на основе сверточных нейронных сетей (CNN). Классификация изображений применяется во множестве практических задач, так что знания о том, как создавать
модели классификаторов изображений и работать с ними, очень важны. Мы
изучили архитектуру CNN, построили собственные модели с нуля, а также
научились использовать предварительно обученные модели из библиотеки
TensorFlow. Распознавание образов — быстро развивающееся направление
искусственного интеллекта, поэтому так важно иметь представление о последних разработках в этой области.
В следующей главе мы познакомимся с еще одной важной практической задачей в сфере «компьютерного зрения», которая называется «обнаружением
объектов».
Основные выводы
• Классификация изображений — это важное направление в сфере
«компьютерного зрения», в которое входит сопоставление изображений с заранее заданными классами или метками. Оно лежит в основе
различных приложений.
• Собирать, загружать и предварительно обрабатывать данные для моделей сверточных нейронных сетей (CNN) можно с помощью библиотеки TensorFlow в блокноте Google Colab. Для этого данные нужно
предварительно разделить на обучающие и тестовые наборы и нормализовать.
• Библиотека TensorFlow позволяет создавать пользовательские модели
CNN, обучать их на обработанных данных и оценивать результаты.
200
■
Pythonic AI
Обучение моделей с помощью аппаратного ускорителя TPU, предоставляемого Google Colab, делает процесс обучения более быстрым.
• Использование предварительно обученных моделей, таких как VGG16
и ResNet50, значительно упрощает процесс классификации изображений. Эти предварительно обученные модели доступны в библиотеке
TensorFlow. Их можно использовать в качестве экстракторов признаков, на основе которых удобно строить свои модели классификации
изображений.
• Помимо работы с готовыми наборами данных, можно настроить обученный классификатор для распознавания пользовательских изображений.
Ссылки
• Набор данных CIFAR-10: https://www.cs.toronto.edu/~kriz/cifar.html
• Предварительно обученные модели TensorFlow: https://www.tensorflow.
org/api_docs/python/tf/keras/applications/
• Глобальная субдискретизация («пулинг») по среднему значению: Lin,
Min, Qiang Chen, Shuicheng Yan. “Network in network. arXiv 2013”. arXiv
preprint arXiv:1312.4400 (2013).
Глава 6
Обучение
и развертывание
моделей обнаружения
объектов
Введение
Искусственный интеллект (ИИ) произвел революцию в различных областях, и одно из наиболее эффективных его применений — это так называемое
«обнаружение объектов».
Основная задача ИИ при обнаружении объектов заключается в том, чтобы
идентифицировать объекты на изображении или видео и предоставить информацию об их местоположении. Она включает в себя как классификацию,
когда система присваивает метки классов различным объектам, так и локализацию, то есть определение точных координат ограничивающей рамки для
каждого объекта на изображении.
Очевидно, что ИИ играет важную роль в обнаружении объектов во многих
областях, требующих выполнения такой задачи. Так, например, в сфере разработки автономных транспортных средств ИИ позволяет распознавать пешеходов, автомобили и дорожные знаки и определять их местонахождение,
что повышает безопасность движения и улучшает навигацию автономного
транспортного средства. В системах видеонаблюдения методы обнаружения объектов используются для выявления подозрительных действий или
лиц в режиме реального времени. В медицине системы обнаружения объектов позволяют выявлять аномалии на рентгеновских снимках и результатах
202
■
Pythonic AI
магнитно-резонансной и компьютерной томографий, что помогает в диагностике и лечении.
По мере развития ИИ-технологий обнаружения объектов, их возможности
для инноваций и преобразований в различных отраслях промышленности
становятся поистине безграничными. В этой главе мы приведем увлекательный обзор этой сферы и постараемся раскрыть секреты создания мощных
систем обнаружения объектов.
Структура
В этой главе мы рассмотрим следующие темы:
• Что такое обнаружение объектов.
• Основные принципы обнаружения объектов.
• Модели обнаружения объектов.
Цели
Пришло время погрузиться в увлекательный мир обнаружения объектов на
изображениях и видео. Мы узнаем о фундаментальных концепциях, методах и инструментах, связанных с обучением моделей обнаружения объектов.
Мы познакомимся с процессом сбора и обработки необходимых обучающих
данных для моделей обнаружения объектов и рассмотрим популярные и широко используемые в этой задаче архитектуры глубокого обучения, такие как
R-CNN, SSD и YOLO, и постараемся при этом разобрать, как они работают
«изнутри». Кроме того, мы получим представление о функциях потерь, оптимизации и методах оценки, а также проверим работу модели на новых для
нее данных.
Что такое обнаружение объектов
Обнаружение объектов — это фундаментальная задача искусственного интеллекта, которая заключается в том, чтобы идентифицировать объекты
и определить их местоположение на изображении или видео. По своей сути
это попытка воспроизвести с помощью компьютерных систем человеческую
способность распознавать и понимать визуальный мир. Искусственный интеллект, воспринимающий мир с помощью технических устройств и интерпретирующий полученные данные, может выполнять широкий спектр задач
Обучение и развертывание моделей обнаружения объектов
■
203
в таких областях, как разработка автономных транспортных средств, использование систем наблюдения, здравоохранение, производство, сельское
хозяйство, робототехника, управление движением, ликвидация последствий
стихийных бедствий и т. д.
Процесс обнаружения объектов включает в себя два основных компонента:
локализацию и классификацию. Локализация — это определение точного
местоположения и размеров объекта на изображении, как правило, посредством построения вокруг него ограничивающей рамки. Локализация относится к задачам типа «регрессия», и ее цель — найти действительные значения
координат ограничивающей рамки на изображении. Классификация же подразумевает присвоение объекту некоей метки или сопоставление его с некоей категорией — например, с «автомобилями», «людьми» или «кошками». На
следующей иллюстрации показаны примеры ограничивающих рамок с названиями соответствующих им классов:
Иллюстрация 6.1. Ограничивающие рамки
В основе концепции обнаружения объектов лежит представление о том, что
объекты обладают уникальными визуальными характеристиками, или признаками, отличающими их от фона и от других объектов. Такими признаками
могут быть форма, цвет, текстура и контекстная информация. На основании
этих признаков человек без труда опознает объекты даже в сложных и загроможденных деталями сценах. Задача обнаружения объектов методами
204
■
Pythonic AI
искусственного интеллекта заключается в том, чтобы воспроизвести такую
врожденную способность человека посредством разработки алгоритмов,
которые могут обучаться и выделять отличительные признаки в изображениях.
В моделях обнаружения объектов на основе сверточных нейронных сетей
нижние слои сети обучаются выделять такие низкоуровневые признаки,
как края и углы, тогда как верхние слои обучаются распознавать более высокоуровневые семантические признаки, такие как форма и составные части объекта. Пропуская изображение через сверточные слои, сеть создает
набор карт признаков, кодирующих различные уровни информации. Эти
карты признаков затем используются для выделения областей на изображении («гипотез»), потенциально содержащих нужные объекты. Реализованные в моделях глубокого обучения алгоритмы обнаружения объектов
нацелены на выделение и использование отличительных признаков разных
объектов для их точной локализации и классификации в сложных визуальных сценах.
Основные принципы
обнаружения объектов
Задача обнаружения объекта с помощью искусственного интеллекта
подразумевает определение местоположения объекта на заданном изображении и его классификацию. В отличие от базовых задач регрессии
и классификации, при обнаружении объектов модель возвращает массив
значений, обычно включающий в себя координаты ограничивающей рамки вместе с соответствующими метками классов и степенью уверенности
(confidence score).
Для каждого обнаруженного объекта модель возвращает координаты ограничивающей рамки, окружающей объект. Для представления ограничивающих
рамок существуют различные форматы, но каждый из них предполагает, что
найденных значений будет достаточно, чтобы нарисовать на искомом изображении рамку вокруг нужного объекта. Согласно одному из принятых форматов ограничивающая рамка задается в виде значений (x, y, w, h), где x и y —
это координаты верхнего левого угла рамки, а w и h — это соответственно
ширина и высота рамки. В другом варианте представления (x, y) обозначают
координаты средней точки рамки. Координаты также могут быть представлены набором из четырех значений: (x_min, y_min, x_max, y_max). Координаты (x_min, y_min) обозначают левый верхний угол ограничивающей рамки
Обучение и развертывание моделей обнаружения объектов
■
205
и обычно отсчитываются от левого верхнего угла изображения или кадра.
Координаты (x_max, y_max) представляют собой правый нижний угол ограничительного поля. При необходимости ширину ограничительной рамки
можно рассчитать как (x_max – x_min), а высоту — как (y_max – y_min). Эти
координаты позволяют точно локализовать объекты на изображении, что
дает возможность последующего анализа, отслеживания или дальнейшей обработки на основе обнаруженных объектов. Пример координат показан на
следующей иллюстрации:
Иллюстрация 6.2. Координаты ограничивающей рамки
Вместе с координатами ограничивающей рамки модель присваивает каждому
обнаруженному объекту метку класса. Эти метки соответствуют категории
или типу объекта, присутствующего в ограничивающей рамке — например,
«автомобиль», «человек», «кошка» и т. д.
Вместе с информацией о границах рамки ИИ выдает значение показателя
«оценка уверенности» (confidence score), который также называют «степенью уверенности/обнаружения» или вероятностью. Оценка уверенности
соответствует вероятности, с которой модель может утверждать, что обнаруженный объект принадлежит к классу, указанному меткой. Более высокие
показатели уверенности обычно соответствуют более надежным и точным
предсказаниям. Вся эта информация позволяет точно локализовать, классифицировать и впоследствии проанализировать объекты в сцене.
206
■
Pythonic AI
Функции потерь в задачах
по обнаружению объектов
Функция потерь, которая используется при обнаружении объектов, обычно
учитывает несколько компонентов, влияющих как на точность локализации, так и на эффективность классификации. Функция потерь локализации
(ошибка/потери локализации) — это степень расхождения между предсказанными и истинными координатами ограничивающей рамки. Этот показатель оценивает точность предсказанного положения и размера ограничивающей рамки. Для этой цели могут использоваться функции потерь регрессии,
такие как среднеквадратичная ошибка (MSE, Mean Square Error).
Функция потерь классификации (потери классификации) — это оценка точности предсказаний меток класса для каждой ограничивающей рамки. Функция потерь классификации измеряет расхождение между предсказанными
вероятностями класса и истинным классом. Обычно для этого используются кросс-энтропийная функция потерь или функция softmax, вычисляющие отрицательный логарифм правдоподобия предсказанных вероятностей
классов.
Суммарная (общая) функция потерь, используемая при обнаружении объектов, часто представляет собой линейную комбинацию или взвешенную
сумму этих отдельных функций потерь. Относительные веса, присвоенные
каждому компоненту, могут корректироваться в зависимости от конкретных
требований задачи или используемого набора данных.
Метрикио ценки
Для оценки производительности моделей обнаружения объектов используется набор метрик, измеряющих точность идентификации и локализации
объектов на изображениях или видео. Ниже описаны несколько из часто используемых метрик оценки обнаружения.
IoU (Intersection over Union, «пересечение над объединением»). IoU — это
важнейшая метрика оценки алгоритмов обнаружения объектов, измеряющая точность и степень перекрытия между предсказанными ограничивающими рамками и аннотациями истинных данных (истинной разметкой). IoU
представляет собой количественную оценку того, насколько хорошо модель
обнаружения объектов локализует объекты на изображении.
Обучение и развертывание моделей обнаружения объектов
■
207
IoU (этот показатель еще часто называют «индексом Жаккара» или «коэффициентом сходства Жаккара») определяет степень совпадения или «пересечения» двух ограничивающих рамок: предсказанной моделью обнаружения
объектов и реальной, отмеченной человеком в процессе анализа изображения. IoU — это число от 0 до 1; более высокое значение говорит о большей
степени совпадения и точности. На следующей иллюстрации показаны ограничивающая рамка, предсказанная нейросетью, и реальная ограничивающая
рамка. Нужно вычислить степень их совпадения.
Иллюстрация 6.3. Две рамки, истинная и предсказанная
Расчет IoU заключается в вычислении простого соотношения между пересечением и объединением двух пространственных множеств. Разделим его на
следующие шаги.
1. Область пересечения. Чтобы вычислить площадь пересечения, для начала
нужно найти координаты левого верхнего и правого нижнего углов этого пересечения. Далее вычисляются ширина и высота этой области.
На следующей иллюстрации выделена область пересечения (она заштрихована).
208
■
Pythonic AI
Иллюстрация 6.4. Пересечение ограничивающих рамок
2. Область объединения. Область объединения охватывает всю область,
покрытую как предсказанной, так и истинной рамками. Она рассчитывается
посредством суммирования площадей отдельных рамок и вычитания площади пересечения, чтобы та не учитывалась дважды.
На следующей иллюстрации показана область объединения (она заштрихована).
Иллюстрация 6.5. Объединение ограничивающих рамок
Обучение и развертывание моделей обнаружения объектов
■
209
3. Расчет IoU. Чтобы получить значение IoU, нужно разделить площадь пересечения на площадь объединения, то есть воспользоваться следующей формулой:
IoU = Площадь пересечения / Площадь объединения
Значение IoU служит мерой эффективности моделей обнаружения объектов.
Оно дает представление о точности локализации и степени совпадения предсказанной и истинной рамок обнаружения. При этом необходимо учитывать
следующие соображения:
a. Порог обнаружения. Обычно на практике устанавливают некое пороговое
значение IoU, исходя из которого предсказанную рамку считают положительным или отрицательным результатом. Это пороговое значение может варьироваться в зависимости от конкретного приложения и требований задачи.
b. Оценка и производительность. IoU используется в качестве метрики для
оценки производительности моделей обнаружения объектов. Высокие значения IoU указывают на высокую точность обнаружения, а низкие значения
говорят о плохой локализации.
c. Установка пороговых значений IoU. Выбор порога IoU зависит от требований приложения. Высокий порог соответствует более строгим критериям
обнаружения, что потенциально сокращает количество ложных срабатываний, но увеличивает вероятность пропуска нужных объектов. И наоборот,
низкое пороговое значение может повысить количество опознаваемых объектов, но также повышает и количество ложных результатов.
IoU — ценный инструмент для контроля за точностью и надежностью систем
обнаружения объектов, способствующий повышению эффективности технологий компьютерного зрения и основанных на них приложений.
Средняя точность
Средняя точность (AP, Average Precision) — это показатель средней точности обнаружения объектов при различных пороговых значениях IoU. Для того
чтобы вычислить среднюю точность, нужно сначала вычислить площадь под
кривой точности и полноты. Эта метрика позволяет оценить эффективность
модели при различных уровнях IoU.
210
■
Pythonic AI
Усредненная точность
Усредненная точность, или средняя точность по всем категориям (mAP,
mean Average Precision) — это среднее значение AP для разных категорий
объектов. Данная метрика широко используется для получения представления об общей производительности модели обнаружения объектов нескольких классов.
Точность и полнота
Точность (precision) — это отношение истинно-положительных случаев
к общему количеству предсказанных положительных результатов. Полнота
(чувствительность, recall) — это отношение истинно-положительных предсказанных результатов к общему количеству истинно-положительных случаев. График точности-полноты позволяет наглядно представить баланс между
ними.
F1-мера
F1-мера — это гармоническое среднее между точностью (precision) и полнотой (recall). Она позволяет в одном показателе учитывать как ложноположительные, так ложноотрицательные случаи, что делает ее полезной для сравнения моделей.
Кривая точности-полноты
Кривая точности-полноты (PR-кривая) — это график, отображающий зависимость между полнотой и точностью модели при различных порогах уверенности. Она помогает выбрать подходящий порог, обеспечивающий баланс
между точностью и полнотой (ложными срабатываниями и пропущенными
объектами) исходя из требований приложения.
ROC-кривая
ROC-кривая, или кривая операционных характеристик (Receiver Operating
Characteristic), чаще используется в задачах бинарной классификации и отображает соотношение между долей истинно-положительных результатов (true
Обучение и развертывание моделей обнаружения объектов
■
211
positive rate) и долей ложно-положительных (false positive rate) при различных порогах уверенности. Площадь под ROC-кривой (AUC-ROC) говорит об
общей эффективности модели.
Примечание. Важно выбирать метрики оценки, соответствующие конкретным целям приложения для обнаружения объектов.
Подавление немаксимумов (NMS)
Алгоритмы обнаружения объектов часто генерируют несколько прогнозов
ограничивающей рамки для одного и того же объекта, что приводит к избыточности и снижению точности. Для устранения избыточных обнаружений
и сохранения только самых точных и уверенных используется метод постобработки под названием «подавление немаксимумов» (Non-maximum
suppression), который чаще обозначается как NMS. Он позволяет проследить
за тем, чтобы каждый объект в сцене был представлен только одной ограничивающей рамкой с наивысшей степенью уверенности. На этом шаге решающую роль играет IoU. Алгоритм отбирает ограничивающие рамки только
с высоким показателем IoU, то есть самые уверенные и точные предсказания.
Рассмотрим пошаговую процедуру подавления немаксимумов:
1. Вход. NMS принимает набор ограничивающих рамок, сгенерированных
алгоритмом обнаружения объектов. Для каждой рамки указаны координаты
(x, y) для левого верхнего угла и (x', y') для правого нижнего угла, а также
оценка уверенности, указывающая на вероятность наличия объекта.
2. Сортировка по оценке уверенности. Ограничивающие рамки сортируются в порядке убывания их оценки уверенности. Так рамки с наивысшей оценкой уверенности рассматриваются в первую очередь.
3. Выбор рамки с наивысшей оценкой. Рамка с наивысшей оценкой уверенности выбирается как случай обнаружения и добавляется в окончательный
список предсказаний. Эта рамка соответствует наиболее вероятному местоположению объекта.
4. IoU. Для измерения пересечения выбранной рамки и оставшихся ограничивающих рамок используется метрика IoU.
212
■
Pythonic AI
5. Установка порога и подавление. Для определения уровня пересечения,
необходимого для подавления, устанавливается заранее определенный порог.
Рамки с показателем IoU выше этого порога считаются избыточными и подавляются, так как они скорее всего относятся к одному и тому же объекту.
Из всех пересекающихся рамок сохраняется только рамка с наивысшей степенью уверенности
6. Итерации. Процесс повторяется итеративно для оставшихся необработанных ограничивающих рамок. Всякий раз при выборе рамки и добавлении
ее к окончательным предсказаниям, пересекающиеся рамки с оценкой выше
порога подавляются, то есть отбрасываются.
7. Окончательные предсказания. После обработки всех рамок оставшиеся
неотброшенными рамки считаются окончательным предсказанием. Эти ограничивающие рамки представляют собой точные и неизбыточные результаты
обнаружения объектов в сцене, как показано на следующей иллюстрации:
Иллюстрация 6.6. До и после подавления немаксимумов
Подавление немаксимумов при обнаружении объектов дает несколько преимуществ:
• NMS устраняет избыточные предсказания рамок и гарантирует, что
каждый объект будет представлен лишь одним результатом с наивысшей оценкой уверенности. Таким образом NMS повышает точность
локализации и эффективность классификации;
Обучение и развертывание моделей обнаружения объектов
■
213
• NMS удаляет рамки, которые могли возникнуть из-за шума или пересечения областей обнаружения, и тем самым помогает снизить количество ложноположительных результатов;
• отсев лишних ограничивающих рамок снижает вычислительные затраты для последующих этапов обработки. NMS помогает оптимизировать процесс обнаружения объектов, устраняя ненужные вычисления
и ускоряя дальнейший анализ;
• NMS помогает оптимизировать сценарии обнаружения объектов нескольких классов. Этот метод выполняет подавление независимо для
каждого класса, предоставляя точные и непересекающиеся прогнозы
для различных категорий объектов.
Якорные рамки
Якорные рамки (anchor boxes) — одна из ключевых концепций, внесшая
значительный вклад в успех современных моделей обнаружения объектов.
Якорные рамки, также называемые «опорными», «предварительно размеченными», или «фиксированными», определяют заранее заданные ограничивающие рамки разных размеров, соотношений сторон и расположений. Они
выступают в качестве опорных шаблонов, помогающих моделям обнаружения объектов локализировать и классифицировать объекты на изображении.
Эти опорные рамки служат отправными точками для модели, которая далее
занимается тем, что уточняет локализацию ограничивающих рамок и классы
объектов.
Объекты на изображениях бывают разных размеров и форм, и якорные рамки предлагают механизм решения этой проблемы. Они позволяют моделям
обнаружения объектов обучаться и обобщать концепцию формы и размера
объекта, то есть обнаруживать объекты в разных масштабах.
Якорные рамки генерируются как набор прямоугольников с разными размерами и соотношениями сторон. Обычно они размещаются в заранее определенных местах сетки на карте признаков, полученной с помощью сверточной
нейронной сети. В процессе обучения якорные рамки сопоставляются с истинными объектами на основе их пересечений или показателей IoU. Каждой
якорной рамке, имеющей наибольший показатель IoU относительно истинного объекта, присваивается положительная метка, а каждой якорной рамке
с показателем IoU ниже определенного порога присваивается отрицательная
метка. Этот процесс помогает установить связь между якорными рамками
и реальными объектами.
214
■
Pythonic AI
Якорные рамки служат опорными точками для предсказания точных координат ограничивающей рамки. В процессе обучения модель учится оценивать
значения смещения (разницы между якорной рамкой и истинной рамкой).
Затем эти значения смещения используются для корректировки якорной
рамки и ее уточнения, чтобы она точнее соответствовала истинным границам объекта.
Благодаря использованию якорных рамок с разными размерами и соотношениями сторон модели обнаружения объектов могут работать с разными
видами объектов на изображении. Несколько наборов якорных рамок, часто
связанных с различными картами признаков, позволяют модели одновременно обнаруживать объекты в разных масштабах и с разным соотношением
сторон.
На следующей иллюстрации показано, как якорные рамки используются
для обнаружения двух объектов разного размера и с разным соотношением
сторон:
Иллюстрация 6.7. Якорные рамки
Метод якорных рамок позволяет обнаруживать несколько объектов на одном
изображении. Модель может отыскать множество ограничивающих рамок,
связанных с разными якорными рамками, и фиксировать присутствие нескольких объектов одновременно.
Обучение и развертывание моделей обнаружения объектов
■
215
Сеть пирамиды признаков
Сеть пирамиды признаков (FPN, Feature Pyramid Network) была первоначально предложена в контексте системы обнаружения объектов с помощью
ускоренной региональной сверточной нейронной сети (RCNN, Region
Convolutional Neuron Network). Ее приняли и приспособили для различных
других моделей. Основная цель FPN — справляться с проблемой обнаружения
объектов разного масштаба. Нередко приходится опознавать на изображении
как маленькие, так и большие объекты. FPN решает эту задачу посредством
построения пирамиды признаков, представляющей собой многоуровневую
(многомасштабную) репрезентацию входного изображения. Она объединяет
признаки с разных уровней CNN с помощью горизонтальных связей и операций апсемплинга (повышения дискретизации). Пирамида признаков позволяет модели улавливать как высокоуровневую семантическую информацию,
так и низкоуровневые пространственные детали. Таким образом она обеспечивает эффективное обнаружение объектов в разных масштабах для лучшей
локализации и распознавания объектов по всему изображению.
Модели обнаружения объектов
Модели обнаружения объектов — это модели компьютерного зрения, предназначенные для идентификации и определения местоположения объектов
на изображениях или видеокадрах. Эти модели обучаются распознавать
и классифицировать несколько объектов, представляющих интерес, а также
предоставляют информацию о положении объекта с помощью ограничивающей его рамки. Модели обнаружения объектов позволяют автоматизировать
процесс, обрабатывать и анализировать большие объемы визуальных данных
и вычленять на их основе конкретные объекты изображения. В этом разделе
мы познакомимся с принципами работы различных моделей обнаружения
объектов.
Модель SSD
Среди различных моделей обнаружения объектов SSD (Single Shot Multibox
Detector) («Детекция объектов за один проход с помощью нескольких рамок») завоевала популярность благодаря своей эффективности и точности.
В этом разделе мы постараемся подробно рассмотреть принципы ее работы
и понять, как она революционным образом изменила методы обнаружения
объектов.
216
■
Pythonic AI
SSD — это модель обнаружения объектов в реальном времени, которую
в 2016 году представил Вэй Лю1 с коллегами. Она была разработана для того,
чтобы преодолеть ограничения традиционных моделей обнаружения объектов с помощью более быстрого и точного алгоритма обнаружения. SSD
расставляет ограничивающие рамки из набора стандартных рамок с различными соотношениями сторон и масштабами. В процессе предсказания модель генерирует оценки присутствия каждой категории объектов в каждой
якорной рамке, после чего корректирует рамки, чтобы они точнее соответствовали форме объектов. Модель также объединяет прогнозы по нескольким картам признаков с разным разрешением, чтобы естественным образом
обрабатывать объекты разных размеров. Карты признаков — это признаки
изображения, созданные сверточными слоями.
В модели SSD присутствуют ряды сверточных слоев, которые анализируют
входное изображение в разных масштабах и выделяют из них признаки. Архитектура модели SSD показана на следующей схеме, взятой из оригинальной
статьи:
Иллюстрация 6.8. Архитектура SSD
Разберем основные этапы рабочего процесса:
1. Базовая сверточная сеть. Первая часть SSD — это базовая сверточная
сеть. В оригинальной статье исследователи использовали сеть VGG-16,
но можно использовать и другие сети, например ResNet. Эта сеть обрабатывает входное изображение и постепенно уменьшает его пространственные размеры, выделяя признаки в разных масштабах.
2. Многомасштабные карты признаков. После базовой сети в модели
SSD добавляются сверточные слои для обнаружения объектов раз1
Wei Liu, Dragomir Anguelov, Dumitru Erhan, Christian Szegedy, Scott Reed, Cheng-Yang Fu,
Alexander C. Berg. “SSD: Single Shot Multibox Detector”. In Computer Vision–ECCV 2016:
14th European Conference, Amsterdam, The Netherlands, October 11–14, 2016, Статья
в сборнике Proceedings, Part I 14, pp. 21–37. Springer International Publishing, 2016.
Обучение и развертывание моделей обнаружения объектов
■
217
личных размеров. Для каждой карты признаков определяется набор
базовых ограничивающих рамок (рамок «по умолчанию»). Сами карты признаков выводятся из нескольких слоев с разным разрешением.
Рамки по умолчанию выступают в качестве эталонных шаблонов с различным соотношением сторон и масштабом. По мере прохождения
изображения через сеть каждый добавленный слой признаков с помощью набора сверточных фильтров выдает фиксированный набор
прогнозов.
3. Прогнозирование оценок и смещений классов. На следующем этапе предсказываются оценки класса и смещения для каждой рамки по
умолчанию. Модель рассчитывается для каждой рамки вероятности
классов, которые указывают на наличие или отсутствие объектов, принадлежащих к разным категориям. Кроме того, она предсказывает смещения (корректировки) для уточнения рамки по умолчанию, чтобы
она точнее соответствовала истинной ограничивающей рамке.
4. Подавление немаксимумов (NMS). Модель выполняет операцию подавления немаксимумов, удаляя избыточные или пересекающиеся ограничивающие рамки.
5. Окончательные прогнозы. Оставшиеся ограничивающие рамки с соответствующими им вероятностями классов и смещениями считаются
окончательными предсказаниями. Эти предсказания содержат информацию об обнаруженных объектах и их положении на входном изображении.
Модель SSD обладает рядом преимуществ, которые делают ее популярным
средством обнаружения объектов:
• благодаря своему однопроходному (single-shot) принципу работы, SSD
позволяет обнаруживать объекты в режиме реального времени, что
исключает необходимость использования сложных региональных сетей прогнозирования;
• благодаря включению карт признаков в нескольких масштабах SSD может точнее обнаруживать объекты разных размеров и в разном масштабе;
• архитектура SSD повышает эффективность вывода (инференса), благодаря чему модель находит применение в устройствах с ограниченными
ресурсами, таких как встраиваемые системы и мобильные устройства.
Поняв внутреннее устройство SSD, мы сможем использовать ее возможности
для создания инновационных решений в области компьютерного зрения.
218
■
Pythonic AI
Использование моделей SSD
В отличие от сверточных нейронных сетей, в библиотеке TensorFlow отсутствуют какие бы то ни было Keras-слои для моделей обнаружения объектов.
Вместо этого можно воспользоваться библиотекой TensorFlow Hub, которая
содержит ряд моделей обнаружения объектов TensorFlow 2, обученных на
наборе данных COCO 2017. TensorFlow Hub — это библиотека и платформа,
разработанная компанией Google. Она представляет собой хранилище предварительно обученных моделей или модулей машинного обучения, в том
числе предварительно обученных моделей обнаружения объектов, которые
можно повторно использовать для выполнения различных задач. Эти достаточно мощные модели можно легко интегрировать в свои пользовательские
приложения без необходимости обучать их с нуля.
Воспользуемся предварительно обученными моделями из TensorFlow Hub,
чтобы создать приложения для обнаружения объектов. Список моделей обнаружения объектов можно найти на следующем сайте:
https://tfhub.dev/tensorflow/collections/object_detection/
Модели обнаружения объектов в библиотеке TensorFlow Hub разработаны с использованием TensorFlow Object Detection API1. TensorFlow Object
Detection API — это фреймворк с открытым исходным кодом, который представляет собой надстройку к TensorFlow, упрощает построение, обучение
и развертывание моделей обнаружения объектов. Однако API обнаружения
объектов TensorFlow больше не поддерживается новыми версиями внешних
библиотек и не совместим с ними. Поэтому мы не будем им пользоваться,
а возьмем стандартный API TensorFlow для построения выводов на основе
предварительно обученных моделей.
Предварительно обученные модели в TensorFlow Hub представлены в формате SavedModels. Это стандартный для TensorFlow 2 формат сохранения и загрузки моделей машинного обучения, не зависящий от языка и платформы,
а также позволяющий легко обмениваться моделями и разворачивать их
в различных средах. Он определяет как архитектуру (граф) модели, так и ее
обученные веса, а также может включать любые дополнительные характеристики или связанные с моделью метаданные.
1
“Speed/accuracy trade-offs for modern convolutional object detectors”. Huang J, Rathod V,
Sun C, Zhu M, Korattikara A, Fathi A, Fischer I, Wojna Z, Song Y, Guadarrama S, Murphy K,
CVPR 2017.
Обучение и развертывание моделей обнаружения объектов
■
219
При сохранении модели TensorFlow 2 с помощью формата SavedModel создается каталог, содержащий несколько файлов и подкаталогов. Файл saved_
model.pb содержит сериализованный граф вычислений TensorFlow (архитектура модели) в двоичном формате. Для сохранения модели TensorFlow
в формате SavedModel используется функция tf.saved_model.save(). И наоборот, для загрузки модели в формате SavedModel используется функция
tf.saved_model.load().
Воспользуемся бесплатной фотографией с сайта pexels.com, доступной по
следующему адресу: https://www.pexels.com/photo/man-on-white-horse-nextto-dog-on-grassy-field-162520/.
Как мы узнали из главы 5 «Разработка приложений для классификации изображений на основе CNN», сайт pexels.com позволяет загружать изображения
нужного пользователю размера. Выбранная нами модель была обучена на
изображениях, масштабированных до размера 640×640, поэтому при загрузке изображения в качестве пользовательского разрешения нужно указать
именно это значение. Это изображение показано ниже:
Иллюстрация 6.9. Входное изображение
Как мы уже говорили, предварительно обученные модели обнаружения
объектов содержатся в библиотеке TensorFlow Hub (tensorflow_hub). Устанавливать ее специально не нужно, потому что в Google Colab она уже
220
■
Pythonic AI
предустановлена. Для начала импортируем необходимые библиотеки, как
показано в следующем фрагменте кода:
1. import numpy as np
2. import cv2
3. from google.colab.patches import cv2_imshow
4. import tensorflow as tf
5. import tensorflow_hub as hub
Изображение, загруженное с сайта pexels.com, нужно загрузить в хранилище сессии Google Colab с помощью значка Файлы (Files), расположенного на
крайней левой панели блокнота Colab. Переименуем изображение в horse.
jpg. Для чтения и предварительной обработки изображения для подачи
в нейросеть мы будем использовать библиотеку Python OpenCV.
OpenCV, или Open-Source Computer Vision, — это библиотека компьютерного зрения и машинного обучения с открытым исходным кодом, изначально
разработанная компанией Intel в 1999 году. Сейчас библиотеку поддерживает
сообщество OpenCV. Она написана на языке C++ и может использоваться
вместе с Python. OpenCV предлагает различные функции и алгоритмы для
обработки изображений и видео, обнаружения и распознавания объектов,
а также машинного обучения. Что касается обработки изображений и видео,
библиотека OpenCV поддерживает чтение и запись изображений, обрезку
и изменение размера изображений, применение фильтров и преобразований
и другие полезные функции.
Как показано в приведенном выше фрагменте кода, мы импортировали библиотеку OpenCV под именем cv2. В Colab она имеется по умолчанию, и отдельно устанавливать ее не нужно. Для вывода изображения на экран cv2
предоставляет функцию imshow(). Однако в Colab эта функция отключена,
поскольку она приводит к сбоям в работе сессий Jupyter. В качестве замены
Colab предоставляет функцию cv2_imshow() из библиотеки google.colab.
patches.
Воспользуемся библиотекой OpenCV из Python для чтения изображения как
показано в следующем фрагменте кода:
1. img = cv2.imread("horse.jpg")
2. img = cv2.resize(img, dsize=(640, 640))
3. cv2_imshow(img)
Обучение и развертывание моделей обнаружения объектов
■
221
Функция imread() считывает изображение и возвращает массив NumPy.
В строке #2 мы изменили размер изображения до (640, 640). Мы загрузили
изображение с сайта pexels.com с уже заданным таким же пользовательским
размером (640, 640), но лучше проверить, чтобы убедиться в том, что мы передаем в модель изображение правильного формата. Третья строка выводит
изображение в блокноте Colab.
Поскольку переменная img — это массив NumPy, форму этого массива можно
проверить с помощью приведенной ниже команды, которая должна вернуть
кортеж (640, 640, 3):
1. img.shape
Воспользуемся сначала предварительно обученной моделью SSD под названием SSD Mobilenet V1 Object detection model with FPN feature extractor. Эта
модель обучена на наборе данных COCO 20171 от Microsoft с тренировочными изображениями, масштабированными до 640×640. Модель принимает на
вход тензор формы (1, высота, ширина, 3). Значит, нам нужно изменить нынешнюю форму изображения (640, 640, 3), добавив дополнительное измерение в начале, как показано в следующем фрагменте кода:
1. img = np.expand_dims(img, 0)
2. img.shape
Атрибут shape должен вернуть значение (1, 640, 640, 3).
Теперь нужно задать модель для обнаружения объектов. Можно вставить
предварительно обученный и сохраненный модуль TensorFlow Hub как слой
Keras с помощью API hub. Список моделей для обнаружения объектов можно
найти по следующему адресу: https://tfhub.dev/tensorflow/collections/object_
detection/.
Этот список показан на следующем изображении:
1
Tsung-Yi Lin, Michael Maire, Serge Belongie, James Hays, Pietro Perona, Deva Ramanan,
Piotr Dollár, C. Lawrence Zitnick. “Microsoft coco: Common objects in context”. Статья
в сборнике Computer Vision–ECCV 2014: 13th European Conference, Zurich, Switzerland,
September 6–12, 2014, Proceedings, Part V 13, стр. 740–755. Springer International Publishing,
2014.
222
■
Pythonic AI
Иллюстрация 6.10. Список моделей в TensorFlow Hub
При щелчке на вариант SSD MobileNet V1 FPN 640×640 браузер переходит на
страницу по следующему адресу: https://tfhub.dev/tensorflow/ssd_mobilenet_
v1/fpn_640×640/1.
Скопировать его можно из адресной строки браузера или нажав на опцию
Copy URL, как показано ниже:
Иллюстрация 6.11. Модель в TensorFlow Hub
Этот адрес нужен для загрузки SavedModel с помощью TensorFlow Hub, как
показано в приведенном ниже фрагменте кода:
1. hub_url = "https://tfhub.dev/tensorflow/ssd_mobilenet_v2/fpnlite_640x640/1"
2. object_detector = hub.KerasLayer(hub_url)
Обучение и развертывание моделей обнаружения объектов
■
223
Так загружается формат SavedModel в качестве слоя Keras. Теперь можно использовать этот слой Keras в нашей модели:
Напишем функцию create_model() для создания модели с помощью функционального API TensorFlow согласно приведенному ниже фрагменту кода:
1. def create_model():
2.
img_input = tf.keras.Input(shape=(None, None, 3), dtype=tf.uint8)
3.
outputs = object_detector(img_input)
4.
model = tf.keras.Model(inputs=img_input, outputs=outputs)
5.
return model
6.
7. model = create_model()
Здесь для создания финальной модели мы использовали входные данные
и слой object_detector на основе SSD. В седьмой строке мы вызвали функцию create_model() и создали модель.
Далее передадим обработанное изображение в качестве входных данных модели для обнаружения объектов согласно следующему фрагменту кода:
1. detector_output = model.predict(img)
Выход модели detector_output представляет собой словарь. Рассмотрим
ключи этого словаря:
Иллюстрация 6.12. Ключи в выходе
224
■
Pythonic AI
Как показано на иллюстрации выше, результат работы модели содержит восемь описанных ниже ключей:
• detection_multiclass_scores. Содержит распределение оценок классов для рамок обнаружения на изображении. В наборе данных Microsoft
COCO 2017 имеется 91 класс.
• raw_detection_scores. Содержит логиты оценок классов для необработанных рамок обнаружения.
• detection_classes. Содержит индекс класса обнаружения из файла
меток. Мы загрузим этот файл меток и извлечем имена 91 класса.
• detection_scores. Содержит оценки обнаружения для всех обнаруженных ограничивающих рамок.
• detection_boxes. Содержит координаты ограничивающих рамок
в следующем порядке: [ymin, xmin, ymax, xmax].
• raw_detection_boxes. Содержит декодированные рамки обнаружения
без подавления немаксимумов.
• detection_anchor_indices. Содержит индексы якорей обнаружений
после NMS.
• num_detections. Содержит количество обнаружений.
Для получения итоговых ограничивающих рамок нам понадобятся
detection_boxes, detection_scores и detection_classes.
Файл, содержащий подробную информацию о метках набора данных
COCO 2017 доступен в репозитории GitHub для Object Detection API библиотеки TensorFlow. Расположен он по следующему адресу: https://github.
com/tensorflow/models/blob/master/research/object_detection/data/
mscoco_ complete_label_map.pbtxt
Скачаем этот файл с помощью команды wget, как показано в следующем
фрагменте кода:
1. !wget -qq https://raw.githubusercontent.com/tensorflow/models/
master/research/object_detection/data/mscoco_complete_label_map.
pbtxt
Для загрузки файла мы использовали команду Linux wget. Обратите внимание, что по указанному адресу находится необработанная версия файла.
Эту ссылку можно найти на GitHub, нажав на опцию "raw". Также обратите
Обучение и развертывание моделей обнаружения объектов
■
225
внимание, что перед командой wget стоит восклицательный знак, поскольку
это команда Linux, а не код Python.
После загрузки файла карты меток в среду Colab его содержимое можно
просмотреть, дважды щелкнув по нему. Рассмотрим следующую иллюстрацию:
Иллюстрация 6.13. Файл карты меток
Как показано на этой иллюстрации, карта меток представляет собой текстовый файл, содержащий названия классов набора данных COCO 2017. Нам не
нужен идентификатор или название элемента. Нас интересует только отображаемое имя: display_name. Создадим функцию get_labels(), которая
226
■
Pythonic AI
будет читать этот файл и извлекать значения, связанные с display_name. Эта
функция задается следующим блоком кода:
1. def get_labels(file):
2.
labels = list()
3.
with open(file, "r") as file:
4.
for line in file:
5.
if "display_name" in line:
6.
7.
labels.append(line.split("\")[1])
return labels
8.
9. LABELS = get_labels("mscoco_complete_label_map.pbtxt")
Этот код прочитывает файл строка за строкой. Каждая строка проходит проверку на наличие в ней "display_name". Если эта подстрока обнаруживается,
строка делится относительно разделителя в виде кавычек («). Так мы в общей
сложности получим три элемента, и первый элемент (после нулевого) будет
содержать фактическое имя класса. В строке номер 6 приведенного выше кода этот первый элемент извлекается из результата разбиения и добавляется
в список под названием labels. После прочтения всех строк файла функция
возвращает список labels. В строке номер 9 мы вызываем функцию get_
labels(), указывая в качестве аргумента имя файла с метками и сохраняя
полученный результат в переменной LABELS. Теперь LABELS — это список
из 91 класса набора данных COCO 2017.
В самом конце нарисуем обнаруженные ограничивающие рамки на заданном
входном изображении. Для хорошей визуализации вместо одного цвета лучше назначить разные цвета для ограничивающих рамок разных обнаруженных классов. Составим список цветов для 91 класса. Значение цвета — это не
что иное, как список из трех целых чисел RGB со значением от 0 до 255. Код
приведен ниже:
1. BB_COLORS = np.random.randint(0, 255, size=(len(LABELS), 3))
Команда len (LABELS) возвращает количество классов, то есть 91. Мы сгенерировали список из 91 значения, где каждая строка соответствует метке,
а каждый столбец представляет собой RGB-значения для цвета этой метки.
Значения RGB генерируются случайным образом в пределах от 0 до 255. Эти
случайно сгенерированные цвета будут использоваться для визуального
Обучение и развертывание моделей обнаружения объектов
■
227
различения ограничивающих рамок и подписей к ним, относящихся к различным обнаруженным объектам.
Теперь можно написать функцию draw_bounding_boxes(), которая считывает результат работы модели и рисует ограничивающие рамки на входном
изображении. Код ее показан ниже:
1. def draw_bounding_boxes(img, output, colors, labels):
2.
bounding_boxes = output["detection_boxes"][0].shape[0]
3.
(H, W) = img.shape[1], img.shape[2]
4.
for i in range(bounding_boxes):
5.
confidence = output["detection_scores"][0][i]
6.
if confidence > 0.5:
7.
ymin,xmin,ymax,xmax = output["detection_boxes"][0][i]
8.
class_id = int(output["detection_classes"][0][i])
9.
10.
color = [int(c) for c in colors[class_id]]
11.
cv2.rectangle(img[0], (round(xmin*W),
round(ymin*H)),(round(xmax*W), round(ymax*H)), color, 2)
12.
13.
text = "{0}: {1:.4f}".format(labels[class_id], confidence)
14.
cv2.putText(img[0],text,(round(xmin*W),
round(ymin*H) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
color, 2)
15. return img
Как показано в приведенном выше блоке кода, заданная функция draw_
bounding_boxes() принимает четыре объекта: входное изображение, выход
модели обнаружения объектов, список значений цветов RGB, соответствующих различным классам объектов, и список меток, соответствующих классам
объектов. В строке номер 2 определяется количество ограничивающих рамок в выходных данных из числа элементов в первом измерении detection_
boxes. Строка номер 3 извлекает высоту (H) и ширину (W) изображения.
Затем код выполняет итерации по каждой ограничивающей рамке и извлекает оценку уверенности из значений detection_scores. Если уверенность
выше порогового значения (в данном случае 0,5), то рамка рассматривается далее. Строка номер 7 извлекает координаты ограничивающей рамки из
значений detection_boxes. Строка номер 8 извлекает идентификатор класса
228
■
Pythonic AI
обнаруженного объекта из значений detection_classes. На основе идентификатора класса определяется соответствующий цвет из списка цветов.
Строка номер 11 рисует прямоугольник на изображении с помощью функции OpenCV rectangle(). Координаты прямоугольника масштабируются
в соответствии с размерами изображения (H и W), а его цвет определяется
переменной color. Толщина линий прямоугольника задана числом 2.
В строке номер 13 создается текстовая строка, которая состоит из метки объекта и оценки уверенности, отформатированной для отображения до четырех знаков после запятой. Строка номер 14 добавляет к изображению текстовую метку с помощью функции OpenCV putText(). Эта метка располагается
чуть выше левого верхнего угла ограничивающей рамки.
Теперь можно вызвать эту функцию и вывести результат ее выполнения на
экран фрагментом кода, показанным ниже:
1. output_img = draw_bounding_boxes(img,detector_output, BB_COLORS, LABELS)
2. cv2_imshow(output_img[0])
На экран будет выведено следующее изображение:
Иллюстрация 6.14. Вывод, сгенерированный моделью SSD
Обучение и развертывание моделей обнаружения объектов
■
229
Как показано на иллюстрации выше, модель правильно определила человека
и лошадь с уверенностью 64,39% и 69,51% соответственно. Однако модель не
обнаружила собаку.
Примечание. Хотя модель SSD обучалась на изображениях, масштабированных до 640×640, не обязательно задавать ей изображения точно
такого же размера. Она может работать с изображениями разной длины. В нашем примере можно было пропустить функцию cv2.resize()
и оставить форму изображения такой, какая она есть.
Теперь, получив результат от модели, можно посмотреть, сколько времени ей
требуется для выдачи предсказания по заданному изображению. Для этого
до и после функции predict() модели добавим команды из модуля time, как
показано на следующей иллюстрации:
Иллюстрация 6.15. Время работы (инференса) модели SSD на основе MobileNets
Как показано выше, модели потребовалось 0,68 секунды, чтобы сделать
вывод. В этой модели SSD используется архитектура MobileNets — легких
сверточных нейронных сетей, специально разработанных для мобильных
и встраиваемых приложений компьютерного зрения. Время вывода оказалось совсем небольшим. Попробуем теперь использовать более сложную
SSD-модель с 152-слойной ResNet, доступную по следующему адресу: https://
tfhub.dev/tensorflow/retinanet/resnet152_v1_fpn_640x640/1
Рассмотрим следующий код:
1. hub_url = "https://tfhub.dev/tensorflow/retinanet/resnet152_v1_
fpn_640x640/1"
2. object_detector = hub.KerasLayer(hub_url)
3.
4. model = create_model()
230
■
Pythonic AI
Здесь мы создали модель по заданной гиперссылке. Как и ранее, можно считывать изображение, изменять и увеличивать его размеры. Проверим время
вывода этой модели, как показано на следующей иллюстрации:
Иллюстрация 6.16. Время работы (инференса) модели SSD на основе ResNet
Как показано выше, время инференса у этой модели значительно больше, чем
у модели SSD MobileNets.
Примечание. Для инференса (вывода предсказаний для новых данных)
использовался только центральный процессор, так как для этого блокнота Colab не был подключен аппаратный ускоритель. При использовании GPU время вычислений значительно сокращается, но модель
SSD на основе MobileNets все равно работает быстрее, чем ее аналог на
базе ResNet.
Региональные сверточные
нейронные сети
Региональные сверточные нейронные сети (R-CNN, Regional-based Convolutional Neural Network) — это двухэтапная система обнаружения объектов, сочетающая в себе возможности CNN и алгоритмов выделения регионов
с целью достижения точной локализации и классификации объектов. Ключевая идея R-CNN заключается в том, чтобы сначала выделить потенциальные
области интереса на изображении, а затем применить CNN к каждому предложенному региону для классификации объектов1. Рассмотрим отдельные
шаги этого процесса:
1
Ross Girshick, Jeff Donahue, Trevor Darrell, Jitendra Malik. “Rich feature hierarchies for
accurate object detection and semantic segmentation”. Статья в сборнике Proceedings of the
IEEE conference on computer vision and pattern recognition, стр. 580–587. 2014.
Обучение и развертывание моделей обнаружения объектов
■
231
1. Выделение регионов. На первом этапе R-CNN на изображении выделяются некоторые области. Обычно это делается с помощью селективного поиска — алгоритма, определяющего потенциальные места
расположения объектов на основе низкоуровневых характеристик
изображения, таких как цвет, текстура и интенсивность.
2. Извлечение признаков. Далее из каждого предложенного региона извлекается 4096-мерный вектор признаков. CNN получает карты признаков, полученные от областей, и пропускает их через ряд сверточных, пулинговых и полносвязных слоев.
3. Регрессия ограничивающих рамок. R-CNN осуществляет регрессию
ограничивающих рамок для уточнения локализации предложенных
регионов.
Концепция R-CNN позволила внести ряд усовершенствований в методику
обнаружения объектов, чтобы повысить точность локализации и добавить
возможность работы с несколькими классами объектов. Однако она имеет
и свои ограничения.
R-CNN требуют больших вычислительных затрат из-за двухэтапного конвейера и необходимости обрабатывать каждый предложенный регион по
отдельности, что делает этот вид сетей более медленным по сравнению с другими моделями обнаружения объектов, а кроме того ограничивает его применимость в реальном времени для некоторых сценариев.
Модель R-CNN проложила путь для последующих достижений в области обнаружения объектов. Ее ограничения побудили программистов к разработке
более быстрых и эффективных моделей, таких как Fast R-CNN, Faster R-CNN
и Mask R-CNN.
В этих моделях появились общие функции извлечения признаков, отдельные
сети выделения регионов и возможности сегментации на уровне пикселей,
что позволило устранить недостатки R-CNN.
Использование моделей R-CNN
Создадим для примера модель R-CNN из предварительно обученных моделей, доступных в библиотеке TensorFlow Hub. Воспользуемся моделью FasterRCNN со 101 слоем ResNet, доступной по следующему адресу: https://tfhub.
dev/tensorflow/faster_rcnn/resnet101_v1_640x640/1
Подключим ее с помощью следующего кода:
232
■
Pythonic AI
1. hub_url = "https://tfhub.dev/tensorflow/faster_rcnn/resnet101_v1_640x640/1"
object_detector = hub.KerasLayer(hub_url)
3.
4. model = create_model()
Запустим распознавание по изображению и посмотрим время его выполнения, как показано на следующей иллюстрации:
Иллюстрация 6.17. Время работы (инференса) модели RNCC
На основе результатов работы модели отобразим ограничивающие рамки
с помощью следующего фрагмента кода:
1. output_img = draw_bounding_boxes(img, detector_output, BB_COLORS, LABELS)
2. cv2_imshow(output_img[0])
Получилась следующая картина:
Иллюстрация 6.18. Вывод, сгенерированный моделью RNCC
Обучение и развертывание моделей обнаружения объектов
■
233
Как видно по изображению, данная модель идентифицировала также и собаку. Кроме того, предсказанные показатели уверенности довольно высоки.
Модели обнаружения объектов часто приходится разворачивать в удаленных местах на мобильных устройствах. Во многих сценариях подключение
к Интернету может полностью отсутствовать или быть нестабильным. В таких случаях строить модель обнаружения объектов по ссылкам на библиотеку TensorFlow Hub бывает нецелесообразным. Перед вводом устройства
в эксплуатацию можно предварительно загрузить модель в виде файла tar.gz,
а затем создать систему обнаружения объектов на основе этой загруженной
модели. Так при каждом перезапуске устройства или ИИ-приложения не возникнет необходимости пользоваться ссылкой, а можно будет просто загружать модель из дискового хранилища устройства.
Файлы моделей в формате tar.gz доступны в «Зоопарке моделей обнаружения
TensorFlow 2» по следующему адресу: https://github.com/tensorflow/models/
blob/master/research/object_detection/g3doc/tf2_detection_zoo.md.
Их список показан на следующей иллюстрации:
Иллюстрация 6.19. Архивы моделей SavedModel
На этой странице нужно щелкнуть правой кнопкой мыши по нужной модели
и скопировать ссылку. Скопированная ссылка, или «URL-адрес», заканчивается расширением tar.gz. Воспользуемся ей для загрузки файла tar.gz с помощью команды wget, как показано в следующем фрагменте кода:
234
■
Pythonic AI
1. !wget -qq http://download.tensorflow.org/models/object_detection/
tf2/20200711/faster_rcnn_resnet101_v1_640x640_coco17_tpu-8.tar.gz
Далее распакуем скачанный архивный файл, чтобы извлечь из него нужные
каталоги и файлы:
1. !tar -zxvf faster_rcnn_resnet101_v1_640x640_coco17_tpu-8.tar.gz
При этом в хранилище сессий Colab будет создан каталог с именем модели,
как показано на следующей иллюстрации:
Иллюстрация 6.20. Каталог сохраненной модели SavedModel
Внутри созданного каталога имеется еще один каталог под именем saved_
model. В нем содержится файл saved_model.pb — файл буферов протокола
(protobuf), используемый для стандартной настройки конфигурации и параметров моделей TensorFlow.
Если в этом каталоге присутствует файл saved_model.pb, то сохраненная
TensorFlow модель готова к загрузке. Для этого нужно воспользоваться функцией saved_model.load() и указать путь к каталогу saved_model, как показано в следующем фрагменте кода:
Обучение и развертывание моделей обнаружения объектов
■
235
1. saved_model = tf.saved_model.load("/content/faster_rcnn_resnet101_
v1_640x640_coco17_tpu-8/saved_model")
2. object_detector = hub.KerasLayer(saved_model)
3. модель = create_model()
Выше мы загрузили сохраненную модель и создали слой Keras. Затем, как
и в предыдущих примерах, мы вызвали функцию create_model(). Теперь
можно использовать эту модель для прогнозирования, после чего, как и ранее, вызвать функцию draw_bounding_boxes().
МодельY OLO
Среди различных моделей обнаружения объектов огромную популярность
приобрела модель You Only Look Once (YOLO)1. YOLO использует принципиально новый подход, рассматривая вопрос обнаружения объектов
как единственную задачу регрессии. Ниже перечислены характеристики
YOLO:
• в своей основе YOLO представляет собой реализацию сверточной нейронной сети. YOLO делит входное изображение на сетку ячеек. Каждая
ячейка отвечает за предсказание наличия и определение характеристик объектов, расположенных в ее границах. Например, если объект
охватывает несколько ячеек, то за его обнаружение и локализацию будет прежде всего отвечать ячейка, на которую приходится центр этого
объекта;
• чтобы учесть объекты различных форм и размеров, YOLO использует
якорные рамки. В процессе обучения модель учится корректировать
и уточнять расположение этих якорных рамок, чтобы они лучше соответствовали реальным рамкам объектов, присутствующих в обучающих данных;
• для каждой ячейки сетки YOLO предсказывает несколько ограничивающих рамок (обычно 2–3) с соответствующими вероятностями классов.
Каждая ограничивающая рамка представлена координатами ее центра
относительно ячейки, своей шириной и высотой, а также оценкой уверенности, отражающей вероятность того, что в этой рамке находится
1
Joseph Redmon, Santosh Divvala, Ross Girshick, Ali Farhadi. “You only look once: Unifi ed,
real-time object detection”. Статья в сборнике Proceedings of the IEEE conference on
computer vision and pattern recognition, стр. 779–788. 2016.
236
■
Pythonic AI
объект. Кроме того, модель присваивает каждой ограничивающей рамке показатель вероятности класса, указывающий на вероятность того,
что данный объект принадлежит к определенному классу;
• для отсева дубликатов и повышения точности в YOLO используется
подавление немаксимумов.
Рассмотрим схему архитектуры YOLO, позаимствованную из оригинальной
статьи про YOLO:
Иллюстрация 6.21. Архитектура YOLO
Благодаря однопроходному процессу YOLO работает невероятно быстро,
позволяя обнаруживать объекты в режиме реального времени. YOLO учитывает контекстную информацию, рассматривая все изображение в целом, что
обеспечивает лучшее понимание взаимосвязей между объектами. За прошедшие годы было разработано несколько версий YOLO, каждая из которых
привнесла новые достижения и улучшения в область компьютерного зрения.
Первая версия YOLOv1 стала пионером в концепции одноэтапного обнаружения объектов. Но, несмотря на производительность в реальном времени,
YOLOv1 столкнулась с проблемами точного обнаружения мелких объектов
и точной локализации.
Чтобы преодолеть ограничения предшественника, в версии YOLOv21 (также
известной под названиемYOLO9000) разработчики добавили якорные рамки. В версии YOLO9000 использовалась архитектура Darknet-19 — глубокая
нейронная сеть, состоящая из 19 сверточных слоев и 5 слоев пулинга по мак1
Joseph Redmon, Ali Farhadi. “YOLO9000: better, faster, stronger”. Статья в Proceedings of
the IEEE conference on computer vision and pattern recognition, стр. 7263–7271. 2017.
Обучение и развертывание моделей обнаружения объектов
■
237
симуму, обученная на крупном наборе данных. В версии YOLOv31 в качестве основной использовалась сеть Darknet-53 — глубокая нейронная сеть с 53
сверточными слоями и остаточными связями. В YOLOv3 реализовано многомасштабное обнаружение и достигнута современная точность при сохранении производительности в реальном времени. Мы не будем рассматривать
все версии YOLO. На данный момент компания Ultralytics выпустила версию
YOLOv8. YOLOv8 поддерживает множество задач компьютерного зрения,
таких как обнаружение объектов, сегментация, оценка положения, отслеживание и классификация. Подробную информацию обо всех версиях можно
найти в подробном обзоре2 YOLO.
Развитие моделей YOLO — свидетельство силы инноваций и постоянного
совершенствования, благодаря которым технологии компьютерного зрения
движутся вперед и позволяют создавать замечательные приложения в различных областях.
Использование моделей YOLO
В отличие от моделей SSD и R-CNN, библиотека TensorFlow Hub не содержит
предварительно обученной модели YOLO. Мы будем использовать модуль
глубоких нейронных сетей (DNN, Deep Neural Networks) из библиотеки
OpenCV. Модуль DNN из OpenCV позволяет использовать предварительно
обученные модели глубокого обучения, что облегчает их интеграцию в приложения компьютерного зрения.
Сам изобретатель YOLO Джозеф Редмон поддерживает фреймворк Darknet
Neural Network Framework с реализациями YOLO. Darknet — это нейросетевой фреймворк с открытым исходным кодом, написанный на C и CUDA, доступный по адресу:
https://github.com/pjreddie/darknet.
Для создания модели мы воспользуемся файлами конфигурации и весов
предварительно обученной модели YOLO3 из фреймворка Darknet и загрузим их в модуль DNN OpenCV.
Сначала загрузим файлы конфигурации и весов с помощью команды wget,
как показано в следующем фрагменте кода:
1
2
Joseph Redmon, “YOLOv3: An Incremental Improvement/Joseph Redmon, Ali FarhadiUniversity of Washington”. (2018).
Juan Terven, Diana Cordova-Esparza. “A comprehensive review of YOLO: From YOLOv1 to
YOLOv8 and beyond”. arXiv preprint arXiv:2304.00501 (2023).
238
■
Pythonic AI
1. !wget -qq https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/
yolov3.cfg
2. !wget -qq https://pjreddie.com/media/files/yolov3.weights
В наборе данных COCO определен 91 класс, но в самих данных содержатся
экземпляры только 80 классов. Если модели TensorFlow Hub, которыми мы
пользовались ранее, были обучены с учетом всех названий 91 класса в качестве меток, модели Darknet обучены с учетом только 80 реальных классов в качестве целевых меток. Загрузим файл, содержащий 80 названий классов, из
репозитория Darknet на GitHub. Для этого выполним следующие действия:
1. !wget -qq https://raw.githubusercontent.com/pjreddie/darknet/
master/data/coco.names
После этого файл coco.names будет доступен в хранилище сессий блокнота
Colab. Можно дважды щелкнуть по этому файлу и посмотреть его содержимое, как показано на следующей иллюстрации:
Иллюстрация 6.22. Файл coco.names
Обучение и развертывание моделей обнаружения объектов
■
239
Как видно, каждая строка файла coco.names содержит одно из имен классов.
Сначала прочитаем изображение с помощью библиотеки OpenCV, как мы делали это ранее:
1. img = cv2.imread("horse.jpg")
2. img = cv2.resize(img, dsize=(640, 640))
3. cv2_imshow(img)
Теперь откроем файл coco.names и создадим список LABELS:
1. LABELS = list()
2. with open("/content/coco.names", "r") as file:
3.
for line in file:
4.
LABELS.append(line.strip())
Как показано в приведенном выше коде, мы читаем файл построчно и добавляем содержимое строки в список под названием LABELS. При проверке
длины LABELS она должна быть равна 80.
Теперь можно прочитать модель сети, хранящуюся в файлах моделей Darknet,
с помощью функции readNetFromDarknet(), как показано в следующем фрагменте кода:
1. model = cv2.dnn.readNetFromDarknet('yolov3.cfg', 'yolov3.weights')
Модель принимает четырехмерный массив NumPy размерами — количество
изображений, количество каналов, ширина и высота. Чтобы передать входное изображение в модель Darknet, нужно предварительно его обработать,
как показано в следующем фрагменте кода:
1. prep_img = img/255.0
2. prep_img = prep_img.transpose(2, 0, 1)
3. prep_img = np.expand_dims(prep_img, 0)
В строке номер 1 указанного выше кода выполняется нормализация изображения посредством деления значений пикселей на 255. Чтобы не менять
исходное изображение (img), результат сохраняется в новой переменной
240
■
Pythonic AI
prep_img. Оригинальное изображение понадобится нам позже для отрисовки ограничительных рамок. В строке номер 2 используется функция NumPy
transpose() для изменения ориентации осей в изображении. По умолчанию
форма изображения имеет вид (высота, ширина, количество каналов). Нам
нужна информация о каналах в начале, чтобы форма приняла вид (количество каналов, высота, ширина). В функцию transpose() мы передаем новую
ориентацию, где сначала идет вторая ось, а затем нулевая и первая. С помощью строки номер 3 добавляем дополнительное измерение в начале, как мы
делали ранее. Формы исходного и предварительно обработанного изображения показаны на следующей иллюстрации:
Иллюстрация 6.23. Формы изображений
Теперь передадим входное изображение модели DNN из OpenCV с помощью
функции setInput(). Воспользуемся функцией forward() модели DNN из
OpenCV, чтобы запустить прямой проход для вычисления выходных данных.
Функции forward() нужно передать имена выходных слоев. Их можно получить с помощью функции getUnconnectedOutLayersNames(), как показано
в следующем фрагменте кода:
1. output_layers = model.getUnconnectedOutLayersNames()
2. model.setInput(prep_img)
3. detector_output = model.forward(output_layers)
Переменную output_layers можно вывести на экран и увидеть имена, как
показано на следующей иллюстрации:
Иллюстрация 6.24. Выходные слои
Модель YOLO имеет три выхода: один — для крупных объектов, один —
для средних и один — для мелких. Таким образом, результат работы модели
Обучение и развертывание моделей обнаружения объектов
■
241
(detector_outputs) представляет собой кортеж из трех наборов выходов.
Каждый набор содержит множество векторов длиной 85 значений. Из этих 85
значений первые 4 связаны с ограничивающей рамкой (X-координата центра, Y-координата центра, ширина рамки, высота рамки), одно значение оценки уверенности и 80 значений оценки уверенности для каждого из целевых
классов, указанных в файле coco.names. Следует отметить, что, в отличие от
модели TensorFlow Hub, модель Darknet YOLO возвращает координаты центра (X, Y) ограничивающих рамок, а также их ширину и высоту. Форма выходных данных показана на следующей иллюстрации:
Иллюстрация 6.25. Форма выходных данных
Извлечем ограничивающие рамки и оценки уверенности (вероятности), перебрав в цикле все три множества результатов, как показано в следующем
блоке кода:
1. def get_bounding_boxes(detector_outputs):
2.
bounding_boxes = []
3.
confidences = []
4.
class_ids = [] 5. (H, W) = img.shape[:2]
6.
7.
for output in detector_outputs:
8.
for detection in output:
9.
detection_scores = detection[5:]
10.
class_id = np.argmax(detection_scores)
11.
confidence = detection_scores[class_id]
12.
13.
if confidence > 0,5:
14.
centerX, centerY, width, height = \
15.
np.array([detection[0]*W,
242
■
Pythonic AI
16.
detection[1]*H,
17.
detection[2]*W,
18.
detection[3]*H])
19.
20.
x = round(centerX - (width / 2))
21.
y = round(centerY - (height / 2))
22.
bounding_boxes.append([x, y, round(width), round(height)])
23.
confidences.append(confidence)
24.
class_ids.append(class_id)
25.
26.
return bounding_boxes, confidences, class_ids
27.
28. bounding_boxes, confidences, class_ids = get_bounding_boxes(detector_
outputs)
Как показано в приведенном выше коде, строка номер 7 выполняет цикл по
результату работы каждого слоя, а строка номер 8 выполняет итерацию по
каждому из случаев обнаружения. Строка номер 9 извлекает значение оценок
обнаружения, которые, как говорилось ранее, начинаются с пятого элемента. Затем определяется индекс максимальной оценки обнаружения в массиве
и само максимальное значение. Максимальное значение дает степень уверенности, а индекс помогает обнаружить предсказанный класс. В строке номер
13 проверяется, превышает ли оценка уверенности, то есть вероятность,
минимальный порог. Если показатель больше порога, то из первых четырех
значений массива извлекаются координаты центра ограничивающей рамки,
а также ее ширина и высота. Строки номер 20 и 21 вычисляют по центральной
точке координаты (x, y) левого верхнего угла ограничивающей рамки. Функция возвращает все эти рассчитанные и определенные значения координат,
оценок уверенности и идентификаторов классов. Для их получения нужно
вызвать эту функцию с аргументом detector_outputs.
В отличие от модели TensorFlow Hub, модель YOLO не применяет к полученным ограничивающим рамкам подавление немаксимумов (NMS).
Мы применим встроенную в модуль DNN библиотеки OpenCV реализацию NMS:
1. indices = cv2.dnn.NMSBoxes(bounding_boxes, confidences, 0.5, 0.3)
Обучение и развертывание моделей обнаружения объектов
■
243
Как показано в приведенном выше фрагменте кода, полученные ограничивающие рамки, оценки уверенности, их пороговые значения и пороговые
значения NMS передаются в функцию NMSBoxes(). Эта функция выполняет
процедуру NMS и возвращает индексы ограничивающих рамок.
Напишем новую функцию draw_bounding_boxes_yolo(), принимающую 7
входных данных — исходное изображение, индексы из NMSBoxes(), выведенные ранее выходные данные функции get_bounding_boxes(), цвета и метки,
как показано в следующем фрагменте кода:
1. def draw_bounding_boxes_yolo(img, indices, bounding_
boxes,confidences, class_ids, colors, labels):
2.
3.
if len(indices) > 0:
4.
for i in indices.flatten():
5.
(x, y) = (bounding_boxes[i][0], bounding_boxes[i][1])
6.
(w, h) = (bounding_boxes[i][2], bounding_boxes[i][3])
7.
8.
color = [int(c) for c in colors[class_ids[i]]]
9.
10.
cv2.rectangle(img, (x, y), (x + w, y + h), color, 2)
11.
t ext = "{0}: {1:.4f}".format(labels[class_ids[i]],
confidences[i])
12.
c v2.putText(img, text, (x, y-5), cv2.FONT_HERSHEY_SIMPLEX,
0.5, color, 2)
13.
return img
Как показано выше, мы рассматриваем ограничивающие рамки только из
индексов, полученных после выполнения операции NMS. Для отрисовки
прямоугольников необходимо извлечь координаты левого верхнего угла,
ширины и высоты и вычислить координаты правого нижнего угла. Вызовем
написанную нами функцию и выведем на экран изображение, как показано
в следующем фрагменте кода:
1. output_img = draw_bounding_boxes_yolo(img, indices,
bounding_boxes, confidences, class_ids, BB_COLORS, LABELS)
2. cv2_imshow(output_img)
244
■
Pythonic AI
Получившееся изображение показано на следующей иллюстрации:
Иллюстрация 6.26. Выходное изображение, сгенерированное моделью YOLO
Заключение
В этой главе мы погрузились в увлекательный мир моделей обнаружения
объектов. Сначала мы усвоили общую концепцию и принципы обнаружения объектов, важные для практического применения моделей. Для закладки
прочного фундамента мы рассмотрели такие важные понятия, как IoU, NMS,
якорные рамки и FPN. Они лежат в основе многих современных алгоритмов
обнаружения объектов и позволяют глубже понять их внутреннее устройство.
Затем мы рассмотрели конкретные модели обнаружения объектов, сосредоточившись на SSD, RCNN и YOLO. Проанализировав архитектуру этих моделей, мы получили представление об их сильных и слабых сторонах и уникальных характеристиках. Мы изучили различные подходы к решению проблемы
обнаружения объектов: от эффективного прогнозирования SSD с однократным прохождением до принципа уточнения областей RCNN и эффективности YOLO в режиме реального времени.
Кроме того, мы научились строить модели обнаружения объектов, используя
предварительно обученные модели глубокого обучения библиотек TensorFlow
Обучение и развертывание моделей обнаружения объектов
■
245
и OpenCV. Мы получили знания об обнаружении объектов, основополагающих принципах, а также практических аспектах построения и развертывания
моделей и заложили прочный фундамент для создания передовых приложений в сфере компьютерного зрения. Обнаружение объектов продолжает оставаться активной областью исследований и разработок, и соответствующие
технологии постоянно совершенствуются. Достижения этой области расширяют границы возможного. Полученные в этой главе знания и навыки позволят вам хорошо подготовиться к решению многих задач и помогут внести
свой вклад в эту увлекательную и многообещающую сферу.
В следующей главе мы узнаем о том, как изображения преобразуются
в текст — о так называемом оптическом распознавании символов (OCR),
который произвел настоящую революцию в нашем взаимодействии с печатными документами и изображениями.
Основные выводы
• Обнаружение объектов — это задача компьютерного зрения, которая
заключается в идентификации и локализации объектов на изображении или видео.
• IoU — важнейшая метрика оценки, которая используется в задачах обнаружения объектов для измерения точности ограничивающих рамок
и пересечения между предсказанными ограничивающими рамками
и аннотациями (истинными рамками), полученными на основе реальных данных.
• NMS — это метод постобработки, используемый для устранения избыточных предсказаний ограничительных рамок.
• Якорные рамки — это рамки предопределенных форм и размеров, используемые для предсказания границ объектов с различными соотношениями сторон и масштабами, благодаря которым повышается универсальность методов обнаружения.
• SSD — популярная модель обнаружения объектов, предсказывающая
местоположение и классы объектов в нескольких масштабах за один
прямой проход.
• R-CNN — это тип сверточных сетей для обнаружения объектов на основе так называемых «регионов», позволяющих точнее обнаруживать
объекты.
• YOLO — это модель обнаружения объектов в реальном времени, которая накладывает на изображение сетку и напрямую предсказывает
ограничивающие рамки и вероятности классов.
246
■
Pythonic AI
Ссылки
• Библиотека моделей обнаружения объектов TensorFlow Hub: https://
tfhub.dev/tensorflow/collections/object_detection/
• Официальный сайт OpenCV: https://opencv.org/
• Официальный сайт Darknet YOLO: https://pjreddie.com/darknet/yolo/
• GitHub-репозиторий Darknet YOLO: https://github.com/pjreddie/darknet
Глава 7
Создание приложения
для чтения текста
и изображений
Введение
Генерация текста по изображениям — это увлекательная область искусственного интеллекта, в которой ИИ-приложения на основе заданного изображения создают некий текст, будь то подписи к рисункам или пригодный
для редактирования документ на основе его отсканированного изображения.
О первом типе приложений мы поговорим в главе 12. В настоящей главе мы
познакомимся со вторым типом приложений, которые позволяют читать
текст, имеющийся на изображении, с так называемым оптическим распознаванием символов (OCR, Optical Character Recognition). OCR — это современная технология, которая произвела настоящую революцию в нашем
взаимодействии с печатными документами и изображениями. В современный цифровой век изображения приобретают все большее значение в разных сферах, от образования до коммуникаций и развлечений. В прежние
времена преобразование текста на изображении в машиночитаемый формат
было трудоемкой и подверженной ошибкам задачей, требующей ручной расшифровки. Именно здесь на помощь приходит технология преобразования
изображений в текст. Она позволяет легко редактировать, искать и анализировать содержимое изображения. Эта технология имеет очень широкий
спектр применения, от оцифровки старых документов до извлечения текста
из изображений в социальных сетях или распознавания номерных знаков
автомобилей. Все подобные программы и приложения значительно улучшают наше взаимодействие с изображениями в повседневной жизни. Области
применения технологии преобразования изображений в текст продолжают
248
■
Pythonic AI
развиваться вместе с достижениями в области искусственного интеллекта
и компьютерного зрения.
Структура
В этой главе мы рассмотрим следующие темы:
• Концепция преобразования изображений в текст.
• Принципы OCR.
• Области применения.
• Создание OCR-приложения с помощью Tesseract.
• Создание приложений для преобразования изображений в текст с помощью TensorFlow 2.
Цели
В этой главе мы познакомимся с важной практической областью искусственного интеллекта, которая называется оптическим распознаванием символов (OCR). Мы узнаем, как использовать библиотеку Tesseract в Python для
распознавания текста на изображениях. Затем мы научимся разрабатывать
OCR-приложения с нуля с помощью модели сверточной нейронной сети
в TensorFlow. Эта модель будет обучаться на наборах данных, содержащих рукописные цифры и буквы английского алфавита.
Концепция преобразования
изображений в текст
В главе 3 «Создание нашей первой модели нейронной сети» мы построили
полносвязную искусственную нейронную сеть (ИНС) для распознавания
рукописных цифр из набора данных MNIST. Эта модель может служить
простым примером применения технологии преобразования изображений
в текст. Процесс преобразования изображения в текст, также известный как
OCR, подразумевает использование различных алгоритмов и программ, которые распознают на изображении образы, соответствующие буквам, цифрам
и другим символам, а затем переводят эти образы в текст. Данная технология
позволяет легко извлекать текст из изображений и делает его доступным для
поиска и редактирования. Смысл технологии преобразования изображений
Создание приложения для чтения текста и изображений
■
249
в текст заключается в том, чтобы сделать процесс распознавания более эффективным, точным и автоматизированным. Это экономит время и ресурсы
по сравнению с преобразованием и вводом текста вручную.
Методы преобразования изображения в текст можно применить и к видео,
поскольку оно представляет собой последовательность изображений. В настоящее время уже применяется технология автоматического распознавания речи (ASR, Automated Speech Recognition), с помощью которой можно преобразовывать аудиодорожки видео в текст. Точно так же технология
преобразования изображения извлекает из видеокадров текст и таким образом позволяет пользователям искать в видеозаписях определенные слова
или фразы. В реальных приложениях эта технология может использоваться
для индексации и поиска видеоконтента на основе его текстового описания,
расшифровки видео для контент-анализа, анализа настроений, извлечения
ценных сведений и т. д.
Принципы OCR
Технология OCR произвела революцию в нашем взаимодействии с документами и изображениями. OCR позволяет преобразовывать присутствующие
на изображении символы в машиночитаемый цифровой текст, который можно редактировать и анализировать.
Технология OCR задействует несколько алгоритмов и методов распознавания деталей изображения, соответствующих тексту: букв, цифр и символов.
Программное обеспечение OCR переводит эти образы в текст, который можно обрабатывать различными способами. Благодаря этой технологии стало
проще оцифровывать и искать документы, переводить текст с одного языка
на другой и даже извлекать информацию из изображений в социальных сетях.
Любой процесс OCR включает в себя несколько основных этапов:
1. Предварительная обработка. После получения изображения к нему
применяются методы предварительной обработки для улучшения качества изображения и подготовки его к дальнейшему анализу.
2. Локализация текста. На этом этапе система OCR определяет области
изображения, содержащие текст. Для обнаружения и выделения текстовых областей из остальной части изображения используются различные методы, такие как обнаружение краев, анализ связанных компонентов или алгоритмы глубокого обучения.
250
■
Pythonic AI
3. Сегментация символов. После локализации текстовых областей система OCR обрабатывает каждый символ в тексте.
Система OCR идентифицирует символы, находя наилучшее соответствие для
каждого символа на основе извлеченных признаков.
Области применения
OCR имеет широкий спектр применения в различных областях:
• Оцифровка документов. OCR широко используется для оцифровки
бумажных документов, таких как договоры, счета-фактуры, рецепты,
квитанции и т. д. Это позволяет предприятиям значительно сократить
ручной ввод данных и автоматизировать обработку документов, что
приводит к повышению эффективности и точности.
• Банковское дело и финансы. Технология OCR позволяет извлекать
информацию из чеков и счетов-фактур и оцифровывать эти документы. Она ускоряет процесс оплаты и сокращает количество ошибок,
допускаемых при вводе данных вручную. Также она используется для
обработки кредитных карт, заявок на кредиты и других финансовых
операций.
• Здравоохранение. Технология OCR позволяет оцифровывать истории
болезни, лабораторные заключения и другие медицинские документы.
Благодаря ей медицинские работники могут легко сохранять данные
о пациентах и анализировать их, что повышает эффективность работы
и улучшает результаты лечения.
• Образование. Технология OCR позволяет оцифровывать учебники,
материалы курсов, конспекты и другой образовательный контент, что
облегчает студентам доступ к информации, а преподавателям дает возможность создавать индивидуальные учебные материалы.
• Государственные службы. Технология OCR используется для обработки заявлений на получение паспорта, регистрации избирателей
и других государственных документов, что помогает сократить время
их обработки и повысить точность.
• Юриспруденция. Технология OCR используется для оцифровки и обработки юридических документов, таких как договоры и судебные
протоколы. Это помогает сократить ручной ввод данных и повысить
эффективность делопроизводства, а, тем самым, и результативность
юридических процессов.
Создание приложения для чтения текста и изображений
■
251
• Розничная торговля и электронная коммерция. Технология OCR используется для распознавания товаров, помогая розничным торговцам
осуществлять эффективный контроль над запасами и сокращать количество ошибок при отслеживании товаров.
Технология OCR постоянно развивается, а достижения в области компьютерного зрения и искусственного интеллекта повышают ее точность и эффективность. Однако разработчики до сих пор сталкиваются с некоторыми
проблемами, такими как трудности распознавания рукописного текста и некоторых шрифтов, а также необходимость получения высококачественного
исходного изображения для достижения точного результата.
Но, несмотря на эти проблемы, OCR — в высшей степени полезная технология, меняющая наши представления о взаимодействии с печатными материалами. Благодаря своей способности быстро и точно извлекать текст из
изображений, технология OCR стала мощным инструментом повышения эффективности и производительности в различных отраслях. В последующих
разделах мы разработаем OCR-приложения для преобразования изображений в текст с использованием различных методов.
Создание OCR-приложения
с помощью Tesseract
Tesseract — это широко используемый движок OCR, универсальность, точность и многоязыковая поддержка которого делают его очевидным выбором
для многих OCR-приложений. В последних обновлениях Tesseract стал еще
более точным и надежным инструментом для рабочих процессов, в которых
используются технологии распознавания текста.
Tesseract — это бесплатный OCR-движок с открытым исходным кодом, поддерживаемый Google. Первоначально его разрабатывала компания HewlettPackard в 1980-х годах, а в 2005 году Tesseract выпустили в качестве проекта
с открытым исходным кодом. Для распознавания текста на изображениях
в Tesseract используются алгоритмы глубокого обучения, и его можно обучить распознаванию текста на различных языках. Движок доступен в виде
инструмента командной строки и библиотеки на различных языках программирования, включая Python, Java и C++. Tesseract развивался на протяжении
многих лет и несколько раз обновлялся с повышением точности и производительности. В настоящее время основной и стабильной считается пятая версия, включающая в себя ряд таких улучшений, как поддержка новых языков
252
■
Pythonic AI
и повышенная точность. Версия 5.0.0 была выпущена 30 ноября 2021 года,
после выходили незначительные дополнения (версии 5.x) с исправлениями
ошибок, доступные в репозитории GitHub.
Основная работа Tesseract заключается в анализе изображения и выявлении
шаблонов (паттернов), напоминающих текст, с помощью ИИ-алгоритмов
распознавания паттернов и преобразования их в текст. Одно из самых значительных преимуществ Tesseract — способность распознавать текст различных
шрифтов и размеров. Он может обрабатывать даже слегка наклонный или искаженный текст, что делает его универсальным OCR-движком. Tesseract также может распознавать текст на разных языках, что делает его полезным для
приложений, требующих многоязыковой поддержки.
Внутренняя модель движка Tesseract состоит из систем компьютерного зрения, обработки естественного языка и машинного обучения. Для распознавания и классификации символов на изображении в нем используются сверточные нейронные сети (CNN) и рекуррентные нейронные сети (RNN).
Эти сети обучаются на больших наборах размеченных изображений для
распознавания символов. Также в движке Tesseract для улучшения качества входного изображения и повышения точности распознавания символов
используются различные методы предварительной обработки, такие как бинаризация, шумоподавление и коррекция перекоса. Tesseract анализирует
изображение пиксель за пикселем, идентифицирует и изолирует символы на
изображении, а затем использует статистические модели для распознавания
символов и преобразования их в машиночитаемый текст. Процесс включает
в себя несколько этапов, в том числе предварительную обработку изображения, сегментацию символов, извлечение и классификацию признаков. Сначала изображение подвергают предварительной обработке для того, чтобы
повысить его качество и удалить шумы и искажения, которые могут помешать распознаванию символов. Затем Tesseract использует алгоритмы для
сегментации изображения на отдельные символы на основе таких признаков,
как цвет, текстура и форма. Затем программа извлекает набор признаков из
каждого символа, включая такие характеристики, как ширина штриха, ориентация краев и кривизна. Наконец, Tesseract применяет модели машинного обучения, такие как искусственные нейронные сети, для классификации
каждого символа на основе извлеченных признаков и преобразования их
в машиночитаемый текст.
Пользоваться движком Tesseract относительно просто. Он доступен в виде
инструмента командной строки, то есть команды по распознаванию текста
можно задавать прямо в терминале. Для этого нужно подать в движок входное
изображение и указать язык текста. Tesseract проанализирует изображение
Создание приложения для чтения текста и изображений
■
253
и выдаст распознанный текст. Также Tesseract доступен в виде библиотеки
или API на языке Python, облегчающих интеграцию движка в то или иное
приложение.
Для оптического распознавания символов с помощью Python мы воспользуемся библиотекой python-tesseract или py-tesseract. Это обертка для движка
Tesseract-OCR Engine от Google, которая умеет читать все типы изображений,
поддерживаемые Python Pillow и библиотеками изображений Leptonica, такие как PNG, jpeg, bmp, gif, tiff и т. д.
Библиотека Pytesseract в Google Colab изначально не установлена. Чтобы использовать py-tesseract в Google Colab, нужно сначала установить ее с помощью команды pip:
1. !pip install pytesseract
Также необходимо установить сам движок Google Tesseract OCR. После этого библиотека py-tesseract сможет вызывать команду tesseract и считывать
текст, присутствующий на изображение. Пакет с движком Tesseract, доступный для Linux-систем на базе Ubuntu или Debian, называется Tesseract-ocr.
В Google Colab можно проверить версию операционной системы (ОС), как
показано на следующей иллюстрации:
Иллюстрация 7.1. Проверка версии ОС
Перед командой ставится восклицательный знак («!»), потому что это команда Linux, а не Python.
Чтобы установить движок tesseract-ocr в Google Colab, нужно выполнить
следующую команду:
254
■
Pythonic AI
1. !sudo apt install tesseract-ocr
Для чтения изображения воспользуемся библиотекой Python Imaging Library
(PIL), а для распознавания текста на нем — библиотекой pytesseract. Попробуем прочитать текст со следующего изображения:
Иллюстрация 7.2. Входное изображение для библиотеки PIL
Показанное выше изображение доступно по следующему адресу: https://
commons.wikimedia.org/wiki/File:Premil_Ratnayake%27s_Business_Card.jpg
Это визитная карточка, и нам нужно прочитать ее содержимое. Загрузить
изображение в Google Colab можно с помощью опции «Файлы» (Files), расположенной на левой панели.
Сначала откроем изображение и преобразуем его в черно-белый формат, чтобы упростить обработку. В библиотеке pytesseract имеется функция image_
to_string(), которая распознает текст на картинке и возвращает немодифицированную строку. После этого можно вывести прочитанный текст на
экран и просмотреть результат, как показано на следующей иллюстрации.
Создание приложения для чтения текста и изображений
■
255
Иллюстрация 7.3. Чтение текста с помощью pytesseract
Итак, распознавать символы с помощью библиотеки pytesseract очень просто, и для этого достаточно вызвать всего лишь одну функцию. Показанный
выше результат вполне удовлетворителен, хотя так бывает не всегда. Библиотека pytesseract лучше всего работает с четкими и яркими изображениями,
а предыдущее изображение, которое мы использовали, имеет очень четкий
текст на белом фоне. Для проверки возьмем другое изображение или другую визитную карточку. На этот раз вместо PIL воспользуемся библиотекой
OpenCV.
Мы познакомились с OpenCV в главе 6 «Обучение и развертывание моделей
обнаружения объектов». Для обнаружения и распознавания объектов в библиотеке OpenCV имеются такие средства, как каскады Хаара и гистограмма
ориентированных градиентов (HOG), а также алгоритмы глубокого обучения, такие как You Only Look Once (С первого взгляда) (YOLO) и Single Shot
Detector (Детектор за один проход) (SSD). С ростом популярности приложений компьютерного зрения OpenCV стала незаменимым инструментом во
всех отраслях.
В данном случае мы воспользуемся библиотекой OpenCV в Python для чтения
изображения. Попытаемся прочитать текст со следующего изображения:
256
■
Pythonic AI
Иллюстрация 7.4. Входное изображение для библиотеки OpenCV
Показанное выше изображение доступно по следующему адресу: https://
commons.wikimedia.org/wiki/File:Lars_Jacob_business_card_1974.jpg
Нам нужно его скачать и загрузить в Google Colab. Для чтения изображения
воспользуемся предустановленной в Google Colab библиотекой OpenCV, импортировав ее под именем cv2, как показано на следующей иллюстрации:
Иллюстрация 7.5. OpenCV и pytesseract
Как показано на предыдущей иллюстрации, мы прочитали изображение с помощью функции imread() в OpenCV. Затем мы преобразовали его в черно-
Создание приложения для чтения текста и изображений
■
257
белый формат с помощью функции cvtColor() с флагом COLOR_BGR2GRAY.
По умолчанию OpenCV считывает цветное изображение в формате BGR (синий, зеленый, красный), а не в формате RGB. Если предполагается, что изображение будет обрабатываться только с помощью библиотеки OpenCV, то
менять ничего не нужно. Но другие библиотеки (например, та же pytesseract)
ожидают, что изображение будет в формате RGB. В этом случае нужно будет
предварительно его обработать. Поэтому мы переформатировали изображение с помощью функции cvtColor() и флага COLOR_BGR2RGB и получили
его в формате RGB. Затем мы вызвали функцию image_to_string() из библиотеки pytesseract, чтобы считать текст с изображения.
На этот раз текст, выведенный функцией image_to_string(), не совсем корректен. Видно, что pytesseract затруднилась с распознаванием некоторых
символов, особенно цифры «3». Затруднение возникло из-за толщины шрифта, и pytesseract не смогла точно понять, чему соответствует данный символ.
Если буквы выцветшие или наклонные, то pytesseract может выдать частично
или полностью бессмысленный текст.
С помощью функции image_to_boxes() можно получить текст с изображения с распознанными символами и ограничивающими их рамками:
Иллюстрация 7.6. Функция image_to_boxes() в pytesseract
258
■
Pythonic AI
Как показано на предыдущей иллюстрации, мы передали тот же объект изображения "im" функции image_ to_boxes(), возвращающей строку с распознанными символами и их ограничивающими рамками. Координаты рамок
для каждого символа извлекаются посредством разбиения возвращаемых
строк.
Функция OpenCV rectangle() рисует на заданном изображении прямоугольники, если заданы начальные координаты, конечные координаты, цвет
прямоугольника и значения толщины. Мы указали координаты, зеленый
цвет и толщину в 2 пикселя. Значение (0, 255, 0) — это цветовой код BGR для
зеленого цвета. Изображение с прямоугольниками отображается с помощью
функции Image()библиотеки PIL.
С помощью функции image_to_data() можно также получить сведения
о границах рамки, уверенности предсказания, номере страницы, номере
строки и другую информацию. Эта функция возвращает строку подробных
значений, разделенных табуляцией.
На иллюстрации 7.7 показан вывод этой функции, где значения разделены
табуляцией: в предпоследнем столбце показана степень уверенности в распознавании, а последний столбец содержит распознанный текст с изображения.
Иллюстрация 7.7. Функция image_to_data() в pytesseract
Создание приложения для чтения текста и изображений
■
259
У Tesseract OCR имеются следующие ограничения:
• Оптическое распознавание ухудшается, если изображение размыто,
если текст на нем загораживают другие объекты, и если оно содержит
шум или сложный фон.
• Если текст не понятен, Tesseract может выдавать бессмысленные символы.
• Tesseract не очень хорошо справляется с распознаванием рукописных
текстов.
• Tesseract не всегда понимает естественный порядок чтения документа. Если в изображении имеется несколько колонок или блоков текста,
Tesseract иногда не понимает, в каком порядке их следует отображать,
и пытается объединить текст из разных колонок или блоков.
Создание приложений
для преобразования изображений
в текст с помощью TensorFlow 2
Библиотека TensorFlow позволяет построить ИИ-приложение для чтения
текста на изображениях с нуля. Частично мы уже делали это в главе 3 «Создание нашей первой модели нейронной сети». Там мы разрабатывали модель
полносвязной нейронной сети и обучали ее на наборе данных MNIST. Набор
данных MNIST содержит только рукописные цифры. Но для задачи чтения
текста с изображения нам потребуется набор данных с буквами алфавита.
Так мы обучим модель распознавать не только рукописные цифры, но и рукописные буквы.
Рукописные буквы от A до Z в черно-белом формате содержатся в наборе
данных платформы Kaggle. Изображения для этой выборки взяты из набора данных NIST (https://www.nist.gov/srd/nist-special-database-19), большого
набора данных NMIST и некоторых других источников. Размер собранных
изображений составляет 28×28, то есть 28 пикселей в длину и 28 пикселей
в ширину. Значения пикселей для каждого изображения сведены и помещены
в виде строки в файл с разделенными запятыми значениями (CSV, Commaseparated value). Имейте в виду, что этот набор данных содержит только рукописные буквы английского алфавита верхнего регистра (всего 26 типов).
Этот набор данных представлен в виде CSV-файла со значениями пикселей
изображений в каждой строке и находится по адресу: https://www.kaggle.com/
datasets/sachinpatel21/az-handwrittenalphabets-in-csv-format.
260
■
Pythonic AI
Для использования в блокноте Colab Google его нужно будет загрузить в виде
zip-архива. Сохраним его на Google Диске в каталоге под названием data.
В главе 2 «Настройка лаборатории искусственного интеллекта» мы научились подключать Google Диск к блокноту Colab для использования любого
файла, хранящегося на этом диске. Аналогично в данном случае мы сначала
подключим Google Диск с помощью следующих команд:
1. from Google.colab import drive
2. drive.mount('/content/gdrive')
Мы сохранили заархивированный CSV-файл в каталоге под названием data.
Поэтому для получения прямого доступа к этому CSV-файлу следует изменить рабочий каталог следующим образом:
1. %cd /content/gdrive/MyDrive/data/
После смены каталога нужно распаковать хранящийся в каталоге data заархивированный CSV-файл. Для этого нужно воспользоваться командой Linux
unzip. Поскольку это команда Linux, в блокноте Colab перед ней ставится
восклицательный знак, как показано ниже:
1. !unzip "A_Z Handwritten Data.csv.zip"
Ранее мы уже пользовались набором данных MNIST. Теперь мы прочитаем
набор данных Kaggle с буквами алфавита A–Z и объединим его с набором
MNIST для обучения нашей модели распознаванию рукописных цифр и букв
английского алфавита в верхнем регистре. На этот раз мы не будем использовать полносвязную нейронную сеть, а обучим на этом объединенном наборе
данных сверточную нейронную сеть.
Для разработки нам нужно импортировать несколько библиотек, как показано ниже:
1. import
tensorflow as tf
2. from tensorflow.keras.datasets import
mnist
3. import numpy as np
4. from sklearn.model_selection import train_test_split
5. from sklearn.preprocessing import LabelBinarizer
6. import matplotlib.pyplot as plt
Создание приложения для чтения текста и изображений
■
261
Сначала мы импортировали TensorFlow. Как нам уже известно, набор данных
MNIST доступен в TensorFlow в виде набора данных Keras. Обратите внимание, что мы импортировали еще две важные функции train_test_split
и labelBinarizer из библиотеки sklearn, или scikit-learn. Scikit-learn — это
бесплатная библиотека Python с открытым исходным кодом, содержащая
различные функции для предварительной обработки и очистки данных, построения моделей машинного обучения, оценки моделей и т. д. С этими двумя импортируемыми функциями мы познакомимся чуть позже. Следующий
фрагмент кода запускает построчное чтение CSV-файла:
1. with open("A_Z Handwritten Data.csv") as f:
2.
file = f.readlines()
3.
4. az_images = list() # для хранения данных изображений A-Z (значения пикселей)
5. az_labels = list() # для хранения меток данных изображений A-Z
6.
7 for line in file:
8.
values = line.split(",")
9.
az_labels.append(values[0])
10.
image_data = np.array([int(i) for i in values[1:]]).reshape((28, 28))
11.
az_images.append(image_data)
Сначала нам нужно открыть CSV-файл, чтобы прочитать его построчно. Для
этого мы воспользовались функцией Python open(). Затем мы задали два
списка az_images и az_labels для хранения данных изображений A–Z и меток. С помощью цикла for мы итеративно прочитали файл строку за строкой.
Поскольку в CSV-файле каждая строка содержит значения, разделенные запятыми, нужно разделить строку с помощью функции Python split() и получить значения в виде списка. Первое значение списка, расположенное под
индексом 0 — это метка, и мы помещаем ее значение в список под названием az_labels. Остальные значения — это значения пикселей изображения.
Поскольку они представляют собой простую последовательность, сначала их
нужно преобразовать в массив NumPy, в матрицу размером 28×28, и только
потом помещать в список az_images.
Теперь данные с буквами алфавита A–Z считаны и доступны для использования. Далее нам нужно считать рукописные цифры из набора данных MNIST.
В главе 3 «Создание нашей первой модели нейронной сети» мы уже использо-
262
■
Pythonic AI
вали функцию load_data(). Воспользуемся ею снова для того, чтобы считать
обучающие и тестовые данные, как показано в следующем фрагменте кода:
1. (digit_train_images, digit_train_labels), (digit_test_images,
digit_test_labels) = mnist.load_data()
2. digit_train_labels = digit_train_labels.astype("str")
3. digit_test_labels = digit_test_labels.astype("str")
Не забывайте, что в нашем наборе данных рукописных букв A–Z метки представлены в виде числовых значений, а для заглавных букв английского алфавита может быть только 26 возможных значений. В наборе данных эти 26 символов представлены в виде числовых значений от 0 до 25, где 0 соответствует
букве «A», а 25 соответствует букве «Z». Но нам нужно обучить нашу модель
как на данных A–Z, так и на данных MNIST. Набор данных MNIST содержит
рукописные цифры, а целевой класс имеет возможные значения от 0 до 9.
При обучении модели нужно объединить эти два набора данных, однако метки со значениями от 0 до 9, присутствующие в обоих наборах данных, будут
путаться между собой и неправильно интерпретироваться. Поэтому перед
объединением этих двух наборов данных мы преобразуем метки набора данных A–Z из числовых значений в символы (буквы алфавита), как показано
в следующем фрагменте кода:
1. actual_labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2. az_labels_actual = np.array([actual_labels[int(i)] for i in az_labels])
В этом фрагменте мы сначала задаем строку actual_labels из 26 букв английского алфавита в верхнем регистре. Затем мы меняем метки с числовых
значений на реальные символы, подставляя на место числа с определенным
индексом соответствующую букву.
Также нужно преобразовать список az_images в массив NumPy следующим
образом:
1. az_images = np.array(az_images)
Массив NumPy az_images должен иметь форму (372451, 28, 28).
Можно для примера визуализировать несколько случайных изображений из выборки az_images. Создадим шесть случайных чисел и возьмем
Создание приложения для чтения текста и изображений
■
263
соответствующие им данные из az_images и метки из az_labels_actual, как
показано в следующем фрагменте кода:
1. i = 1
2. for num in np.random.randint(0, 372450, [6,]):
3.
ax = plt.subplot(2, 3, i)
4.
ax.set_title("Alphabet: {0}".format(az_labels_actual[num]))
5.
ax.imshow(az_images[num], cmap='gray')
6.
i+=1
Здесь мы запускаем цикл for, чтобы выбрать и отобразить шесть изображений с помощью функции subplot() из библиотеки Matplotlib. Мы воспользовались функцией NumPy randint() для создания шести случайных чисел
в диапазоне от 0 до 372 450, поскольку в наборе данных имеется 372 451 изображение. Эти изображения мы представили в виде «подграфиков» (supblots)
в двух строках и трех столбцах, а соответствующие им метки отобразили
в виде заголовков. Результат выполнения приведенного выше фрагмента кода показан на следующей иллюстрации:
Иллюстрация 7.8. Отображение данных с рукописными буквами A–Z
английского алфавита
264
■
Pythonic AI
Как видно из предыдущей иллюстрации, каждое изображение представляет
собой черно-белую картинку размером 28×28 пикселей.
Примечание. В данном случае мы сгенерировали шесть случайных чисел, чтобы вывести на экран шесть случайных изображений из набора
данных с буквами A–Z. При выполнении этого же кода у себя вы можете получить другой результат.
Теперь нужно разделить набор данных A–Z на обучающий и тестовый наборы с помощью функции train_test_split() библиотеки scikit-learn или
sklearn. Эта функция случайным образом разбивает массивы на обучающее
и тестовое подмножества. Аргумент test_size функции train_test_split()
определяет долю набора данных, которая будет включена в тестовую выборку. Значение random_state — это фиксированный «показатель случайности».
При каждом выполнении функции train_test_split() с одним и тем же
значением random_state будут получаться одинаковые подмножества. Перед
разбиением мы также перемешаем (shuffle) данные, как показано в следующем фрагменте кода:
1. az_train_images, az_test_images, az_train_labels, az_test_labels =
train_ test_split(az_images,
2.
az_labels_actual,
3.
random_state=123,
4.
test_size=0.25,
5.
shuffle=True)
Теперь, когда у нас имеются разделенные наборы данных, можно удалить ненужные больше переменные az_images и az_actual_labels. Так мы сэкономим место в оперативной памяти (RAM), поскольку переменная az_images
содержит большое количество данных с изображениями. Ниже показаны команды удаления переменных:
1. del az_images
2. del az_labels_actual
Далее необходимо объединить наборы данных A–Z и MNIST, чтобы обучить модель искусственного интеллекта на всем объеме данных. Проведем
Создание приложения для чтения текста и изображений
■
265
операцию объединения отдельно для обучающих данных с метками и для
тестовых данных с метками.
В случае с данными можно воспользоваться функцией vstack() из NumPy,
выполняющей вертикальное (по рядам) склеивание массивов. Она берет последовательность массивов и располагает их по вертикали, создавая новый массив. Все массивы для этой операции должны иметь одинаковый размер по всем
осям, кроме первой. Итоговый массив имеет форму, которая представляет собой конкатенацию форм входных массивов вдоль первой оси (вертикальной).
Метки в нашем наборе данных представляют собой одномерные массивы.
Для объединения меток воспользуемся функцией hstack() из NumPy, которая объединяет массивы по горизонтали (по столбцам). Массивы должны
иметь одинаковую форму по всем осям, кроме второй. Итоговый массив имеет форму, которая представляет собой конкатенацию форм входных массивов вдоль второй оси (горизонтальной). Эти операции выполняются следующими командами:
1. training_data = np.vstack([az_train_images, digit_train_images])
2. training_labels = np.hstack([az_train_labels, digit_train_labels])
3.
4. test_data = np.vstack([az_test_images, digit_test_images])
5. test_labels = np.hstack([az_test_labels, digit_test_labels])
После слияния наборов данных удалим ненужные переменные для освобождения оперативной памяти:
1. del az_train_images
2. del az_train_labels
3. del digit_train_images
4. del digit_train_labels
5.
6. del az_test_images
7. del az_test_labels
8. del digit_test_images
9. del digit_test_labels
Выполним минимально-максимальную нормализацию, разделив массивы
на 255.
266
■
Pythonic AI
1. training_data = training_data/255
2. test_data = test_data/255
Для прямого унитарного кодирования целевого класса или меток применим
функцию LabelBinarizer() из библиотеки scikit-learn или sklearn. Всего
в метках может находиться 36 возможных классов: 26 заглавных букв и десять цифр. Все они хранятся в формате строк, и для расчета потерь во время
обучения их нужно преобразовать в двоичную форму по принципу «один
из всех возможных» с помощью функции LabelBinarizer(), как показано
в следующем фрагменте кода:
1. le = LabelBinarizer()
2. training_labels = le.fit_transform(training_labels)
3. test_labels = le.transform(test_labels)
Мы использовали функцию fit_transform() для обучающих данных и функцию transform() для тестовых данных. Экземпляр LabelBinarizer должен
опознавать возможные мультиклассы для бинаризации только из обучающих
данных. Предполагается, что обучающие и тестовые данные принадлежат
к одному распределению, поэтому в тестовых данных не должно оказаться
новых классов. Поэтому для тестовых данных мы использовали только функцию transform().
Примечание. Модуль tf.keras.utils библиотеки TensorFlow содержит
функцию to_categorical(), преобразующую векторы классов в бинарную матрицу классов посредством унитарного кодирования. Однако
здесь она не используется, потому что to_categorical() работает только
с целыми числами, а не с другими типами переменных. В данном случае наш целевой класс имеет строковый тип, и поэтому нужно использовать LabelBinarizer() из sklearn, а не to_categorical().
Теперь можно создать модель, которая будет обучаться на этом объединенном наборе данных. Для классификации изображений рукописных знаков
зададим простую архитектуру CNN с двумя сверточными блоками с 32 и 64
фильтрами соответственно.
Создание приложения для чтения текста и изображений
■
267
Каждый сверточный слой имеет ядра размером 5×5 и функцию активации
ReLu, после чего следует слой пулинга по максимуму с формой 2×2 и слой
дропаута с коэффициентом 0,3. После сверточных слоев данные разворачиваются в плоскую структуру. Затем добавляются два плотных слоя
со 128 фильтрами и функциями активации ReLu. Последний, или выходной,
слой будет иметь функцию активации softmax для вычисления вероятности
появления каждого из возможных целевых классов. Настроенный экземпляр
LabelBinarizer содержит атрибут classes_ — массив, содержащий метку
для каждого класса. Длина этого массива соответствует общему количеству
выходных классов, а значит и выходных нейронов. Ниже показан блок кода,
задающий эту модель:
1. def create_model():
2.
inputs = tf.keras.layers.Input(shape=(28,28,1))
3.
x = tf.keras.layers.Conv2D(filters=32, kernel_size=(5, 5))(inputs)
4.
x = tf.keras.layers.Activation("relu")(x)
5.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
6.
x = tf.keras.layers.Dropout(0.3)(x)
7.
8.
x = tf.keras.layers.Conv2D(filters=64, kernel_size=(5, 5))(inputs)
9.
x = tf.keras.layers.Activation("relu")(x)
10.
x = tf.keras.layers.MaxPool2D(pool_size=(2, 2))(x)
11.
x = tf.keras.layers.Dropout(0.3)(x)
12.
13.
x = tf.keras.layers.Flatten()(x)
14.
x = tf.keras.layers.Dense(128, activation='relu')(x)
15.
x = tf.keras.layers.Dense(128, activation='relu')(x)
16.
predictions = tf.keras.layers.Dense(len(le.classes_),
activation='softmax')(x)
17.
18.
model = tf.keras.models.Model(inputs=inputs, outputs=predictions)
19.
return model
Модель создается посредством вызова функции create_model(), а ее архитектура выводится через вызов функции summary(), как показано на следующей иллюстрации:
268
■
Pythonic AI
Иллюстрация 7.9. Характеристики модели
Всего в модели этой архитектуры 1 202 596 параметров для обучения.
Примечание. Попробуйте разные комбинации слоев с разными архитектурами и сравните их производительность. Также можно воспользоваться технологией аугментации изображения.
Скомпилируем модель с оптимизатором Adam и категориальной кросс-энтропией в качестве функции потерь. Затем настроим модель на обучающий
набор данных за 20 эпох и с размером пакета равным 128, как показано на
следующей иллюстрации:
Создание приложения для чтения текста и изображений
■
269
Иллюстрация 7.10. Компиляция и обучение модели
После обучения модели можно построить график изменения точности и потерь по эпохам с помощью следующих команд:
1. # plot accuracy
2. plt.plot(history.history['categorical_accuracy'])
3. plt.plot(history.history['val_categorical_accuracy'])
4. plt.title('model accuracy')
5. plt.ylabel('accuracy')
6. plt.xlabel('epoch')
7. plt.legend(['train', 'validation'], loc='upper left')
8. plt.show()
9. # plot loss
10. plt.plot(history.history['loss'])
11. plt.plot(history.history['val_loss'])
12. plt.title('model loss')
13. plt.ylabel('loss')
14. plt.xlabel('epoch')
15. plt.legend(['train', 'validation'], loc='upper left')
16. plt.show()
270
■
Pythonic AI
В результате получим следующие графики:
Иллюстрация 7.11. Графики точности и потерь
Производительность модели на тестовом наборе данных можно оценить
с помощью функции evaluate():
Создание приложения для чтения текста и изображений
■
271
Иллюстрация 7.12. Оценка модели
Теперь модель может распознавать рукописные цифры и буквы алфавита на
изображениях с точностью 98,89%.
Наконец, еще одним способом распознавания текста на изображениях является использование предварительно обученных моделей трансформеров,
которых предоставляет компания Hugging Face для создания приложений по
распознаванию текста. Hugging Face — это американская компания, занимающаяся созданием программного обеспечения с открытым исходным кодом
в области обработки естественного языка (NLP). Заявленная ею цель — демократизация и ускорение развития ИИ-технологий посредством предоставления современных алгоритмов машинного обучения в формате, удобном
для исследователей, разработчиков и компаний. Hugging Face разработала
несколько инструментов и фреймворков для NLP, включая Transformers,
Tokenizers, Datasets и др. Библиотека Transformers обрела очень широкую
популярность в NLP-сообществе благодаря реализации современных моделей глубокого обучения, позволяющих добиваться значительных прорывов
в ряде NLP-задач, таких как классификация текстов, ответы на вопросы, генерация текстов и т. д.
Чтобы воспользоваться библиотекой transformers, нужно установить ее
с помощью следующей команды:
1. !pip install transformers
Для оптического распознавания символов можно воспользоваться предварительно обученной моделью TrOCR, доступной в библиотеке. Она содержит
предварительно обученные модели трансформера для изображений и для
текста, позволяя считывать изображения и генерировать результат. Более
подробные сведения о принципах работы этой предварительной обученной
модели и о том, как ею пользоваться, можно узнать на странице по адресу:
https://huggingface.co/microsoft/trocr-base-printed.
272
■
Pythonic AI
Заключение
В этой главе мы научились разрабатывать приложения для преобразования
изображений в текст с помощью библиотеки Tesseract в Python. Затем мы создали оптический распознаватель символов с нуля, обучив модель на наборе данных с рукописными цифрами и буквами английского алфавита. Под
конец мы кратко ознакомились с предварительно обученными моделями на
основе трансформеров компании Hugging Face.
Технологии оптического распознавания текстов, или OCR, радикальным
образом изменили наше взаимодействие с изображениями, сделав процесс
распознавания текста более доступным и эффективным. Благодаря использованию передовых методов компьютерного зрения и алгоритмов машинного обучения теперь можно с высокой точностью извлекать текст из изображений и преобразовывать его в редактируемый цифровой формат. По мере
развития этих технологий ожидается появление еще большего количества
программ и приложений, которые еще сильнее изменят нашу работу с изображениями и текстами. В целом технологии распознавания текстов значительно расширяют возможности так называемого «компьютерного зрения»
и обработки естественного языка, коренным образом меняя способы нашего
общения с компьютерами и прочими цифровыми устройствами.
В следующей главе мы затронем такую увлекательную тему, как обработка
естественного языка, и научимся разрабатывать ИИ-приложения, имитирующие процесс понимания человеком речи.
Основные выводы
• Преобразование изображений в текст — это процесс извлечения текстового содержимого из изображений или отсканированных документов. Ключевую роль в этом процессе играет технология оптического
распознавания текстов, или OCR, позволяющая узнавать на изображениях цифры, буквы и прочие символы.
• Технологии OCR находят широкое применение в различных отраслях
при оцифровке печатных документов для их архивирования и поиска
по ним.
• Tesseract — это популярный OCR-движок с открытым исходным кодом, который можно интегрировать в приложения на Python. Tesseract
предлагает широкую языковую поддержку и позволяет решать различные задачи распознавания текста.
Создание приложения для чтения текста и изображений
■
273
• Приложения для преобразования изображений в текст можно разрабатывать с помощью библиотеки TensorFlow 2 посредством обучения
CNN-моделей с использованием наборов данных MNIST и наборов
данных рукописных букв A–Z. Также для этого можно использовать
предварительно обученные модели, предоставленные компанией
Hugging Face в библиотеке Transformers.
Ссылки
• Репозиторий Tesseract на GitHub: https://github.com/tesseract-ocr/tesseract
• Python Tesseract, или pytesseract: https://github.com/madmaze/pytesseract
• OpenCV: https://opencv.org/
• Специальная база данных NIST: https://www.nist.gov/srd/nist-specialdatabase-19
• Библиотека Scikit-Learn: https://scikit-learn.org/stable/index.html
• TrOCR компании Hugging Face: https://huggingface.co/docs/transformers/
model_doc/trocr
Глава 8
NLP как средство
расширенного
анализа текста
Введение
Обработка естественного языка (NLP, Natural Language Processing) — это
область знаний, объединяющая искусственный интеллект, информатику
и лингвистику. Основной задачей является позволить компьютерам взаимодействовать с людьми с помощью естественного языка. Под обработкой
естественного языка подразумеваются анализ, определение смысла и генерация текстов на естественном языке (который используется для общения
между людьми) с помощью компьютерных программ и алгоритмов.
В последние годы с резким ростом количества данных, генерируемых на естественных языках, выросла и роль NLP. Причем количество этих неструктурированных и требующих обработки данных ошеломляет, если принять
во внимание все тексты на естественных языках, от сообщений в социальных сетях до электронных писем и новостных статей. Алгоритмы NLP могут
помочь компаниям и организациям в анализе этих данных, автоматизации
взаимодействия с клиентами и разработке интеллектуальных виртуальных
помощников.
NLP — междисциплинарная область, использующая методы и теоретические
основы из информатики, лингвистики, психологии и других наук. Это быстроразвивающаяся сфера, в которой постоянно появляются новые методы
и приложения. Несмотря на многочисленные сложности, за последние годы
в области обработки естественного языка удалось добиться значительного
прогресса, и в будущем она определенно окажет большое влияние на наше
взаимодействие с компьютерами.
NLP как средство расширенного анализа текста
■
275
В последнее время, особенно после выхода ChatGPT от компании OpenAI,
широкую популярность приобрели так называемые Большие языковые модели (LLM, Large Language Model), способные обрабатывать и генерировать
текст на естественном языке, отвечать на вопросы, выполнять перевод с одного языка на другой и решать многие другие задачи. Различные варианты
LLM внесли значительный вклад в область обработки естественного языка.
Ожидается, что их влияние будет расти по мере разработки новых приложений и вариантов использования. В этой главе мы начнем наше знакомство
с увлекательной областью NLP.
Структура
В этой главе мы рассмотрим следующие темы:
• Введение в обработку естественного языка.
• Обработка естественного языка с помощью NLTK.
• Обработка естественного языка с помощью spaCy.
• Векторное представление слов (эмбеддинг).
• Эмбеддинг в TensorFlow.
• Эмбеддинг с помощью GloVe.
Цели
В этой главе мы познакомимся с основными методами обработки естественного языка. Мы научимся пользоваться распространенными библиотеками
Python, такими как Spacy и NLTK, для обработки естественного текста в исходной форме. Мы познакомимся с концепцией эмбеддинга (embedding), то
есть представления слов в виде векторов, и ее реализацией в библиотеках
Gensim и TensorFlow. Также мы научимся получать векторные представления
слов с помощью алгоритма обучения без учителя GloVe.
Введение в обработку
естественного языка
Естественные языки, на которых говорят люди, развивались на протяжении
тысячелетий. То, что человеку кажется простым и понятным, для машины
может оказаться сложным и неоднозначным. Но если придумать способ
276
■
Pythonic AI
превратить текст или речь на естественном языке в удобную для компьютерного восприятия форму, то машины смогут легко и эффективно обрабатывать данные, содержащиеся в текстах и высказываниях. Машины хороши
тем, что безупречно выполняют повторяющиеся задачи, хотя заставить их
понимать наш язык не так-то просто. Если собрать достаточно данных и правильно обучить модель, то машины смогут найти закономерности, скрытые
в естественном языке, и научиться распознавать различные компоненты
языка, такие как слова, морфемы, грамматические формы, части речи, имена
собственные и т. п.
«Обработка естественного языка» — это, скорее, обобщающий термин, охватывающий различные области применения NLP, такие как анализ текста,
генерация (синтез) текста, машинный перевод, преобразование речи в текст,
разработка диалоговых систем, поиск информации и т. д. Текстовые данные
относятся к категории неструктурированных данных, то есть не имеющих какой-либо предопределенной структуры или схемы организации. В неструктурированных данных может присутствовать шум. Шумом считается любая
часть текста, не имеющая отношения к контексту данных. Машины с трудом
понимают необработанный естественный текст, и они могут обрабатывать
только четко определенные данные с ясной структурой, которые имеют
фиксированную длину и представлены в числовом виде. Следовательно, неупорядоченные текстовые данные нужно предварительно очистить, а затем
перекодировать из строкового типа в числовой с сохранением контекстной
информации и лингвистических особенностей текста. Для начала мы изучим
некоторые фундаментальные методы предварительной обработки, характерные для большинства задач обработки естественного языка.
Для очистки и предварительной обработки данных весьма полезными могут
оказаться базовые программы языка Python, описанные в главе 1 «Основы
Python. Концепции, библиотеки и написание кода». Кроме того, для разработки NLP-приложений широко используются две библиотеки Python: Natural
Language Toolkit (NLTK) и SpaCy.
Обработка естественного языка
с помощью NLTK
NLTK1 — это популярная библиотека Python с открытым исходным кодом.
Она предоставляет собой набор инструментов и ресурсов, используемых
1
Книга про NLTK: Bird, Steven, Edward Loper and Ewan Klein (2009), Natural Language
Processing with Python. O’Reilly Media Inc.
NLP как средство расширенного анализа текста
■
277
для решения различных задач обработки естественного языка, таких как токенизация, стемминг, тегирование, синтаксический анализ, семантическое
рассуждение и т. д.
NLTK включает в себя различные корпуса текстов, лексические ресурсы и алгоритмы, облегчающие работу с данными на естественном языке в Python. Среди
наиболее часто используемых функций NLTK — предварительная обработка
и очистка текста, тегирование частей речи, распознавание именованных сущностей, анализ настроений и языковое моделирование. NLTK широко используется в научных кругах, в промышленности и в исследованиях для решения
многочисленных ИИ-задач, таких как классификация текстов, анализ тональности текста (настроений), машинный перевод, поиск информации и т. д.
Библиотека NLTK предустановлена в блокноте Google Colab, и специально
устанавливать ее не нужно. Ею можно воспользоваться непосредственно
после импорта:
1. import nltk
Токенизация
Предложение, написанное на естественном языке (например, на английском),
может иметь произвольную длину. Поэтому во многих приложениях обработка
длинных предложений представляет собой довольно трудную задачу. Обычно
принято разбивать предложение на мелкие фрагменты, называемые «токенами».
В обработке естественного языка этот процесс называется «токенизацией».
В качестве токенов могут выступать любые слова, фразы, группы слов, цифры, аббревиатуры и прочие единицы, представляющие собой составные части предложения. После получения токенов из всех предложений в корпусе
данных можно создать словарь для уникальной идентификации и представления токенов в наборе данных.
В библиотеке NLTK имеется модуль tokenize(), содержащий набор подмодулей для различных типов токенизации. Модуль word_tokenizer() разбивает предложения на слова и знаки пунктуации, которые и используются как
токены. Пример такого разбиения предложения на токены (в переводе на
русский: «Она сказала: “Добрый день, мистер Рэй… могу я принести что-нибудь для вас?”») показан ниже:
1. from nltk.tokenize import word_tokenize
2. text = "She said, 'good afternoon Mr. Ray…may I bring something for you?'"
3. word_tokenize(text)
278
■
Pythonic AI
Но если запустить этот фрагмент кода в блокноте Google Colab впервые, то
он выдаст сообщение об ошибке доступа — ресурс punkt не найден, как это
показано на следующей иллюстрации:
Иллюстрация 8.1. Ошибка "punkt not found"
Библиотека NLTK содержит несколько встроенных текстовых корпусов
и обученных моделей. Полный их список можно найти по адресу: https://
www.nltk.org/nltk_data/.
Эти данные не поставляются непосредственно с библиотекой NLTK, и их
нужно устанавливать по мере необходимости с помощью загрузчика. При
этом можно загрузить как отдельные пакеты данных, так и всю коллекцию.
В данном случае нам нужно загрузить модель токенизатора punkt для работы
с пунктуацией в тексте с помощью показанной ниже команды:
1. nltk.download('punkt')
После загрузки ресурса punkt попробуем снова воспользоваться функцией
word_tokenize(), как показано ниже:
NLP как средство расширенного анализа текста
■
279
Иллюстрация 8.2. Токенизатор слов
На иллюстрации выше видно, что входное предложение содержит как слова,
так и знаки препинания. Функция word_tokenize() возвращает список этих
слов и знаков препинания в виде токенов.
Примечание. Вспомните функцию разбиения строк split() в Python,
которая делит строковое значение на подстроки на основе заданного
разделителя (по умолчанию это пробел). В этом отношении функция
word_tokenize() полностью аналогична функции split(). Однако
функция split() сможет выполнить токенизацию, только если все
токены будут разделены пробелами (или каким-либо другим одинаковым разделителем). Если функция split() соответствует целям того
или иного приложения, то можно воспользоваться и ею.
Подобно тому, как предложения делят на токены в виде слов и знаков пунктуации, можно поделить и текст на токены в виде предложений. Это делает
функция sentence_tokenize(), как показано на следующей иллюстрации:
280
■
Pythonic AI
Иллюстрация 8.3. Токенизатор предложений
Удаление стоп-слов
В NLP «стоп-слова» — это обычные слова, которые, как правило, удаляются
из текстовых данных. Для построения предложений на естественном языке
стоп-слова необходимы, однако считается, что они не несут практически никакого смысла для понимания содержания текста. В качестве примеров стопслов в английском языке можно привести следующие: the, and, a, an, in, of, to,
is (артикли, предлоги, вспомогательные глаголы и т. д.). Стоп-слова удаляются перед дальнейшей обработкой, поскольку они замедляют процесс обработки и анализа и не приносят особой пользы для понимания смысла текста.
Удаление стоп-слов помогает повысить эффективность и точность NLP-алгоритмов, выявляющих закономерности в текстовых данных.
Библиотека NLTK содержит списки стоп-слов из разных языков, и мы можем воспользоваться этими списками для удаления стоп-слов из текстовых
данных. Но, как и в случае с данными punkt, пакет stopwords сначала нужно
загрузить, как показано ниже:
1. import nltk
2. nltk.download('stopwords')
После загрузки данных stopwords можно импортировать их и вывести список стоп-слов для английского языка.
Иллюстрация 8.4. Стоп-слова английского языка
NLP как средство расширенного анализа текста
■
281
Как показано на иллюстрации выше, все стоп-слова хранятся в библиотеке
NLTK в нижнем регистре. Также можно посчитать их — всего в этой библиотеке находится 179 стоп-слов для английского языка. Воспользуемся этим
списком для удаления стоп-слов из текстовых данных.
Сначала введем текст и токенизируем его на уровне слов. Каждое слово текста проверим на принадлежность к списку стоп-слов, и составим окончательный список слов, содержащий только те, которые не являются стоп-словами
английского языка.
Иллюстрация 8.5. Удаление стоп-слов
Как показано на предыдущей иллюстрации, сначала мы создали переменную
words, представляющую собой пустой список. Эта переменная будет содержать все слова, не входящие в список стоп-слов английского языка. Затем
мы токенизировали исходный текст на уровне слов с помощью функции
word_tokenize(). Обратите внимание на то, что перед токенизацией текст
был переведен в нижний регистр. Поскольку слова в списке стоп-слов NLTK
написаны в нижнем регистре, слова текста можно сравнивать со словами из
этого списка, только если они также написаны в нижнем регистре. Иначе
первое слово "The", написанное с большой буквы, не будет считаться стоп-
282
■
Pythonic AI
словом. Далее каждое слово проверяется по списку стоп-слов и включается
(либо нет) в список words.
Совет. На иллюстрации выше показан цикл for, но все условие для
удаления стоп-слов и добавления остальных в список можно записать
одной следующей строкой:
1. words = [word for word in word_tokenize(text.lower())
if word not in stopwords.words("english")]
Такую упрощенную запись условий в Python называют «генератором
списка» (List Comprehension). Она позволяет создавать один список из
другого с помощью более короткого синтаксиса.
Иногда бывает нужно очистить текст от стоп-слов, но сохранить при этом
предложения. В этом случае текст делится на токены в виде предложений,
затем каждое предложение делится на токены в виде слов. После удаления
стоп-слов слова из получившегося списка слов соединяются в «очищенное»
предложение, как показано ниже:
Иллюстрация 8.6. Очистка предложений
NLP как средство расширенного анализа текста
■
283
Стемминг
Стемминг — это метод сокращения слов до их основы (или корня), который
используется в обработке естественного языка и улучшает качество анализа
текста. В тексте на естественном языке одно и то же слово может присутствовать в различных формах, определяемых грамматикой этого языка. Рассмотрим предложение на английском языке: “Running is a good exercise, but I hardly
run” («Бег — это хорошее упражнение, но я почти не бегаю»). В этом предложении базовое слово "run" присутствует в двух формах: "run" и "running".
Сохранять эти два слова как два разных токена в словаре часто бывает излишне и бесполезно. Все эти токены передают одно и то же значение, и в большинстве случаев их разделение не способствует улучшению понимания текста. Поэтому в процессе стемминга из слов удаляются префиксы, суффиксы
и другие аффиксы, после чего остается только основа. Слово "reading" будет преобразовано в основу "read", а слово "playing" — в основу "play".
Стемминг полезен в тех случаях, когда важно сгруппировать слова с разной
формой, но с одинаковым основным значением, однако стемминг может привести и к неточностям, поскольку не всегда учитывает контекст и конкретное
значение слова.
В NLTK можно воспользоваться одним из доступных стеммеров, таких как
Porter Stemmer, Snowball Stemmer или Lancaster Stemmer.
Porter Stemmer, или «стеммер Портера» был написан и опубликован в 1980 году Мартином Портером. Это один из наиболее широко используемых алгоритмов стемминга, в основе которого лежит ряд эвристических правил удаления распространенных английских суффиксов. Стеммер Портера отличается
простотой и быстротой, но иногда бывает слишком агрессивным и не всегда
справляется со всеми неправильными формами слов.
Преемником стеммера Портера стал более эффективный и настраиваемый
стеммер Snowball, основанный на тех же правилах, что и стеммер Портера,
но включающий дополнительные правила для работы с языками помимо английского.
Стеммер Lancaster, представленный в 1990 году Крисом Д. Пейсом, основан
на другом наборе правил, отличном от стеммеров Портера и Snowball. Он
более агрессивен и может работать с различными неправильными формами
слов, однако иногда выдает менее читаемые и узнаваемые результаты.
Ниже проиллюстрировано использование стеммера Портера в NLTK:
284
■
Pythonic AI
Иллюстрация 8.7. Стемминг
В этом примере мы сначала импортировали модуль PorterStemmer и создали
объект класса PorterStemmer. Затем мы применили к тексту функцию word_
tokenize(), чтобы получить отдельные слова, и применили метод stem()
объекта stemmer для обработки каждого слова. В результате все используемые в предложении формы глагола "play" («играть»), то есть played, playing
и play, были преобразованы в основу "play". Также обратите внимание и на
лишнее преобразование — на то, что слово "tennis" было неправильно преобразовано в "tenni".
Вместо стеммера Портера можно воспользоваться другим алгоритмом стемминга — Snowball Stemmer или Lancaster Stemmer.
Лемматизация
Лемматизация — это используемый в обработке естественного языка метод
сокращения слов до их начальной, или словарной формы, называемой «леммой». В отличие от стемминга, который просто удаляет из слов суффиксы
NLP как средство расширенного анализа текста
■
285
и окончания, при лемматизации учитываются контекст и принадлежность
слова к той или иной части речи, в результате чего это слово преобразуется в свою начальную форму. Полученная форма должна быть тем же словом
с тем же значением, что и изначальное.
Так, например, слово "ran" («бежал», прошедшее время глагола) при лемматизации становится словом "run" («бежать»), а слово "went" («шел») становится словом "go" («идти). Лемматизация бывает полезной во многих приложениях, где важно передавать основное значение слов.
Лемматизация обычно предполагает использование словаря и морфологического анализа для определения леммы слова на основе принадлежности
его к той или иной части речи. Этот процесс сложнее стемминга, но он, как
правило, дает более точные результаты. В то же время по сравнению со стеммингом лемматизация бывает более медленной и ресурсоемкой.
В NLTK можно воспользоваться лемматизатором WordNet, частью модуля
стеммера NLTK. WordNet — это большая лексическая база данных английского языка и мощный инструмент для задач обработки естественного языка
и компьютерной лингвистики. WordNet для библиотеки NLTK предоставляет
доступ к более чем 147 000 слов и фраз, организованных в группы синонимов, которые называются «синсетами» (synsets). Кроме того, инструментарий
NKTK содержит средства для работы с базой данных WordNet, включая поиск синсетов и родственных слов, поиск гиперонимов и гипонимов, а также
навигацию по графу WordNet. Воспользоваться лемматизатором WordNet
можно так, как показано на следующей иллюстрации:
Иллюстрация 8.8. Лемматизация в NTLK
Сначала мы импортировали необходимые для данного примера модули и создали
объект класса WordNetLemmatizer(). Затем мы использовали метод lemmatize()
объекта lemmatizer для лемматизации слов. В первом примере лемма слова
«trees» («деревья») была определена как «tree» («дерево»). Во втором примере слово «matrices» («матрицы») было лемматизировано как «matrix» («матрица»).
286
■
Pythonic AI
Учтите, что метод lemmatize() принимает еще один аргумент, а именно pos,
то есть часть речи (part of speech), к которому принадлежит слово. Допустимые
значения этого аргумента следующие: "n" — для существительных, "v" — для
глаголов, "a" — для прилагательных, "r" — для наречий и "s" — для сателлитных прилагательных. Если часть речи не указана, лемматизатор по умолчанию принимает ее за существительное, что не всегда приводит к желаемым
результатам. Поэтому важно указывать часть речи всегда, когда это возможно, особенно для глаголов. Рассмотрим следующий пример:
Иллюстрация 8.9. Лемматизация с параметром pos
Если бы мы попытались лемматизировать слово «walking» без указания на
часть речи, то получили бы «walking», то есть существительное со значением
«ходьба». Но если задать аргумент pos как «v» (verb, «глагол»), получим лемму
«walk» («ходить»).
POS-теггинг, или разметка частей речи
Под теггингом частей речи (POS, Part of speech) в обработке естественного
языка подразумевается определение принадлежности каждого слова в тексте
к одной из таких частей речи, как существительное, глагол, прилагательное,
наречие, местоимение, предлог, союз или междометие.
Для создания POS-тегов можно воспользоваться библиотекой NLTK. Для
правильного определения части речи, к которой принадлежит слово в предложении, POS-теггер библиотеки NLTK использует методы машинного «обучения с учителем», в частности, на основе перцептрона.
NLP как средство расширенного анализа текста
■
287
Чтобы воспользоваться POS-теггером из NLTK, нужно сначала загрузить два
пакета, которые называются tagsets и averaged_perceptron_tagger:
1. nltk.download('tagsets')
2. nltk.download('averaged_perceptron_tagger')
В модуле nltk.tag имеется функция pos_tag(). Сначала токенизируем текст на
уровне слов, чтобы получить список слов. Затем передадим список слов POSтеггеру, чтобы он сопоставил отдельные слова с соответствующими им частями речи. POS-теггер NLTK возвращает список кортежей, где каждый кортеж
содержит слово и соответствующий ему POS-тег, как показано на примере
ниже:
Иллюстрация 8.10. POS-теггинг
Как показано на предыдущей иллюстрации, всем словам были присвоены
метки частей речи. Метка «DT» означает определитель (determiner), «JJ» означает прилагательное, «NN» — существительное, «VBZ» — глагол в настоящем
времени, «IN» — предлог. Полный список POS-тегов (меток) с их подробным
значением можно просмотреть с помощью следующей команды:
288
■
Pythonic AI
Иллюстрация 8.11. Список POS-тегов
POS-теггер библиотеки NLTK поддерживает множество языков. Основной
язык текста входных данных можно указать в качестве аргумента lang функции pos_tag(). Этот аргумент принимает значение в виде кода языка по
стандарту ISO 639, например "eng" — для английского, "rus" — для русского и т. д.
Обработка естественного языка
с помощью spaСy
spaCy — это популярная библиотека Python с открытым исходным кодом,
предназначенная для решения сложных NLP-задач, включая токенизацию,
распознавание именованных сущностей (NER), синтаксический анализ зависимостей, классификацию текста и т. д. Она разработана с упором на эф-
NLP как средство расширенного анализа текста
■
289
фективность, простоту использования и масштабируемость и предоставляет
предварительно обученные модели для нескольких языков.
Библиотека spaCy широко используется для выполнения различных практических NLP-задач, таких как извлечение информации, анализ настроения
(эмоциональной окраски, или тональности), резюмирование текста, машинный перевод и т. д. Она построена на основе языка Cython, статически типизированного языка программирования, что позволяет ей быть чрезвычайно
быстрой и экономить память. Одно из ключевых преимуществ использования
spaCy — ее способность быстро и точно обрабатывать большие объемы текстовых данных. Она также предоставляет разработчикам и исследователям
удобный интерфейс для экспериментов с различными задачами и методами
обработки естественного языка. Кроме того, spaCy хорошо интегрируется
с другими популярными библиотеками Python, такими как NLTK, TensorFlow
и т. д., что позволяет пользователям создавать сквозные (end-to-end) конвейеры обработки естественного языка.
Архитектура библиотеки spaCy специально спроектирована с учетом эффективности, быстроты и масштабируемости. Она основана на следующих компонентах:
Language class. Language class («класс языка») — это основной компонент
spaCy, предоставляющий точку входа для обработки текста на определенном
языке. Language class загружает соответствующие модели и данные, необходимые для выполнения таких NLP-задач, как токенизация, синтаксический
анализ и распознавание именованных сущностей.
Vocabulary. Компонент Vocabulary («словарь») spaCy хранит все уникальные
слова и их атрибуты, встречающиеся в процессе обработки. Он сопоставляет
слова с соответствующими им целочисленными идентификаторами и эффективно выполняет обработку векторов слов.
StringStore. StringStore («хранилище строк») — это таблица поиска, сопоставляющая строки с целочисленными идентификаторами. Она используется для
уменьшения объема памяти spaCy, поскольку сохраняет одинаковые строки
только по одному разу.
Doc. Объект Doc — это центральная структура данных, используемая в spaCy
для представления обработанного текстового документа. Он содержит последовательность объектов token, которые представляют собой отдельные слова или лексемы в документе, с различными аннотациями, такими как POSтеги, зависимости и именованные сущности. Класс Language обрабатывает
290
■
Pythonic AI
вводимый текст и преобразует его в объект Doc, который обычно хранится
в переменной nlp.
Token. Объект token («токен») представляет собой слово или лексему в документе. Он содержит различные аннотации, такие как POS-теги, леммы и метки именованных сущностей.
Конвейер обработки (Pipeline). Конвейер обработки SpaCy состоит из ряда
компонентов, которые последовательно применяются к текстовому документу. Каждый компонент выполняет определенную NLP-задачу, такую как токенизация, POS-теггинг или распознавание именованных сущностей.
Модели (Models). Библиотека spaCy предоставляет предварительно обученные модели для нескольких языков, которые можно использовать для
решения различных NLP-задач. Эти модели обучены на больших массивах
текстовых данных, и их можно дополнительно настраивать для конкретных
сценариев использования. В целом архитектура spaCy является модульной,
что позволяет разработчикам настраивать и расширять ее функциональность в соответствии со своими требованиями.
Рассмотрим теперь основные возможности spaCy и покажем, как использовать их в коде на языке Python.
В Coogle Colab библиотека spaCy уже установлена, так что нам не понадобится устанавливать ее отдельно. Но в другой среде разработки вам, скорее всего, нужно будет установить эту библиотеку с помощью установщика
pip. Для этого наберите в терминале или в командной строке следующую
команду:
1. pip install spacy
Далее нам нужно будет загрузить одну или несколько языковых моделей или
конвейеров. Эти модели содержат необходимые данные и алгоритмы выполнения различных задач. Загружаются они следующей командой:
1. !python -m spacy download en_core_web_sm
Эта команда загрузит модель и конвейер английского языка en_core_web_sm,
обученный на корпусе письменных веб-текстов (блоги, новости, комментарии и т. д.), включая словарный запас, синтаксис и сущности. Более подробную информацию о модели можно найти на сайте: https://spacy.io/models/en.
NLP как средство расширенного анализа текста
■
291
Токенизация
После загрузки языковой модели можно использовать ее для обработки текста. На следующей иллюстрации показан код для загрузки языковой модели
и ее использования для токенизации текстов на уровне слов и предложений:
Иллюстрация 8.12. Токенизация
Сначала мы импортировали библиотеку spaCy и загрузили конвейер английского языка en_core_web_sm. Затем мы создали объект Doc, передав текстовую
строку языковому объекту nlp. Объект doc представляет собой последовательность лексических объектов-токенов. Каждый токен содержит информацию о фрагменте или слове. После этого, перебирая токены в объекте Doc, мы
выводили их на экран с помощью атрибута .text. Так в библиотеке spaCy выполняется токенизация на уровне слов. Для токенизации на уровне предложений из объекта doc нужно получить токены атрибута sents. Токенизация
292
■
Pythonic AI
предложений не всегда дает корректные результаты, поэтому пользоваться
ею нужно осторожно.
Примечание. Вместо того чтобы передавать текст на вход языковому
объекту непосредственно, его можно считывать из файла. Для этого
нужно указать путь к файлу и использовать функцию read_text(), как
показано в примере кода ниже:
1. import pathlib
2. input_file = «file.txt»
3. doc = nlp(pathlib.Path(input_file).read_text(encoding=»utf-8»))
Для каждого извлеченного токена можно получить некоторые сведения, вроде того, является ли токен цифрой, состоит ли он только из букв, является
ли он знаком препинания, и т. п. Пример получения таких сведений показан
ниже:
Иллюстрация 8.13. Токены библиотеки spaCy
Как показано на предыдущей иллюстрации, лексема «3» отмечена как цифра
(digit). Токены «,» и «.» отмечены как знаки препинания. Стоп-слова из заданного текста можно удалить с помощью атрибута is_stop, как показано
в следующем фрагменте кода:
NLP как средство расширенного анализа текста
■
293
1. for token in doc:
2. if not token.is_stop:
3.
print(token.text)
Эти команды проверяют, является ли извлеченный токен стоп-словом или
нет. Если нет, то токен будет напечатан.
POS-теггинг, или разметка частей речи
Ранее мы размечали части речи (POS-теггинг) с помощью библиотеки NLTK.
Под POS-теггингом подразумевается процесс присвоения каждому слову в тексте грамматических категорий (таких как существительное, глагол,
прилагательное и т. п.). POS-теггер spaCy основан на статистической модели,
обученной на большом корпусе текстов. Чтобы получить доступ к POS-тегам (меткам принадлежности к частям речи) для каждого слова, можно перебрать все лексемы в объекте Doc и вывести атрибут pos_ каждого токена, как
показано на следующей иллюстрации:
Иллюстрация 8.14. POS-теги в spaCy
Здесь мы воспользовались атрибутом pos_, чтобы вывести на печать POS-тег
для каждого токена в предложении.
294
■
Pythonic AI
Распознавание именованных сущностей
Распознавание именованных сущностей (NER, Named Entity Recognition) —
это процесс идентификации в тексте и классификации имен собственных
(таких как имена людей, названия мест, организаций и т. п.). Модель NER
в библиотеке spaCy основана на статистической модели, обученной на большом корпусе текстов. Чтобы получить доступ к именованным сущностям
в объекте Doc, можно выполнить итерацию по свойству ents объекта Doc,
как показано на следующей иллюстрации:
Иллюстрация 8.15. Распознавание именованных сущностей (NER) в spaCy
Здесь в цикле for мы выполняли итерации по элементам свойства ents, выводя текст и метку каждой именованной сущности в предложении. На выходе получился текст с указанием соответствующих меток, где NORP означает
национальность или принадлежность к определенной религиозной или политической группе (Nationality or Religious or Political groups) , а GPE — «геополитическую сущность» (Geo-Political Entity). Список именованных сущностей, доступных в текущем языковом контейнере, можно вывести с помощью
команды, показанной на следующей иллюстрации:
NLP как средство расширенного анализа текста
■
295
Иллюстрация 8.16. Список именованных сущностей в spaCy
Разбор зависимостей
Разбор зависимостей (dependency parsing) — это процесс анализа грамматической структуры предложения и присвоения синтаксических ролей словам в предложении. Парсер зависимостей spaCy использует статистическую
модель для анализа синтаксических связей между словами в предложении
и представляет эти связи в виде дерева зависимостей. Для просмотра зависимостей можно итеративно перебирать токены в предложении с указанием
дочернего атрибута токена, как показано в фрагменте кода на следующей иллюстрации:
296
■
Pythonic AI
Иллюстрация 8.17. Разбор зависимостей в spaCy
Здесь атрибуты dep_ и head используются для вывода метки зависимости
и предсказанного главного слова (head) для каждого токена в предложении,
а также списка возможных зависимых слов (children).
Библиотека spaCy предоставляет инструмент визуализации под названием
Displacy, позволяющий представлять структуру зависимостей и именованные
сущности предложения или документа в графическом виде. Displacy построен
на основе возможностей синтаксического разбора и распознавания сущностей spaCy, и с его помощью легко изучать связи между словами в предложении и сущностями, имеющимися в тексте. Для визуализации зависимостей
в предложении воспользуемся функцией displacy.render(), как показано
на следующей иллюстрации.
Иллюстрация 8.18. Визуализация разбора зависимостей
В этой визуализации каждое слово представлено в виде узла, а стрелки показывают синтаксические связи между ними. Надписи у стрелок указывают на
тип зависимости между словами.
NLP как средство расширенного анализа текста
■
297
Для визуализации именованных сущностей нужно в функции displacy.
render() указать стиль «ent», как это показано на следующей иллюстрации:
Иллюстрация 8.19. Визуализация NER
Код выше выделяет цветом именованные сущности в данном предложении.
При этом цвет каждого выделенного токена зависит от типа именованной
сущности.
Displacy — это мощный инструмент для изучения структуры предложений
и документов, позволяющий легко визуализировать связи между словами
и именованными сущностями, имеющимися в тексте. Особенно он полезен
для работы с результатами разбора зависимостей и распознавания сущностей в библиотеке spaCy.
Лемматизация
Процесс извлечения начальной формы, или леммы слова, с помощью библиотеки spaCy довольно прост. В spaCy имеется встроенная поддержка лемматизации, благодаря чему применять ее для документа легко. На следующей
иллюстрации показан пример такой лемматизации для предложения на английском языке “The tourists went to the forest yesterday. Today they are going to
the temple” («Вчера туристы ходили в лес. Сегодня они идут в храм»):
Иллюстрация 8.20. Лемматизация с помощью spaCy
298
■
Pythonic AI
В данном случае из заданного текста мы создали объект spaCy doc и выполнили итерацию по каждой лексеме doc, выводя на печать текст каждой лексемы
и ее лемму. Обратите внимание, что процесс лемматизации в spaCy учитывает части речи. Например, лемма глагола "went" («ходили») — это "go" («идти»), то есть неопределенная форма (инфинитив), а лемма существительного
в множественном числе "tourists" («туристы») — это "tourist" («турист»)
в единственном числе.
Теперь, зная как находить леммы и другие атрибуты токенов, мы можем рассчитать частоту встречаемости токенов в тексте, как показано на следующей
иллюстрации:
Иллюстрация 8.21. Подсчет слов
Как показано на предыдущей иллюстрации, сначала мы из библиотеки коллекций Python импортировали Counter (счетчик). На вход мы подали длинный текст. Затем мы определили объект doc и создали генератор списка.
В генераторе списка мы проверили каждый токен token в объекте doc на принадлежность к стоп-словам или знакам препинания. Также мы проверили,
состоит ли токен только из букв алфавита или нет. В случае, когда все нужные
условия выполнены, извлекалась лемма токена. Наконец, мы использовали
функцию Counter() для подсчета количества этих слов-лемм в данном тексте
и вывели пять наиболее часто встречающихся слов.
NLP как средство расширенного анализа текста
■
299
Сходство
Библиотека spaCy позволяет вычислять сходство между двумя документами,
последовательностями или токенами с помощью метода similarity(). Метод возвращает оценку сходства в виде числа от 0 до 1, где 1 означает, что два
объекта одинаковы, а 0 означает, что они совершенно несхожи.
Вот пример вычисления сходства в spaCy между двумя документами:
Иллюстрация 8.22. Сходство
В первой ячейке мы загрузили в объект nlp модель en_core_web_sm и создали из двух текстов два объекта doc. Затем мы воспользовались методом
similarity() для оценки сходства между этими двумя doc-объектами. Обратите
внимание, в первой ячейке на выходе показана оценка сходства с предупреждением о том, что в используемую нами модель не загружены векторы слов,
поэтому результат метода сходства основывается на теггинге, парсере и методе NER, которые могут и не давать полезного вывода о сходстве. Поэтому во
второй ячейке мы воспользовались более крупным модулем en_core_web_md,
применив ту же функцию similarity(). В этом случае оценка сходства будет
выше, что ожидаемо. Перед тем как пользоваться какой-то новой языковой
моделью, не забудьте загрузить ее с помощью следующего кода:
1. !python -m spacy download en_core_web_md
300
■
Pythonic AI
Итак, как мы уже сказали, оценка сходства — это показатель между 0 и 1,
говорящий о степени сходства между двумя документами. Аналогичным образом можно вычислить сходство между двумя последовательностями или
токенами. Обратите внимание, метод similarity() основан на векторах
слов, которые модель создает в процессе обучения. Поэтому этот показатель
не всегда бывает точным и им нужно пользоваться с осторожностью.
Векторное представление слов (эмбеддинг)
Мы знаем, что алгоритмы и модели искусственного интеллекта не понимают
значения текста, или переменных строкового типа. Поэтому для работы с естественным языком нужно преобразовать текст в какое-то числовое представление. Существует множество способов преобразования текстовых значений в числовые. Самый простой из них — однократное кодирование, или
«кодирование с одним активным битом» (one-hot encoding).
Иллюстрация 8.23. Однократное кодирование
NLP как средство расширенного анализа текста
■
301
При однократном кодировании каждое слово преобразуется в вектор, содержащий только нули и единицы. Для начала нужно взять уже существующий
словарь (список слов) или составить его из всех уникальных слов входного
текста. Размер вектора слова будет равен размеру словаря корпуса (текста).
В векторе конкретного слова на словарной позиции этого слова стоит единица, а в остальных позициях стоят нули. Ниже показан пример кодирования
слов заданного предложения, для которого мы воспользовались функцией OneHotEncoder из библиотеки Scikit-Learn. Как показано на предыдущей
иллюстрации, сначала мы разделили текст, чтобы получить слова-токены,
преобразовали список в массив NumPy и переформировали его так, чтобы
каждый объект в нем сам стал массивом. Далее мы трансформировали его
в объект OneHotEncoder. Всего в предложении 12 уникальных слов, и, следовательно, длина вектора равна 12. В выходном массиве каждое слово в предложении представлено вектором с длиной, равной 12. В каждом векторе в позиции, соответствующей позиции данного слова в словаре, стоит единица.
Атрибут categories_ показывает словарь и позиции слов в нем.
Основной недостаток однократного кодирования заключается в том, что этот
метод не позволяет передавать контекстную информацию о тексте. Кроме того, с увеличением количества слов увеличивается размер вектора, представляющего каждое слово.
Более качественное кодирование можно выполнить с помощью частотного
векторизатора. Этот метод работает аналогично однократному кодированию,
но вместо двоичных значений (1 или 0) позиции слова в словаре частотный
векторизатор выдает общую частоту слова в предложении. Благодаря этому
такой метод позволяет передать больше контекстной информации, чем метод
однократного кодирования. Пример частотной векторизации показан на следующей иллюстрации:
Иллюстрация 8.24. Частотный векторизатор
302
■
Pythonic AI
В качестве образца мы взяли текст о Луне со страницы Википедии, расположенной по адресу: https://en.wikipedia.org/wiki/Moon. Как показано на предыдущей иллюстрации, функция CountVectorizer из библиотеки ScikitLearn
сначала определяет уникальные слова в заданном тексте и создает из них словарь. Затем она преобразует целевое предложение (“The Earth and the Moon
form a satellite system”) в вектор. Длина вектора — это длина словаря. Элементы вектора — это показатели частоты словарных слов в целевом предложении. Методу частотной векторизации присущи те же недостатки, что и методу однократного кодирования.
Для оценки важности слова в документе относительно всего корпуса документов широко используется такая статистическая мера, как TF-IDF (Term
Frequency-Inverse Document Frequency). В частности, в обработке естественного языка и в информационном поиске она позволяет определить релевантность документа поисковому запросу.
Показатель TF-IDF рассчитывается посредством умножения частоты слова
(TF, Term Frequency) на обратную частоту (inverse frequency) этого слова во
всем корпусе документов. Частота слова («термина») говорит о том, насколько часто оно встречается в нашем документе, а обратная частота показывает,
насколько редко слово используется во всех документах корпуса. Произведение этих показателей дает оценку, отражающую важность слова в документе
или в корпусе.
Упомянутый выше частотный векторизатор подсчитывал частоту слова в документе. Но высокая частота еще не свидетельствует о высокой важности
этого слова в корпусе. Поэтому нужно принимать во внимание и показатель
IDF (обратной частоты). Формула для IDF выглядит следующим образом: IDF
= log (N/df); где N — общее количество документов в корпусе, а df (document
frequency, частота документа) — количество документов, содержащих данный термин. Если термин встречается во многих документах, его значение
IDF будет низким, что указывает на его низкую значимость. И наоборот, если
термин встречается в небольшом количестве документов, его значение IDF
будет высоким, что указывает на его значимость. На следующей иллюстрации показано, как с помощью функции TfidfVectorizer библиотеки ScikitLearn можно закодировать слова конкретного предложения:
NLP как средство расширенного анализа текста
■
303
Иллюстрация 8.25. Векторизатор Tf-IDF
Помимо простых форм векторизации слов со временем были разработаны
и более сложные, такие как «эмбеддинг». Эмбеддинг — это фундаментальная концепция в сфере обработки естественного языка, произведшая настоящую революцию в способах представления и анализа текстовых данных.
Проще говоря, эмбеддинг слов — это числовое представление слов или фраз
в высокоразмерном пространстве, где каждое измерение соответствует некоей характеристике или некоему атрибуту слова или фразы. Эти числовые
представления отражают контекст, а также семантические и синтаксические
связи между словами, что делает данную концепцию полезной для широкого спектра NLP-задач, таких как классификация текстов, машинный перевод
и поиск информации.
Существует несколько методов эмбеддинга, и один из самых распространенных — это модель word2vec, разработанная исследователями компании
Google. Модель word2vec представляет собой нейросетевую архитектуру, которая обучается определять связи между словами, создавая их распределенные представления на основе больших коллекций текстовых данных. В модели word2vec используется неглубокая нейронная сеть с одним скрытым слоем
для обучения эмбеддингу слов из большого корпуса текстов. Она обучается
предсказывать контекст, в котором в тексте встречается каждое слово. Существуют две основные разновидности Word2Vec:
304
■
Pythonic AI
• Continuous Bag of Words (CBOW). Модель CBOW предсказывает центральное слово контекста, учитывая окружающие слова в качестве
входных данных. Это означает, что слова контекста используются для
предсказания целевого слова в центре фразы. Для примера возьмем
текст «the fat cat sat on the mat» («толстый кот сидел на коврике»), из которого модель CBOW извлекает слова «the», «fat», «sat» и «on» и предсказывает центральное слово «cat», как показано в таблице 8.1:
the
fat
?
sat
on
rhe
Mat
Таблица 8.1. Предсказание контекстного слова с помощью CBOW
• Skip-gram. Модель skip-gram, наоборот, предсказывает окружающие
слова, получая на вход центральное слово. Принцип ее работы показан
в таблице 8.2.
?
?
cat
?
?
Таблица 8.2. Предсказание контекста по модели skip-gram
Полученные эмбеддинги представляют собой векторы вещественных чисел,
которые отражают семантические и синтаксические связи между словами
и основаны на их совместной встречаемости в обучающих данных.
Другой популярный алгоритм для эмбеддинга слов без учителя — это Global
Vectors (GloVe)1, основанный на методах матричной факторизации. GloVe
ориентирован на поиск глобальной статистики совпадений слов в корпусе
и использует ее для вычисления эмбеддингов слов на основе решения задачи
взвешенных наименьших квадратов.
Вот некоторые ключевые свойства эмбеддингов слов:
• Высокая размерность. Эмбеддинги слов обычно имеют сотни или тысячи измерений, в зависимости от объема обучающих данных и сложности модели. Высокая размерность позволяет эмбеддингам отражать
широкий спектр характеристик и атрибутов слов, что делает их весьма
подробными.
• Плотность. В отличие от разреженных представлений, таких как однократное кодирование, эмбеддинги слов отличаются плотностью, то
1
Pennington, Jeffrey, Richard Socher, Christopher D. Manning. “Glove: Global vectors for word
representation”. В сборнике Proceedings of the 2014 conference on empirical methods in
natural language processing (EMNLP), стр. 1532–1543. 2014.
NLP как средство расширенного анализа текста
■
305
есть большинство элементов в векторе имеют ненулевые значения. Это
делает их более эффективными в моделях машинного обучения, поскольку позволяет легко обрабатывать данные с помощью матричных
операций.
• Распределенное представление. Каждый элемент вектора эмбеддинга
представляет собой отдельную характеристику или атрибут слова, например его значение, часть речи или синтаксическую роль. Эти признаки не маркируются и не определяются явно, а выявляются в процессе
обучения на основе закономерностей пересечения слов в обучающих
данных.
• Переносимость. эмбеддинги слов, полученные для одной задачи или
области, часто можно переносить в другую задачу или область при условии, что они отражают соответствующие характеристики и атрибуты слов. Например, эмбеддинги слов, полученные из большого корпуса
новостных статей, могут оказаться полезными для анализа настроений
в социальных сетях, если слова и их значения в этих областях схожи.
Важно отметить, что эмбеддинги слов нельзя назвать панацеей от всех проблем, встречающихся в области обработки естественного языка. У них есть
свои ограничения и недостатки, такие как чувствительность к смещению
данных (предвзятости текстов) и неспособность отражать сложные лингвистические явления.
Один из способов реализации эмбеддингов слов в Python предоставляет
библиотека gensim. Это библиотека с открытым исходным кодом, используемая для выполнения некоторых NLP-задач. В Google Colab библиотека
gensim установлена по умолчанию, так что нам не придется устанавливать ее
отдельно.
Перед тем как воспользоваться моделями CBOW и Skip-gram из библиотеки gensim, нам нужно определить обучающий набор данных. Создадим этот
набор данных из текстового файла цифровой книги. Воспользуемся простой
текстовой версией книги «Путешествия Гулливера» Джонатана Свифта на английском языке.
Сначала скачаем файл с текстом книги с сайта Project Gutenberg по адресу:
https://www.gutenberg.org/files/65473/65473-0.txt. Затем загрузим этот файл
в блокнот Google Colab с помощью значка «Файл» (File), расположенного
на левой боковой панели. Для создания обучающих данных прочитаем этот
.txt-файл с помощью spaCy и выполним токенизацию на уровне предложений
и слов, как показано на следующем рисунке:
306
■
Pythonic AI
Иллюстрация 8.26. Создание обучающих данных
Как показано на предыдущем рисунке, мы выбирали только те токены-слова,
которые состоят из букв алфавита, и преобразовывали их текст в строчные
буквы.
Иллюстрация 8.27. Модель gensim CBOW
NLP как средство расширенного анализа текста
■
307
Функция Word2Vec модели gensim реализует модели CBOW и Skip-gram. Параметр min_count содержит значение, позволяющее игнорировать все слова во входном тексте, частота которых меньше этого значения. Параметр
vector_size задает размер векторов слов. Параметр window указывает
значение максимального расстояния между текущим и предсказываемыми
словами в предложении. Параметр sg принимает только двоичные значения
(1 или 0) и указывает тип модели: Skip-gram или CBOW. Выполнение этой
функции показано на предыдущей иллюстрации.
Как показано на предыдущей иллюстрации, после обучения модели можно
получить эмбеддинг любого слова с помощью функции get_vector().
Можно также получить n слов, наиболее похожих на данное, с помощью функции most_similar(), как показано на следующей иллюстрации:
Иллюстрация 8.28. Наиболее похожие слова в модели CBOW
Как показано на предыдущей иллюстрации, если игнорировать стоп-слова, то самым похожим словом на слово «king» («король») оказывается слово
«emperor» («император»). Это сходство было правильно определено моделью
на основе предложенного текста.
Поскольку в результате создания эмбеддинга слов мы получаем векторы, то
степень схожести двух любых векторов можно найти с помощью косинусного сходства. Косинусное сходство измеряет косинус угла между двумя ненулевыми векторами в пространстве внутреннего произведения. Математически косинусное сходство (cosine similarity) между двумя векторами X и Y
определяется по формуле:
cosine_similarity(X, Y) = (X dot Y) / (||X|| * ||Y||)
308
■
Pythonic AI
где X dot Y — скалярное произведение X и Y, а ||X|| и ||Y|| модули векторов X
и Y соответственно. Для измерения косинусного сходства между двумя заданными векторами в библиотеке Gensim используется функция similarity().
Эту же формулу косинусного сходства можно реализовать и в NumPy, как
показано на следующей иллюстрации:
Иллюстрация 8.29. Косинусное сходство
Аналогичным образом можно разработать и обучить модель Skip-gram, как
показано на следующей иллюстрации:
Иллюстрация 8.30. Модель gensim Skip-gram
NLP как средство расширенного анализа текста
■
309
Обратите внимание, что показанные на предыдущей иллюстрации слова,
которые модель Skip-gram определила как максимально похожие на слово
«king» («король»), действительно кажутся более релевантными с точки зрения контекста (среди них присутствуют слова с такими значениями, как «император», «королева», «целый», «королевство» и т. д.).
Для последующего использования и загрузки обученной модели word2vec
при необходимости можно сериализовать ее и сохранить. В этом случае для
моделей Gensim можно воспользоваться методами save() и load(), как показано на следующей иллюстрации.
Иллюстрация 8.31. Сохранение и загрузка модели в Gensim
При этом необязательно сохранять все состояние модели, особенно если не
требуется дальнейшее ее обучение. Можно сохранить только слова и сгенерированные ими векторы эмбеддингов. Попробуем сохранить только векторы
эмбеддингов, выводимые по атрибуту wv модели. Для загрузки этих векторов
нам понадобится модуль KeyedVectors, как показано на следующей иллюстрации:
310
■
Pythonic AI
Иллюстрация 8.32. Загрузка вектора в Gensim
При использовании метода load() с KeyedVectors векторы хранятся в памяти режиме «только для чтения».
Библиотека Gensim также предоставляет модели для векторизации предложений, абзацев или документов. Модель Gensim Doc2Vec создает эмбеддинги
для предложений, которые можно передавать в модель классификации в качестве входных данных для задач машинного обучения. Входные документы для модели Doc2Vec должны быть в формате TaggedDocument, который
представляет собой документ вместе с его тегом. Создадим итератор для генерации тегированных документов из имеющегося набора данных. Слишком
большие документы не загружаются в память целиком, а итератор позволяет
обрабатывать данные из потока, считывая их с диска по частям. В данном
случае обучение на созданных тегированных данных будет длиться в течение
30 эпох, как показано на следующей иллюстрации:
NLP как средство расширенного анализа текста
■
311
Иллюстрация 8.33. Векторизация документа в Gensim
Как показано на предыдущей иллюстрации, обученной моделью можно воспользоваться для вывода вектора эмбеддинга заданного предложения.
Эмбеддинг в TensorFlow
Эмбеддинги слов можно создавать и в TensorFlow. Для этого сначала нужно
создать словарь. Сделаем это с помощью токенов spacy на основе того же текстового файла книги «Путешествия Гулливера», как показано в следующем
блоке кода:
1. import pathlib
2. import spacy
3.
4. nlp = spacy.load('en_core_web_sm')
5. input_file = "book.txt"
6. doc
= nlp(pathlib.Path(input_file).read_text(encoding="utf-8"))
7.
8. vocab = []
9. for token in doc:
312
■
Pythonic AI
10.
if token.is_alpha:
11.
vocab.append(token.text.lower())
12.
vocab = list(set(vocab))
Из заданного текстового файла мы извлекли токены, выбрали те, которые состоят только из букв, и перевели их текст в строчные буквы. Таким образом
мы получили уникальный набор токенов и создали список-словарь.
Теперь из списка словаря создадим набор данных TensorFlow. Затем создадим
векторизатор vectorizer из слоя Keras TextVectorization. Слой TextVectorization
преобразовывает слова (строки) в соответствующие им словарные индексы.
Ниже показан соответствующий блок кода:
1. import tensorflow as
tf
2.
3. dataset = tf.data.Dataset.from_tensor_slices(vocab)
4.
5. vectorizer = tf.keras.layers.TextVectorization(
6. max_tokens=len(vocab),
7. output_mode='int',
8. output_sequence_length=10)
Мы указали максимальное количество токенов для векторизатора, это и будет длина словаря. Выходная последовательность будет иметь фиксированную длину, равную 10. Если на входе не будет десяти слов, выходная последовательность дополнится соответствующим количеством нулей.
Векторизатор vectorizer изучает словарь с помощью функции adapt. Для обработки больших наборов данных нужно указать размер пакета, как показано в следующем фрагменте кода:
1. vectorizer.adapt(dataset.batch(64))
Далее создадим модель с помощью векторизатора. Входной слой модели должен иметь длину (1,) — в качестве подтверждения, что на вход за раз передается ровно одна строка. Для создания модели воспользуемся последовательным (Sequential) API TensorFlow, как показано ниже:
NLP как средство расширенного анализа текста
■
313
1. model = tf.keras.models.Sequential()
2. model.add(tf.keras.Input(shape=(1,), dtype=tf.string))
3. model.add(vectorizer)
Модель просто сопоставляет входные слова (строки) с целыми числами, то
есть с индексами словаря. Обучать явно модель не нужно. Теперь можно воспользоваться этой моделью векторизатора для предсказания векторизованной
формы любой входной строки, как показано на следующей иллюстрации:
Иллюстрация 8.34. Предсказание с помощью векторизатора
В заданном входном предложении шесть слов. Получив словарные индексы
каждого слова, модель сформировала вектор этого предложения. Поскольку слов всего 6, а длина выходной последовательности, заданная для модели,
равна 10, в выходной сигнал вставляются 4 нуля. Если мы запустим цикл for,
чтобы получить значения для указанных словарных индексов, то получим те
же самые слова.
Теперь можно воспользоваться слоем эмбеддинга для сопоставления этих
целых чисел с обученными эмбеддингами. Для этой задачи в библиотеке
TensorFlow имеется слой Embedding, который принимает на вход аргумент
input_dim, представляющий собой не что иное, как размер используемого
словаря. Аргумент output_dim задает размерность плотного представления
эмбеддингов на выходе. Аргумент input_length — это длина входных последовательностей, которая в нашем случае равна 10. Пример работы этого
процесса показан на следующей иллюстрации:
314
■
Pythonic AI
Иллюстрация 8.35. Слой эмбеддинга
Слой Embedding принимает результат работы заданной ранее модели векторизатора vectorizer. В данном случае мы воспользовались тем же самым входным предложением. Как было показано ранее, выход модели векторизатора — это вектор размером 10. Форма выходных данных слоя Embedding имеет
вид (1×10×16), где 1 — размер пакета для одного предложения, 10 — количество слов (индексов) на входе, а 16 — размер векторов, сгенерированных для
каждого слова. Таким образом можно преобразовывать текстовые данные
в векторные эмбеддинги и использовать их в дальнейшем для NLP-задач.
Эмбеддинг с помощью GloVe
Ранее мы уже упоминали об алгоритме Global Vectors (GloVe) для создания
эмбеддинга слов. Это метод преобразования слов в векторы, разработанный сотрудниками Стэнфордского университета и работающий без учителя.
В одной из моделей GloVe имеются заранее определенные плотные векторы
примерно для 6 миллиардов слов английского языка. Другие предварительно
обученные модели содержат 42 и 840 миллиардов токенов.
NLP как средство расширенного анализа текста
■
315
Для начала нужно скачать предварительно обученную модель GloVe с ее домашней страницы1 и разархивировать ее с помощью следующих команд:
1. !wget "http://nlp.stanford.edu/data/glove.6B.zip"
2. !unzip "glove.6B.zip"
После распаковки мы получим четыре текстовых файла. Каждый из них содержит эмбеддинги слов с различными размерами вектора (50-мерный, 100мерный, 200-мерный и 300-мерный).
Пользоваться заранее заданной моделью GloVe в Gensim довольно просто. Для
этого нужно прочитать текстовый файл как KeyedVectors. Воспользуемся функцией load_word2vec_format() модуля KeyedVectors. Эта функция считывает
файл и загружает из него ключевые векторы KeydVectors. Затем этой моделью
можно пользоваться так же, как и другими моделями библиотеки Gensim:
Иллюстрация 8.36. Использование GloVe в Gensim
1
https://nlp.stanford.edu/projects/glove/
316
■
Pythonic AI
Заключение
В этой главе мы научились эффективно использовать различные основные
методы обработки естественного языка (NLP) для анализа текста. Мы узнали
о том, как пользоваться популярной библиотекой Python под названием NLTK
для токенизации, удаления стоп-слов, лемматизации, стемминга, тегирования частей речи и других NLP-задач. Еще одна подобная библиотека — это
spaCy, отличающаяся эффективностью, быстротой и масштабируемостью.
NLTK — это универсальная библиотека с широким функционалом, поддержкой сообщества и обучающими ресурсами. SpaCy делает упор на производительность, предлагает предварительно обученные модели и имеет более
удобный API. Выбор между этими двумя вариантами во многом зависит от
наших конкретных требований к NLP-задачам и от компромисса между простотой использования, производительностью и доступными ресурсами. Так
как модели машинного обучения могут понимать данные только в цифровом
виде, то для обработки текстовых данных применяются различные методы
кодирования. Один из важных и эффективных методов кодирования слов
в числовые векторы, хранящие контекстную информацию — это эмбеддинг
(векторизация слов). Модель word2vec предлагает нейросетевой подход к эмбеддингу посредством обучения на текстовых данных и получения распределенных представлений слов. Мы научились создавать модели word2vec,
CBOW и Skip-gram с помощью библиотеки Gensim. Получать эмбеддинги
слов можно и с помощью библиотеки TensorFlow. Слой векторизатора текста в TensorFlow векторизует слова в соответствии с их наличием в словаре.
Далее эти представления слов можно передавать в эмбеддинговый слой для
получения собственно эмбеддинга, то есть плотного вектора слова. Также
для генерации эмбеддинга слов вместе с библиотекой Gensim можно использовать предварительно обученные модели GloVe.
В следующей главе мы изучим и построим различные модели последовательностей для выполненияNLP-задач.
Основные выводы
• NLTK — это весьма эффективная библиотека Python для выполнения NLP-задач. Она помогает решать такие важные задачи по обработке текста, как токенизация, удаление стоп-слов, стемминг, лемматизация и POS-тегирование. Все эти задачи представляют собой
основные методы подготовки текстовых данных к анализу и моделированию.
NLP как средство расширенного анализа текста
■
317
• Spacy — это еще одна популярная библиотека Python для обработки естественного языка, предлагающая расширенные возможности анализа
текста. Помимо основных NLP-задач она позволяет выполнять распознавание именованных сущностей (NER-анализ), разбор зависимостей,
измерение сходства и другие подобные задачи.
• «Эмбеддинг» — это ключевое понятие в области NLP и принцип представления слов в виде плотных числовых векторов. Эти векторы отражают семантические связи между словами, что повышает производительность различных NLP-приложений. Векторы эмбеддинга из текста
можно создавать с помощью библиотеки Gensim, а также слоев эмбеддинга и векторизаторов текста библиотеки TensorFlow.
• GloVe (сокращение от Global Vectors for Word Representation) — это метод представления слов в виде «глобальных векторов». С его помощью
создают предварительно обученные модели эмбеддингов, позволяющие значительно экономить время и ресурсы. Эти высококачественные модели представления слов можно настраивать для разработки
конкретных приложений.
Ссылки
• Официальный сайт NLTK: https://www.nltk.org/
• Официальный сайт spaCy: https://spacy.io/
• Библиотека Gensim: https://radimrehurek.com/gensim/
• Векторизация текста в TensorFlow: https://www.tensorflow.org/api_docs/
python/tf/keras/layers/TextVectorization
• Эмбеддинг текста в TensorFlow: https://www.tensorflow.org/api_docs/
python/tf/keras/layers/Embedding
• GloVe: https://nlp.stanford.edu/projects/glove/
• Иллюстрированное объяснение Word2vec: https://jalammar.github.io/
illustratedword2vec
Глава 9
Запускаем модели
последовательностей
Введение
Препятствием для применения полносвязных нейросетевых моделей для обработки последовательных данных (таких как данные временных рядов, естественный язык и т. п.) служат переменная длина входных данных и размер
кодирующих векторов. Для обработки последовательных данных требуется
специально разработанная архитектура так называемых «моделей последовательностей». При обучении модели последовательностей выявляют временные зависимости и закономерности в последовательных данных, благодаря
чему удается применять гибкий подход к различным проблемам и пользоваться имеющимся знаниями в разных областях.
На моделях последовательностей основаны современные модели трансформеров, используемые в больших языковых моделях (таких как BERT, GPT и пр.),
поэтому для понимания принципа работы и построения этих новейших моделей нужно иметь представление о моделях последовательностей. В этой
главе мы как раз и начнем свое путешествие в этот увлекательный мир.
Структура
В этой главе мы рассмотрим следующие темы:
• Введение в модели последовательностей.
• Построение модели рекуррентной нейронной сети.
• Построение модели долгосрочной и краткосрочной памяти.
• Построение модели с управляемыми рекуррентными блоками.
Запускаем модели последовательностей
■
319
• Двусторонняя RNN.
• Языковая модель и генерация последовательностей.
Цели
В этой главе мы узнаем о том, какую важную роль играют модели последовательностей при выполнении задач, связанных с моделированием естественного языка. Мы познакомимся с различными архитектурами моделей
последовательностей, такими как рекуррентная нейронная сеть (RNN), двунаправленная RNN, модель долгой краткосрочной памяти (LSTM), модель
с управляемыми рекуррентными блоками (GRU), и построим эти модели
в TensorFlow 2. Мы узнаем, почему модели LSTM и GRU работают лучше, чем
стандартные модели RNN. Мы также познакомимся с языковым моделированием и научимся с его помощью генерировать последовательности. Мы построим модель последовательностей и обучим ее предсказывать следующие
символы на основе последовательности подаваемых на вход символов.
Введение в модели
последовательностей
За последние годы в сфере искусственного интеллекта удалось добиться
значительных успехов — в немалой степени благодаря «моделям глубокого
обучения», находящимся в авангарде исследований. Одно из направлений
глубокого обучения, в котором наблюдается заметный прогресс, — это моделирование последовательностей. Из главы 8 «NLP как средство расширенного
анализа текста» мы узнали о том, как текстовые данные (последовательности
слов или строк) преобразуются в векторы, или «эмбеддинги». Если эти векторы передавать в полносвязную нейронную сеть для предсказания текстовых
данных, то объем входных данных получится поистине огромным. Предположим для примера, что каждое слово задается вектором длиной 300 элементов, а в каждом предложении примерно 15 слов или более. Для того чтобы
обработать все эти данные, требуется специальная архитектура нейронной
сети, предназначенная именно для последовательностей. В этой главе мы
познакомимся с моделями последовательностей, узнаем, как они работают на
практике, и рассмотрим некоторые варианты их применения.
Модели последовательностей — это тип моделей машинного обучения, предназначенных для работы с данными, которые имеют последовательную или
временную структуру. К ним относятся данные временных рядов, тексты на
320
■
Pythonic AI
естественном языке, аудиозаписи, последовательности ДНК и т. д. Цель этих
моделей состоит в том, чтобы выявить возможные сложные зависимости
между элементами последовательности. Одна из основных проблем при разработке моделей последовательностей заключается в том, что длина входной
последовательности бывает разной. Например, при обработке естественного
языка длина предложений может варьироваться от нескольких слов до сотен.
Для того чтобы справляться с такой вариативностью, модели последовательностей должны уметь работать с входными данными разной длины.
Модели последовательностей обычно основываются на нейронных сетях.
Основным структурным элементом многих моделей последовательностей
служит рекуррентная нейронная сеть (RNN, Recurrent Neural Network). Рекуррентные нейронные сети предназначены для работы с цепочками данных.
RNN обрабатывают по одному элементу последовательности за раз, сохраняя
в себе информацию об уже обработанных элементах. Это сохраненное внутреннее состояние затем используется для предсказания следующего элемента. Рекуррентные нейронные сети доказали свою эффективность для многих
задач моделирования последовательностей, однако их недостатком является
так называемая проблема исчезающих градиентов. По мере обратного распространения градиенты, обновляющие параметры модели, со временем
значительно уменьшаются. В поисках решений этой проблемы исследователи разработали альтернативные модели последовательностей, такие как сети
долгой краткосрочной памяти и трансформеры.
Со времени своей разработки модели последовательностей нашли применение для решения различных задач, включая распознавание речи, машинный
перевод, анализ настроения (тональности), генерацию музыки. Среди этих
задач можно перечислить следующие:
Распознавание речи. Модели последовательностей используются для транскрипции устной речи в текст, и эти технологии применяются при создании
виртуальных помощников, в колл-центрах и в других сферах.
Машинный перевод. Модели последовательностей используются для перевода текста с одного языка на другой, и эти технологии находят применение
в международном бизнесе и дипломатии.
Анализ настроения (эмоциональной окраски). Модели последовательностей анализируют сообщения в социальных сетях и другие текстовые данные
для определения настроения авторов этих сообщений, что находит применение в маркетинге и службах поддержки клиентов.
Запускаем модели последовательностей
■
321
Генерация музыки. Модели последовательностей могут обучаться на наборе
музыкальных данных для создания новых композиций, что может быть полезным при написании музыки и в сфере развлечений.
Генерация подписей к изображениям. Модели последовательностей могут
генерировать текстовые последовательности в качестве подписей к заданному изображению, что находит применение в социальных сетях и поисковых
системах.
Модели последовательностей — это мощный инструмент обработки данных,
используемый для решения широкого круга задач в сфере искусственного интеллекта. В большой степени на моделях последовательностей основаны даже современные большие языковые модели (LLM, Large Language Models),
такие как ChatGPT. Модели LLM предсказывают распределения вероятности следующего слова по заданным предыдущим словам последовательности. Для этого в LLM используются модели последовательностей (например,
трансформеры), выявляющие дальнодействующие зависимости между словами в предложении, что очень важно для точного моделирования языка. Если вы интересуетесь распознаванием речи, обработкой естественного языка
или созданием музыки, то вам, как и любому специалисту в области ИИ, будет очень полезно познакомиться с основными принципами работы моделей
последовательностей.
Построение модели рекуррентной
нейронной сети
RNN — это тип нейронной сети, специально разработанный для работы
с последовательными данными. Основной структурный элемент RNN — это
один нейрон, или нейронная ячейка, которая принимает на вход текущий
элемент последовательности и внутреннее состояние с предыдущей ячейки. Выходные данные используются для обновления внутреннего состояния
и передачи информации следующей ячейке в последовательности.
В отличие от традиционных нейронных сетей, обрабатывающих входные
данные фиксированного размера, RNN могут обрабатывать входные последовательности переменной длины, что делает их удобными для таких задач,
как обработка естественного языка и распознавание речи. На каждом временном шаге внутреннее состояние RNN обновляется на основе текущего
входного элемента и предыдущего внутреннего состояния, и это обновлен-
322
■
Pythonic AI
ное состояние затем используется для предсказания следующего элемента
в последовательности.
Внутреннее состояние RNN часто называют скрытым состоянием, и его можно рассматривать как сводку информации, которую RNN извлекла из предыдущих элементов последовательности. Выходной сигнал RNN на каждом
временном шаге является функцией как текущего входного элемента, так
и скрытого состояния, и используется для предсказания следующего элемента последовательности.
Основы моделей RNN
Базовая архитектура традиционной RNN показана на следующей иллюстрации:
Иллюстрация 9.1. Базовая архитектура модели RNN
На каждом шаге RNN принимает определенную последовательность данных,
как показано горизонтальными стрелками на схеме выше. Например, если
на вход поступает некое предложение или слово, то они являются входными
данными шага 1, второе предложение или слово — это входные данные шага 2, и т. д. Входные данные ассоциируются с некоей матрицей входных весов.
Кроме того, на очередном шаге RNN получает еще одну порцию входных данных — вектор скрытого состояния предыдущего шага, как показано горизонтальными стрелками. С векторами скрытых состояний также ассоциированы
Запускаем модели последовательностей
■
323
веса. Так как для первого шага предыдущего состояния нет, то входящий вектор инициализируется нулями.
На каждом временном шаге RNN генерирует две порции выходных данных:
• Вектор скрытого состояния текущего временного шага, вычисляемый на
основе взвешенных сумм вектора скрытого состояния предыдущего временного шага и текущих входных данных. Как и в нейросетях других архитектур, эти выходные данные проходят через функцию активации —
как правило, функцию гиперболического тангенса (tanh) или ReLu.
• Текущие выходные векторы, генерируемые на основе взвешенных
сумм выходных весов и текущих векторов скрытых состояний. Эти выходные данные также проходят через функцию активации — обычно
сигмоидальную или softmax.
Итак, следует запомнить, что в RNN существуют три типа весов, или коэффициентов: веса, подаваемые вместе с входными данными; веса, подаваемые
вместе с векторами скрытых состояний и веса, подаваемые вместе с выходными векторами.
Все веса и коэффициенты смещения для всех временных шагов в скрытых
слоях одинаковы.
Иллюстрация 9.2. Схема архитектуры RNN в сжатом виде
Из иллюстрации 9.1 не следует делать вывод, будто модель RNN состоит из
нескольких слоев, где каждый соответствует очередному временному шагу.
324
■
Pythonic AI
Информация для следующего временного шага поступает из входных данных
и не заложена в архитектуру модели. Модель RNN, скорее, похожа на один
слой с циклом for, осуществляющим итерации по времени. Вектор скрытого
состояния, полученный на предыдущем временном шаге, возвращается в такой же слой на следующем временном шаге. На иллюстрации ниже архитектура RNN показана в более сжатом виде.
Функция потерь в RNN также должна распространяться назад по временным
шагам. На каждом шаге вычисляются потери, которые затем сводятся к общей величине (агрегируются) по всем шагам, а обратное распространение
ошибки также выполняется на каждом шаге.
Модель RNN обладает следующими преимуществами:
• RNN может обрабатывать входные данные любой длины, что делает ее
пригодной для работы с последовательными данными;
• размер модели не увеличивается с ростом объема входных данных;
• RNN хранит в памяти историческую информацию с предыдущих временных шагов.
Для архитектуры глубокой RNN характерно большое количество временных
шагов. При обратном ходе в глубокой RNN функция потерь должна распространяться от последнего временного шага к начальному, что предполагает
множество операций умножения производных для рекуррентного обновления весов. Если значения этих производных меньше 1, то результат многочисленных умножений может обратиться в 0. Так веса перестают обновляться,
и возникает так называемая проблема исчезающего градиента. Если значения
производных больше 1, то многочисленные умножения могут привести к так
называемой проблеме взрывающегося градиента. Это две основные проблемы
традиционных моделей RNN.
Различные архитектуры RNN
Конкретную архитектуру RNN выбирают в зависимости от типа решаемой
ИИ-задачи. На практике используются следующие типы архитектур RNN:
• RNN «один ко многим» (one-to-many). В данном типе архитектуры
один входной вектор и много выходных, следовательно, на выходе получается последовательность. Такую архитектуру используют, например, для генерации музыки, когда на вход подают одну ноту, а на выходе получают сгенерированную музыку, или последовательность нот.
Запускаем модели последовательностей
■
325
Упрощенная схема архитектуры «один ко многим» показана на следующей иллюстрации:
Иллюстрация 9.3. Архитектура RNN «один ко многим»
RNN «многие к одному» (many-to-one). На вход RNN архитектуры данного
типа подается последовательность, но на выходе генерируется только один
результат. Такую архитектуру используют для классификации тональности
(определения эмоциональной окраски текста), когда на вход подают текст или
последовательность слов, а на выходе получают двоичное значение (положительное или отрицательное) текста. Упрощенная схема архитектуры «многие
к одному» показана на следующей иллюстрации:
Иллюстрация 9.4. Архитектура RNN «многие к одному»
RNN «многие ко многим» (many-to-many). В RNN данной архитектуры на вход подаются последовательности, и на выходе тоже получаются
326
■
Pythonic AI
последовательности. Архитектура «многие ко многим» бывает двух типов.
Первый тип позволяет получать выход уже с первого временного шага. Подобный тип архитектуры мы уже рассматривали на иллюстрации 9.1. Его
используют для таких задач, как распознавание именованных сущностей,
расстановка POS-тегов и т. д. на основе входной последовательности. Другой тип архитектуры выдает результат после завершения всех временных
шагов, как показано на иллюстрации ниже. Такую архитектуру используют
для машинного перевода, когда на вход подают последовательность слов
на одном языке, а на выходе получают последовательность слов на другом
языке.
Иллюстрация 9.5. Архитектура RNN «многие ко многим»
Как показано на иллюстрации выше, длина входной и выходной последовательностей может быть разной. Для входной последовательности существует m временных шагов. После того как входные данные полностью поданы
в RNN, начинается выдача выходной последовательности, состоящей из (n-m)
временных шагов. Такой тип модели также называется «последовательность
к последовательности» (sequence-to-sequence) или «энкодер-декодер» (encoderdecoder, «кодировщик — декодеровщик»). Входной частью модели является энкодер, а выходной — декодер.
В приведенном выше примере машинного перевода с использованием архитектуры «многие-ко-многим» машины не переводят предложение или текст
по одному слову. Для передачи смысла текста нужно выявить семантику всего предложения или текста, и первое слово в выходной последовательности
Запускаем модели последовательностей
■
327
может определяться словами, присутствующими как в начале, так и в конце
входной последовательности. Выявление таких дальнодействующих зависимостей представляет собой достаточно сложную задачу для RNN. Основные
ограничения RNN — это проблемы исчезающего градиента и взрывающегося градиента. Эти ограничения позволяют преодолеть другие модели последовательностей, такие как LSTM, GRU и пр.
Построение RNN с помощью
TensorFlow
Модуль tensorflow.keras.layers библиотеки TensorFlow позволяет реализовать
простую архитектуру рекуррентной нейронной сети в виде слоя SimpleRNN.
Этот слой принимает последовательность входных данных и применяет
к каждому элементу последовательности набор рекуррентных преобразований, выявляя временные зависимости в этих данных. Слой SimpleRNN состоит из набора рекуррентных блоков, у каждого из которых два входа: на один
подаются текущие входные данные, а на другой — предыдущее состояние
рекуррентного блока. Выходные данные передаются в качестве входных следующему рекуррентному блоку в последовательности.
Класс SimpleRNN принимает в качестве аргумента количество рекуррентных
блоков. Это значение определяет размерность выходных векторов. Например, если задать для слоя SimpleRNN 64 рекуррентных блока, то на выходе
получим последовательность 64-мерных векторов, по одному на каждый
элемент входной последовательности. Также для модели RNN можно задать
функцию активации. Как и в случае со сверточной нейронной сетью (CNN)
и полносвязной нейронной сетью, в модели RNN можно использовать регуляризацию в виде дропаута. При этом в архитектуру модели можно добавить
отдельные слои дропаута или задать степень дропаута в аргументах dropout
и recurrent_dropout слоя. Для добавления регуляризации в слой также используются аргументы kernel_regularizer, recurrent_regularizer, bias_
regularizer и activity_regularizer с функцией регуляризации, как это
делалось для моделей CNN. Бинарный (булев) аргумент return_sequences
по умолчанию имеет значение false. Он определяет, должна ли модель возвращать только последний элемент выходной последовательности (как, например, в архитектуре «многие к одному») или всю последовательность (как,
например, в архитектуре «многие ко многим»). Другой бинарный аргумент
go_backwards (по умолчанию имеющий значение false) определяет, нужно
ли обрабатывать входную последовательность в обратном направлении, чтобы вернуть данные в виде обратной последовательности.
328
■
Pythonic AI
В процессе обучения веса слоя SimpleRNN обновляются с помощью метода
обратного распространения ошибки во времени (BPTT, Backpropagation
Through Time). Одно из ограничений слоя SimpleRNN — это упомянутая выше проблема исчезающего градиента, затрудняющая выявление дальнодействующих зависимостей во входной последовательности.
Слой SimpleRNN принимает на вход трехмерный тензор с формой (размер пакета, временные интервалы, входные признаки). Размер пакета представляет
собой количество образцов в каждой партии входных данных. Временные
интервалы — количество шагов в каждой последовательности входных данных. Входные признака — размерность каждого элемента входной последовательности. Слой обрабатывает каждую последовательность по одному временному шагу за раз. В результате получается последовательность выходных
векторов с тем же количеством временных шагов и заданным пользователем
количеством элементов.
Перед тем как создавать модель RNN, сформируем для начала случайные данные, как показано в следующем фрагменте кода:
1. import numpy as np
2. import tensorflow as tf
3.
4. batch_size = 32
5. timesteps = 10
6. input_dim = 20
7.
8. x = np.random.random((batch_size, timesteps, input_dim))
9. print("Shape of the data: ", x.shape)
Этот фрагмент кода создаст массив x с размерами (32, 10, 20). Далее нужно
определить форму входного тензора в соответствии с набором данных, как
показано ниже:
1. input_shape = (timesteps, input_dim)
2. inputs = tf.keras.Input(shape=input_shape, batch_size=batch_size)
Так как мы создаем функциональную модель TensorFlow Keras, мы воспользовались классом tf.keras.Input вместо tf.keras.layers.InputLayer. Класс
Input создает экземпляр тензора для модели RNN. Теперь с помощью функционального API можно создать модель RNN, как показано ниже:
Запускаем модели последовательностей
■
329
1. rnn_layer = tf.keras.layers.SimpleRNN(units=64)(inputs)
2. dense_layer = tf.keras.layers.Dense(units=32)(rnn_layer)
3. model = tf.keras.models.Model(inputs=inputs, outputs=dense_layer)
Здесь для слоя SimpleRNN мы задали 64 блока. К слою SimpleRNN можно добавлять другие слои (например, Dense). Наконец, мы создали модель, указав входы и выходы. Краткая сводка модели показана на следующей иллюстрации:
Иллюстрация 9.6. Сводка модели Simple RNN
Из главы 8 «NLP как средство расширенного анализа текста» мы узнали, что для
применения алгоритма искусственного интеллекта к текстовым данным нужно создавать эмбеддинги входной последовательности. Поэтому создадим слой
Embedding и подадим его выход на слой RNN. Из слоев SimpleRNN можно создавать стек, один слой за другим. При этом на выходе слоя одного SimpleRNN
получается двумерный тензор формы (размер партии, количество элементов),
тогда как последующий слой SimpleRNN ожидает на входе трехмерный тензор.
В таком случае нужно присвоить аргументу return_sequence значение True,
чтобы на выходе первой модели RNN генерировался 3D-тензор, как показано
ниже. Здесь мы воспользовались последовательным API (Sequential):
1. model= tf.keras.models.Sequential([
2. tf.keras.layers.Embedding(100,64),
3. tf.keras.layers.SimpleRNN(64, return_sequences=True),
4. tf.keras.layers.SimpleRNN(64),
5. tf.keras.layers.Dense(1, activation='sigmoid')])
330
■
Pythonic AI
Далее можно скомпилировать модель с нужными функциями потерь, оптимизатором и метриками и обучить ее. Функция обучения модели fit принимает трехмерные обучающие данные. Три измерения в них — это размер
пакета, количество временных шагов и размерность входного вектора.
Помимо встроенного слоя SimpleRNN, TensorFlow при разработке модели
RNN также дает возможность пользоваться API на уровне ячеек, например классом SimpleRNNCell. Это класс ячейки для простой RNN из модуля
tf.keras.layers. Слой SimpleRNN — это высокоуровневый слой, реализующий полносвязную архитектуру RNN. Он бывает полезен, когда вся последовательность входных данных доступна сразу в виде пакетов. Слой же
SimpleRNNCell — это низкоуровневый слой, реализующий базовую ячейку
RNN. Он принимает за один раз один фрагмент входных данных и обновляет
свое внутреннее состояние на основе текущей и предшествующей информации. Слой SimpleRNNCell позволяет точнее управлять архитектурой RNN —
например, при создании пользовательской структуры сети.
Итак, слой SimpleRNN — это абстракция более высокого уровня, реализующая полносвязную архитектуру RNN. Слой же SimpleRNNCell — это строительный блок более низкого уровня, реализация базовой ячейки RNN.
Модуль tf.keras.layers библиотеки TensorFlow содержит еще один слой
под названием RNN. Это базовый класс для рекуррентных слоев, и основное
отличие заключается в том, что SimpleRNN — это особый тип слоя RNN, реализующий только базовую архитектуру рекуррентной нейронной сети. Слой
RNN же — это более общий слой, допускающий реализацию других типов
архитектур рекуррентных нейронных сетей помимо базовой.
В частности, слой SimpleRNN — это базовый слой рекуррентной нейросети
с одним набором весов, общим для всех временных шагов входной последовательности. Он имеет фиксированный объем памяти, и в нем может проявляться проблема исчезающего градиента. С другой стороны, слой RNN
в TensorFlow — это более общий слой, позволяющий задавать тип архитектуры RNN — например, LSTM или GRU. В этих архитектурах используются разные веса, что позволяет им лучше справляться с исчезающими градиентами.
Теперь рассмотрим, как создать модель RNN с помощью API SimpleRNNCell.
Эта ячейка работает внутри цикла for слоя RNN. Для обработки пакетов
последовательностей задействуем SimpleRNNCell внутри слоя RNN. Как
это сделать, показано в следующем фрагменте кода. Заметим, что с математической точки зрения RNN(SimpleRNNCell(64)) дает тот же результат, что
и SimpleRNN(64). Однако встроенные слои обеспечивают лучшую производительность на GPU по сравнению с отдельными ячейками.
Запускаем модели последовательностей
■
331
1. import numpy as np
2. import tensorflow as tf
3.
4. batch_size = 32
5. timesteps = 10
6. input_dim = 20
7.
8. x = np.random.random((batch_size, timesteps, input_dim))
9.
10. input_shape = (timesteps, input_dim)
11. inputs = tf.keras.Input(shape=input_shape, batch_size=batch_size)
12. rnn_layer = tf.keras.layers.RNN(tf.keras.layers.
SimpleRNNCell(units=64)) (inputs)
13. dense_layer = tf.keras.layers.Dense(units=32)(rnn_layer)
14. model = tf.keras.models.Model(inputs=inputs, outputs=dense_layer)
Рассмотрим созданный выше rnn_layer. Мы создали экземпляр класса
SimpleRNNCell с 64 узлами «внутри» слоя RNN. Учтите, что API ячеек на входе предполагает двухмерный тензор данных, но поскольку ячейка встроена
(обернута) в слой RNN, на вход можно подавать трехмерный тензор.
Построение модели долгой
краткосрочной памяти (LSTM)
В анализе последовательных данных широко используется такой тип RNN,
как предложенная Хохрайтером и Шмидхубером модель долгой краткосрочной памяти1, или LSTM (Long Short-Term Memory). Модели LSTM позволяют решать проблему исчезающего градиента в традиционных RNN — общую
проблему, возникающую при обучении глубоких нейронных сетей.
Одно из ключевых преимуществ модели LSTM — это ее способность обрабатывать долговременные зависимости2 в последовательных данных. Традиционные RNN с трудом справляются с этой задачей, поскольку градиенты
1
2
Hochreiter, Sepp, and Jürgen Schmidhuber. “Long short-term memory”. Neural computation 9,
no. 8 (1997): 1735–1780.
Karpathy, Andrej, Justin Johnson, and Li Fei-Fei. “Visualizing and understanding recurrent
networks”. arXiv preprint arXiv:1506.02078 (2015).
332
■
Pythonic AI
при обратном распространении, как правило, со временем становятся все
меньше. Из-за этой проблемы исчезающего градиента производительность
модели ухудшается, так как модель не может выявить долговременные зависимости в данных.
Модели LSTM решают проблему исчезающего градиента с помощью так называемых «вентилей», которые избирательно отфильтровывают нерелевантную информацию и сохраняют в ячейке памяти только важные сведения.
Этот механизм позволяет модели LSTM поддерживать долговременные зависимости без затухания градиента.
LSTM-модели успешно используются во многих приложениях, в том числе
при моделировании языка, распознавании речи, анализе тональности (эмоциональной окраски), прогнозировании цен на акции и т. д. Для улучшения
производительности их также комбинируют с нейросетями другой архитектуры, такими как CNN и сети с механизмом внимания. Помимо анализа
последовательных данных, LSTM-модели используют для создании подписей
к изображениям, текстовых описаний изображений и в генеративном моделировании для создания новых последовательностей данных, таких как текст
или музыка.
Модели LSTM — это весьма эффективный тип RNN, позволивший добиться значительных успехов в анализе последовательных данных. Способность
моделей LSTM обрабатывать долговременные зависимости сделала их незаменимым инструментом во многих сферах. Архитектура LSTM продолжает
совершенствоваться ради увеличения производительности. По мере общего
развития сферы глубокого обучения, модели LSTM, вероятно, начнут играть
все более важную роль в обработке последовательных данных и других областях практического применения.
Основы LSTM моделей
Базовая структура LSTM модели состоит из ячейки памяти и трех вентилей:
входного, забывающего и выходного. Каждый компонент выполняет определенную функцию и взаимодействует с другими для обработки последовательных данных.
На следующей иллюстрации показана базовая ячейка памяти LSTM. Если
схема показалась вам сложной, не волнуйтесь; далее мы подробно рассмотрим ее компоненты.
Запускаем модели последовательностей
■
333
Иллюстрация 9.7. Архитектура LSTM
Ячейка памяти — ключевой компонент архитектуры LSTM. Она отвечает
за хранение прошлой информации и ее соответствие текущему состоянию,
а также за передачу информации с одного временного шага на другой. Эта
ячейка выборочно обновляет свое содержимое и позволяет ему распространяться по сети.
Входной вентиль контролирует поступление в ячейку памяти новой информации. Сигмоидальный слой определяет, сколько новой информации нужно
будет добавить в ячейку памяти. Если входной вентиль закрыт, новая информация в ячейку памяти не поступает. Как показано на иллюстрации выше,
входные данные текущего временного шага и выход предыдущего временного
шага конкатенируются (объединяются). Входной вентиль вычисляет сигмоидально-взвешенную сумму этих объединенных входных данных. Благодаря
сигмоидальной функции выход этого вентиля всегда находится в диапазоне
от нуля до единицы.
Вентиль забывания управляет потоком старой информации из ячейки памяти и определяет, какие части содержимого ячейки памяти нужно отбросить,
а какие сохранить. Если вентиль забывания закрыт, ячейка памяти сохраняет
всю свою информацию. Как показано на иллюстрации выше, вентиль забывания вычисляет сигмоидально-взвешенную сумму того же конкатенированного входа. Опять же, благодаря сигмоидальной функции, выход этого вентиля представляет собой значения от нуля до единицы.
334
■
Pythonic AI
Выходной вентиль управляет выходом сети LSTM, определяя, какую часть
содержимого ячеек памяти нужно выводить в качестве конечного результата.
Выходной вентиль представляет собой сигмоидальный слой, принимающий
конкатенированные входные сигналы, так что значение на выходе опять же
находится в диапазоне от нуля до единицы.
Взвешенная сумма конкатенированных входных данных, обработанных функцией активации гиперболического тангенса, поэлементно умножается на
выходе входного вентиля. Кроме того, информация из памяти за предыдущий временной шаг поэлементно умножается на выходе вентиля забывания.
Эти два результата поэлементно складываются и составляют информацию
в памяти на текущем временном шаге.
Информация памяти текущего временного шага, активированная функцией
гиперболического тангенса, поэлементно умножается на информацию, поступившую из выходного вентиля. Итоговое значение подается на вход для
следующего временного шага. Этот же результат рассматривается как выход
текущего временного шага после прохождения сигмоидальной функции активации.
Во время прямого прохода в модели LSTM входные данные и выходной сигнал предыдущего шага проходят соответственно через входной вентиль
и вентиль забывания. Эти вентили определяют важность входной и предыдущей информации для текущего состояния. Затем ячейка памяти обновляет
свое состояние на основе входных данных, прошлой информации и результата действия вентилей. Наконец, выходной вентиль вычисляет выходное
значение модели LSTM.
В процессе обучения LSTM-сеть обновляется с помощью метода обратного
распространения во времени (BPTT, Backpropagation Time), подразумевающего обратное распространение ошибки по сети и обновление весов каждого компонента на основе этой ошибки.
Существует также модифицированная версия модели LSTM, в ячейке памяти которой применяется так называемое «сквозное соединение» (peephole
connection, «глазок»)1. В этом случае к подаваемому на вентили конкатенированному входу добавляется информация из памяти за последний временной шаг.
1
Gers, Felix A., Nicol N. Schraudolph, and Jürgen Schmidhuber. “Learning precise timing with
LSTM recurrent networks”. Journal of machine learning research 3, no. Aug (2002): 115–143.
Запускаем модели последовательностей
■
335
Построение LSTM с помощью TensorFlow
В библиотеке TensorFlow средства реализации архитектуры модели LSTM содержатся в модуле tensorflow.keras.layers. Создаваемый им слой LSTM
хранит два вектора состояния: информацию памяти о состоянии ячейки
и выход скрытого состояния за последний временной шаг. Состояние ячейки — это долгосрочная память LSTM, а скрытое состояние — кратковременная память.
Класс LSTM также принимает число рекуррентных единиц в качестве аргумента, определяющего размерность выходных векторов. У этого слоя есть два
аргумента, связанных с активацией: activation и recurrent_activation. Значение аргумента activation (по умолчанию «tanh») задает функцию активации
для выхода ячейки. Эта функция активации применяется к выходу ячейки
перед отправкой этого выхода на следующий слой сети. Значение аргумента
recurrent_activation (по умолчанию «sigmoid») задает функцию активации
для рекуррентного шага ячейки LSTM. Рекуррентная связь в LSTM позволяет ячейке сохранять информацию предыдущих временных шагов. Функция
recurrent_activation определяет количество сохраняемой информации
о предыдущем состояния ячейки и количество новой информации, добавляемой к состоянию ячейки. Как было сказано выше, функция recurrent_
activation используется для активации у входных, забывающих и выходных
вентилей модели LSTM.
Аналогично слою SimpleRNN для слоя LSTM можно указывать аргументы
дропаута, регуляризаторов и тому подобных настроек. Кроме того, следует
помнить о том, что на вход слоя LSTM подается трехмерный тензор формы
(размер пакета, количество временных шагов, входная размерность признака). В показанном ниже фрагменте коде мы определили архитектуру LSTM
типа «многие к одному» с двумя последовательными слоями LSTM, за которыми следует плотный слой:
1. model = tf.keras.models.Sequential()
2. model.add(tf.keras.layers.Embedding(100,64))
3. model.add(tf.keras.layers.LSTM(64, return_sequences=True))
4. model.add(tf.keras.layers.LSTM(64))
5. model.add(tf.keras.layers.Dense(units=1))
На следующей иллюстрации показана сводка модели:
336
■
Pythonic AI
Иллюстрация 9.8. Характеристики (сводка) модели LSTM
Как и в случае со слоем SimpleRNNCell, библиотека TensorFlow также предоставляет API для LSTM на уровне ячеек. Слой LSTMCell представляет собой
строительный блок нижнего уровня, реализующий базовую ячейку RNN. Эту
ячейку LSTMCell для обработки пакета последовательностей можно обернуть слоем RNN, как показано в следующем фрагменте кода:
1. input_shape = (timesteps, input_dim)
2. inputs = tf.keras.Input(shape=input_shape, batch_size=batch_size)
3. lstm_layer = tf.keras.layers.RNN(tf.keras.layers.LSTMCell(units=64))
(inputs)
4. dense_layer = tf.keras.layers.Dense(units=32)(lstm_layer)
5. model = tf.keras.models.Model(inputs=inputs, outputs=dense_layer)
Построение модели управляемого
рекуррентного блока
В 2014 году Чо и его коллеги1 представили тип архитектуры модели последовательностей под названием GRU (Gated Recurrent Unit), управляемый
1
Cho, Kyunghyun, Bart Van Merriënboer, Dzmitry Bahdanau, and Yoshua Bengio. “On the
properties of neural machine translation: Encoder-decoder approaches”. arXiv preprint
arXiv:1409.1259 (2014).
Запускаем модели последовательностей
■
337
рекуррентный блок. Это упрощенная версия модели LSTM, которая требует
меньше затрат на вычисления и быстрее обучается. Несмотря на свою простоту, модель GRU показала многообещающие результаты1 в различных задачах, таких как распознавание речи, машинный перевод и создание подписей
к изображениям.
Архитектура модели GRU похожа на архитектуру модели LSTM с тем лишь
отличием, что в ней входной вентиль и вентиль забывания объединены
в один вентиль обновления. Этот вентиль обновления определяет количество информации о прошлом состоянии, передаваемой на следующий временной шаг, и количество включаемой информации о новом состоянии. Другой
вентиль, называемый вентилем сброса (reset gate), определяет, какую часть
прошлого скрытого состояния нужно забыть, и какую часть текущей входной
информации включить. Выход модели GRU определяется новым скрытым состоянием, которое вычисляется на основе предыдущего скрытого состояния
и текущего входа. Архитектура GRU показана на следующей иллюстрации:
Иллюстрация 9.9. Архитектура GRU
Как показано на иллюстрации выше, вентиль обновления и вентиль сброса модели GRU применяют сигмоидальную функцию к взвешенной сумме
выхода предыдущего шага (скрытого состояния предыдущего шага) и входа
1
Chung, Junyoung, Caglar Gulcehre, KyungHyun Cho, and Yoshua Bengio. “Empirical
evaluation of gated recurrent neural networks on sequence modeling”. arXiv preprint
arXiv:1412.3555 (2014).
338
■
Pythonic AI
текущего шага. При этом у обоих вентилей имеется свой собственный набор
весовых матриц и смещения. Оба вентиля применяют сигмоидальную функцию для того, чтобы выходные значения попадали в диапазон между нулем
и единицей.
Предварительное скрытое состояние вычисляется с помощью вентиля сброса, который принимает скрытое состояние предыдущего шага и вход текущего шага. Функция tanh используется для приведения предварительного скрытого состояния к значениям между минус единицей и единицей.
Выход вентиля обновления разделяется на две ветви. В одной из них значение
вычитается из единицы и поэлементно умножается на предыдущее скрытое
состояние, а в другой значение поэлементно умножается на предварительное
скрытое состояние. Окончательное скрытое состояние вычисляется посредством объединения предыдущего скрытого состояния и нового предварительного скрытого состояния, взвешенных вентилем обновления.
Модель GRU имеет ряд преимуществ перед моделью LSTM. У нее меньше
параметров, благодаря чему обучение становится менее затратным и более
быстрым. Она также отличается более простой и понятной архитектурой,
а кроме того ее легче реализовать. При этом во многих приложениях модель
GRU достигла производительности, сравнимой с производительностью модели LSTM.
Наряду со средствами разработки моделей RNN и LSTM в библиотеке
TensorFlow имеются средства разработки моделей GRU: модули tf.keras.
layers.GRU и tf.keras.layers.GRUCell. Задавать архитектуру GRU-модели
с помощью этих API довольно просто, как показано в следующем фрагменте
кода:
1. model = tf.keras.models.Sequential()
2. model.add(tf.keras.layers.Embedding(100,64))
3. model.add(tf.keras.layers.GRU(64, return_sequences=True))
4. model.add(tf.keras.layers.GRU(64))
5. model.add(tf.keras.layers.Dense(units=1))
В приведенном выше примере мы определили GRU-модель с помощью
tf.keras.layers.GRU. На иллюстрации ниже показана краткая характеристика модели:
Запускаем модели последовательностей
■
339
Иллюстрация 9.10. Краткая характеристика (сводка) GRU
Можно также задать GRU-модель, обернув tf.keras.layers.GRUCell
в tf.keras.layers.RNN, как показано в следующем фрагменте кода:
1. input_shape = (timesteps, input_dim)
2. inputs = tf.keras.Input(shape=input_shape, batch_size=batch_size)
3. gru_layer = tf.keras.layers.RNN(tf.keras.layers.GRUCell(units=64))
(inputs)
4. dense_layer = tf.keras.layers.Dense(units=32)(gru_layer)
5. model = tf.keras.models.Model(inputs=inputs, outputs=dense_layer)
Двунаправленная RNN
Стандартная рекуррентная нейронная сеть (RNN) обрабатывает последовательность слева направо (или справа налево). Это означает, что она учитывает только прошлый (или будущий) контекст каждого элемента в последовательности. Однако во многих случаях для прогноза важно учитывать как
прошлый, так и будущий контекст. Как раз для решения такого рода задач
и были предложены двунаправленные RNN.
340
■
Pythonic AI
Двунаправленная рекуррентная сеть (Bidirectional RNN)1 — это тип рекуррентной нейронной сети, обрабатывающий входную последовательность
в обоих направлениях, от начала к концу и от конца к началу. Другими словами, такая сеть имеет два скрытых слоя, один из которых обрабатывает последовательность в прямом направлении, а другой — в обратном. Два скрытых
слоя обычно подключены к общему выходному слою, объединяющему информацию из обоих направлений.
Двунаправленные RNN полезны в ситуациях, когда для обработки текущего
элемента важен будущий контекст последовательности. Например, при распознавании речи текущий звук может зависеть как от предшествующих, так
и от последующих звуков. Аналогично при обработке естественного языка
значение слова может зависеть от окружающих слов, находящихся в потоке
речи или в тексте как до этого слова, так и после него.
Архитектура двунаправленной RNN аналогична архитектуре стандартной RNN
с добавлением второго скрытого слоя, обрабатывающего последовательность
в обратном направлении. На каждом шаге входные данные поступают в прямой и обратный скрытые слои, вследствие чего выходных данных получаются
тоже два набора. Затем эти два набора выходных данных некоторым образом
объединяются или комбинируются для получения конечного результата.
На следующей иллюстрации показана архитектура двунаправленной RNN:
Иллюстрация 9.11. Двунаправленная RNN
1
Schuster, Mike, and Kuldip K. Paliwal. “Bidirectional recurrent neural networks”. IEEE
transactions on Signal Processing 45, no. 11 (1997): 2673–2681.
Запускаем модели последовательностей
■
341
Как видно из схемы, входная последовательность поступает как в прямой,
так и в обратный скрытые слои. Выходы прямого и обратного скрытых слоев
(зеленые стрелки) на каждом временном шаге объединяются и подаются на
сигмоидальную функцию активации.
Двунаправленные RNN успешно используют для создания различных приложений, выполняющих такие задачи, как распознавание речи, обработка
естественного языка и генерация подписей к изображениям. Вот несколько
примеров практического применения сетей такой архитектуры:
• Распознавание речи. При распознавании речи двунаправленные RNN
могут выявлять контекст как из прошлых, так и из будущих кадров речевого сигнала, благодаря чему повышается точность распознавания.
• Обработка естественного языка (NLP). Двунаправленные RNN способны выявлять контекст из предыдущих и последующих слов в предложении, благодаря чему повышается точность обработки естественного языка в таких практических областях, как языковое моделирование,
машинный перевод и анализ настроения (эмоциональной окраски).
• Генерация подписей к изображениям. При создании подписей к изображениям двунаправленные RNN учитывают контекст предшествующих и последующих частей изображения.
Для реализации двунаправленной RNN-модели в библиотеке TensorFlow предусмотрен слой tf.keras.layers.Bidirectional. Подобно слою tf.keras.
layers.RNN Bidirectional API также работает как обертка для других экземпляров слоев последовательностей. Для этого нужно задать аргумент layer класса-обертки Bidirectional равным одному из экземпляров слоя RNN, например
SimpleRNN, LSTM или GRU. В двунаправленной RNN конечный выход получается посредством объединения двух выходов из прямого и обратного
скрытых слоев. Для класса Bidirectional библиотеки TensorFlow способ объединения этих слоев можно задать, как аргумент merge_mode, принимающий
значение sum, mul, concat, ave или None. При значении None, два выхода не
объединяются и возвращаются в виде списка. Для класса Bidirectional можно
также задать обратный слой, отличающийся от прямого — при этом для аргумента backward_layer указывается один из экземпляров последовательного слоя. Если этот аргумент не задан, то по умолчанию в качестве обратного
слоя задается экземпляр прямого слоя. Если аргумент backward_layer задан,
экземпляры backward_layer и слоя layer должны иметь разные значения аргумента go_backwards. Пример задания двунаправленной рекуррентной сети
показан в приведенном ниже фрагменте кода:
342
■
Pythonic AI
1. input_shape = (timesteps, input_dim)
2. inputs = tf.keras.Input(shape=input_shape, batch_size=batch_size)
3.
4. bidirectional_layer = tf.keras.layers.Bidirectional(
5.
layer=tf.keras.layers.GRU(units=64, return_sequences=True),
6.
backward_layer=tf.keras.layers.LSTM(units=64,
return_sequences=True, go_backwards=True))(inputs)
7.
8. dense_layer = tf.keras.layers.Dense(units=1)(bidirectional_layer)
9. model = tf.keras.models.Model(inputs=inputs, outputs=dense_layer)
Итак, в показанном выше фрагменте кода мы определили двунаправленный
слой. Прямой слой — это GRU-слой, а в качестве обратного выбран LSTMслой. Обратите внимание на то, что для обратного слоя значение аргумента
go_backwards задано как True. Краткая характеристика модели показана на
следующей иллюстрации:
Иллюстрация 9.12. Краткая характеристика (сводка) двунаправленной модели
Двунаправленные RNN — это мощный расширенный вариант стандартных
RNN, способный учитывать прошлый и будущий контексты в последовательных данных. Если ваша работа связана с последовательными данными,
и вы хотите раскрыть весь потенциал определения контекста, как прошлого,
так и будущего, то вам определенно стоит поглубже изучить принципы работы двунаправленных рекуррентных сетей.
Запускаем модели последовательностей
■
343
Языковая модель
и генерация последовательности
В контексте обработки естественного языка под языковым моделированием подразумевается предсказание того, с какой вероятностью появляются
определенные слова или символы с учетом предыдущих слов или символов
последовательности. Выявляя закономерности и рассчитывая вероятности,
модель генерирует связный и контекстуально подходящий текст. По сути,
можно сказать, что модель изучает грамматику, семантику и синтаксис языка,
благодаря чему делает интеллектуальные прогнозы относительно слов, которые, вероятнее всего, могут появиться дальше в данной последовательности
и в данном контексте. Языковые модели широко используются в различных
NLP-задачах, таких как автозаполнение или предиктивный набор текста, выдача результатов поисковых систем, распознавание речи, машинный перевод,
резюмирование текста, генерация диалогов и т. д.
Языковые модели разрабатываются как на уровне слов, так и на уровне символов. В зависимости от области применения слово или символ рассматривается как единица текста, или токен, который модель считывает и обрабатывает
на этапах обучения и инференса (предсказания, прогноза). Языковая модель
изучает распределение вероятностей всех доступных токенов, где каждому
из них присваивается вероятность появления с учетом предыдущих токенов
в последовательности. На основе этого распределения вероятностей делается предсказание о следующем токене в последовательности или генерируется
новая текстовая последовательность.
Генерация последовательностей — это задача создания новых текстовых
последовательностей на основе некоей языковой модели. При этом сначала
задается начальная последовательность слов или символов (токенов), затем
языковая модель прогнозирует распределение вероятностей следующего
слова или символа; из этого распределения выбирается слово или символ,
добавляемые в последовательность. При повторении процесса получается
цепочка слов или символов. Под качеством генерации последовательности
подразумевается способность модели генерировать связный, грамматически
правильный и семантически осмысленный текст.
Языковые модели разрабатываются на основе разных моделей нейронных
сетей, таких как RNN и трансформеры. Для обучения этих моделей используются большие текстовые массивы, позволяющие рассчитывать распределение вероятностей слов или символов.
С помощью библиотеки TensorFlow мы построим языковую модель для
прогнозирования следующих символов для заданной последовательности.
344
■
Pythonic AI
Сгенерированные символы будут походить на исходный обучающий текст.
Для начала, как показано ниже, импортируем несколько важных библиотек,
необходимых для создания этого приложения:
1. import pathlib
2. import spacy
3. import numpy as np
4. import tensorflow as tf
5. from sklearn.model_selection import train_test_split
Как и в главе 8 «NLP как средство расширенного анализа текста» для построения данной модели воспользуемся простой текстовой версией книги «Путешествия Гулливера», доступной на сайте Project Gutengerg по адресу: https://
www.gutenberg.org/files/65473/65473-0.txt. Загрузим этот текстовый (txt) файл
в блокнот Google Colab с помощью значка «Файл» (File), расположенного на
левой панели. Прочитаем этот txt-файл с помощью библиотеки spaCy и токенизируем его на уровне слов, как показано в следующем блоке кода:
1. nlp = spacy.load('en_core_web_sm')
2. input_file = "book.txt"
3. doc = nlp(pathlib.Path(input_file).read_text(encoding="utf-8"))
4.
5. words = []
6. for token in doc:
7.
8.
if token.is_alpha:
words.append(token.text.lower())
9.
10. clean_text = " ".join(words)
Как показано в приведенном выше блоке кода, мы создали список words, извлекая из текста только буквенные слова и преобразуя все буквы в строчные.
Объединив слова из списка words, мы получили очищенную версию входного текста, сохранив ее, как переменную clean_text.
Для генерации обучающей выборки, которая послужит входными данными
для прогнозирования следующего символа, зададим длину последовательности в 30 символов. С этим значением можно поэкспериментировать. Рассмотрим следующий фрагмент кода:
Запускаем модели последовательностей
■
345
1. SEQ_LENGTH = 30
2. char_sequences = list()
3.
4. for j in range(len(clean_text) - SEQ_LENGTH):
5.
char_sequences.append(clean_text[j:j + SEQ_LENGTH + 1])
Как показано выше, мы задали цикл for, в котором из очищенного текста
clean_text выделяются все последовательности, состоящие из 31 символа,
и добавляются в список char_sequences. Далее для каждой из этих последовательностей мы будем вырезать за один раз по 30 символов и прогнозировать на их основе тридцать первый символ. Ниже показаны несколько примеров последовательностей из списка char_sequences:
Иллюстрация 9.13. Последовательности символов
Теперь создадим список присутствующих в тексте уникальных символов. Для
этого преобразуем список clean_text в множество, а затем из этого множества снова сделаем список. Далее создадим два словаря — первый для кодирования каждого символа по его индексу в списке уникальных символов,
а второй для декодирования каждого индекса в соответствующий ему символ. Фрагмент кода показан ниже:
1. unique_chars = sorted(list(set(cleanan_text)))
2. char_to_idx = dict((c, i) for i, c in enumerate(unique_chars))
3. idx_to_char = dict((i, c) for i, c in enumerate(unique_chars))
4.
5. encoded_sequence = list()
6. for seq in char_sequences:
7.
encoded_sequence.append([char_to_idx[char] for char in seq])
346
■
Pythonic AI
Как известно, ИИ-модели могут работать только с числовыми значениями,
а не со строками. Следовательно, нам нужно закодировать символы в числа.
Как показано в приведенном выше фрагменте кода, первый словарь char_to_
idx содержит уникальные символы в качестве ключей и их индекс в списке
в качестве значений. Второй словарь, idx_to_chars, наоборот, содержит в качестве ключей индексы, а в качестве значений уникальные символы. Далее
мы задали цикл for для получения каждой последовательности из 31 символа, присутствующей в списке char_sequences. Затем мы закодировали каждый символ соответствующим ему индексом из словаря char_to_idx. Этот
список индексов добавляется в другой список («кодированной последовательности») под названием encoded_sequence.
Сохраним значение длины словаря char_to_idx в переменной vocab_len.
Для генерации обучающих данных преобразуем encoded_sequence в массив NumPy и разделим его на признаки и метки. Первые 30 символов будут
считаться признаками X, а тридцать первый символ — целевой меткой для
прогноза. Функция train_test_split() из библиотеки Scikit-learn разбивает
данные на обучающий и тестовый наборы. Все эти действия в коде выглядят
следующим образом:
1. vocab_len = len(char_to_idx)
2. encoded_sequence = np.array(encoded_sequence)
3.
4. X, y = encoded_sequence[:,:-1], encoded_sequence[:,-1]
5. X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15)
Теперь создадим модель. Воспользуемся слоем Embedding для сопоставления индексов символов (целых чисел) с обученным эмбеддингом. Первый
аргумент input_dim — это размер используемого словаря. Второй аргумент
output_dim — это размерность выхода плотного слоя эмбеддинга, равная 50.
Третий аргумент input_length — длина входных последовательностей, которая в нашем случае равна 30. Модель задается при помощи следующего фрагмента кода:
1. model = tf.keras.models.Sequential()
2. model.add(tf.keras.layers.Embedding(vocab_len, 50, input_length=30,
trainable=True))
3. model.add(tf.keras.layers.GRU(100, dropout=0.6))
4. model.add(tf.keras.layers.Dense(vocab, activation='softmax'))
Запускаем модели последовательностей
■
347
На следующей иллюстрации показаны характеристики (сводка) модели:
Иллюстрация 9.14. Характеристики (сводка) модели
Далее нужно скомпилировать модель и обучить ее на тренировочных данных. При этом воспользуемся функцией потерь sparse_categorical_
crossentropy и соответствующей метрикой, потому что в нашем наборе
данных метки представлены в исходном виде, а не в формате однократного
(one-hot) кодирования. Код будет следующий:
1. model.compile(loss='sparse_categorical_crossentropy', metrics=['sparse_
categorical_accuracy'], optimizer='adam')
2. model.fit(X_train, y_train, epochs=20, verbose=2, validation_data=(X_test,
y_test))
Совет. Для более быстрого обучения модели в Colab можно переключиться на среду выполнения GPU. Также для улучшения качества модели можно попробовать увеличить количество эпох или воспользоваться иной архитектурой модели последовательностей.
После того как модель обучится, ею можно будет пользоваться для прогнозирования следующих n символов на основе заданной последовательности.
Для этого зададим функцию генерации generate_sequence(). В качестве аргументов эта функция будет принимать обученную модель, словари char_to_
idx и idx_to_char, входной текст или входную последовательность символов,
348
■
Pythonic AI
а также количество следующих символов для прогнозирования, как показано
в следующем блоке кода:
1. def generate_sequence(model, char_to_idx, idx_to_char, input_seq,
num_chars):
2.
for i in range(num_chars):
3.
encoded = [char_to_idx[char] for char in input_seq]
4.
encoded = tf.keras.utils.pad_sequences([encoded], maxlen=30)
5.
pred = model.predict(encoded, verbose=0)
6.
gen_char = idx_to_char[np.argmax(pred[0])]
7.
input_seq = input_seq + gen_char
8.
9.
return input_seq
В функции generate_sequence() цикл for выполняется заданное количество (num_chars) раз для генерации следующей последовательности символов.
Каждый представленный в заданном входном тексте (input_seq) символ
кодируется с помощью словаря char_to_idx. Затем закодированная входная последовательность подается в функцию pad_sequences библиотеки
TensorFlow, которая задает этой последовательности длину строго в 30 символов. Далее эта последовательность с фиксированной длиной передается
обученной модели. Прогнозируемый выход pred представляет собой массив
массивов, в котором прогноз содержит нулевой элемент, поэтому для вывода результата выбирается pred[0]. Индекс массива прогнозов, содержащий
максимальную оценку, извлекается с помощью функции argmax библиотеки
NumPy, а словарь idx_to_char используется для декодирования прогнозированного символа. Сгенерированный символ gen_char добавляется к заданному входному тексту input_seq.
Примечание. Функция pad_sequences доступна в модуле tf.keras.util.
Она преобразует заданную последовательность в последовательность
фиксированного размера (фиксированного временного шага). Значение этого фиксированного шага задается аргументом maxlen. Здесь мы
задали значение maxlen равным 30. При этом любая входная последовательность, содержащая менее 30 символов, будет дополнена нулями до длины в 30 символов. Последовательности, содержащие более
30 символов, будут обрезаны до длины в 30 символов.
Запускаем модели последовательностей
■
349
Теперь можно вызвать эту функцию generate_sequence() с аргументами
в виде обученной модели, словарей декодирования и кодирования, входного
текста и количества символов для генерации. Результат работы функции показан на следующей иллюстрации:
Иллюстрация 9.15. Прогнозируемый выход
Как показано на иллюстрации выше, входной текст («they closely observed that
it», «они пристально наблюдали, что это») состоит из 29 символов. Сначала
мы попытались заставить модель сгенерировать следующие восемь символов для данного входного текста. Обратите внимание на то, что слово «was»
(«был, была»), следующее после слова «it», грамматически корректно, так как
входное предложение было оформлено в прошедшем времени. Далее мы попытались сгенерировать последующие 12 и 18 символов, получив осмысленные и синтаксически правильные последовательности.
Примечание. Прогноз, сгенерированный функцией generate_sequence(),
в вашем случае может отличаться от показанного выше.
Так на основе модели последовательностей можно построить языковую модель автоматической генерации текста.
350
■
Pythonic AI
Заключение
В этой главе мы рассмотрели разные модели последовательностей, играющие
важную роль при обработке последовательных данных. Мы познакомились
с архитектурой и принципами работы модели рекуррентной нейронной сети
(RNN), двунаправленной модели рекуррентной нейронной сети, модели долгой кратковременной памяти (LSTM) и модели управляемого рекуррентного
блока (GRU). Для моделей традиционных рекуррентных нейронных сетей
характерны проблемы так называемых «исчезающего» и «взрывающегося»
градиентов, из-за которых они плохо запоминают длинные последовательности. Эти ограничения можно преодолеть с помощью моделей LSTM и GRU,
которые самостоятельно решают, что им запоминать или забывать. Мы узнали, как можно задавать модели этих архитектур с помощью библиотеки
TensorFlow 2, а также как форматировать входные данные для этих моделей.
Мы познакомились с языковым моделированием — процессом обучения модели на текстовых данных с целью выявления структуры и закономерностей
естественного языка. После обучения модель используют для прогнозирования возможных следующих последовательностей слов или символов для текста, подаваемого на вход модели.
Модели последовательностей прошли долгий путь от изобретения LSTM
в 1997 году до появления таких высокоэффективных больших языковых моделей (LLM), как ChatGPT, выпущенный в 2022 году. Благодаря многочисленным
исследованиям и техническим достижениям с каждым годом разрабатываются
все более мощные и производительные модели. Современные большие языковые модели, такие как Bard от Google или GPT-4 от OpenAI, имеют миллиарды
обучаемых параметров, имитирующих генерацию текста на уровне человека.
Эти огромные модели в настоящее время способны составлять компьютерные
программы, сочинять музыку и даже отвечать на вопросы тестов.
В следующей главе мы изучим и построим модели последовательностей для
классификации текстов.
Основные выводы
• Модели последовательностей играют очень важную роль в обработке
таких последовательных данных, как временные ряды, текст на естественном языке, аудиозаписи и т. д.
• Рекуррентные нейронные сети (RNN) — это класс нейронных сетей,
предназначенных для обработки последовательных данных с помощью
Запускаем модели последовательностей
■
351
информации о скрытом состоянии, то есть информации, полученной
на предыдущих временных шагах. Классические RNN просты, но страдают от проблемы исчезающего градиента, поэтому были разработаны
такие более сложные модели, как LSTM и GRU.
• Модель долгой краткосрочной памяти (LSTM) — это модель RNN специализированной архитектуры, разработанная для преодоления проблемы исчезающего градиента и способная выявлять дальнодействующие зависимости в последовательностях. Альтернативная ей модель
управляемого рекуррентного блока GRU также решает проблему исчезающего градиента, но при этом обладает более простой архитектурой.
Двунаправленные рекуррентные нейронные сети обрабатывают последовательности как в прямом, так и в обратном направлениях, и они
полезны для задач, в которых требуется учитывать контекст прошлых
и будущих элементов данных.
• Библиотека TensorFlow 2 предоставляет основательную платформу для
эффективного создания и обучения моделей последовательностей.
• Языковое моделирование подразумевает автоматическую генерацию
связных и контекстуально релевантных последовательностей в различных приложениях по обработке естественного языка (NLP).
• Модели последовательностей используются для задач языкового моделирования, в частности, для прогноза и генерации очередной последовательности символов.
Ссылки
• Простая RNN в TensorFlow: https://www.tensorflow.org/api_docs/python/
tf/keras/layers/SimpleRNN
• Простая ячейка RNN в TensorFlow: https://www.tensorflow.org/api_docs/
python/tf/keras/layers/SimpleRNNCell
• LSTM в TensorFlow: https://www.tensorflow.org/api_docs/python/tf/keras/
layers/LSTM
• Ячейка LSTM в TensorFlow: https://www.tensorflow.org/api_docs/python/
tf/keras/layers/LSTMCell
• GRU в TensorFlow: https://www.tensorflow.org/api_docs/python/tf/keras/
layers/GRU
• Ячейка GRU в TensorFlow: https://www.tensorflow.org/api_docs/python/
tf/keras/layers/GRUCell
352
■
Pythonic AI
• Обертка RNN в TensorFlow: https://www.tensorflow.org/api_docs/python/
tf/keras/layers/RNN
• Обертка двунаправленной RNN в TensorFlow: https://www.tensorflow.
org/api_docs/python/tf/keras/layers/Bidirectional
• Функция pad_sequences в TensorFlow: https://www.tensorflow.org/api_
docs/python/tf/keras/utils/pad_sequences
• Парадоксальная эффективность рекуррентных нейронных сетей:
http://karpathy. github.io/2015/05/21/rnn-effectiveness/
• Принципы работы сетей LSTM: http://colah.github.io/posts/2015-08UnderstandingLSTMs/
Глава 10
Модели
последовательностей
для автоматической
классификации текста
Введение
В современный век изобилия данных и огромного количества информации
особенно актуальным становится вопрос извлечения ценных для компаний и разработчиков сведений из неструктурированных текстовых данных.
Ситуацию усложняют огромный объем и невероятное разнообразие имеющейся текстовой информации. В связи с этим особую важность приобретает автоматическая классификация текстов — задача, относящаяся к сфере обработки естественного языка (NLP), которая стала ценным методом
организации, категоризации и осмысления огромного количества текстов.
Значительный прогресс в области автоматической классификации текстов
был достигнут благодаря разработке передовых моделей последовательностей, таких как рекуррентные нейронные сети (RNN) и трансформеры. Эти
модели предназначены для последовательной обработки текстовых данных
с учетом контекста и связей между словами и фразами. Рекуррентные нейросети фиксируют последовательную информацию, сохраняя во внутренней
памяти ранее обработанные данные. Они способны анализировать контекст
слов и учитывать порядок их появления, что делает их эффективными для
таких задач, как анализ настроения (эмоциональной окраски текстов), классификация документов и т. д.
354
■
Pythonic AI
С другой стороны в последние годы большое внимание к себе привлекли
трансформеры, во многом благодаря своей исключительной эффективности
в различных задачах по обработке естественного языка. Эти высокопроизводительные модели последовательностей позволяют предпринимателям
и исследователям демонстрировать замечательные результаты в области автоматической классификации текстов. Они способны распознавать закономерности и выявлять значимые признаки из неструктурированных текстовых данных, что позволяет им классифицировать и хорошо организовывать
огромные объемы текстовой информации. Такая автоматизация экономит
время и усилия и позволяет ответственным лицам принимать решения на
основе ценных сведений из многочисленных текстовых данных, благодаря
чему повышается качество этих решений, персонализируется опыт и растет
эффективность в различных областях.
Структура
В этой главе мы рассмотрим следующие темы:
• Введение в классификацию текстов.
• Набор данных и их структура.
• Очистка и предварительная обработка данных.
• Построение и обучение моделей последовательностей.
• Построение и обучение одномерной модели сверточной сети.
Цели
В этой главе мы затронем тему классификации текстов как одной из задач
в сфере обработки естественного языка. Эту задачу мы попробуем выполнить на примере набора данных о жалобах потребителей на определенные
продукты. Для очистки и предварительной обработки данных мы воспользуемся библиотекой Pandas языка Python. С помощью библиотеки TensorFlow 2
мы построим различные архитектуры моделей последовательностей, такие
как двунаправленная модель RNN, модель долгой краткосрочной памяти
и модель с управляемым рекуррентным блоком и применим их для классификации текстов. Мы узнаем о том, как пользоваться в этих моделях предварительно обученными эмбеддингами слов с помощью библиотеки TensorFlow
Hub. Мы познакомимся с концепцией одномерной сверточной нейронной
сети (1D CNN) и ее возможностями для работы с последовательными данными. Мы также построим модель 1D CNN для классификации текстов.
Модели последовательностей для автоматической классификации текста
■
355
Введение в классификацию текстов
Задача классификации, или категоризации, текста подразумевает присвоение
определенному фрагменту текста заданных меток, или категорий, на основе
его содержания и контекста. Классификация текстов позволяет анализировать, сортировать и обрабатывать текстовые данные в различных приложениях, в том числе выполнять такие задачи, как анализ настроения, фильтрация спама, моделирование тем и маршрутизация клиентской поддержки.
Инструменты и методы извлечения ценной информации из текстовых данных относятся к сфере обработки естественного языка (NLP, Natural Language
Processing). Эти методы позволяют компьютерам понимать и интерпретировать человеческий язык, что и делает возможной собственно классификацию
текстов. Комбинируя ИИ-алгоритмы с методами NLP, мы можем строить надежные модели, изучающие закономерности и взаимосвязи в текстовых данных, а также позволяющие делать точные прогнозы.
В связи с этим особый интерес представляют модели последовательностей —
класс моделей глубокого обучения, которые способны выявлять последовательную природу текстовых данных и успешно моделировать зависимости
между словами или символами в предложении. Они отлично справляются
с входными данными переменной длины и особенно хорошо подходят для
задач, связанных с пониманием и генерацией текстов на естественном языке.
Один из типов нейронных сетей, специально разработанных для обработки последовательных данных — это рекуррентные нейронные сети (RNN,
Recurrent Neural Networks), для которых характерна рекуррентная связь,
позволяющая передавать информацию с одного временного шага на другой
и тем самым сохранять контекстную информацию. Широкое распространение в области классификации текстов получили такие RNN, как модель
долгой краткосрочной памяти (LSTM, Long Short-Term Memory) и модель
управляемого рекуррентного блока (GRU, Gated Recurrent Unit), выявляющие временные зависимости между словами и делающие прогнозы на основе
предоставленных последовательностей.
Для построения автоматизированного конвейера классификации текстов
с использованием методов NLP и моделей последовательностей, нужно выполнить несколько основных шагов:
1. Подготовка данных. Сбор и предварительная обработка текстовых
данных, в том числе их очистка, токенизация и нормализация. Данные
при этом делятся на обучающий и тестовый наборы.
2. Извлечение признаков. Представление текстовых данных в числовом формате, понятном моделям машинного обучения. К распростра-
356
■
Pythonic AI
ненным методам помимо прочих относятся частотная векторизация,
метод соотношения частотности термина и обратной частотности
документа (TF-IDF, Term Frequency-Inverse Document Frequency),
эмбеддинг слов (Word2Vec или GloVe).
3. Разработка модели. Разработка и обучение модели последовательностей для классификации текстов. После выбора подходящей архитектуры (RNN и ее варианты) нужно настроить модель на обучающих
данных и оптимизировать гиперпараметры модели для достижения
наилучшей производительности.
4. Оценка. Оценка эффективности модели с помощью соответствующих метрик оценки, таких как точность (accuracy), прецизионность
(precision), полнота и F1-score. Для оценки эффективности моделей используется тестовый набор данных.
В следующей главе мы узнаем, как с помощью архитектуры на основе трансформеров удалось получить отличные результаты в различных NLP-задачах,
включая классификацию текстов.
Набор данных и их структура
База данных жалоб Бюро финансовой защиты потребителей (CFPB) — это
онлайн-платформа, созданная правительством США для сбора и отслеживания обращений потребителей по вопросам качества финансовых продуктов
и услуг. Она служит центральным хранилищем жалоб потребителей, обеспечивает прозрачность их отзывов и помогает им принимать взвешенные
решения.
Потребители могут подавать жалобы через веб-сайт CFPB или по телефону.
Они сообщают подробную информацию о своем случае, например, о финансовом продукте или услуге (кредитная карта, ипотека, студенческий кредит
и т. д.), название компании и описание проблемы. После регистрации обращения CFPB направляет его в соответствующую компанию для получения ответа. Компании дается определенный срок — как правило, 15 суток.
Фирма рассматривает жалобу и отправляет ответ в CFPB с указанием того,
будет ли она принимать меры, предлагать решение или оспаривать жалобу.
Ответ компании доводится до сведения потребителя. Потребитель вправе
ознакомиться с ним и оспорить его в течение определенного срока. При
этом потребитель может предоставить дополнительную информацию или
Модели последовательностей для автоматической классификации текста
■
357
разъяснить подробнее свою проблему. CFPB анализирует жалобу и все подтверждающие доказательства, предоставленные обеими сторонами. В случае выявления неправомерных действий или потенциального нарушения
законов о потребительском кредитовании, CFPB может начать расследование или принять судебные меры. Однако основная роль CFPB заключается
в посредничестве и в урегулировании отношений между потребителями
и компаниями.
Все жалобы потребителей и ответы на них анонимизируются и добавляются в общедоступную базу данных жалоб CFPB. Она содержит широкий
спектр информации, включая тип жалобы, название компании, наименование продукта, дату и краткое описание проблемы. В целях защиты конфиденциальности потребителей идентифицирующая их личная информация
удаляется.
База данных жалоб CFPB позволяет потребителям искать и просматривать
отзывы, чтобы получать представление о репутации компаний и потенциальных проблемах с конкретными финансовыми продуктами или услугами.
Опыт других людей помогает потребителям принимать более взвешенные
решения. CFPB рассматривает базу данных жалоб как важнейший инструмент мониторинга финансовой индустрии, выявления тенденций и определения приоритетности регулирующих мер. Эта база помогает бюро
определять так называемые «болевые точки» потребителей и предпринимать необходимые действия для защиты их интересов. База данных жалоб
CFPB — это общественный ресурс, способствующий прозрачности, подотчетности и честной практике на финансовом рынке. Она расширяет возможности потребителей, предоставляя им платформу для высказывания
своих мнений и опасений, а также служит цели CFPB по защите потребителей в финансовом секторе.
Загрузка набора данных
Для разработки нашего приложения по классификации текстов воспользуемся выборкой из базы данных потребительских жалоб CFPB. Скачаем сначала набор данных с сайта CFPB, расположенного по адресу: https://www.
consumerfinance.gov/data-research/consumer-complaints/.
По указанному выше адресу находится страница с разными разделами, среди которых есть Get data («Получить данные»). Нужно нажать на ссылку
Download complaint data («Скачать данные жалоб»), выделенную красной
рамкой на следующей иллюстрации:
358
■
Pythonic AI
Иллюстрация 10.1. Веб-сайт CFPB
После нажатия на эту ссылку появятся варианты загрузки данных. Здесь
можно сразу получить весь набор данных по жалобам в виде файлов CSV или
JSON, либо сначала отфильтровать его. Весь набор жалоб довольно большой,
поэтому мы будем работать с его подмножеством. Нажмем на вариант Filter
the full data set before you download («Отфильтровать полный набор данных
перед загрузкой»), как показано на следующей иллюстрации:
Иллюстрация 10.2. Загрузка данных с веб-сайта CFPB
Модели последовательностей для автоматической классификации текста
■
359
Далее откроется страница с выбором параметров фильтрации для получения подмножества данных. По состоянию на май 2023 года во всем наборе
данных имеется более 3,5 миллиона жалоб. Чтобы получить нужную нам
выборку, запросим данные за последние шесть месяцев в разделе Date range
(«Диапазон дат») на левой панели, как показано на следующей иллюстрации.
Всего в наборе данных девять финансовых продуктов, но мы выберем только
три из них: Debt collection («взыскание долгов»), Checking or savings accounts
(«расчетные или сберегательные счета») и Credit card or prepaid card («кредитные или предоплаченные карты»). Для этого установим флажки напротив
указанных разделов и нажмем на опцию экспорта данных (Export data), как
показано на следующей иллюстрации:
Иллюстрация 10.3. Фильтрация данных на веб-сайте CFPB
Инструмент экспорта данных предлагает вариант: загрузить набор данных
либо в формате CSV, либо в формате JSON, а также в виде полного или отфильтрованного набора данных. Как показано на иллюстрации ниже, нужно
выбрать формат CSV с фильтрацией набора данных и нажать кнопку Start
export («Начать экспорт»). Через некоторое время набор данных будет загружен.
360
■
Pythonic AI
Иллюстрация 10.4. Экспорт данных
После скачивания отфильтрованного набора данных в виде CSV-файла его
можно использовать для построения модели классификации текста. Но для
начала загрузим этот CSV-файл на наш Google Диск — например, в каталог
под названием «data». Подключим Google Диск к блокноту Google Colab, изменим рабочий каталог и получим доступ к этому файлу, как было описано
ранее в главе 2 «Настройка лаборатории искусственного интеллекта», и как
показано в следующем фрагменте кода:
1. from google.colab import drive
2. drive.mount('/content/gdrive')
3.
4. %cd /content/gdrive/MyDrive/data/
Для чтения и работы с CSV-файлом мы воспользуемся библиотекой Python
под названием Pandas. Сначала кратко опишем основные особенности этой
библиотеки, а затем вернемся к нашему набору данным и обработаем его.
Модели последовательностей для автоматической классификации текста
■
361
Обработка данных с помощью библиотеки
Pandas языка Python
Библиотека Pandas языка Python стала основным инструментом аналитиков
и исследователей, занимающихся обработкой данных. Pandas предоставляет
простые и эффективные методы обработки, очистки, преобразования и анализа структурированных данных, что делает ее незаменимым помощником
в экосистеме Python.
В Pandas представлены две основные структуры данных: Series и DataFrame,
лежащие в основе работы с данными в этой библиотеке. Series — это одномерный маркированный массив, способный хранить различные типы данных,
а DataFrame — это двумерная табличная структура данных с маркированными осями (строками и столбцами). Эти гибкие структуры данных позволяют
эффективно хранить данные и выполнять сложные операции над ними легким и понятным образом.
Pandas предлагает множество функций для чтения данных из различных источников, таких как файлы CSV или Excel, базы данных SQL и пр. Загрузить
данные в DataFrame и исследовать их можно с помощью всего одной строки
кода. Pandas предоставляет разнообразные методы для обобщения, фильтрации, сортировки и нарезки данных, которые помогают получить из них ценные сведения и провести глубокий анализ этих данных.
Реальные наборы данных часто бывают неупорядоченными, содержат пропущенные значения или выбросы (аномальные значения), а также могут
быть представлены в разных, несовместимых друг с другом форматах. Pandas
предоставляет множество инструментов для очистки и предварительной обработки данных, включая функции для обработки пропущенных значений,
удаления дубликатов, обработки выбросов и преобразования типов данных.
С помощью Pandas можно эффективно очищать данные и повышать их качество перед собственно анализом.
Среди доступных в Pandas возможностей преобразования данных имеются математические и статистические операции, способы вычисления новых
столбцов на основе существующих, изменения формы данных, объединения
и группировки нескольких наборов данных, а также операции агрегирования и обобщения данных. Эти преобразования позволяют изменять форму
и структуру данных в соответствии с конкретными задачами.
Библиотека Pandas построена на базе библиотеки NumPy, реализующей
эффективные операции с числами на языке Python. Такая интеграция способствует совместимости Pandas с другими библиотеками для научных
362
■
Pythonic AI
вычислений. Кроме того, Pandas можно использовать вместе с популярной
библиотекой машинного обучения Scikit-learn, благодаря чему с ее помощью
можно эффективно предварительно обрабатывать и подготавливать данные
для моделей машинного обучения.
Библиотека Pandas языка Python произвела настоящую революцию в сфере
обработки и анализа данных. Интуитивно понятный синтаксис, эффективные структуры данных и богатый набор функций делают ее поистине незаменимым инструментом. С помощью Pandas можно выделять из наборов
данных действительно ценные сведения и на их основе принимать обоснованные решения.
В этом проекте мы воспользуемся библиотекой Pandas для чтения данных из
CSV-файла, а также для их очистки и предварительной обработки с целью
дальнейшего анализа с помощью моделей последовательностей. Специально устанавливать библиотеку Pandas не нужно, потому что она установлена
в Google Colab изначально.
Очистка и предварительная
обработкад анных
Итак, у нас на Google Диске уже имеется CSV-файл с жалобами CFPB. Также
мы подключили Google Драйв к блокноту Colab. Предположим, что CSV-файл
называется «complaints.csv». Прочитать CSV-файл в Pandas довольно просто.
Для этого нужно импортировать библиотеку Pandas и воспользоваться ее
функцией read_csv(). Эта функция принимает в виде аргумента путь к CSVфайлу и создает из него объект Pandas DataFrame, как показано в следующем
фрагменте кода:
1. import pandas as pd
2. df = pd.read_csv("complaints.csv")
Как было сказано выше, объект DataFrame содержит двумерные табличные
данные. В приведенном выше фрагменте кода мы создали объект DataFrame
с именем df. Просмотреть фрагмент (образец) данных можно с помощью
функции head() объекта DataFrame, как показано ниже:
Модели последовательностей для автоматической классификации текста
■
363
Иллюстрация 10.5. Функция head() библиотеки Pandas
Свойство shape объекта DataFrame возвращает кортеж из количества строк
и количества столбцов, как показано на следующей иллюстрации:
Иллюстрация 10.6. Свойство shape объекта DataFrame
Как мы видим, всего в наборе данных 76 696 строк и 18 столбцов. Так как мы
не собираемся работать со всеми, то выберем из набора данных только четыре столбца, которые называются ‘Product’ («продукт»), ‘Issue’ («проблема»),
‘Sub-issue’ («проблемная ситуация») и ‘Consumer complaint narrative’ («описание жалобы потребителя»). Столбец ‘Product’ содержит название финансового продукта, на который была подана жалоба. Столбцы ‘Issue’ и ‘Sub-issue’
содержат название типа проблемы и краткое описание ситуации, которые
потребитель указал при подаче жалобы. ‘Consumer complaint narrative’ — это
описание жалобы, отправленное потребителем. Цель этого приложения для
классификации текста — предсказать целевой продукт по заданным проблеме, описанию ситуации и тексту жалобы потребителя.
В Pandas можно получить подмножество столбцов, передав список столбцов,
которые нужно оставить во фрейме данных, как показано в следующем фрагменте кода:
1. df = df[['Product', 'Issue', 'Sub-issue', 'Consumer complaint narrative']].
364
■
Pythonic AI
Теперь наш объект DataFrame df содержит только четыре столбца, но столько
же строк, сколько и раньше. Как показано на следующей иллюстрации, можно
посмотреть отдельно образец текста жалобы, выбрав из объекта DataFrame
df только столбец ‘Consumer complaint narrative’:
Иллюстрация 10.7. Образец текста жалобы
На иллюстрации выше значение 0 внутри квадратной скобки означает значение столбца ‘Consumer complaint narrative’ в строке с нулевым индексом.
Личная идентифицирующая информация в тексте жалобы замаскирована
с помощью буквы «x».
Проверить, имеются ли недостающие значения в каждом столбце DataFrame,
можно с помощью функции isna(). Чтобы получить общее количество отсутствующих значений по столбцам, после функции isna() добавим функцию sum(), как показано на следующей иллюстрации:
Иллюстрация 10.8. Отсутствующие значения в DataFrame
Итак, видно, что в столбце ‘Consumer complaint narrative’ отсутствуют
43 233 значения. Мы не сможем провести анализ текста, если в нем отсутствует описание жалобы потребителя, поэтому удалим эти пропущенные значения из массива данных. Для этого воспользуемся функцией Pandas dropna(),
как показано в следующей строке кода:
1. df = df.dropna()
Модели последовательностей для автоматической классификации текста
■
365
Объединим столбцы с описанием проблемы (Issue), ситуации (Sub-issue)
и жалобы потребителя (Consumer complaint narrative) и создадим новый
столбец под названием "Complaints" («Жалобы») как показано в следующем
фрагменте коде:
1. df["Complaints"] = df["Issue"].map(str) + ". " + df["Sub-issue"].map(str)
+ ". " + df["Consumer complaint narrative"].map(str)
Все эти три столбца мы объединили как строки Python. Для этого мы сначала
преобразовали их в строковый тип, а затем соединили (с помощью операции
конкатенации), поставив между ними точку и пробел. Теперь можно удалить
прежние три столбца, как показано в следующей строке кода:
1. df = df.drop(["Issue", "Sub-issue", "Consumer complaint narrative"], axis=1)
Параметр axis функции drop() определяет, следует ли удалять заданные
метки по строкам или по столбцам. Значение 1 означает, что метки удаляются по столбцам.
Теперь займемся базовой очисткой текста жалобы. Во-первых, мы переведем
все буквы в строчный регистр с помощью функции lower(), как показано на
следующей иллюстрации :
Иллюстрация 10.9. Перевод текста в нижний регистр
Для дальнейшего анализа нужно сохранить в тексте жалобы только буквы
и пробелы. Любые другие символы, такие как знаки препинания, цифры, числа и т. д., будут удалены. В тексте жалобы присутствуют также фрагменты
личной информации, замаскированной с помощью символа «x». Поскольку
эти фрагменты не содержат значимой информации, мы заменим последовательные «x» пустыми строками. В Pandas можно преобразовать элементы
366
■
Pythonic AI
столбцов в строку и пользоваться функцией замены replace(). Для поиска
и замены символов в строках воспользуемся параметром regex (сокращение
от regular expressions, «регулярные выражения»). Это мощный инструмент для
поиска по шаблону и работы с текстом, позволяющий находить подстроки,
сопоставлять их и манипулировать ими на основе определенных шаблонов.
Рассмотрим следующие строки кода:
1. df["Complaints"] = df["Complaints"].str.replace("[^a-z ]", " ", regex=True)
2. df["Complaints"] = df["Complaints"].str.replace("[x+]",
" ",
regex=True)
Здесь мы преобразовали столбец "Complaints" в строку и воспользовались
функцией replace(), указав для нее три параметра. Первый параметр — это
образец строки, подлежащий замене, а второй — это пробел, то есть заменяющее строковое значение. Третий параметр, regex, это булева переменная,
определяющая, является ли первый параметр регулярным выражением.
Первый regex-шаблон в строке 1 приведенного выше фрагмента кода выглядит
так: «[^a-z ]». В квадратных скобках задается набор символов для проверки.
Здесь указан диапазон символов в виде строчных букв от a до z и пробел. В начале этого набора стоит символ «карет» («^»), означающий, что мы хотим выбрать
все символы, кроме указанных строчных букв и пробелов. Выбранные символы
заменяются на пробел, указанный во втором параметре. Таким образом в тексте
жалоб останутся только буквы в нижнем регистре и пробелы (учтите, что мы
уже заменили все буквы в верхнем регистре на буквы в нижнем регистре).
Второй regex-шаблон в строке 2 приведенного выше фрагмента кода выглядит как «[x+]». Знак «плюс» («+») указывает на то, что предыдущий символ
будет присутствовать один или несколько раз. В данном случае мы хотим заменить один или несколько символов «x» в тексте на пустую строку.
Мы также будем рассматривать только слова в их базовой форме (стеммированные). Для этого воспользуемся стеммером Портера (Porter Stemmer)
из библиотеки NLTK. Сначала импортируем NLTK и загрузим необходимые
корпуса, как показано в следующем фрагменте кода:
1. import nltk
2. nltk.download('stopwords')
3. nltk.download('punkt')
4.
5. from nltk.stem import PorterStemmer
6. ps = PorterStemmer()
Модели последовательностей для автоматической классификации текста
■
367
Теперь нужно задать функцию для получения стеммированной версии каждого слова. Также мы будем хранить список всех уникальных слов, имеющихся в нашем наборе данных, как показано в следующем фрагменте кода:
1. all_words = []
2.
3. def get_clean_text(text):
4.
words = text.split()
5.
if len(words)>10 and len(words)<=1000:
6.
clean_word_list = [ps.stem(word.strip()) for word in words]
7.
all_words.extend(clean_word_list)
8.
return " ".join(clean_word_list)
9.
10.
else:
return None
Как показано выше, мы задали функцию get_clean_text(), которая принимает на вход текст и разбивает его на части. После разбиения мы получаем
список слов из данного текста. С помощью блока if в строке номер 5 выше
мы отфильтровываем текст, содержащий более десяти и менее тысячи слов.
Текст, содержащий менее десяти слов, не содержит особо ценной информации, а текст, содержащий более тысячи слов, слишком длинный для анализа. Если это условие не выполняется, функция возвращает нулевое значение
None. В строке 6 выше мы использовали генератор списка. Сначала для каждого слова, присутствующего в списке, мы применили функцию strip, чтобы
удалить ведущие и последующие пробелы. Затем с помощью стеммера Портера мы извлекли основы слов. В списке под названием all_words хранятся
все уникальные слова для словаря нашего набора данных. В конце функция
возвращает очищенные слова, склеив их с помощью функции join().
Теперь можно применить функцию get_clean_text() к столбцу "Complaints" объекта DataFrame и получить очищенный текст в другом столбце
под названием 'clean_text', как показано в следующей строке кода. Этот
процесс может занять некоторое время:
1. df['clean_text'] = df['Complaints'].apply(get_clean_text)
Поскольку функция get_clean_text() возвращает значение null, если количество слов в тексте меньше десяти или больше тысячи, в столбце 'clean_
text' мы получим несколько нулевых значений. Удалим эти нулевые значения из объекта DataFrame, как показано в следующей строке кода:
368
■
Pythonic AI
1. df = df.dropna()
Теперь обработаем наш целевой столбец "Product" в DataFrame. Значения
в столбце "Product" — это просто текстовые значения из трех возможных,
как показано на иллюстрации ниже:
Иллюстрация 10.10. Уникальные продукты
Нам нужно преобразовать эти строковые значения в числовые метки с помощью LabelEncoder из библиотеки Scikit Learn. Также весь набор данных нужно разделить на обучающую и тестовую выборки, как показано в следующем
фрагменте кода:
1. from sklearn.preprocessing import LabelEncoder
2. from sklearn.model_selection import train_test_split
3.
4. le = LabelEncoder()
5. df["target"] = le.fit_transform(df["Product"])
6.
7. X_train, X_test, y_train, y_test = train_test_split(df["cleanan_
text"], df["target"], test_size=0.3)
Как показано в блоке кода выше, мы создали экземпляр класса LabelEncoder
le и использовали его функцию fit_transform() для преобразования столбца "Product" объекта DataFrame df в метки, закодированные целыми числами. Для разделения всего набора данных на обучающий и тестовый мы также
воспользовались функцией train_test_split(), при этом в тестовый набор
попали 30% данных.
Модели последовательностей для автоматической классификации текста
■
369
Примечание. Для простоты мы разбили набор данных на обучающий
и тестовый наборы только один раз. В реальной задаче стоило бы разбить обучающие данные еще один раз с помощью функции train_test_
split(), чтобы получить обучающий набор и набор для валидации (проверочный набор). Во время обучения набор для валидации передается
как аргумент validation_data функции fit() модели, а тестовый набор
используется исключительно для окончательной оценки модели.
Выборочные значения (образцы) столбцов "product" и "target" объекта
DataFrame df можно просмотреть с помощью функции head(), как показано
на следующей иллюстрации.
Иллюстрация 10.11. Образцы значений “product” и “target”
Как показано выше, текстовые значения в столбце "product" теперь закодированы в виде целочисленных меток в столбце "target". Значение “Checking
of Savings account” («расчетный или сберегательный счет») кодируется как 0,
значение “Credit or prepaid card” («кредитная или предоплаченная карта») кодируется как 1, а значение “Debt collection” («взыскание долгов») кодируется как 2.
Кроме того, из списка all_words мы составим набор уникальных слов или
словарный запас набора данных, как показано в следующей строке кода:
1. all_words = list(set(all_words))
370
■
Pythonic AI
Импортируем библиотеку TensorFlow и преобразуем наши обучающий
и тестовый наборы в тензоры TensorFlow. Затем создадим наборы данных
TensorFlow из этих двух тензоров с заданными размерами пакета (партии)
и автоматической настройкой AUTOTUNE, как показано в следующем фрагменте кода:
1. X_train = tf.convert_to_tensor(X_train)
2. X_test = tf.convert_to_tensor(X_test)
3. BATCH_SIZE = 64
4.
5. train_ds = tf.data.Dataset.from_tensor_slices((X_train, y_train)).
batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)
6. test_ds = tf.data.Dataset.from_tensor_slices((X_test, y_test)).
batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)
Теперь определим архитектуру модели, которая будет обучаться на этих наборах данных.
Построение и обучение моделей
последовательностей
Далее мы создадим энкодер для векторизации текстовых данных с помощью
слоя TextVectorization в TensorFlow. О слое TextVectorization мы узнали из главы 8 «NLP как средство расширенного анализа текста». Обучающий
набор данных будет передаваться кодировщику для обучения или адаптации словаря из токенов с помощью функции adapt(). Благодаря функции
adapt() этот слой анализирует набор данных и создает словарь. Вместо функции adapt() можно также воспользоваться аргументом vocabulary класса
TextVectorization() и передать ему список all_words. Рассмотрим следующий фрагмент кода:
1. VOCAB_SIZE = len(all_words)
2.
3. encoder = tf.keras.layers.TextVectorization(
4.
max_tokens=VOCAB_SIZE)
5.
6. encoder.adapt(train_ds.map(lambda text, label: text))
Модели последовательностей для автоматической классификации текста
■
371
Совет. Если входной текст не был предварительно обработан, часть
этой работы можно выполнить внутри функции TextVectorization().
У этой функции есть аргумент standardize, который позволяет стандартизировать текст, если задать ему значение "lower_and_strip_
punctuation". Аргументу split можно задать значение в виде пробела
или другого символа разделения входного текста.
Получив входной текст, слой encoder преобразует каждое слово в соответствующий индекс в списке словаря. Вывести список словаря на экран можно
с помощью функции get_vocabulary(), как показано на следующей иллюстрации:
Иллюстрация 10.12. Словарь энкодера
Как показано выше, слой encoder будет кодировать слово "the" как число 3,
потому что "the" находится на третьей позиции в сформированном списке
словаря.
Теперь зададим архитектуру модели для классификации текста.
Модель LSTM
Теперь можно задать архитектуру модели, в которой первым слоем будет
encoder, кодирующий строковые значения в целых числах. Со слоями эмбеддинга и LSTM мы подробно познакомились в главе 8 «NLP как средство
расширенного анализа текста» и в главе 9 «Запускаем модели преобразования многослойных последовательностей» соответственно. За слоем encoder
последует слой Embedding библиотеки TensorFlow, преобразующий закодированные строки в разученные эмбеддинги слов. Из-за того, что длина последовательностей будет варьироваться, для эмбеддинга нужно задать аргументу
mask_zero значение True.
Далее определим слой LSTM с 64 узлами, слой Dense из 64 узлов и финальный
слой Dense из 3 узлов, как показано в следующем блоке кода:
■
372
Pythonic AI
1. model = tf.keras.Sequential([
2.
encoder,
3.
tf.keras.layers.Embedding(
4.
input_dim=len(encoder.get_vocabulary()),
5.
output_dim=64,
6.
mask_zero=True),
7.
tf.keras.layers.LSTM(64),
8.
tf.keras.layers.Dense(64, activation='relu'),
9.
tf.keras.layers.Dense(3)
10. ])
11.
12. model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy
(from_logits=True),
13.
optimizer=tf.keras.optimizers.Adam(1e-4),
14.
metrics=['sparse_categorical_accuracy'])
Последний слой состоит из трех элементов, потому что нам нужно определить принадлежность к одному из трех целевых классов по каждому входному тексту. Задав архитектуру, скомпилируем модель с функцией потерь
SparseCategoricalCrossentropy. Обратите внимание, что в этой модели нет
последнего слоя softmax, который выдавал бы вероятностную оценку. Поэтому аргументу from_logits функции потерь SparseCategoricalCrossentropy
нужно задать значение True. Для обучения воспользуемся оптимизатором
Adam. Далее введем в модель данные и запустим ее обучение в течение нескольких эпох согласно следующему фрагменту кода:
1. history = model.fit(train_ds, epochs=50,
2. validation_data=test_ds,
3. validation_steps=30, verbose=2)
Совет. Для всех описанных моделей с целью их лучшего обучения советуем применить оптимизацию гиперпараметров, регуляризацию,
слои дропаута и прочие дополнительные настройки. В блокноте Colab
не забудьте включить среду выполнения GPU.
Чтобы получить показатели потерь и точности, нужно оценить работу модели на тестовом наборе данных, как показано на следующей иллюстрации:
Модели последовательностей для автоматической классификации текста
■
373
Иллюстрация 10.13. Оценка модели LSTM
Двунаправленная модель GRU
Двунаправленная рекуррентная нейронная сеть при составлении прогнозов
учитывает информацию как прошлых, так и будущих шагов, благодаря чему она способна выявлять более полную контекстную информацию из всей
последовательности. Двунаправленные RNN обнаруживают зависимости, не
всегда очевидные при использовании простых RNN, обрабатывающих информацию только в одном направлении.
В следующем блоке кода показано, как построить двунаправленную модель
на основе GRU:
1. model = tf.keras.Sequential([
2.
encoder,
3.
tf.keras.layers.Embedding(
4.
input_dim=len(encoder.get_vocabulary()),
5.
output_dim=64,
6.
mask_zero=True),
7.
tf.keras.layers.Bidirectional(tf.keras.layers.GRU(64)),
8.
tf.keras.layers.Dense(64, activation='relu'),
9.
tf.keras.layers.Dense(3)
10. ])
11.
12. model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy
(from_logits=True),
13.
optimizer=tf.keras.optimizers.Adam(1e-4),
14.
metrics=['sparse_categorical_accuracy'])
15.
16. history = model.fit(train_ds, epochs=50,
17.
18.
validation_data=test_ds,
validation_steps=30, verbose=2)
374
■
Pythonic AI
Результат оценки на тестовом наборе данных показан на следующей иллюстрации:
Иллюстрация 10.14. Оценка двунаправленной модели GRU
Использование предварительно
обученныхэ мбеддингов
Предварительно обученные эмбеддинги слов, или векторы — это числовые
представления слов, полученные в результате обработки больших массивов
текстовых данных с помощью методов обучения без учителя. Идея состоит
в том, чтобы пользоваться уже имеющимися числовыми представлениями слов, которые учитывают свойства распределения слов в таких больших
корпусах, как «Википедия», Twitter или огромные коллекции новостных статей. Таким образом разработчики могут воспользоваться закодированными
в этих эмбеддингах знаниями без необходимости обучать языковую модель
с нуля на конкретной задаче или отдельном наборе данных. Такой подход экономит время и вычислительные ресурсы, а также позволяет придерживаться
строгих требований к маркировке данных.
В главе 6 «Обучение и развертывание моделей обнаружения объектов» мы
познакомились с библиотекой TensorFlow Hub, представляющей собой хранилище предварительно обученных моделей машинного обучения или модулей, включая предварительно обученные эмбеддинги слов, которые можно
повторно использовать для решения различных задач. Эти модули легко интегрируются в NLP-проекты на базе библиотеки TensorFlow, так что мы тоже
будем пользоваться возможностями предварительно обученных эмбеддингов без необходимости строить их модели с нуля.
Модуль "nnlm" в TensorFlow Hub — это предварительно обученная на больших
текстовых данных нейронная языковая модель (NNLM, Neural Language
Model). Этот модуль доступен для нескольких языков. Мы воспользуемся модулем под названием "nnlm-en-dim50", где "en" означает, что модель обучена
на английском языке, а "dim50" обозначает размерность эмбеддингов, генерируемых моделью (50). Эмбеддинги слов этой модели представляют собой
Модели последовательностей для автоматической классификации текста
■
375
плотные векторы (большинство элементов которых ненулевые), отражающие семантические и синтаксические свойства слов, что позволяет модели
понимать значения разных слов и отношения между ними.
Для использования предварительно обученного модуля из библиотеки
TensorFlow Hub, нужно сначала импортировать эту библиотеку (tensorflow_
hub). Устанавливать ее не нужно, так как в Google Colab это уже сделано. Предварительно обученный и сохраненный модуль TensorFlow Hub можно обернуть
слоем Keras с помощью API hub, что демонстрирует следующий фрагмент кода:
1. import tensorflow_hub as hub
2. embedding_url = "https://tfhub.dev/google/nnlm-en-dim50/2"
3. embedding_layer = hub.KerasLayer(embedding_url, input_shape=[],
dtype=tf.string, trainable=True)
Как показано выше, embedding_url — это ссылка для загрузки модели «nnlmen-dim50». Аргумент input_shape задан как пустой список. Это означает, что
слой ожидает входной тензор только формы [BATCH_SIZE]. Напомним, что
ранее, при создании наборов данных TensorFlow из обучающих и тестовых
данных, мы уже задали параметру BATCH_SIZE значение 64. Аргумент dtype
имеет значение «string», означающий, что слой ожидает входные данные
строкового типа. В отличие от предыдущих сценариев, в данном случае мы
не используем никакого слоя TextVectorization() для кодирования входного текста целыми числами. Следовательно, этот слой эмбеддинга получает
непосредственно строковые значения.
Для заданного текста слой embedding_layer генерирует 50-мерный вектор. Но, как было сказано выше, слой модели последовательностей (такой
как RNN, LSTM, GRU и т. п.) ожидает на входе трехмерный тензор формы
[BATCH_SIZE, временные шаги, признаки]. Поэтому выход слоя embedding_
layer нужно преобразовать в трехмерный тензор, и только потом направлять его на вход слоя типа RNN. Для этого в библиотеке TensorFlow имеется
функция переформирования reshape. Дополним архитектуру нашей модели
слоем Reshape, как показано в следующем блоке кода:
1. model = tf.keras.Sequential([
2.
embedding_layer,
3.
tf.keras.layers.Reshape((1, 50)),
4.
tf.keras.layers.GRU(64),
5.
tf.keras.layers.Dense(64, activation='relu'),
6.
tf.keras.layers.Dense(3)
7. ])
376
■
Pythonic AI
Выше мы преобразовали форму каждого 50-мерного вектора в форму (1, 50),
где 1 — это временной шаг, а 50 — размерность вектора признаков. Затем
модель компилируется и обучается, как это делалось в предыдущих случаях.
Рассмотрим следующий блок кода:
1.model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy
(from_logits=True),
2.
optimizer=tf.keras.optimizers.Adam(1e-4),
3.
metrics=['sparse_categorical_accuracy'])
4.
5. history = model.fit(train_ds, epochs=50,
6.
validation_data=test_ds,
7.
validation_steps=30, verbose=2)
На следующей иллюстрации показана оценка модели на тестовых данных:
Иллюстрация 10.15. Оценка модели с предварительно обученными эмбеддингами
Далее мы познакомимся с одномерными сверточными сетями и воспользуемся ими для классификации текста.
Использованиеэ мбеддинговG loVe
В главе 8 «NLP как средство расширенного анализа текста» уже упоминался
алгоритм эмбеддинга GloVe. До передачи текста в любой последовательный
слой можно воспользоваться этим методом для преобразования слов в эмбеддинги.
Как уже говорилось в главе 8 «NLP как средство расширенного анализа текста», сначала нужно загрузить заархивированный файл векторов GloVe с сервера Стэнфордского университета и распаковать его, как показано в следующем фрагменте кода:
Модели последовательностей для автоматической классификации текста
■
377
1. !wget "http://nlp.stanford.edu/data/glove.6B.zip"
2. !unzip "glove.6B.zip"
Для чтения файла эмбеддингов и создания модели эмбеддинга мы воспользуемся классом KeyedVectors библиотеки Gensim, как показано в следующем
фрагменте кода:
1. from gensim.models import KeyedVectors
2.
3. glove_input_file = "glove.6B.50d.txt"
4. embedding_model = KeyedVectors.load_word2vec_format(glove_input_
file, no_header=True)
В приведенном выше фрагменте мы указали файл glove.6B.50d.txt. Следовательно, для каждого слова мы получим вектор размерностью 50. Для
кодирования текстовых строк создадим слой TextVectorization(). Для сопоставления слов с их индексами в словаре энкодера создадим словарь, как
показано в следующем фрагменте кода:
1. VOCAB_SIZE = len(all_words)
2.
3. encoder = tf.keras.layers.TextVectorization(max_tokens=VOCAB_SIZE)
4. encoder.adapt(train_ds.map(lambda text, label: text))
5.
6. word_to_index = dict()
7. vocab = encoder.get_vocabulary()
8.
9. for i in range(len(vocab)):
10.
word_to_index[vocab[i]] = i
В строке 9 кода мы применили цикл for для создания словаря word_to_index,
сопоставляющего каждое слово из словаря энкодера с позиционным индексом этого слова в словаре.
378
■
Pythonic AI
Далее создадим матрицу эмбеддингов, которая будет содержать векторы всех
слов словаря. Векторы извлекаются из модели эмбеддинга GloVe, как показано в следующем фрагменте кода:
1. import numpy as np
2.
3. word_vector_dim = 50
4. embedding_matrix = np.zeros((len(vocab), word_vector_dim))
5.
6. for key in word_to_index.keys():
7.
if key in embedding_model:
8.
word_emb_vector = embedding_model[key]
9.
embedding_matrix[i] = word_emb_vector
10.
Мы инициализировали матрицу эмбеддингов всеми нулями. Так как мы
пользуемся файлом glove.6B.50d.txt, размерность векторов слов равна 50.
Матрица будет хранить 50-мерные векторы всех присутствующих в словаре слов. Если какое-то слово из словаря word_to_index также присутствует
и в модели GloVe embedding_model, то его вектор извлекается и помещается
на соответствующее место в матрице эмбеддингов.
Наконец, укажем эту матрицу как аргумент weights слоя Embedding, как показано в следующем фрагменте кода:
1. model = tf.keras.Sequential([
2.
encoder,
3.
tf.keras.layers.Embedding(
4.
input_dim=len(encoder.get_vocabulary()),
5.
output_dim=50,
6.
weights=[embedding_matrix],
7.
trainable=False),
8.
tf.keras.layers.LSTM(64)
9.
tf.keras.layers.Dense(64, activation='relu'),
10.
tf.keras.layers.Dense(3)
11. ])
Модели последовательностей для автоматической классификации текста
■
379
Обратите внимание, что в слое embedding используется аргумент trainable
со значением False, чтобы во время обучения веса не обновлялись.
Также заметим, что предварительно обученные эмбеддинги обычно содержат слова в их исходной полной форме. Если использовать основы слов (их
версии после стемминга), то могут найтись не все слова, поэтому нужно
будет модифицировать функцию get_clean_text() и удалить из нее функцию стемминга, а вместо нее воспользоваться лемматизацией. Кроме того,
в тексте жалоб содержится много неправильно написанных слов. Если текст
предварительно не откорректировать с помощью надежного корректора орфографии, эмбеддинги для многих слов извлечены не будут, что повлияет на
обучение модели.
Построение и обучение одномерной
модели CNN
Классификация текстов — одна из фундаментальных задач в области естественной обработки языка (NLP), и ее цель — автоматическое присвоение
текстовым данным заранее определенных категорий или меток. Одним из
эффективных инструментов выполнения задачи классификации текстов помимо прочих стали одномерные сверточные сети (1D CNN). Изначально
сверточные сети широко использовались в области компьютерного зрения,
но их также успешно адаптировали для обработки и последовательных данных, таких как текст. Одномерные свертки, также называемые временными
свертками, обрабатывают одномерные данные, что делает их подходящими
для анализа текстовых последовательностей. Ключевая идея одномерной
свертки заключается в том, чтобы проходить по входным последовательным
данным небольшим окном — так называемым фильтром, или ядром, — выявляя в них локальные закономерности и важные признаки.
С двумерными сверточными нейронными сетями мы познакомились в главе 5
«Разработка приложений для классификации изображений на основе CNN».
Различие между двумерными и одномерными сверточными сетями заключается в типе входных данных, которые они обрабатывают, и в том, как именно
применяется операция свертки. В одномерных сетях операция свертки подразумевает скольжение одномерного фильтра, или ядра, по входной последовательности в одном направлении (обычно горизонтальном), благодаря
чему и выявляются локальные закономерности и особенности. В двумерных
сверточных сетях операция свертки подразумевает скольжение двумерного
фильтра, или ядра, по изображению или двумерному представлению каких-
380
■
Pythonic AI
либо входных данных, то есть по матрице, или сетке, с двумя направлениями
(по горизонтали и по вертикали) с целью выявления пространственных шаблонов и признаков.
Одномерные сверточные сети обладают рядом преимуществ для задач классификации текста:
• Локальное выделение признаков. Благодаря прохождению ядра по
входной последовательности во время операции свертки получается
эффективно выявлять локальные признаки и основные закономерности в тексте.
• Общие параметры. В одномерных сверточных сетях к различным частям входной последовательности применяется один и тот же фильтр,
что позволяет сети обучаться общим параметрам. Это свойство значительно сокращает количество параметров по сравнению с полносвязными слоями и повышает эффективность модели.
• Иерархическое представление. Для выявления все более абстрактных
признаков можно объединять несколько сверточных слоев. Нижние
слои фокусируются на базовых закономерностях, таких как признаки
на уровне символов или слов, тогда как более высокие слои выявляют
более сложные закономерности.
• Инвариантность к сдвигу. Сверточные слои обеспечивают определенный уровень инвариантности к сдвигу, то есть сеть может распознавать
закономерности независимо от точного положения связанных с ними
элементов во входной последовательности. Для текстовых данных это
означает способность определять особенно важные признаки независимо от их места в предложении, что очень полезно для понимания
семантики текста.
• Взаимодействие признаков. Сверточные слои позволяют модели выявлять взаимодействие между соседними элементами во входной последовательности. Что касается текста, то это бывает полезно для задач,
где важна связь между словами или символами — например, для задач
моделирования языка, генерации текста и преобразования одной последовательности в другую.
• Интеграция с пулингом. Для уменьшения размерности извлеченных
признаков с сохранением наиболее релевантной информации после сверточных слоев могут применяться слои пулинга, такие как слой пулинга
по максимальному значению или слой пулинга по среднему значению.
• Совместимость с предварительно обученными эмбеддингами
слов. В одномерных сверточных сетях для инициализации представления слов в сети можно использовать предварительно обученные
Модели последовательностей для автоматической классификации текста
■
381
эмбеддинги слов, такие как Word2Vec или GloVe. Подобная инициализация помогает модели производить обобщения и повышает точность,
особенно при недостатке обучающих данных.
Одномерные сверточные сети зарекомендовали себя как ценный инструмент
для классификации текстов в NLP-задачах. Особенно привлекательным вариантом их делают такие свойства, как способность выявлять локальные
особенности, иерархическое представление и совместимость с предварительно обученными эмбеддингами слов. С помощью таких методов глубокого
обучения, как одномерная свертка удается эффективно обрабатывать и классифицировать текстовые данные, и благодаря этому открываются широкие
возможности для практического применения ИИ, например, для анализа
эмоциональной окраски, категоризации документов и обнаружения спама.
Область обработки естественного языка продолжает развиваться, и интеграция одномерных сверточных сетей в системы классификации текстов повышает точность и эффективность рабочих процессов в этой сфере.
Рассмотрим использование одномерной сверточной сети для задачи классификации текстов на примере классификации жалоб в базе CFRB. В библиотеке TensorFlow одномерная сверточная сеть представлена в виде слоя под
названием tf.keras.layers.Conv1D. Подобно слою двумерной сверточной
сети, этот слой также принимает в качестве входных аргументов количество фильтров, размер ядра, функцию активации, дополнение (padding) и прочие параметры. За слоем одномерной сверточной сети следует слой пулинга
GlobalAveragePooling1D. В этом слое для каждого выхода каждого фильтра выполняется пулинг по среднему значению. Рассмотрим следующий блок кода:
1. model = tf.keras.Sequential([
2.
encoder,
3.
tf.keras.layers.Embedding(
4.
input_dim=len(encoder.get_vocabulary()),
5.
output_dim=64,
6.
mask_zero=True),
7.
tf.keras.layers.Conv1D(filters=64,
kernel_size=5,
activation='relu',
padding='same'),
8.
tf.keras.layers.GlobalAveragePooling1D(),
9.
tf.keras.layers.Flatten(),
10.
tf.keras.layers.Dense(64, activation='relu'),
11.
tf.keras.layers.Dense(3)
382
■
Pythonic AI
12. ])
13.
14. model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy
(from_logits=True),
15.
optimizer=tf.keras.optimizers.Adam(1e-4),
16.
metrics=['sparse_categorical_accuracy'])
17.
18.
history = model.fit(train_ds, epochs=10,
19.
validation_data=test_ds,
20.
validation_steps=30, verbose=2)
Оценка модели на тестовых данных показана на следующей иллюстрации:
Иллюстрация 10.16. Оценка модели одномерной сверточной сети
Так мы можем разработать приложение для классификации текста, используя различные модели последовательностей и эмбеддингов. Теперь применим
обученную модель к нашим данным.
При удовлетворительной оценке протестируем модель на новых текстах. Напишем жалобу или комментарий по поводу какого-то продукта и передадим
этот текст модели для предсказания целевого класса. Для простоты пропустим стадию предварительной обработки и передадим модели чистый текст
(в переводе: «я не активировал свою кредитную карту, но была произведена
транзакция на сумму»), как показано на следующей иллюстрации:
Иллюстрация 10.17. Прогнозирование с помощью модели
Модели последовательностей для автоматической классификации текста
■
383
Как показано выше, текст нашей жалобы был преобразован в набор данных
TensorFlow и передан функции модели predict(). Вывод функции показывает, что наибольшую оценку уверенности получил класс 1, за ним следуют
класс 0 и класс 2. Выходит, что, скорее всего, жалоба принадлежит к классу
1, которому соответствует категория продукта «Кредитная или предоплаченная карта». На иллюстрации 10.11 показан пример сопоставления названия
продукта и целевого класса.
Заключение
В этой главе мы рассмотрели приложения по классификации текстов. Мы
изучили набор данных о жалобах потребителей, на основе которого можно
создать приложение, позволяющее классифицировать категорию продукта
по тексту жалобы. Реальные тексты на естественном языке часто содержат
много шума, то есть лишней и неправильно оформленной информации. Для
чтения набора данных мы воспользовались библиотекой Pandas и различными методами работы с данными. Мы очистили и предварительно обработали текст внутри объекта DataFrame. С помощью библиотеки TensorFlow 2
мы построили модели последовательностей разной архитектуры, обучили
их и произвели их оценку с помощью тестовых данных. Мы научились использовать в наших моделях предварительно обученные эмбеддинги слов для
преобразования текста в векторный формат. Мы также познакомились с архитектурой одномерных сверточных нейронных сетей и узнали, как такие
сети можно использовать для работы с последовательными данными. Современные модели трансформеров способны векторизовать текст не только на
уровне слов, но и на уровне предложений.
В следующей главе мы познакомимся с моделями внимания и трансформерами, потрясающие возможности которых открывают новые перспективы
в области обработки естественного языка.
Основные выводы
• Классификация текста — это процесс автоматического соотнесения
текстовых данных с заранее определенными категориями или классами.
• Данные о жалобах CFPB (Бюро финансовой защиты потребителей) —
это ценный набор данных для задач классификации текстов, связанных с жалобами потребителей в финансовой индустрии.
384
■
Pythonic AI
• Pandas — это мощная библиотека Python для манипулирования данными, их очистки и предварительной обработки.
• Для классификации текстов мы построили модели последовательностей, такие как LSTM и двунаправленные RNN.
• Для векторизации и разработки моделей последовательностей мы
воспользовались предварительно обученными эмбеддингами библиотеки TensorFlow Hub. Другой популярный выбор представления слов
для классификации текстов — это эмбеддинги Global Vectors for Word
Representation (GloVe). Такие эмбеддинги, предварительно обученные
на основе больших корпусов, часто можно применять к текстовым данным непосредственно.
• Область применения сверточных сетей не ограничивается задачами
обработки данных изображений, и одномерные сверточные сети можно использовать также для классификации текстов.
• Мы разработали одномерную сверточную нейронную сеть и воспользовались ею для классификации текстов.
Ссылки
• Бюро финансовой защиты потребителей: https://www.consumerfinance.
gov/
• Официальный сайт Pandas: https://pandas.pydata.org/
• Энкодер ярлыков Scikit Learn: https://scikit-learn.org/stable/modules/
generated/sklearn.preprocessing.LabelEncoder.html
• TensorFlow Hub: https://www.tensorflow.org/hub
• Слой Keras библиотеки TensorFlow Hub: https://www.tensorflow.org/hub/
api_docs/python/hub/KerasLayer
• Слой одномерной сверточной сети библиотеки TensorFlow: https://
www.tensorflow.org/api_docs/python/tf/keras/layers/Conv1D
• Одномерный слой пулинга по максимуму библиотеки TensorFlow:
http s : / / w w w. te ns or f l ow. org / api _ d o c s / py t h on / t f / ke r a s / l aye rs /
GlobalMaxPooling1D
Глава 11
Модели внимания
и трансформеры
Введение
За последние несколько лет в сфере искусственного интеллекта произошли
стремительные изменения. Особенно пристальное внимание общественности привлекло появление больших языковых моделей (LLM, Large Language
Models). У всех на слуху теперь ChatGPT. Всего лишь за пять лет модели на основе трансформеров произвели настоящую революцию в области обработки
естественного языка (NLP, Natural Language Processing). Создатели модели
трансформеров расширили ее для работы с письменными текстами и другими
типами информации, такими как изображения или звуки. Языковые модели
широко применяются для различных NLP-задач, таких как резюмирование и генерация текста, анализ эмоциональной окраски, работа виртуального помощника, распознавание именованных сущностей, распознавание речи, аннотация
изображений, синтез речи на основе текста, машинный перевод, генерация
программного кода и пр. Основной элемент при разработке моделей трансформеров — это механизм внимания. Способность модели трансформера выявлять
сложные отношения и обрабатывать дальнодействующие зависимости, а также
ее общая эффективность в различных NLP-задачах обеспечили настоящий прорыв в сфере глубокого обучения и обработки естественного языка. В этой главе
мы рассмотрим принципы работы моделей внимания и трансформеров, значение этих моделей и их влияние на решение практических NLP-задач.
Структура
В этой главе мы рассмотрим следующие темы:
• Механизм внимания в RNN.
• Архитектура трансформера.
386
■
Pythonic AI
• Модель BERT.
• Реализация слоя внимания.
• Реализация блока трансформера.
• Генеративные предобученные трансформеры.
Цели
В этой главе мы познакомимся с революционным механизмом внимания
и с архитектурой трансформеров. С помощью библиотеки TensorFlow мы
построим модели со слоями внимания и модели трансформеров и воспользуемся ими для классификации текстов. Мы также познакомимся с продвинутыми архитектурами трансформеров, такими как BERT, GPT, и воспользуемся их предварительно обученными моделями посредством API.
Механизм внимания в RNN
В главе 9 «Запускаем модели преобразования многослойных последовательностей» мы познакомились с традиционными архитектурами рекуррентных
нейронных сетей (RNN, или РНС). Традиционные рекуррентные нейронные
сети сталкиваются с определенными проблемами при работе с длинными
последовательностями или при обработке релевантной информации на разных временных шагах. Преодолеть ограничения традиционных RNN позволяют механизмы внимания — весьма эффективное средство повышения
производительности рекуррентных сетей.
В своей основе механизм внимания позволяет модели сосредоточиться на определенных частях входной последовательности для генерации релевантного
вывода. Благодаря этому механизму модель присваивает вес, или важность,
различным частям входной последовательности, и тем самым избирательно
уделяет повышенное внимание наиболее релевантной информации. Иными
словами, механизмы внимания дают модели возможность решать, на что обращать внимание во входной последовательности, то есть какие элементы
или временные шаги наиболее важны.
Механизм внимания был разработан в основном для задач машинного перевода,
но позже он нашел применение и в других практических областях. Этот инновационный механизм был описан в работе Бахданау, Чо и Бенджио применительно
к машинному переводу с использованием сетей и механизмов внимания1.
1
Dzmitry Bahdanau, Kyunghyun Cho, and Yoshua Bengio. “Neural machine translation by
jointly learning to align and translate”. arXiv preprint arXiv:1409.0473 (2014).
Модели внимания и трансформеры
■
387
Позднее предложенную ими модель усовершенствовали и дополнили Луонг
и другие исследователи. Предложенная Бахданау и его коллегами модель состояла из архитектуры кодировщика-декодировщика (энкодера-декодера), аналогичной существующим моделям преобразования последовательностей, о которых мы узнали из главы 9 «Запускаем модели преобразования многослойных
последовательностей». Кодировщик, или «энкодер», представляющий собой
двунаправленную рекуррентную нейронную сеть (RNN), шаг за шагом обрабатывал исходное предложение (входную последовательность) и генерировал
аннотации, отражающие контекстную информацию каждого слова. В архитектуре RNN кодер может представлять собой рекуррентный слой, такой как долгосрочная краткосрочная память (LSTM) или управляемый рекуррентный
блок (GRU). Механизм внимания в декодировщике (декодере) рассчитывает
веса внимания для каждого слова в исходном предложении на основе релевантности этого слова для текущего генерируемого целевого слова. Декодер также
может быть реализован в виде слоя RNN. Рассмотрим следующую иллюстрацию, позаимствованную непосредственно из работы Бахданау:
Иллюстрация 11.1. Механизм внимания
На иллюстрации выше показано, что на стороне энкодера используется двунаправленная сеть RNN, а на стороне декодера — обычная RNN. Веса внимания используются для создания вектора контекста, объединяющего исходные аннотации с соответствующими весами.
Модель на основе внимания превзошла традиционные модели и значительно повысила точность перевода. Механизм внимания позволил модели работать с более длинными предложениями, улавливать сложные зависимости
и генерировать более точные переводы, обращая избирательное внимание на
388
■
Pythonic AI
соответствующие части исходного предложения. Эта работа заложила основу
для последующих достижений в области моделей, основанных на внимании,
и оказала значительное влияние на область обработки естественного языка.
Архитектура трансформера
В сфере глубокого обучения под трансформером понимается особый тип архитектуры нейронных сетей, который описан в статье Васвани и его коллег1
«Внимание — это все, что вам нужно» (“Attention is All You Need”), опубликованной в 2017 году. Трансформеры произвели настоящую революцию в области
обработки естественного языка и нашли применение для решения различных
задач. Ключевая инновация, связанная с трансформерами, — это механизм самовнимания, позволяющий модели оценивать важность различных слов или
токенов в последовательности при их обработке. В отличие от традиционных
рекуррентных (RNN) или сверточных нейронных сетей (CNN), трансформеры не полагаются на последовательную обработку или сверточные фильтры
фиксированного размера. Вместо этого они способны обрабатывать всю входную последовательность параллельно, что делает их высокоэффективными
и хорошо подходящими для параллельных вычислений.
Векторы запроса, ключа и значения
Представьте себе принцип работы простой поисковой системы. Мы задаем
запросы для поиска, а поисковая система уже имеет индекс, содержащий различные поисковые термины и соответствующие веб-страницы, на которых
эти термины можно найти. Термины в индексе — это ключи. Когда поступает поисковой запрос, поисковая система сравнивает термины запроса с ключами индекса и решает, насколько релевантна каждая веб-страница и какое
внимание ей стоит уделить. Затем с соответствующих веб-страниц система
получает информацию (значение), выдаваемую в качестве ответа на запрос.
В модели трансформера нам нужно создать вектор запроса, вектор ключа
и вектор значения для каждого слова во входной последовательности или
предложении. Запрос означает слово, на которое мы хотим обратить внимание.
Векторы ключей содержат информацию о каждом элементе входной последовательности и служат ориентиром для вычисления оценки внимания между
1
Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez,
Łukasz Kaiser, and Illia Polosukhin. “Attention is all you need”. Advances in neural information
processing systems 30 (2017).
Модели внимания и трансформеры
■
389
запросами и ключами. Векторы значений содержат фактическую информацию
или «контент», связанный с каждым элементом входной последовательности.
Они вычисляются как взвешенная сумма во время операции внимания.
Механизмс амовнимания
В моделях трансформеров для отражения отношений между различными
токенами входной последовательности широко используется механизм самовнимания, также называемый «внутренним вниманием» или «масштабируемым вниманием на основе скалярного произведения». Для примера рассмотрим следующее предложение:
“Books are our best friends” («Книги — наши лучшие друзья»).
Если мы хотим воспользоваться механизмом самовнимания, то в этом предложении нужно начать с первого слова "Books" («Книги»). Будем рассматривать
его как запрос, а все остальные слова — как ключи. Оценивая термин "Books"
в сравнении со всеми ключами, мы определяем степень внимания (самовнимания), какое нужно уделить другим компонентам входного текста. Оценка
самовнимания вычисляется посредством скалярного произведения вектора
запроса и вектора каждого из ключей. Затем к результатам этих скалярных
произведений применяется функция SoftMax, вычисляющая нормализованные оценки внимания (всех ключей), которые в сумме равны единице.
Чтобы придать вес релевантным словам и отсеять нерелевантные, эти оценки, вычисленные функцией SoftMax, умножаются на векторы значений. Наибольший вес получают самые релевантные слова. Сумма этих взвешенных
значений и будет оценкой внимания первого слова.
В трансформере механизм самовнимания вычисляет оценки внимания разных слов параллельно, независимо от других слов.
Энкодер и декодер
Архитектура трансформера состоит из энкодера и декодера. Энкодер принимает входную последовательность, например, предложение или документ,
и параллельно обрабатывает ее, создавая последовательность эмбеддингов
входной последовательности. Затем на основе этих эмбеддингов декодер генерирует выходную последовательность.
Энкодер содержит стек идентичных кодирующих слоев. Каждый кодирующий слой имеет два подуровня. Первый подуровень реализует многоголовый
390
■
Pythonic AI
механизм самовнимания, а второй подуровень представляет собой обычную
полносвязную нейронную сеть. Энкодер также содержит остаточные (сквозные)
связи вокруг каждого из двух подуровней. Об остаточных слоях с пропущенными, или сквозными, связями в модели ResNet говорилось в главе 4 «Проектирование CNN с помощью TensorFlow». Для нормализации активации нейронов
внутри уровня применяется слой нормализации, схожий со слоем пакетной
нормализации, но обрабатывающий каждую выборку, а не мини-пакеты.
Декодер также содержит стек идентичных декодирующих слоев. Каждый такой слой, как и кодирующие слои, состоит из подслоев внимания и прямого
распространения, а также из дополнительного подслоя многоголового (перекрестного) внимания на выходе стека кодеров. Как и слои энкодера, слои декодера содержат сквозные связи и слои нормализации. Ниже показана схема
из оригинальной статьи о трансформерах “Attention is What You Need”:
Иллюстрация 11.2. Архитектура трансформера
Модели внимания и трансформеры
■
391
Как показано на иллюстрации выше, энкодер наряду с собственно эмбеддингами принимает и позиционные эмбеддинги текстов. В нем используется метод позиционного кодирования, который предоставляет трансформеру
информацию об относительном положении слов или элементов в последовательности. Благодаря позиционному кодированию модель понимает, что значение слова или элемента может зависеть не только от признаков этого слова
или элемента, но и от его положения в последовательности.
Многоголовое внимание
Многоголовое внимание — один из инновационных механизмов, используемых в архитектуре трансформеров. Трансформер случайным образом инициализирует несколько наборов весовых матриц запроса, ключа и значения,
каждый из которых отражает различные отношения между словами. Эти
матрицы обрабатываются параллельно благодаря распределению вычислительной нагрузки между несколькими процессорами или GPU.
Следует иметь в виду, что показанная на иллюстрации 11.2 модель трансформера представляет собой архитектуру типа «последовательность-последовательность» или «многие ко многим». Она была разработана для машинного
перевода, то есть для генерации последовательности (переведенного предложения) на целевом языке из другой последовательности (входного предложения) на исходном языке. Но в сфере обработки естественного языка существует ряд задач, в которых не нужно генерировать последовательности.
В качестве примера можно привести анализ эмоциональной окраски (настроения), представляющий собой задачу бинарной классификации, когда
на основе заданной последовательности (предложения) генерируется один
выходной показатель. Классификация текстов — это задача многоклассовой
классификации, когда вывод генерируется для заданного набора классов.
В таких задачах декодирующая часть модели трансформера не нужна, но для
получения входных эмбеддингов предложений с механизмом внимания можно пользоваться кодирующей частью.
Практическое применение трансформеров в сфере обработки естественного
языка разделилось на два направления. Одно из них — это генеративный ИИ
с полной архитектурой типа «последовательность-последовательность», энкодером и декодером. Он используется для машинного перевода, генерации
и резюмирования текстов, ответов на вопросы и т. д. Другое направление —
прогностическое моделирование, в котором модели трансформеров только
с кодирующей частью используются для классификации текстов и прочих
задач.
392
■
Pythonic AI
Модель BERT
Модель BERT1 (Bidirectional Encoder Representations from Transformers,
«Двунаправленные представления энкодера из трансформеров»), разработанная исследователями из Google на основе трансформеров, пожалуй, самым коренным образом изменила всю сферу обработки естественного языка и открыла новые перспективы для точного понимания языка машинами.
В отличие от традиционных моделей, рассматривающих слова изолированно
или в фиксированном контекстном окне, BERT может выявлять значения
слов с учетом всего контекста предложения. Таким образом она способна устанавливать тонкие связи и зависимости между словами, благодаря чему машинам удается гораздо полнее понимать естественный человеческий язык.
Одна из ключевых особенностей модели BERT — это ее двунаправленный характер. Она обрабатывает предложение в обоих направлениях, слева направо и справа налево, что позволяет охватить весь контекст и все зависимости между словами. Такой двунаправленный процесс устраняет ограничения
традиционных моделей, которые при интерпретации значения текущего слова опираются только на предыдущие или последующие слова. Как следствие,
модель BERT способна гораздо лучше понимать исходный текст и генерировать гораздо более осмысленный текст с учетом контекста.
Эффективность модели BERT обусловлена процессом предварительного
обучения и тонкой настройки. Изначально BERT проходит предварительное
обучение на большом корпусе текстовых данных, таких как книги или статьи
Википедии. Во время предварительного обучения модель случайным образом маскирует некоторые слова из входных данных. Затем на основе контекста и выявленных связей она пытается предсказать исходные словарные
идентификаторы замаскированных слов. Такое предварительное неконтролируемое обучение («обучение без учителя») позволяет BERT создавать прочную основу для понимания естественного языка. Поэтому BERT называют
еще маскированной языковой моделью (MLM, Masked Language Model).
После предварительного обучения BERT настраивается для решения конкретных задач NLP. Тонкая настройка включает в себя обучение модели на
небольшом наборе данных, специфичном для целевой задачи, например, анализа настроения или ответов на вопросы. Благодаря тонкой настройке BERT
адаптирует свои знания к нюансам и требованиям задачи, что делает ее очень
универсальной и эффективной для различных приложений. На схеме ниже
показаны процессы предварительного обучения и тонкой настройки модели
1
Jacob Devlin, Ming-Wei Chang, Kenton Lee, and Kristina Toutanova. “Bert: Pre-training of deep
bidirectional Transformers for language understanding”. arXiv preprint arXiv:1810.04805 (2018).
Модели внимания и трансформеры
■
393
BERT. Эта схема позаимствована непосредственно из оригинальной статьи
о модели BERT:
Иллюстрация 11.3. Предварительное обучение и тонкая настройка модели BERT
Реализация слоя внимания
В этом разделе мы построим модель для задачи классификации текста с использованием слоя внимания. Мы будем строить эту модель с помощью библиотеки TensorFlow 2, используя набор данных фильмов IMDB1.
IMDB (Internet Movie Database, «Интернет-база фильмов») — это онлайн-база
данных, в которой хранится информация, связанная с фильмами, телесериалами, видеоиграми и подобными произведениями. Различные типы данных
включают краткое описание сюжета, актерский состав, состав съемочных
групп, биографии, рейтинги, обзоры и прочие данные, связанные с указанной темой. Большой набор данных IMDB по рецензиям на фильмы состоит
из 25 000 обучающих и 25 000 тестовых данных. Данные в этих наборах представляют собой текстовые рецензии на фильмы, и их можно использовать
для бинарной классификации настроения (эмоциональной окраски).
В библиотеке TensorFlow набор данных IMDB доступен в модуле tf.keras.
datasets.imdb. Для загрузки наборов данных этот модуль предлагает функцию load_data(). Аргумент num_words этой функции load_data() задает
количество наиболее частых слов, которые нужно сохранить в тренировочном и тестовом наборах данных. Мы сохраним в наборах 5000 наиболее часто
встречающихся слов, как это сделано в следующем фрагменте кода:
1
Andrew L. Maas, Raymond E. Daly, Peter T. Pham, Dan Huang, Andrew Y. Ng, and Christopher
Potts. (2011). Learning Word Vectors for Sentiment Analysis. The 49th Annual Meeting of the
Association for Computational Linguistics (ACL 2011).
394
■
Pythonic AI
1. import tensorflowas tf
2.
3. num_words_to_keep = 5000
4. X_train, y_train), (X_test, y_test) = tf.keras.datasets.imdb.load_
data(num_words=num_words_to_keep)
Целевые переменные y_train и y_test представляют собой бинарные переменные, имеющие только два возможных значения, 0 и 1, то есть «отрицательный» и «положительный» настрой рецензий.
Поскольку набор данных IMDB содержит текстовые данные рецензий на
фильмы, в нем могут встречаться предложения или последовательности разной длины. Для приведения всех последовательностей к одинаковой длине
воспользуемся функцией tf.keras.utils.pad_sequences. Определим также
аргумент maxlen, задающий максимальную длину всех последовательностей.
Это означает, что в каждом обзоре будет фиксированное (maxlen) количество
слов. Соответствующий фрагмент кода показан ниже:
1. max_words = 500
2. X_train = tf.keras.utils.pad_sequences(X_train, maxlen=max_words)
3. X_test = tf.keras.utils.pad_sequences(X_test, maxlen=max_words)
Теперь можно построить модель последовательности, используя слой внимания (Attention). Для начала воспользуемся слоем внимания tf.keras.layers.
Attention библиотеки TensorFlow.
Слой внимания библиотеки TensorFlow
Класс tf.keras.layers.Attention реализует механизм внимания на основе
скалярного произведения или метода Луонга (Luong). Этот слой принимает
на входе два списка — тензор запросов и тензор значений. Также можно указать необязательный тензор ключей, но если он не указан, то в качестве ключа
и значения используется тензор значений. Поскольку слой внимания принимает несколько входных данных, воспользоваться последовательным API не
получится.
В данном случае мы воспользуемся функциональным API. Рассмотрим следующий фрагмент кода:
Модели внимания и трансформеры
■
395
1. text_input = tf.keras.Input(shape=(None,))
2.
3. text_embeddings = tf.keras.layers.Embedding(num_words_to_keep, 32,
input_length=max_words)(text_input)
4.
5. sequence_encoding= tf.keras.layers.Conv1D(filters=100, kernel_
size=4, padding='same')(text_embeddings)
6.
7. attention_sequence = tf.keras.layers.Attention(use_scale=True,
dropout=0.6)([sequence_encoding, sequence_encoding])
8.
9. query_encoding = tf.keras.layers.GlobalAveragePooling1D()
(sequence_encoding)
10. query_value_attention = tf.keras.layers.GlobalAveragePooling1D()
(attention_sequence) 11.
12. concat_layer = tf.keras.layers.Concatenate()([query_encoding,
query_value_attention])
13.
14. outputs = tf.keras.layers.Dense(1)(concat_layer)
15.
16. model = tf.keras.Model(inputs=text_input, outputs=outputs)
17.
18. model.compile(loss=tf.keras.losses.BinaryCrossentropy
(from_logits=True), optimizer=tf.keras.optimizers.Adam(1e-4),
metrics=['accuracy'])
19.
20. model.fit(X_train, y_train, validation_data=(X_test, y_test),
epochs=20, batch_size=128, verbose=2)
Приведенный выше фрагмент кода представляет собой пример построения
модели нейронной сети с использованием слоя внимания. В первой строке
создается входной слой для целочисленных последовательностей варьируемой длины. Форма shape=(None,) указывает на то, что последовательности
могут иметь любую длину.
Далее входные последовательности подаются на слой tf.keras.layers.
Embedding, который переводит каждое целочисленное значение в последо-
396
■
Pythonic AI
вательностях в плотный вектор. Параметр num_words_to_keep задает размер словаря, 32 — размерность векторов эмбеддингов на выходе, а input_
length=max_words — максимальная длина входных последовательностей.
Далее мы создали одномерный сверточный слой с сотней (100) фильтров
и размером ядра 4. Параметр padding='same' говорит о том, что данные
на выходе будут иметь ту же форму, что и на входе. Входные эмбеддинги
text_embeddings проходят через заданный сверточный слой и на его выходе
получается закодированная последовательность sequence_encoding. Затем
эта закодированная последовательность sequence_encoding проходит через
слой внимания для вычисления параметров внимания «запрос-значение».
Эта последовательность sequence_encoding в списке упомянута дважды:
в качестве векторов запроса и векторов значения на входе слоя внимания
(Attention). Механизм внимания присваивает разные веса различным частям
входной последовательности, отбирая важную информацию. Аргументу use_
scale задано значение True, включающее масштабирование весов внимания;
также задан коэффициент дропаута для уменьшения переобучения.
Далее для последовательности sequence_encoding мы задали слой глобального пулинга по среднему значению по оси последовательности. Этот слой
вычисляет среднее значение каждого фильтра по всей длине последовательности, в результате чего производится кодирование запроса. Аналогичным
образом мы применили слой глобального пулинга к последовательности
attention_sequence. После этого результат кодирования запроса и оценка
внимания запрос-значение объединяются (конкатенируются), и выход этого
слоя подается на следующий далее обычный плотный слой, который вычисляет конечный выход модели. Наконец, мы создали экземпляр класса модели
с указанием входов и выходов, скомпилировали модель и обучили ее на данных IMDB для бинарной классификации.
Примечание. Вместо одномерного сверточного слоя для кодирования
можно воспользоваться слоем типа RNN (рекуррентной нейронной
сети). Например, в приведенном выше коде в строке номер 5 можно
заменить слой Conv1D на слой LSTM, как показано ниже:
1. sequence_encoding = tf.keras.layers.LSTM(100, return_
sequences=True) (text_embeddings)
Модели внимания и трансформеры
■
397
Ниже показаны характеристики (обзор) модели:
Иллюстрация 11.4. Предварительный обзор модели внимания
Примечание. Тексты рецензий в наборе данных IMDB, доступном
в модуле tf.keras.datasets.imdb, уже предварительно обработаны,
и каждый текст рецензии закодирован как список индексов слов (целых чисел). Поэтому нам не нужно использовать слой TextVectorization
для кодирования исходных текстов.
На следующей иллюстрации показана итоговая оценка модели на тестовых
данных:
Иллюстрация 11.5. Оценка модели
На тестовых данных точность модели достигла 87,56%.
398
■
Pythonic AI
Пользовательский слой внимания
Вместо того чтобы использовать слой внимания из библиотеки TensorFlow, можно создать собственный слой внимания. В TensorFlow пользовательские слои
создаются как объекты наследования класса tf.keras.layers.Layer. В методе __init__ можно задать любые параметры, характерные для слоя, и вызвать
метод super().__ init__() для инициализации базового класса слоя Layer.
Далее нужно реализовать метод build() для задания обучаемых переменных
(весов) слоя. Можно воспользоваться методом add_weight с нужной формой
и инициализаторами для создания и регистрации весов в слое. Также нужно
реализовать метод call(), задающий логику прямого прохождения слоя. Здесь
мы указываем, как слой обрабатывает входные данные и производит желаемый
выход. После определения своего пользовательского слоя, мы можем использовать его в нейросетевых моделях, создавая экземпляр слоя и добавляя его в архитектуру модели. Пользовательские слои расширяют функциональность библиотеки TensorFlow и позволяют реализовывать специализированные слои или
сложные операции, недоступные в стандартном наборе слоев. Рассмотрим следующий фрагмент кода, в котором задается пользовательский слой внимания:
1. class Attention(tf.keras.layers.Layer):
2.
def __init__(self, return_sequences=True):
3.
self.return_sequences = return_sequences
4.
super(Attention,self).__init__()
5.
6.
def build(self, input_shape):
7.
self.W=self.add_weight(shape=(input_shape[-1],1),
initializer="normal")
8.
self.b=self.add_weight(shape=(input_shape[1],1),
initializer="zeros")
9.
super(Attention,self).build(input_shape)
10.
11.
def call(self, x):
12.
dot_prod = tf.keras.activations.tanh(tf.matmul(x,self.W)+self.b)
13.
attention = tf.keras.activations.softmax(dot_prod, axis=1)
14.
out_sequences = x*attention
15.
16.
if self.return_sequences:
17.
return out_sequences
18.
19.
return tf.math.reduce_sum(out_sequences, axis=1)
Модели внимания и трансформеры
■
399
В этом блоке мы создали пользовательский слой внимания Attention как
подкласс класса tf.keras.layers.Layer. Он принимает необязательный
аргумент return_sequences. Для инициализации базового класса Layer используется функция super(). В методе build() мы создали две переменные
для весов: весовую матрицу W размерностью (64, 1), инициализированную
с помощью нормального распределения, и вектор смещения b размерностью
(500, 1), инициализированный нулями. В качестве энкодера в основной модели используем двунаправленную LSTM из 32 узлов. Выход энкодера подадим
на слой внимания. Поскольку это двунаправленная LSTM, измерений каждого скрытого состояния (по одному на каждое слово) будет 2 * 32 = 64, что
и указано в размерности весовой матрицы W. Это значение извлекается из
последнего показателя размерности input_shape. Также каждая последовательность на выходе слоя эмбеддинга Embedding станет 500-мерным вектором, поскольку аргумент input_length класса Embedding задан как 500. Это
и есть размерность вектора смещения. Данное значение можно извлечь из
первого показателя размерности input_shape.
В методе call() мы сначала вычисляем скалярное произведение dot_prod
входного значения x и весовой матрицы W с применением функции активации tanh. Для получения весов внимания результат пропускается через
функцию активации SoftMax. Затем для получения последовательностей внимания эти веса поэлементно умножаются на входное значение x. Если аргументу return_sequences задано значение True, то возвращаются имеющиеся
последовательности. В противном случае имеющиеся последовательности
суммируются, и в результате получается единое векторное представление.
Теперь с помощью этого пользовательского слоя можно задать архитектуру
всей модели, как показано в следующем блоке кода:
1. text_input = tf.keras.Input(shape=(max_words,))
2.
3. x = tf.keras.layers.Embedding(num_words_to_keep, 32, input_length=max_
words)(text_input)
4. x = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32, return_
sequences=True))(x)
5. x = Attention(return_sequences=True)(x)
6. x = tf.keras.layers.LSTM(32)(x)
7. outputs = tf.keras.layers.Dense(1)(x)
8. model= tf.keras.Model(inputs=text_input, outputs=outputs)
9. model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
optimizer=tf.keras.optimizers.Adam(1e-4), metrics=['accuracy'])
400
■
Pythonic AI
Ниже показаны краткие характеристики (сводка ) модели:
Иллюстрация 11.6. Сводка модели
Теперь можно загружать в модель данные для обучения и обучать ее обычным способом:
1. model.fit(X_train, y_train, validation_data=(X_test, y_test),
epochs=20, verbose=2)
Реализация блока трансформера
Архитектура модели трансформера в ее базовой форме подразумевает реализацию энкодера с механизмами многоголового внимания, нормализации
слоев и полносвязной нейронной сетью прямого распространения. Энкодер
принимает на входе конкатенацию позиционных эмбеддингов и эмбеддингов
токенов входного текста. Архитектура декодера трансформера практически
аналогична архитектуре энкодера. В данном примере классификации рецензий на фильмы IMDB мы реализуем модель классификации на основе модели
трансформера с энкодером, с помощью заданного в библиотеке TensorFlow
стандартного слоя трансформера, а также с помощью пользовательского
слоя.
Модели внимания и трансформеры
■
401
Использование слоев из официальных
моделейT ensorFlow
TensorFlow Model Garden представляет собой репозиторий моделей машинного обучения и связанных с ними ресурсов, созданных с помощью библиотеки TensorFlow. В TensorFlow Model Garden представлен широкий спектр
моделей, включая самые современные модели для выполнения таких задач,
как классификация изображений, обнаружение объектов, обработка естественного языка и пр. Также Model Garden содержит и другие ресурсы, такие как скрипты предварительной обработки данных, инструменты оценки
и конвейеры обучения. Эти модели реализованы с помощью библиотеки
TensorFlow и поставляются с программным кодом, предварительно обученными весами и подробной документацией, облегчающей их использование
и понимание. Помимо всего Model Garden содержит официальные модели
TensorFlow — коллекцию моделей, созданных с использованием высокоуровневых API TensorFlow. Слои трансформеров в TensorFlow — это часть
NLP-библиотек официальных моделей TensorFlow.
Чтобы воспользоваться слоями трансформеров, нужно отдельно установить
библиотеку tf-models-official, так как по умолчанию она в Google Colab не
установлена. Это можно сделать с помощью следующей команды:
1. !pip3 install tf-models-official
После установки библиотеки ее сначала нужно импортировать:
1. import tensorflow_models as tfm
Для создания энкодера трансформера нашей модели воспользуемся блоком
TransformerEncoderBlock из модуля tfm.nlp.layers. Обратите внимание,
что здесь мы решаем задачу бинарной классификации, и декодирующая часть
трансформера нам не нужна.
Ранее мы задали аргументу maxlen функции pad_sequences значение max_
words, равное 500. Таким образом, в наших входных данных для каждого
анализа будет строго 500 слов. В этой модели на основе трансформера мы
зададим размерность входного слоя в соответствии с ожидаемой длиной,
а именно (max_words,).
На иллюстрации 11.2 видно, что кодирующая часть трансформера принимает конкатенацию позиционного эмбеддинга и обычного эмбеддинга токена
402
■
Pythonic AI
входных данных. Генерировать эмбеддинги токенов можно с помощью слоя
tf.keras.layers.Embedding. На вход этого слоя подаются данные размерностью словаря num_words_to_keep, которую мы ранее задали как 500. Генерировать позиционный эмбеддинг можно с помощью слоя tfm.nlp.layers.
PositionEmbedding, которому в качестве аргумента нужно передать максимальный размер входной последовательности. В нашем случае это max_words,
заданный ранее как 500. Далее код будет такой:
1. inputs = tf.keras.layers.Input(shape=(max_words,))
2. embedding = tf.keras.layers.Embedding(input_dim =num_words_to_keep,
output_dim=32)(inputs)
3. position = tfm.nlp.layers.PositionEmbedding(max_words)(embedding)
4.
5. x = tf.keras.layers.add([embedding, position])
6. x = tfm.nlp.layers.TransformerEncoderBlock(num_attention_heads=2,
inner_dim=32, inner_activation="tanh")(x)
7. x = tf.keras.layers.GlobalAveragePooling1D()(x)
8. x = tf.keras.layers.Dropout(0.1)(x)
9. x = tf.keras.layers.Dense(32, activation="relu")(x)
10. x = tf.keras.layers.Dropout(0.1)(x)
11. outputs = tf.keras.layers.Dense(1)(x)
12. model = tf.keras.Model(inputs=inputs, outputs=outputs)
13.
14.
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_
logits=True),
15.
optimizer=tf.keras.optimizers.Adam(1e-4),
16.
metrics=['accuracy'])
17. model.fit(X_train, y_train, validation_data=(X_test, y_test),
epochs=20, verbose=2)
Как показано выше, в строках номер 2 и 3 мы сгенерировали эмбеддинги токенов и эмбеддинги позиций. Затем мы объединили (конкатенировали) эти
два вида эмбеддингов с помощью слоя tf.keras.layers.add. Эти объединенные данные мы передали в слой TransformerEncoderBlock. В этом слое tfm.
nlp.layers.TransformerEncoderBlock совмещены слой tf.keras.layers.
MultiHeadAttention и двухслойная сеть с прямым распространением. Для
слоя TransformerEncoderBlock мы задали аргументы num_attention_heads,
Модели внимания и трансформеры
■
403
inner_dim и inner_activation. За блоком трансформера следует блок глобального пулинга по среднему значению для агрегации информации от всех
токенов в последовательности и получения репрезентации входной последовательности фиксированной длины. Для предотвращения переобучения
добавлены слои дропаута. Для окончательной бинарной классификации закодированный выход был подан в плотный слой Dense. Модель была скомпилирована и подготовлена для обучения как обычно.
Создание пользовательского
слоя-трансформера
Подобно рассмотренному выше пользовательскому слою внимания, с помощью API TensorFlow можно также создать пользовательский слой трансформера. Создадим класс под названием TransformerLayer, дочерний для
класса tf.keras.layers.Layer. В методе __init__() определим экземпляры слоя MultiHeadAttention, нейронной сети прямого распространения с помощью последовательного (Sequential) API, и слоя нормализации
LayerNormalization. MultiHeadAttention и LayerNormalization представлены здесь как слои из библиотеки TensorFlow. Далее зададим логику прямого
прохода слоя реализацией метода call(). Эти операции показаны в следующем фрагменте кода:
1. class TransformerLayer(tf.keras.layers.Layer):
2.
def __init__(self, embed_size, num_heads, ff_units, rate=0.1):
3.
super().__init__()
4.
self.attention = tf.keras.layers.MultiHeadAttention(num_
heads=num_heads, key_dim=embed_size, dropout=rate)
5.
6.
self.feedforward = tf.keras.Sequential()
7.
self.feedforward.add(tf.keras.layers.Dense(ff_units,
activation="relu"))
8.
self.feedforward.add(tf.keras.layers.Dense(embed_size))
9.
10.
self.layernormalization_1 = tf.keras.layers.LayerNormaliza
tion(epsilon=1e-6)
11.
self.layernormalization_2 = tf.keras.layers.LayerNormaliza
tion(epsilon=1e-6)
12.
404
13.
■
Pythonic AI
def call(self, inputs):
14.
attn_output = self.attention(inputs, inputs)
15.
ln_out = self.layernormalization_1(inputs + attn_output)
16.
ff_output = self.feedforward(ln_out)
17.
return self.layernormalization_2(ln_out + ff_output)
Трансформеру
нужно передать конкатенацию позиционных эмбеддингов
и эмбеддингов токенов входного текста. Для этого мы можем воспользоваться встроенными слоями библиотеки TensorFlow, как делали ранее, либо создать собственный слой для получения эмбеддингов токенов и позиций.
Для создания пользовательского слоя воспользуемся базовым слоем
TensorFlow tf.keras.layers.Embedding, генерирующим эмбеддинги токенов и позиционные эмбеддинги, как показано в следующем блоке кода:
1. class TokenAndPositionEmbedding(tf.keras.layers.Layer):
2.
def __init__(self, max_seq_len, vocab_size, embedding_dim):
3.
super().__init__()
4.
self.max_seq_len = max_seq_len
5.
self.token_embedding = tf.keras.layers.Embedding(input_
dim=vocab_size, output_dim=embedding_dim)
6.
self.position_embedding = tf.keras.layers.Embedding(input_
dim=max_seq_len, output_dim=embedding_dim)
7.
8.
def call(self, x):
9.
position_tensor = tf.range(start=0, limit=self.max_seq_
len, delta=1)
10.
position_tensor = self.position_embedding(position_
tensor)
11.
x = self.token_embedding(x)
12.
return x + position_tensor
Как показано выше, метод __init__() принимает аргумент max_seq_len, который представляет собой не что иное, как max_words или максимальное количество слов во входном предложении, уже определенное ранее. Также в качестве аргументов нужно передать vocab_size, или общее количество уникальных
токенов, или слов, хранящихся во входном корпусе, и embedding_dim, или размерность эмбеддингов. Слой token_embedding представляет собой экземпляр
Модели внимания и трансформеры
■
405
tf.keras.layers.Embedding, где параметр input_dim принимает значение
vocab_size. Таким образом, этот слой принимает на вход последовательность
токенов и для каждого токена создает плотное векторное представление размерностью embedding_dim. Слой position_embedding — это еще один экземпляр tf.keras.layers.Embedding, но для него параметр input_dim задается
равным max_seq_len. Основная идея заключается в том, чтобы назначить
уникальный вектор эмбеддинга для каждой позиции в последовательности.
Эти эмбеддинги определяются в процессе обучения, и их можно рассматривать как кодирование относительных расстояний между различными позициями. position_embedding в методе call() принимает на вход индексы
позиций от 0 до (max_seq_len - 1) и преобразует каждый из них в плотное
векторное представление размерностью embedding_dim. Эмбеддинги токенов
и эмбеддинги позиций складываются вместе с помощью оператора «+» — так
объединяется информация о токене и его позиции в последовательности. Полученный тензор возвращается в качестве выхода слоя.
Теперь с помощью этих двух пользовательских слоев создадим нашу модель,
как показано в следующем фрагменте кода:
1. embed_size = 32
2. num_heads = 2
3. ff_units = 32
4.
5. inputs = tf.keras.layers.Input(shape=(max_words,))
6. embedding_layer = TokenAndPositionEmbedding(max_words, num_words_
to_keep, embed_size)
7. x = embedding_layer(inputs)
8. transformer_layer = TransformerLayer(embed_size, num_heads, ff_
units)
9. x = transformer_layer(x)
10. x = tf.keras.layers.GlobalAveragePooling1D()(x)
11. x = tf.keras.layers.Dropout(0.1)(x)
12. x = tf.keras.layers.Dense(20, activation="relu")(x)
13. x = tf.keras.layers.Dropout(0.1)(x) 14. outputs = tf.keras.
layers.Dense(1)(x)
15.
16. model = tf.keras.Model(inputs=inputs, outputs=outputs)
406
■
Pythonic AI
Если мы не хотим создавать собственный слой для токенов и позиционных эмбеддингов, то можно воспользоваться встроенными слоями вместе с пользовательским слоем трансформера, как показано в следующем фрагменте кода:
1. embed_size = 32
2. num_heads = 2
3. ff_units = 32
4.
5. inputs = tf.keras.layers.Input(shape=(max_words,))
6. embedding = tf.keras.layers.Embedding(num_words_to_keep, embed_
size) (inputs)
7. position = tfm.nlp.layers.PositionEmbedding(max_words)
(embedding)
8. x = tf.keras.layers.add([embedding, position])
9. transformer_layer = TransformerLayer(embed_size, num_heads,
ff_units)
10. x = transformer_layer(x)
11. x = tf.keras.layers.GlobalAveragePooling1D()(x)
12. x = tf.keras.layers.Dropout(0.1)(x)
13. x = tf.keras.layers.Dense(20, activation="relu")(x)
14. x = tf.keras.layers.Dropout(0.1)(x) 15. outputs = tf.keras.
layers.Dense(1)(x)
16.
17. model = tf.keras.Model(inputs=inputs, outputs=outputs)
Теперь можно скомпилировать модель и обучить ее на входных данных как
обычно, что показано ниже:
1.model.compile(loss=tf.keras.losses.BinaryCrossentropy
(from_logits=True), optimizer=tf.keras.optimizers.Adam(1e-4),
metrics=['accuracy'])
2.model.fit(X_train, y_train, validation_data=(X_test, y_test),
epochs=20, verbose=2)
Модели внимания и трансформеры
■
407
Использование предварительно обученных
трансформеров
Строить модели трансформеров можно и на основе предварительно обученных моделей BERT. Модели BERT обычно обучаются на больших объемах
текстовых данных, и их можно тонко настраивать для решения конкретных
NLP-задач. В главе 5 «Разработка приложений для классификации изображений на основе CNN» мы познакомились с предварительно обученными
моделями и воспользовались ими для обучения с переносом знаний (трансферного обучения). Теперь проделаем нечто подобное для решения задачи
по обработке естественного языка с использованием предварительно обученных моделей BERT из библиотеки TensorFlow Hub.
TensorFlow Hub — это библиотека и платформа экосистемы TensorFlow, позволяющая публиковать и повторно использовать предварительно обученные
модели машинного обучения. Она представляет собой центральное хранилище, в которое разработчики загружают и где находят широкий спектр
предварительно обученных моделей для различных областей практического
применения, таких как компьютерное зрение, обработка естественного языка и т. д.
TensorFlow Hub позволяет пользователям легко включать эти модели в свои
проекты на основе TensorFlow. Благодаря этому экономятся время и вычислительные ресурсы, которые в противном случае потребовались бы для
обучения моделей с нуля. Эти модели часто обучаются на больших наборах
данных и учатся извлекать полезные признаки или делать прогнозы для конкретных задач.
Благодаря TensorFlow Hub разработчики могут легко получить доступ к предварительно обученным моделям и использовать их в своей работе, написав
всего несколько строк кода. Это значительно упрощает процесс трансферного
обучения, когда предварительно обученная модель используется как основа
для решения новой задачи путем её тонкой настройки или непосредственно
для получения выводов (инференса).
Помимо доступа к предварительно обученным моделям, TensorFlow Hub
также предлагает инструменты и утилиты для облегчения исследования,
визуализации и сравнения моделей. Это своеобразный центр сообщества
TensorFlow для обмена информацией и совместной работы по разработке
и внедрению моделей. Более подробно о TensorFlow Hub можно узнать на
веб-сайте по следующему адресу: https://tfhub.dev/.
408
■
Pythonic AI
Предварительно обученные модели из TensorFlow Hub выполняют различные
операции, определенные в библиотеке под названием TensorFlow text. Сначала нужно установить эту библиотеку, поскольку в блокноте Google Colab она
не установлена по умолчанию. Делается это с помощью следующей команды:
1. !pip install tensorflow-text
Мы будем использовать модель Small BERT1 из TensorFlow Hub. Эта разновидность модели содержит меньшее количество слоев, меньший размер скрытого состояния и меньшее количество головок внимания и подходит для задач
с ограниченными вычислительными ресурсами. Эти предварительно обученные модели принимают на вход обычный текст, который сначала обрабатывается моделью. Затем предварительно обработанные данные поступают
в реальный энкодер BERT.
Предварительно обученные модели BERT из TensorFlow Hub доступны по
следующему адресу: https://tfhub.dev/google/collections/bert/1.
Мы воспользуемся энкодером Small BERT с шестью слоями и скрытыми векторами размером 128. Эта модель доступна по следующему адресу: https://
tfhub.dev/tensorflow/small_bert/bert_en_uncased_L-6_H-128_A-2/2.
Соответствующая модель препроцессора доступна по следующему адресу:
https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3.
Для выполнения задачи классификации текста нам понадобятся обе модели.
Поскольку предварительно обученные BERT-модели принимают на вход препроцессоров необработанный текст, то в отличие от предыдущих примеров,
модулем tf.keras.datasets.imdb пользоваться не нужно. Этот модуль содержит данные о рецензиях на фильмы в векторном формате. Мы же на этот
раз воспользуемся тем же набором данных IMDB с необработанным текстом
рецензий из библиотеки tensorflow_datasets. Импортируем необходимые
библиотеки, как показано в следующем фрагменте кода:
1. import tensorflow as tf
2. import tensorflow_datasets
as tfds
3. import tensorflow_hub as hub
4. import tensorflow_text as text
1
Iulia Turc, Ming-Wei Chang, Kenton Lee, and Kristina Toutanova. “Well-read students learn
better: On the importance of pre-training compact models”. arXiv preprint arXiv:1908.08962
(2019).
Модели внимания и трансформеры
■
409
Загрузим рецензии IMDB как обучающий и проверочный наборы данных согласно следующему фрагменту кода:
1. dataset = tfds.load('imdb_reviews', as_supervised=True)
2. train_dataset, test_dataset = dataset['train'], dataset['test']
Рассмотрим пример входных обучающих данных, как показано на следующей
иллюстрации:
Иллюстрация 11.7. Образец входных данных
Как показано выше, набор данных содержит текстовый тензор и целочисленное значение. Это целое число — метка текста. Так как показанный выше
текст представляет собой явно отрицательный отзыв, то его метка равна 0.
Метка положительных отзывов равна 1.
Теперь преобразуем эти данные в объекты наборов данных TensorFlow:
1. train_dataset = train_dataset.batch(64).prefetch(tf.data.AUTOTUNE)
2. test_dataset = test_dataset.batch(64).prefetch(tf.data.AUTOTUNE)
Как показано выше, мы создали пакеты (партии) данных, и каждый из них
содержит по 64 примера.
Мы также добавили этап предварительной выборки (prefetching) данных
в конвейер набора данных. Значение tf.data.AUTOTUNE позволяет TensorFlow
динамически определять оптимальное количество элементов для предварительной выборки, основываясь на доступных ресурсах и ходе выполнения
задачи. Предварительная выборка позволяет модели параллельно выполнять
этапы предварительной обработки и обучения.
Теперь можно задать URL-адреса моделей предпроцессора и энкодера BERTкодировщика в двух переменных. Эти две модели будут загружаться с указанных адресов согласно следующему фрагменту кода:
1. bert_preprocessor = ("https://tfhub.dev/tensorflow/bert_en_
uncased_preprocess/3")
2. bert_encoder = ("https://tfhub.dev/tensorflow/small_bert/bert_en_
uncased_L-6_H-128_A-2/2")
410
■
Pythonic AI
Воспользуемся предварительно обученными моделями из TensorFlow Hub
с помощью класса KerasLayer, предоставляемого библиотекой TensorFlow.
KerasLayer позволяет легко обернуть вызываемый объект и интегрировать
его в наши модели TensorFlow, как показано в следующей строке кода:
1. preprocessor = hub.KerasLayer(bert_preprocessor)
Как показано выше, мы создали слой препроцессора для энкодера BERT с помощью класса KerasLayer. Этому слою препроцессора можно передать образец текста, чтобы проверить его работу, как показано на следующей иллюстрации (на примере текста “I liked the movie”, то есть «Мне понравился фильм»):
Иллюстрация 11.8. Выходные данные препроцессора
Как видно на этой иллюстрации, слой препроцессора вернул словарь из трех
элементов: input_word_ids, input_mask и input_type_ids. Теперь обработанный текст можно подавать в энкодер BERT, как показано в следующих
строках кода:
Модели внимания и трансформеры
■
411
1. preprocessed_text = preprocessor(sample_text)
2. bert_model = hub.KerasLayer(bert_encoder)
3. bert_model(preprocessed_text)
Как показано выше, мы создали слой для энкодера BERT и передали ему
предварительно обработанный текст. Для дальнейшего построения модели
из результата bert_encoder мы возьмем массив pooled_output, представляющий каждую из входных последовательностей. Теперь разработаем полную
архитектуру модели, показанную в следующем блоке кода:
1. text_input = tf.keras.layers.Input(shape=(), dtype=tf.string)
2. preprocessor = hub.KerasLayer(bert_preprocessor)
3. preprocessed_text = preprocessor(text_input)
4. encoder = hub.KerasLayer(bert_encoder, trainable=True)
5. encoded_data = encoder(preprocessed_text)["pooled_output"]
6. x = tf.keras.layers.Dropout(rate=0.2)(encoded_data)
7. outputs = tf.keras.layers.Dense(1)(x)
8. model = tf.keras.Model(inputs=text_input, outputs=outputs)
9. model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_
logits=True), optimizer=tf.keras.optimizers.Adam(1e-4),
metrics=['accuracy'])
10. model.fit(train_dataset, validation_data=test_dataset, epochs=20,
verbose=2)
Из этого блока видно, что мы подали необработанный текст на слой препроцессора. Предварительно обработанный текст мы передали энкодеру BERT.
В строке номер 5 мы выбрали из выходных данных массив pooled_output
и передали его дальнейшей нейронной сети с прямым распространением.
После завершения обучения модели можно передать ей необработанный текст
отзыва и получить положительную или отрицательную оценку в соответствии
с эмоциональной окраской, как показано на следующей иллюстрации (на примере текста “I disliked the movie”, то есть «Мне не понравился фильм»):
Иллюстрация 11.9. Оценка модели
412
■
Pythonic AI
Выше показано, что модель дала предсказание в виде отрицательного десятичного числа (-3,8075325) — оценки эмоциональной окраски данного предложения. Отсюда делаем вывод, что данное предложение содержит негативный
отзыв. Оценка положительных отзывов представляет собой положительное
десятичное число.
Генеративные предварительно
обученные трансформеры
Поистине эпохальным достижением в обширной сфере искусственного интеллекта и обработки естественного языка стали модели под названием генеративные предварительно обученные трансформеры (GPT, Generative
Pre-trained Transformers). Их появление ознаменовало собой начало настоящей революции в этой области. В наше время у всех на слуху модель GPT,
разработанная компанией OpenAI. Она покорила весь мир своей способностью генерировать связный и контекстуально релевантный текст. В отличие от
моделей BERT, модели GPT являются однонаправленными и используют метод декодирования под названием «авторегрессия». Модели с авторегрессией
генерируют текст по одному токену за раз, основывая свои предсказания на
ранее сгенерированных токенах. Декодирование по типу авторегрессии позволяет модели выявлять дальнодействующие зависимости и генерировать
последовательные и контекстуально релевантные ответы. Модель GPT лежит
в основе самых мощных современных больших языковых моделей (LLM), таких как ChatGPT1.
Самая первая модель, GPT-12, вышла в свет в 2018 году. Ее создатели поставили перед собой задачу разработать модель, способную с помощью методов
предварительного обучения и тонкой настройки понимать человеческий язык
и генерировать тексты, похожие на написанные человеком. Предварительное
обучение без учителя — задача языкового моделирования, которая заключается в том, что через модель пропускают огромное количество текстов из
Интернета, чтобы научить ее шаблонам, грамматике и семантике человеческого языка. Используемая модель представляет собой многослойный декодер
типа трансформера с технологией многоголового внимания, применяемой ко
входным контекстным векторам, и с полносвязными слоями, применяемыми
к каждому элементу последовательности. Тонкая настройка представляет собой обучение с использованием наборов размеченных данных.
1
2
https://openai.com/blog/chatgpt
Alec Radford, Karthik Narasimhan, Tim Salimans, and Ilya Sutskever. “Improving language
understanding by generative pre-training”. (2018).
Модели внимания и трансформеры
■
413
Модель GPT-1 послужила фундаментом для последующих вариантов, таких как GPT-2, GPT-3 и GPT-4, еще более расширивших границы генерации
языка. Исследователи продемонстрировали, что большие языковые модели
(LLM) способны к обучению без примеров (zero-shot) и с небольшим количеством примеров (few-shot). Обучение без примеров и с малым количеством
примеров — это методы, решающие проблему обучения на ограниченных
или отсутствующих данных. Эти методы позволяют ИИ-моделям обобщать
и предсказывать новые классы или концепции, благодаря чему расширяются возможности практического применения этих моделей в реальных ситуациях.
В 2019 году в свет вышла модель GPT-21, также основанная на архитектуре
декодера трансформера с 48 слоями и обученная на 40 ГБ интернет-данных.
Модель GPT-2 имеет впечатляющие 1,5 миллиарда параметров, что позволяет ей быстро и эффективно решать широкий спектр языковых задач. Увеличенный размер модели дает возможность создавать более детализированное
представление языка и лучше обнаруживать сложные взаимосвязи в тексте.
Как и ее предшественница, модель GPT-2 проходит через двухэтапный процесс предварительного обучения и тонкой настройки.
GPT-32 — это очень сложная, крупномасштабная языковая модель, имеющая
175 миллиардов параметров. Она обучается на массивных текстовых данных
из различных ресурсов с упором на методы обучения без примеров (zero-shot),
с одним примером (one-shot) и с небольшим количеством примеров (few-shot).
Организация OpenAI выпустила различные базовые модели серии GPT-3, такие как davinci, curie, ada, babbage и др. OpenAI также выпустила более мощную серию GPT3.53, в которую входят такие модели, как text-davinci-002, textdavinci-003 и пр., называемые моделями InstructGPT4. Модели InstructGPT
создаются на основе предварительно обученной базовой модели GPT-3 (например, модели code-davinci-002) и настраиваются (дообучаются) с помощью
обучения с подкреплением на основе обратной связи от человека (RLHF,
Reinforcement Learning from Human Feedback) на сгенерированных парах
«запрос-ответ». При этом модель вознаграждения строится на том, что люди распределяют полученные ответы по степени значимости. Модель возна1
2
3
4
Alec Radford, Jeffrey Wu, Rewon Child, David Luan, Dario Amodei, and Ilya Sutskever.
“Language models are unsupervised multitask learners”. OpenAI blog 1, no. 8 (2019): 9.
Tom Brown, Benjamin Mann, Nick Ryder, Melanie Subbiah, Jared D. Kaplan, Prafulla
Dhariwal, Arvind Neelakantan, et al. “Language models are few-shot learners.” Advances in
neural information processing systems 33 (2020): 1877–1901.
https://platform.openai.com/docs/models/gpt-3-5
Long Ouyang, Jeffrey Wu, Xu Jiang, Diogo Almeida, Carroll Wainwright, Pamela Mishkin,
Chong Zhang et al. “Training language models to follow instructions with human feedback.”
Advances in Neural Information Processing Systems 35 (2022): 27730–27744.
414
■
Pythonic AI
граждения обновляет предварительно обученную модель GPT-3 с помощью
подкрепления с оптимизацией проксимальной политики. По сравнению со
своими предшественницами модели InstructGPT демонстрируют значительно лучшие результаты с точки зрения достоверности, беспристрастности
и отсутствия токсичности.
Последняя версия крупномасштабной мультимодальной модели от OpenAI —
это GPT-41. В некоторых академических и профессиональных тестах эта модель показывает результаты, схожие с результатами реальных людей. В частности ей удалось сдать Единый экзамен для юристов в США.
Примечание. Начиная с модели GPT-2 OpenAI не выкладывала в открытый доступ код или модели серии GPT, сделав официальное заявление2 о своей обеспокоенности в связи с возможным вредоносным применением этой технологии. Было предложено множество альтернатив
GPT с открытым исходным кодом, таких как OPT3 от компании Meta,
BLOOM4 и др.
Важно отметить, что процесс обучения больших языковых моделей (LLM)
требует значительных вычислительных ресурсов, в том числе использования
специализированного оборудования, такого как графические процессоры
(GPU) или тензорные процессоры (TPU), а также крупномасштабные распределенные системы для параллельного обучения. Поэтому часто бывает
выгоднее использовать предварительно обученную LLM, как в нашем случае,
или дорабатывать ее под конкретную задачу.
Модели GPT от Hugging Face
Из главы 7 «Создание приложения для чтения текста и изображений» мы
узнали о компании Hugging Face и о ее предварительно обученных моделях трансформеров. Hugging Face разработала несколько инструментов
1
2
3
4
GPT-4 Technical Report: https://arxiv.org/abs/2303.08774
https://openai.com/research/better-language-models
Susan Zhang, Stephen Roller, Naman Goyal, Mikel Artetxe, Moya Chen, Shuohui Chen,
Christopher Dewan et al. “Opt: Open pre-trained transformer language models.” arXiv
preprint arXiv:2205.01068 (2022).
Teven Le Scao, Angela Fan, Christopher Akiki, Ellie Pavlick, Suzana Ilić, Daniel Hesslow,
Roman Castagné, et al. “Bloom: A 176b-parameter open-access multilingual language model.”
arXiv preprint arXiv:2211.05100 (2022).
Модели внимания и трансформеры
■
415
и фреймворков для задач обработки естественного языка, включая трансформеры, токенизаторы, наборы данных и многое другое. В нашей задаче мы
воспользуемся предварительно обученными моделями GPT от Hugging Face
для вывода результатов (инференса).
Как говорилось в главе 7 «Создание приложения для чтения текста и изображений», для начала нужно установить библиотеку трансформеров от Hugging
Face следующей командой:
1. !pip install transformers
Библиотека transformers предоставляет класс pipeline, обертку для нескольких задач. Это простой API, используемый для инференса.
В библиотеке transformers имеется несколько предварительно обученных
современных моделей. Что касается задач по обработке естественного языка,
то трансформеры могут выполнять классификацию текста, POS-теггинг (разметку частей речи), распознавание именованных сущностей (NER, Named
Entity Recognition), ответы на вопросы, резюмирование текста, перевод,
языковое моделирование и т. д.
Пользоваться API pipeline довольно просто. Прежде всего нужно определить тип выполняемой задачи или тип модели, или и то и другое. На иллюстрации ниже мы определили задачу языкового моделирования как ‘textgeneration’ (генерация текста) и указали модель GPT-2:
Иллюстрация 11.10. Генерация текста с помощью GPT-2
Как показано на иллюстрации выше, конвейер pipeline воспользовался моделью GPT-2 и сгенерировал пять возможных последовательностей для заданного входного текста input_text. Можно генерировать и более длинные последовательности, задавая большее значение аргумента max_length. Давайте
посмотрим на результат для того же входного текста imput_text, выданный
моделью GPT-1 и показанный на следующей иллюстрации:
416
■
Pythonic AI
Иллюстрация 11.11. Генерация текста с помощью GPT-1
Можно сравнить эти результаты и убедиться в том, что качество текста, сгенерированного моделью GPT-2, явно лучше.
Также можно подавать на вход модели несколько фрагментов текста за один
раз. Воспользуемся этой возможностью для решения задачи анализа эмоциональной окраски (настроения), как показано на следующей иллюстрации:
Иллюстрация 11.12. Классификация нескольких фрагментов текста за раз
На иллюстрации выше показано, что мы определили задачу как классификация текста (text-classification). Конвейер загрузил модель distilbert-baseuncased-finetuned-sst-2-english, используемую по умолчанию как раз для задачи
классификации. При первой загрузке модели конвейер скачивает ее и связанные с ней файлы. В качестве входных данных для конвейера классификатора
текстов мы предоставили модели три предложения. Модель классифицировала тексты как положительные и отрицательные, и присвоила им соответствующие баллы.
Модели GPT от OpenAI
Компания OpenAI не выкладывает свои модели GPT в открытый доступ,
а предоставляет доступ к серии GPT через API. Модели GPT от OpenAI можно
использовать в различных ИИ-приложениях в качестве бэкенд-сервиса. Далее
мы вкратце расскажем о том, как получить доступ к GPT-моделям от OpenAI.
Модели внимания и трансформеры
■
417
Чтобы воспользоваться моделями GPT, сначала нужно установить библиотеку OpenAI для Python с помощью следующей команды:
1. !pip install openai
Чтобы пользоваться API OpenAI, нужно создать учетную запись на сайте
этой организации и получить ключ для аутентификации. Перейдем на сайт
https://platform.openai.com/account/api-keys и зарегистрируемся с помощью адреса электронной почты или войдем в систему, если мы уже зарегистрированы. Войдя в систему, мы увидим опцию для создания нового
секретного ключа. После создания секретного ключа нужно сохранить его
в безопасном и доступном месте. Секретный ключ обычно начинается с символов sk-. По соображениям безопасности заново просмотреть этот ключ через нашу учетную запись OpenAI не получится. При утрате этого секретного
ключа придется сгенерировать новый.
Далее импортируем библиотеку openai и укажем секретный ключ в api_key.
После этого вызовем модель с запросом и получим ответ со стороны API, как
показано на следующей иллюстрации:
Иллюстрация 11.13. Использование GPT от OpenAI
Как показано выше, в данном случае мы воспользовались моделью text-davinci003 типа InstructGPT, принадлежащей к серии GPT-3.5. В запросе («промпте»)
мы попросили модель GPT кратко изложить суть абзаца, который скопировали со страницы "Climate change" («Изменение климата») в Википедии,
доступной по адресу: https://en.wikipedia.org/wiki/Climate_change.
418
■
Pythonic AI
Аргумент temperature («температура») устанавливается в диапазоне
от 0 до 2. Более высокие значения повышают случайность вывода, а более
низкие повышают сфокусированность и детерминированность ответа.
В контексте языковой модели, подобной GPT, запрос (называемый также
«промптом») представляет собой начальный текст или инструкцию, на основе которой модель должна сгенерировать ответ. Промпт может быть одним
предложением, абзацем или даже длинным фрагментом текста, задающим
контекст или описывающим желательный результат. Промпт для языковой
модели служит руководством по созданию связного и релевантного текста
в ответ на заданный ввод.
Заключение
В этой главе мы рассмотрели обширную область современных методов, используемых в обработке естественного языка. Мы узнали о механизме внимания
и о том, как на его основе была разработана архитектура трансформеров. Мы
познакомились с некоторыми деталями архитектуры трансформеров и c методами кодирования и декодирования. Архитектуру трансформеров можно
назвать поистине феноменальным достижением в области обработки естественного языка и искусственного интеллекта. Ее разработка и внедрение радикально изменило всю эту область и открыло новые возможности для решения
различных задач, связанных с языком. Благодаря механизму внутреннего внимания и параллельным вычислениям архитектура трансформеров значительно повысила эффективность и результативность обработки последовательных
данных, что сделало ее незаменимым инструментом в разработке современных
языковых моделей. Для построения блоков внимания и трансформеров мы
воспользовались разными библиотеками экосистемы TensorFlow. Также мы узнали о больших языковых моделях на основе трансформеров, таких как BERT
и GPT. Архитектура GPT произвела настоящую революцию в области обработки естественного языка и проложила дорогу к практическому использованию
этих моделей в широком спектре приложений и областей.
Исследования в области больших языковых моделей (LLM) и генеративного
ИИ в целом в настоящее время ведутся с беспрецедентной скоростью. Располагающие значительными ресурсами технологические гиганты часто предлагают новые LLM, способные выполнять общие или специализированные
задачи. За последние несколько лет появился ряд базовых моделей семейства
трансформеров. Сфера LLM, их архитектур и возможностей настолько динамична, что для поддержания актуальности знаний в ней нужно тратить много времени и усилий. Тем не менее это очень ценное вложение, позволяющее
Модели внимания и трансформеры
■
419
оставаться в курсе достижений по обработке естественного языка и добиваться успеха в этой области.
В следующей главе мы узнаем о том, как совмещать анализ изображений
и анализ текста, а также о том, как с помощью трансформеров создавать подписи к изображениям.
Основные выводы
• Механизмы внимания позволяют моделям фокусироваться на определенных частях входной последовательности, благодаря чему повышается их способность выявлять дальнодействующие зависимости.
• Архитектура трансформеров произвела революцию в области обработки естественного языка (NLP), представив распараллеливаемую,
масштабируемую и высокоэффективную схему для задач, которые связаны с преобразованием одной последовательности в другую. В этой
архитектуре используются механизмы самовнимания для оценки степени важности различных входных элементов.
• Значительного улучшения производительности разных NLP-задач позволила добиться предварительно обученная модель BERT на основе
трансформера с так называемым механизмом «маскированного языкового моделирования» (MLM).
• Мы разработали модели последовательностей со слоем внимания
и блоком трансформера, используя как встроенные слои, так и API
пользовательских слоев TensorFlow.
• Модели GPT — это предварительно обученные трансформеры, прославившиеся своей способностью генерации текста. GPT — это разновидность так называемой «авторегрессивной языковой модели», которая
генерирует текст по одному токену за раз в соответствии с условиями
на основе ранее сгенерированных лексем.
• Мы научились использовать предварительно обученные большие языковые модели с помощью API.
Ссылки
• Иллюстрированное руководство по механизму внимания: https://
jalammar.github.io/visualizing-neural-machine-translation-mechanics-ofseq2seq-models-withattention/
• Иллюстрированное руководство по трансформерам: http://jalammar.
github.io/illustratedTransformer/
420
■
Pythonic AI
• Слой внимания TensorFlow: https://www.tensorflow.org/api_docs/python/
tf/keras/layers/Attention
• Слой TensorFlow Multi-Head Attention: https://www.tensorflow.org/api_
docs/python/tf/keras/layers/MultiHeadAttention
• Базовый слой TensorFlow: https://www.tensorflow.org/api_docs/python/
tf/keras/layers/Layer
• TensorFlow Model Garden: https://github.com/tensorflow/models
• Официальные модели TensorFlow: https://github.com/tensorflow/models/
tree/master/ official
• Код и модель для GPT-1: https://github.com/openai/finetune-transformerlm
• Код и модель для уменьшенной версии GPT-2: https://github.com/openai/
gpt-2
• Трансформеры Hugging Face: https://huggingface.co/docs/transformers/
index
• Список ресурсов практического руководства по большим языковым
моделям (LLM): https://github.com/Mooler0410/LLMsPracticalGuide
• Weng, Lilian. (январь 2023). «Семейство трансформеров версии 2.0».
Lil'Log: https://lilianweng.github.io/posts/2023-01-27-the-transformerfamily-v2/
• Иллюстрированный GPT-2 (Визуализация языковых моделей трансформеров): https://jalammar.github.io/illustrated-gpt2/
• Как работает GPT3, визуализации и анимации: https://jalammar.github.
io/howgpt3-works-visualizations-animations/
• Платформа OpenAI, документация, справочник по API, примеры и т. д.:
https://platform.openai.com/
Глава 12
Генерация подписей
к изображениям
Введение
Создание подписей к изображениям — увлекательная область применения
искусственного интеллекта. Она находится на пересечении областей компьютерного зрения и обработки естественного языка и включает в себя автоматическую генерацию подписей или текстовых пояснений к изображениям.
Основная цель этой задачи — генерация подписей, которые точно отображают содержание и контекст изображения. Например, система генерации подписей к изображениям должна уметь распознавать группу людей, играющих
в футбол в парке, и выдавать описание наподобие «Группа друзей в парке наслаждается игрой в футбол в солнечный день».
Модели создания подписей к изображениям обычно сочетают в себе возможности глубоких нейронных сетей и методы обработки естественного
языка. Модели компьютерного зрения извлекают из изображений значимые
визуальные признаки, а модели последовательностей генерируют осмысленные и связные текстовые описания на основе выявленных визуальных признаков.
Одна из основных проблем при создании подписей к изображениям — это
присущая естественному языку неоднозначность и субъективность. Разные
люди могут описывать одно и то же изображение по-разному, и каждый способ будет иметь свои нюансы и интерпретации. Кроме того, изображения могут содержать насыщенные сцены с множеством объектов, имеющих сложные взаимосвязи между собой, что делает задачу создания подписей весьма
непростой.
422
■
Pythonic AI
Структура
В этой главе мы рассмотрим следующие темы:
• Методология и подход.
• Набор данных и их структура.
• Подготовка данных.
• Обучение и оценка модели генерации подписей.
• Использование предварительно обученных моделей генерации подписей от Hugging Face.
Цели
Цель этой главы — изучить принципы генерации подписей к изображениям и разработать систему по выполнению поставленной задачи. Эта система
должна автоматически создавать описательный и контекстуально релевантный текст к заданному входному изображению. Главное — преодолеть разрыв между визуальным контентом и пониманием естественного языка, то
есть научить машину понимать и описывать визуальную информацию так,
как это делают люди. В первую очередь мы постараемся более или менее подробно описать процесс сборки и подготовки подходящего набора данных для
обучения и оценки системы создания подписей к изображениям. Мы обсудим общедоступный набор данных для создания подписей к изображениям
и перечислим необходимые этапы предварительной обработки, включая выявление признаков изображения и векторизацию текста. Мы изучим архитектуру и различные компоненты моделей создания подписей к изображениям и воспользуемся предварительно обученными моделями с помощью API.
Методология и подход
Благодаря быстрому развитию методов глубокого обучения ИИ за последние
годы в области создания подписей к изображениям были достигнуты значительные успехи. Задача генерации подписей к изображениям включает в себя систематический процесс преобразования визуальных входных данных
в осмысленные текстовые описания. Основные этапы этого процесса следующие:
1. Предварительная обработка изображения. Входное изображение
подвергается предварительной обработке для извлечения (выявления)
Генерация подписей к изображениям
■
423
полезных признаков. Для этой задачи обычно используются сверточные нейронные сети (CNN) и предварительно обученные модели (такие как VGG-16, ResNet и т. д.), извлекающие из изображения высокоуровневые визуальные признаки. Для компактного представления
визуального содержимого извлеченные визуальные признаки кодируются в вектор фиксированной длины.
2. Моделирование языка. Закодированные визуальные признаки объединяются с языковой моделью, которая обычно имеет архитектуру
последовательностей или трансформера. Эти модели генерируют подписи слово за словом, учитывая как визуальные признаки, так и ранее
созданные слова. В процессе создания подписи языковая модель предсказывает наиболее вероятное слово для каждого шага.
Объединение визуальных и текстовых модальностей привело к разработке
мультимодальных моделей, обрабатывающих как признаки изображений,
так и содержание текста. Такие модели генерируют подписи, описывающие
визуальный контент с учетом контекстной информации, благодаря чему повышается полнота и информативность этих подписей.
Несмотря на то что в сфере генерации подписей к изображениям достигнут
ряд значительных успехов, она по-прежнему сталкивается с рядом проблем,
над решением которых активно работают исследователи. Вот некоторые из
этих проблем:
• Неоднозначность и субъективность. Изображения можно интерпретировать по-разному, и это значит, что верных подписей может быть
множество. Устранение двусмысленности и субъективности языка —
это весьма серьезная задача. Улучшить согласованность между визуальными и текстовыми элементами помогают методы вроде механизма
внимания, которые во время генерации подписи фокусируются на определенных областях изображения.
• Работа со сложными сценами. Изображения могут содержать сложные сцены со множеством объектов, связей и контекстной информации. Для точного описания таких сложных сцен требуются модели
с глубоким пониманием контекста и способностью генерировать связные и контекстуально релевантные подписи.
• Сбор данных и аннотирование. Для обучения моделей создания
подписей к изображениям используются большие аннотированные
наборы данных. Сбор и аннотирование таких наборов данных — трудоемкая задача, требующая от составляющего аннотации человека
точности и внимательности при описании широкого круга изображений.
424
■
Pythonic AI
В сфере генерации подписей к изображениям достигнут ряд впечатляющих
успехов, расширяющих границы возможного. Значительно повысить качество и релевантность генерируемых подписей позволила интеграция механизмов внимания и архитектуры трансформеров в модели создания подписей
к изображениям.
Доказали свою эффективность при создании подписей к изображениям методы
трансферного обучения, такие как предварительное обучение на масштабных
наборах данных изображений и текстов. Предварительно обученные модели
довольно хорошо понимают изображения и текст, благодаря чему генерируют
подписи, демонстрирующие глубокое понимание визуального контента.
Технология генерации подписей к изображениям находит применение в различных областях, в том числе в следующих:
• Доступность среды. Технология генерации подписей к изображениям
повышает доступность информации для людей с ослабленным зрением. Текстовые описания позволяют пользователям с ослабленным
зрением получать доступ к визуальному контенту и лучше ориентироваться в окружающей обстановке.
• Создание контента. Эта технология способна произвести революцию
в создании контента благодаря автоматическому формированию подписей к большим объемам изображений. Особенно это актуально для
тех случаев, когда создание подписей вручную отнимает много времени и является весьма непрактичным — например, на таких платформах, как социальные сети, в новостных агентствах или на сайтах электронной коммерции.
• Поиск и извлечение изображений. Технология генерации подписей
к изображениям может значительно повысить удобство поиска по базам данных изображений. Благодаря созданию описательных подписей и автоматической маркировке искать изображения по контенту
и контексту становится удобнее. Пользователи быстрее и эффективнее
находят нужные изображения для личного пользования, исследований
или решения творческих задач.
• Мультимедийное повествование. Подписи к изображениям можно использовать для создания увлекательных мультимедийных материалов. Сочетание изображений с соответствующими подписями
делает повествование более захватывающим и интерактивным, благодаря чему пользователи сильнее вовлекаются в приложения виртуальной реальности (VR, Virtual Reality) или дополненной реальности (AR, Augmented Reality).
Генерация подписей к изображениям
■
425
Как и в случае с любым ИИ-алгоритмом, рассматривая технологию создания
подписей к изображениям нужно учитывать этические аспекты, такие как
непредвзятость, конфиденциальность и безопасность данных. При разработке и внедрении систем генерации подписей к изображениям исследователи
и программисты должны уделять особое внимание справедливости, инклюзивности и прозрачности. По мере внедрения технологических инноваций
и преодоления трудностей стоит ожидать появления более точных, контекстуально насыщенных и универсальных систем создания подписей к изображениям, которые как никогда ранее облегчат наше взаимодействие с визуальными средствами передачи информации.
Набор данных и их структура
В сфере генерации подписей к изображениям широко используются ставшие
эталонными наборы данных Flickr8K1 и Flickr30K2. Набор данных Flickr8K
представляет собой коллекцию из 8092 изображений, каждое из которых сопровождается пятью описаниями его содержания. Изображения в этом наборе данных были получены с популярного сайта обмена фотографиями Flickr.
Набор данных изначально создавался для исследований в области генерации
подписей к изображениям, и с тех пор стал стандартным для оценки моделей
генерации подписей.
Изображения в наборе данных Flickr8K были отобраны из большего числа
изображений, размещенных на сайте Flickr.com. В процессе отбора большое внимание уделялось разнообразию содержания, сюжетов и фотографических стилей. Набор данных охватывает широкий спектр тем, включая людей, животных, предметы, сцены в помещениях и на улице, пейзажи
и многое другое.
Каждое изображение в наборе сопровождается пятью уникальными подписями, благодаря чему обеспечивается разнообразие текстовых описаний
одного и того же визуального контента. Для сбора подписей использовался
сервис Amazon Mechanical Turk, где текст составляли люди, перед которыми
стояла задача предоставить как можно более разные описания для каждого
изображения.
1
2
Micah Hodosh, Peter Young, and Julia Hockenmaier. “Framing image description as a ranking
task: Data, models and evaluation metrics.” Journal of Artificial Intelligence Research 47
(2013): 853–899.
Peter Young, Alice Lai, Micah Hodosh, and Julia Hockenmaier. “From image descriptions to
visual denotations: New similarity metrics for semantic inference over event descriptions.”
Transactions of the Association for Computational Linguistics 2 (2014): 67–78.
426
■
Pythonic AI
Набор данных Flickr8K обычно разбивается на три подмножества — выборки
для обучения, проверки и тестирования. Как правило, это происходит следующим образом:
• Обучающий набор. Состоит из 6000 изображений и соответствующих
им подписей. Это подмножество используется для обучения моделей
генерации подписей к изображениям.
• Проверочный (валидационный) набор. Содержит 1000 изображений
и подписей к ним. Используется для тонкой настройки моделей и подбора гиперпараметров на этапе разработки.
• Тестовый набор. Содержит оставшиеся 1000 изображений с соответствующими подписями. Это подмножество используется для оценки
производительности обученных моделей и их способности к обобщению.
Набор данных Flickr8k состоит из двух основных компонентов: наборов
изображений и наборов подписей. Давайте подробно рассмотрим файлы,
присутствующие в каждом компоненте:
• Flickr8k_Dataset. Эта папка содержит коллекцию из 8000 изображений
в формате JPEG. Изображения представляют собой разнообразные сюжеты и сцены, взятые с платформы Flickr.
• Flickr8k.token.txt. Этот текстовый файл содержит подписи к изображениям в наборе данных. Каждая строка в файле соответствует одной
подписи и имеет определенный формат: «image_name#caption_number
caption_text», где image_name — это идентификатор файла изображения, caption_text — это текст описания данного изображения,
а caption_number — это номера от 1 до 5 для различных подписей к одному и тому же изображению.
Ниже показаны примеры пяти описаний одного и того же изображения:
1000268201_693b08cb0e.jpg#0 A child in a pink dress is climbing up a set of stairs in
an entry way. («Ребенок в розовом платье поднимается по лестнице у входа».)
1000268201_693b08cb0e.jpg#1 A girl going into a wooden building. («Девочка заходит в деревянное строение».)
1000268201_693b08cb0e.jpg#2 A little girl climbs into a wooden playhouse. («Девочка забирается в деревянный игрушечный дом»).
Генерация подписей к изображениям
■
427
1000268201_693b08cb0e.jpg#3 A little girl climbing the stairs to her playhouse. («Девочка поднимается по лестнице в игрушечном доме».)
1000268201_693b08cb0e.jpg#4 A little girl in a pink dress going into a wooden cabin.
(«Девочка в розовом платье забирается в деревянный домик».
• Flickr_8k.trainImages.txt. Этот файл содержит список идентификаторов изображений, входящих в обучающий набор. Каждая строка представляет собой идентификатор изображения, соответствующий файлу
в папке Flickr8k_Dataset. Эти изображения обычно используются для
обучения моделей генерации подписей к изображениям. Ниже для
примера показаны первые три строки файла:
2513260012_03d33305cf.jpg
2903617548_d3e38d7f88.jpg
3338291921_fe7ae0c8f8.jpg
• Flickr_8k.devImages.txt. Этот файл содержит список идентификаторов
изображений для отладки или проверки (валидации). Как и в случае
с файлом обучающего набора, каждая строка соответствует идентификатору изображения, расположенному в папке Flickr8k_Dataset.
Изображения в этом наборе обычно используются для тонкой настройки моделей и настройки гиперпараметров на этапе разработки.
Подготовка данных
Перед тем как передавать моделям искусственного интеллекта изображения
и текстовые данные, их нужно предварительно обработать. Для начала откроем блокнот Google Colab и скачаем данные с помощью следующих команд:
1. !wget https://github.com/jbrownlee/Datasets/releases/download/
Flickr8k/Flickr8k_Dataset.zip
2. !wget https://github.com/jbrownlee/Datasets/releases/download/
Flickr8k/Flickr8k_text.zip
После загрузки заархивированных файлов в Colab нужно разархивировать
их, используя следующие команды:
1. !unzip -q Flickr8k_Dataset.zip
2. !unzip -q Flickr8k_text.zip
428
■
Pythonic AI
Теперь, когда у нас имеются нужные файлы и папки, можно удалить заархивированные файлы, так как они больше не нужны. Делается это с помощью
следующей команды:
1. !rm Flickr8k_Dataset.zip Flickr8k_text.zip
Далее импортируем необходимые библиотеки, как показано ниже:
1. import numpy as np
2. import pandas as pd
3. import
tensorflow as tf
4. from PIL import Image
5. import os
6. import joblib
7. import matplotlib.pyplot as plt
8.
9. from tqdm.notebook import tqdm
10.
11. from google.colab import drive
12. drive.mount('/gdrive')
В строке номер 6 мы импортировали библиотеку joblib. Это библиотека
Python, предоставляющая инструменты для высокоуровневых функций и сериализации объектов. Она предназначена для эффективной записи объектов
Python на диск и их чтения с диска для последующего использования или
распространения. Мы будем использовать joblib для сохранения результатов дорогостоящих вычислений на диске, чтобы не производить повторные
вычисления каждый раз, как они потребуются. Библиотека особенно полезна
при работе с моделями машинного обучения или при обработке больших наборов данных, так как позволяет значительно ускорить процесс разработки
и экспериментов, избавляя от необходимости выполнять лишнюю работу.
В строке номер 9 мы также импортировали библиотеку tqdm. Это библиотека
Python, которая представляет собой простой и интуитивно понятный инструмент создания шкалы прогресса («прогресс-бара») для циклов или задач,
требующих значительного времени для завершения. С помощью этой библиотеки можно в удобной форме отображать ход выполнения процесса и следить за предполагаемым временем до его завершения.
Генерация подписей к изображениям
■
429
Для хранения и загрузки сериализируемых объектов мы подключили Google
Диск к блокноту Colab.
Для чтения текстовых файлов мы будем использовать библиотеку Pandas.
В библиотеке Pandas имеется функция read_csv(), которая читает файлы
различных форматов, в том числе ".txt", и преобразует их в объект типа
DataFrame библиотеки Pandas. Прочитаем файл Flickr8k.token.txt, как показано в следующих строках кода:
1. cap_df = pd.read_csv("Flickr8k.token.txt", sep="\t", header=None)
2. cap_df.columns = ["file_name", "caption"]
Здесь мы воспользовались функцией read_csv() для чтения файла Flickr8k.
token.txt. Параметр sep имеет значение "\t", то есть в качестве разделителя используется табуляция. После чтения файла он преобразуется в объект
DataFrame cap_df, содержащий данные файла Flickr8k.token.txt. Так как
в текстовом файле отсутствует заголовок, мы задали для DataFrame имена
столбцов. Просмотрим данные с помощью команды head(), как показано на
следующей иллюстрации:
Иллюстрация 12.1. Объект DataFrame с подписями
Теперь можно выполнять различные операции с данными и анализировать содержимое объекта DataFrame с помощью инструментов библиотеки
Pandas.
Как говорилось выше, файл Flickr8k.token.txt содержит пять подписей
к каждому файлу изображения. На иллюстрации выше видно, что столбец file_name содержит собственно фактическое имя файла изображения
430
■
Pythonic AI
и идентификатор подписи. Имя файла необходимо, чтобы прочитать изображение из папки Flickr8k_Dataset. Для его получения нужно удалить из
имени файлов символ "#" и идентификатор подписи. Также для отображения
пути к файлу, к его фактическому имени нужно будет добавить "./Flickr8k_
Dataset/", как показано в следующих строках кода:
1. cap_df["file_name"] = cap_df["file_name"].apply(lambda x: x.split("#")[0])
2. cap_df["file_name "] = cap_df["file_name"].apply(lambda x: "./Flickr8k_Dataset/"+x)
В строке номер 1 показанного выше фрагмента кода мы применили лямбда-функцию, чтобы разделить значения в столбце "file_name" объекта
DataFrame по символу "#" и сохранить только первую часть результата. Измененные значения вновь присваиваются столбцу "file_name". Код в строке номер 2 добавляет к каждому значению в столбце «file_name" объекта
DataFrame строку "./Flicker8k_Dataset/". Измененные значения вновь
присваиваются столбцу "file_name".
Теперь предварительно обработаем данные в столбце "caption", как показано в приведенном ниже фрагменте кода:
1. cap_df["caption"] = cap_df["caption"].str.replace('[^\w\s]','', regex=True)
2. cap_df["caption"] = cap_df["caption"].apply(lambda x: x.lower().strip())
3. cap_df["caption"] = cap_df["caption"].apply(lambda x: "[START] "+x+" [END]")
В первой строке этого фрагмента используется регулярное выражение (regex)
для удаления из значений в столбце "caption" объекта DataFrame всех символов кроме букв и пробела. Вторая строка преобразует значения в столбце
"caption" в нижний регистр, удаляя из каждого значения пробелы в начале
и в конце. Последняя строка добавляет строки "[START] " и " [END]" в начало и конец каждого значения в столбце "caption" соответственно. Измененные значения вновь присваиваются столбцу "caption".
Объясним вкратце, зачем нужно добавлять строки "[START] " и " [END]"
в начало и конец записей. В этом приложении мы будем генерировать соответствующие изображению подписи, то есть последовательности.
При использовании
(Sequence-to-Sequence,
последовательностям
ла обозначает начало
моделей «последовательность-последовательность»
Seq2Seq) принято добавлять к входной и выходной
специальные символы начала и конца. Символ начапоследовательности. Он сигнализирует модели о том,
Генерация подписей к изображениям
■
431
что начинается новая последовательность, и служит начальным вводом для
генерации последующих слов или лексем. Символ конца обозначает конец
последовательности. Он помогает модели понять, когда нужно прекратить
генерировать выходные токены, и служит условием завершения.
Теперь можно отобразить первые пять строк объекта DataFrame cap_df, чтобы просмотреть изменения, внесенные в ходе предыдущих операций:
Иллюстрация 12.2. Подписи в объекте DataFrame после предварительной обработки
Среди подписей бывают фрагменты текста с очень маленьким количеством
слов и с большим количеством слов.
Выделим сначала строки из cap_df, содержащие менее пяти слов и более
30 слов с помощью следующих строк кода:
1. MIN_SEQ_LENGTH = 5
2. MAX_SEQ_LENGTH = 30
3. images_to_remove = cap_df[cap_df["caption"].apply(lambda x: len(x.
split())<=MIN_SEQ_LENGTH or len(x.split())>MAX_SEQ_LENGTH)].
Выше мы задали две переменные, MIN_SEQ_LENGTH и MAX_SEQ_LENGTH, представляющие минимальную и максимальную допустимые длины последовательностей соответственно. Затем мы профильтровали объект DataFrame
cap_df, чтобы найти подписи, длина которых выходит за пределы указанного
диапазона. В строке номер 3 приведенного выше фрагмента к каждому значению в столбце "caption" объекта DataFrame применяется лямбда-функция
и создается новый объект DataFrame под названием images_to_remove, содержащий строки, в которых длина подписи (в словах) меньше или равна MIN_
SEQ_LENGTH или больше MAX_SEQ_LENGTH. Рассмотрим образец нового объекта DataFrame images_to_remove, как показано на следующей иллюстрации:
432
■
Pythonic AI
Иллюстрация 12.3. Изображения для удаления
Теперь получим значения из столбца "file_name" объекта DataFrame
images_to_remove и преобразуем их в список Python с тем же именем, то есть
images_to_remove. Далее отфильтруем объект DataFrame cap_df на основе
значений из списка images_to_remove. Для этого воспользуемся следующими командами:
1. images_to_remove = images_to_remove["file_name"].to_list()
2. cap_df = cap_df[~cap_df["file_name"].isin(images_to_remove)]
В строке номер 1 мы создали список images_to_remove. В строке номер 2 метод isin() проверяет, присутствуют ли значения столбца "file_name" объекта cap_df в списке images_to_remove. Оператор тильда (~) меняет условие
на отрицательное, выбирая строки, в которых значение "file_name" НЕ присутствует в списке images_to_remove. Результат этой операции возвращается
в cap_df и благодаря этому в cap_df удаляются строки, в которых "file_
name" совпадает с каким-либо из имен файлов в списке images_to_remove.
Теперь у нас имеется объект DataFrame cap_df, содержащий все имена файлов изображений, подписи к которым состоят более чем из пяти слов, но менее чем из 30 слов. Список images_to_remove больше не нужен, и его можно
удалить с помощью следующей строки:
1. del images_to_remove
Файл Flickr_8k.trainImages.txt содержит названия 6000 обучающих изображений. Для генерации обучающего набора данных нам нужно прочитать
этот файл с помощью следующих команд:
1. train_df = pd.read_csv("Flickr_8k.trainImages.txt", header=None)
2. train_df.columns = ["file_name"]
Генерация подписей к изображениям
■
433
Как показано выше, в объекте DataFrame имеется только один столбец,
и мы назвали его "file_name". Аналогичным образом мы считываем файл
"Flickr_8k.testImages.txt" и создаем тестовый набор из 1000 изображений с помощью следующих команд:
1. train_df["file_name"] = train_df["file_name"].apply(lambda x: "./
Flicker8k_Dataset/"+x)
2. test_df["file_name"] = test_df["file_name"].apply(lambda x: "./
Flicker8k_Dataset/"+x)
Теперь можно отфильтровать объект DataFrame cap_df на основе значений
в столбце "file_name" и в соответствующих столбцах "file_name" в train_
df и test_df. Запишем отфильтрованные обучающие и тестовые данные снова в train_df и test_df. Строки кода показаны ниже:
1. train_df = cap_df[cap_df["file_name"].isin(train_df["file_name"])]
2. test_df = cap_df[cap_df["file_name"].isin(test_df["file_name"])]
Построение модели для обработки
изображений
Для извлечения значимых признаков из данных изображений мы воспользуемся предварительно обученной моделью. Перед подачей данных в модель
нужно их прочитать и предварительно обработать. Для наших целей воспользуемся архитектурой модели Xcepticon1 из модуля tf.keras.applications.
xception.Xception. Xception представляет собой интерпретацию модулей Inception в сверточных нейронных сетях. Также мы напишем функцию
extract_image_features, принимающую предварительно обученную модель и имена файлов изображений для чтения и предварительной обработки
изображений. Рассмотрим следующий блок кода:
1. image_model = tf.keras.applications.xception.Xception(include_
top=False, pooling='avg')
2.
3. def extract_image_features(model, files):
4.
1
features = {}
François Chollet. “Xception: Deep learning with depthwise separable convolutions.”
In Proceedings of the IEEE conference on computer vision and pattern recognition, pp. 1251–
1258. 2017.
434
5.
■
Pythonic AI
for filename in tqdm(files):
6.
image = Image.open(filename)
7.
image = image.resize((299,299))
8.
image = np.expand_dims(image, axis=0)
9.
image = image/255.0
10.
11.
feature = model.predict(image, verbose=0)
12.
features[filename] = feature
13.
return features
Как показано выше, мы создали image_model как экземпляр модели tf.keras.
applications.xception.Xception. При этом для архитектуры Xception мы
не включили верхний полносвязный слой. Модель предварительно обучена
с весами "imagenet". В функции extract_image_features мы перебираем
все имена файлов входных изображений и считываем изображения с помощью функции open() класса Image библиотеки PIL. Мы отмасштабировали изображения до размера (299×299) — это стандартный размер входного
изображения для модели Xception. В строке номер 8 мы использовали функцию expand_dims из библиотеки NumPy для изменения размерности массива
изображений. Функция expand_dims вставляет в массив новое измерение по
указанной оси, фактически превращая его в 3-мерный массив. Размер добавленного измерения будет равен 1, а остальные размеры останутся такими же,
как в исходном массиве изображений. В строке номер 9 мы нормализовали
значения пикселей.
Предварительно обработанное изображение мы передали экземпляру модели Xception. Затем мы сохранили предсказанные признаки изображения
в словаре, где ключи — это имена файлов изображений, а значения — соответствующие векторы признаков.
Учтите, что предсказание признаков изображения с помощью модели
Xception для всего обучающего набора данных занимает довольно много времени, около часа. После создания векторов признаков для обучающих изображений рекомендуется весь словарь, содержащий векторы, сохранить на
диске. Делается это с помощью следующих команд:
1. features = extract_image_features(image_model, train_df["file_name"])
2. joblib.dump(features, "train_features.p")
Генерация подписей к изображениям
■
435
В строках кода выше мы вызвали функцию extract_image_features, передав ей в качестве аргументов экземпляр модели Xception и объект DataFrame
train_df. Затем мы сохранили сгенерированный словарь features в файл
"train_features.p" с помощью функции dump() библиотеки joblib. Теперь
файл "train_features.p" можно скачать или сохранить на Google Диск для
дальнейшего использования.
Построение модели для генерации
подписей
Для задачи генерации подписей помимо данных об изображениях нужно обрабатывать и текстовые данные. Нам потребуется текстовый векторизатор
для отображения текстовых признаков в целочисленные последовательности. В строках кода ниже показано, как создать текстовый векторизатор для
обработки текстов подписей:
1. vectorizer = tf.keras.layers.TextVectorization(
2. standardize=None, ragged=True
3. )
4. vectorizer.adapt(list(cap_df["caption"]))
Как показано выше, мы создали текстовый векторизатор с помощью слоя
TextVectorization библиотеки TensorFlow. Этот векторизатор преобразует текстовые данные в числовые векторы. Так как мы уже преобразовали
текст подписей в строчные буквы и удалили знаки препинания, то аргументу
standardize задано значение None. Аргумент ragged указывает на то, могут
ли входные данные иметь различную длину (ragged tensors). При значении
этого аргумента True слой TextVectorization сможет обрабатывать последовательности разной длины.
В строке номер 4 мы применили к объекту vectorizer метод adapt, чтобы этот
объект выучил словарь на основе заданных данных. В процессе адаптации
слой TextVectorization токенизирует текст, формирует словарь на основе
уникальных токенов, содержащихся в данных, и присваивает каждому токену уникальный целочисленный индекс. После адаптации объект векторизатора vectorizer будет готов к преобразованию новых текстовых данных
в числовые векторы с использованием выученного словаря и схемы токенизации.
436
■
Pythonic AI
С помощью следующего кода можно получить длину словаря объекта vectorizer:
1. vocab_size = len(vectorizer.get_vocabulary())
Выведем на печать первые 10 слов словаря, как показано на иллюстрации ниже:
Иллюстрация 12.4. Образец словаря векторизатора
Помимо слов, извлеченных из текстов подписей, векторизатор vectorizer
добавил еще два токена — один для пустой строки и один для неизвестных
слов.
Для примера можно задать векторизатору образец предложения («ребенок
в розовом платье поднимается по лестнице у входа») и получить векторизованную форму текста, как показано на следующей иллюстрации:
Иллюстрация 12.5. Пример векторизованного текста
Подписи к изображениям и изображения в модель генерации подписей нужно передавать одновременно.
Ввод текста необходимо оформлять как приложение для предсказания следующего слова. В данном случае мы должны передавать набор слов, чтобы
модель предсказала следующее слово.
Разделим каждый текст подписи так, чтобы модель предсказывала каждое
слово на основе последовательности предыдущих слов. Например, если
текст подписи имеет вид "[START] a girl going into a wooden building
[END]", мы должны разбить его на части так, как показано в следующей
таблице:
Генерация подписей к изображениям
Текст ввода
■
437
Предсказываемое слово
[START]
a
[START] a
girl
[START] a girl
going
[START] a girl going
into
[START] a girl going into
a
[START] a girl going into a
wooden
[START] a girl going into a wooden
building
[START] a girl going into a wooden building
[END]
Таблица 12.1. Текстовые данные
Определим сначала максимальную длину, или количество слов подписи,
в имеющемся наборе данных, как показано в следующей строке кода:
1. max_length = max(cap_df["caption"].apply(lambda x:len(x.split())))
Здесь к элементам столбца "caption" в объекте DataFrame cap_df мы применили лямбда-функцию, разбивающую каждую подпись на слова и возвращающую длину полученного списка слов. Функция max(…) вычисляет
максимальное значение длины всех надписей в столбце "caption", и это
максимальное значение присваивается переменной max_length. Мы будем
использовать эту максимальную длину для подгонки последовательностей
к фиксированной длине при дальнейшей обработке подписей.
Как мы сказали выше, нужно написать функцию для разбивки текста подписи, которая также будет генерировать входные данные для модели, объединяющей изображение и текст. Следующее слово в подписи будет подаваться
в качестве выходных данных, предсказанных на основе заданного изображения и предыдущих слов. Рассмотрим следующий блок кода:
1. def get_data(df, features, vectorizer):
2.
3.
while 1:
for i, row in df.iterrows():
4.
image_feature = features[row["file_name"]][0]
5.
input_image, input_sequence, output_word = get_
sequences(vectorizer, row["caption"], image_feature)
6.
yield [[input_image, input_sequence], output_word]
438
■
Pythonic AI
7.
8. def get_sequences(vectorizer, caption, image_feature):
9.
10.
input_image, input_sequence, output_sequence = list(), list(),
list()
word_sequence = vectorizer(caption).numpy()
11.
12.
for i in range(1, len(word_sequence)):
13.
input_image.append(image_feature)
14.
input_sequence.append(tf.keras.utils.pad_
sequences([word_sequence[:i]], maxlen=max_length)[0])
15.
output_sequence.append(word_sequence[i])
16.
return np.array(input_image), np.array(input_sequence),
np.array(output_sequence)
В этом блоке функция get_data() создает генератор, который непрерывно
выдает образцы данных для цикла обучения модели подписей. Эта функция
принимает на вход обучающий объект DataFrame, словарь признаков, созданный ранее с помощью image_model, и векторизатор. Внутри функции цикл
while выполняется бесконечно (while 1), то есть образцы данных генерируются бесконечно. Внутри цикла каждая строка в обучающем объекте DataFrame
перебирается с помощью цикла for. Эта функция вызывает функцию get_
sequences(), передавая ей векторизатор, текст надписи из строки DataFrame
и имя файла изображения. Функция get_sequences() преобразует текст
надписи в целочисленную последовательность с помощью векторизатора. Затем функция выполняет итерации по этой последовательности — начиная со
второго слова (целое число), она преобразует векторизованный текст подписи
в фрагменты, подобные тем, что показаны в таблице 12.1 выше. Поскольку
векторизатор создает тензоры разной длины (ragged tensors), нам нужна функция tf.keras.utils.pad_sequences, чтобы подгонять их к одинаковой длине
(max_length). Функция get_sequences() возвращает кортеж из трех массивов numpy. Функция get_data() использует оператор yield для получения
списка, содержащего [[input_image, input_sequence], output_word] для
каждой строки в данном объекте DataFrame. Это означает, что на каждом шаге
цикла генератор будет создавать пару входных данных (input_image и input_
sequence) и соответствующие им выходные данные (output_word).
Протестировать функцию get_data() можно, передав ей объект DataFrame
train_df. Так как эта функция — генератор, для получения одиночно-
Генерация подписей к изображениям
■
439
го вывода воспользуемся функцией next(). Рассмотрим следующую иллюстрацию:
Иллюстрация 12.6. Размерность данных
Как показано на иллюстрации выше, вывод x1 — это вектор изображения,
вывод x2 — это входной текст, а вывод y — выходной текст. В используемой
здесь архитектуре Xception размер создаваемого моделью вектора изображения составляет 2048. Текст подписи в данном случае следующий: "[START]
a child in a pink dress is climbing up a set of stairs in an
entry way [END]". Поскольку от начала (“[START]”) до предпоследнего слова
("way") всего 18 токенов, или слов, то первое значение размерности x2 равно 18. Каждый фрагмент входного текста с помощью векторизатора vectorizer
и функции pad_sequences был преобразован в последовательность из 30 целых чисел, следовательно, второй показатель размерности x2 равен 30. Для y
это просто 18 отдельных слов (целых чисел), аналогичных столбцу «Предсказываемое слово» в таблице 12.1, показанной выше.
Теперь можно перейти к построению модели для генерации подписей.
Обучение и оценка модели
генерациип одписей
Создание текстовых подписей на основе изображений — это пример мультимодального обучения. Поскольку в данном случае мы имеем дело как
с изображением, так и с текстом, итоговая модель не будет последовательной
в полном смысле. Для построения модели генерации подписей мы воспользуемся функциональным API TensorFlow. Попробуем сначала создать модель
с архитектурой, в которой текстовые данные обрабатываются слоем LSTM.
Создание модели на основе LSTM
В модели генерации подписей имеются три основных компонента: экстрактор признаков изображения, экстрактор текстовых признаков и декодер для
создания итоговой подписи. В следующем блоке кода зададим функцию для
создания модели:
440
■
Pythonic AI
1. def create_model(vocab_size, max_length):
2.
img_input = tf.keras.Input(shape=(2048,))
3.
x = tf.keras.layers.Dropout(0.5)(img_input)
4.
img_feature = tf.keras.layers.Dense(256, activation='relu')(x)
5.
6.
txt_input = tf.keras.Input(shape=(max_length,))
7.
x = tf.keras.layers.Embedding(vocab_size, 256, mask_zero=True)
(txt_input)
8.
x = tf.keras.layers.Dropout(0.5)(x)
9.
txt_feature = tf.keras.layers.LSTM(256)(x)
10.
11.
x = tf.keras.layers.Add()([img_feature, txt_feature])
12.
x = tf.keras.layers.Dense(256, activation='relu')(x)
13.
outputs = tf.keras.layers.Dense(vocab_size)(x)
14.
15.
model = tf.keras.Model(inputs=[img_input, txt_input],
outputs=outputs) 16.
17.
sparse_cat_loss = tf.keras.losses.SparseCategoricalCrossentro
py(from_logits=True)
18.
model.compile(loss=sparse_cat_loss, optimizer='adam')
19.
20.
return model
Как показано выше, функция create_model() принимает в качестве входных
данных размер словаря и максимальную длину входной последовательности.
Здесь мы задали два входных параметра. Во-первых, мы указали входной слой
для данных изображений с формой (размерностью) (2048,), говорящей о том,
что изображение имеет 2048 признаков. Как известно, векторы признаков,
генерируемые моделью Xception, имеют размер 2048. Для регуляризации мы
применили слой дропаута, а для уменьшения количества признаков с 2048 до
256 — плотный слой с 256 узлами (нейронами).
Еще один слой мы задали для текстовых данных, указав, что каждая входная
текстовая последовательность может иметь максимальную длину max_length.
Для перевода каждого токена входной последовательности в плотный вектор мы применили слой эмбеддинга. Параметр vocab_size задает количество
уникальных токенов в словаре, а размер эмбеддинга установлен как 256. Для
Генерация подписей к изображениям
■
441
обработки входных данных в виде текстовых последовательностей мы задали
слой LSTM c 256 узлами. В строке номер 11 признаки изображения и текста поэлементно объединяются с помощью операции сложения. Затем объединенные
признаки подаются на плотный слой. В завершение добавляется выходной слой
с vocab_size узлами. Этот слой выдает предсказания модели, где каждый узел
представляет собой вероятность появления определенного токена в словаре.
Мы скомпилировали модель с помощью функции потерь tf.keras.losses.
SparseCategoricalCrossentropy, задав аргументу from_logits значение True,
поскольку в последнем слое мы не использовали функцию активацию softmax.
С помощью утилиты tf.keras.utils.plot_model можно построить схему
модели. Она создается с помощью следующих команд:
1. captioner_model = create_model(vocab_size, max_length)
2. tf.keras.utils.plot_model(captioner_model)
Сама схема показана на следующей иллюстрации:
Иллюстрация 12.7. Архитектура модели на основе LSTM
442
■
Pythonic AI
Как показано выше, модель имеет два входа: один — для данных изображения, другой — для текстовых данных. Теперь мы можем, как обычно, запустить процесс обучения с помощью следующих команд:
1. generator = get_data(train_df, train_features, vectorizer)
2. captioner_model.fit(generator, epochs=20, steps_per_epoch=1000, verbose=1)
Как показано выше, сначала мы вызвали функцию get_data(), чтобы сгенерировать входные данные, а затем передали экземпляр генератора в модель
для обучения.
Примечание. Вы можете испробовать разные модели, добавляя больше
слоев LSTM/GRU и Dense (плотных слоев), двунаправленных последовательных слоев (обертку), больше узлов (нейронов) на слой, а также
использовать другую предварительно обученную модель распознавания изображения, увеличивая количество эпох, меняя скорость обучения, применяя аугментацию данных и т. д. Для другой предварительно
обученной модели изображения нужно будет проверить ее входную
форму (размерность) и соответствующим образом изменить размер
изображений. В данном случае для модели Xception мы изменили размер каждого входного изображения до (299, 299). Некоторые другие
модели (например, MobileNetV3Large, DenseNet201 и т. д.) принимают изображения размером (224, 224). В зависимости от архитектуры
используемой модели также меняется форма данных изображения во
входном слое модели генерации подписей.
После завершения обучения модели можно приступить к собственно генерации подписей к заданному изображению. Для этого создадим две функции.
Первая будет выполнять предварительную обработку заданного изображения так же, как мы выполняли предварительную обработку перед процессом
обучения. Эта функция использует модель распознавания изображения для
создания вектора признаков данного изображения. Вторая функция генерирует подпись на основе вектора признаков изображения. Рассмотрим следующий фрагмент кода:
1. img_path = './Flicker8k_Dataset/3385593926_d3e9c21170.jpg' 2.
3. def extract_image_features(model, files):
4.
features = {}
Генерация подписей к изображениям
5.
■
for filename in tqdm(files):
6.
image = Image.open(filename)
7.
image = image.resize((299,299))
8.
image = np.expand_dims(image, axis=0)
9.
image = image/255.0
10.
11.
feature = model.predict(image, verbose=0)
12.
features[filename] = feature
13.
return features
14.
15.
16.
def generate_desc(model, vectorizer, image_feature, max_
length):
17.
caption = "[START]"
18.
for i in range(max_length):
19.
sequence = vectorizer(caption).numpy()
20.
sequence = tf.keras.utils.pad_sequences([sequence],
maxlen=max_length)
21.
pred = model.predict([image_feature,sequence],
verbose=0)
22.
pred_index = np.argmax(pred)
23.
24.
word = vectorizer.get_vocabulary()[pred_index]
25.
26.
if word is None:
27.
break
28.
29.
if word == "[END]":
30.
caption = caption + " " + word
31.
break
32.
33.
caption = caption + " " + word
34.
35.
return caption
443
444
■
Pythonic AI
36.
37.
38. features = extract_image_features(image_model, [img_path])
39.
40. description = generate_desc(captioner_model, vectorizer,
features[img_path], max_length)
41.
42. test_img = Image.open(img_path)
43. plt.imshow(test_img)
44. print(description)
Выше заданы две основные функции: extract_image_features() и generate_
desc(), которые используются соответственно для извлечения признаков из
изображения и генерации подписи к нему. Для генерации подписи мы задали путь к изображению в переменной img_path. Функция extract_image_
features() принимает на вход модель распознавания изображения и список
путей к файлам. Она обрабатывает каждый файл изображения в списке files,
изменяет его размер до (299, 299) пикселей, нормализует значения пикселей
в диапазоне [0, 1] и пропускает обработанное изображение через предварительно обученную модель изображения, чтобы получить вектор признаков.
Эти векторы признаков хранятся в словаре features, где имя файла — ключ,
а вектор признаков — значение. Эта функция возвращает словарь признаков
features.
Функция generate_desc() принимает на вход модель создания подписей,
векторизатор vectorizer, вектор признаков изображения, сгенерированный
функцией extract_image_features(), и максимальную длину генерируемой подписи (max_length). Эта функция генерирует текстовое описание для
заданного вектора признаков изображения. Она инициализирует подпись
строкой "[START]" и итеративно предсказывает следующее слово в подписи, пока не достигнет максимальной длины или не встретит токен "[END]".
Предсказанное слово добавляется к подписи, и процесс продолжается. Функция возвращает сгенерированную надпись.
В строке номер 38 мы вызвали функцию extract_image_features(), чтобы
извлечь признаки для указанного изображения. В строке номер 40 мы вызвали функцию generate_desc(). Заданное изображение и сгенерированная
подпись показаны на следующей иллюстрации:
Генерация подписей к изображениям
■
445
Иллюстрация 12.8. Сгенерированная подпись для изображения 3385593926_d3e9c21170.jpg
Сгенерированная надпись для данного изображения имеет вид [START] a dog
is running through the snow [END] («собака бежит по снегу»). С точки зрения
контекста, сгенерированная надпись не совсем корректна, но интересно отметить, что модель смогла обнаружить на изображении собаку и снег. Испытаем модель на другом изображении, расположенном по адресу: './Flicker8k_
Dataset/101669240_b2d3e7f17b.jpg'. Рассмотрим следующую иллюстрацию:
Иллюстрация 12.9. Сгенерированная подпись к изображению 101669240_b2d3e7f17b.jpg
446
■
Pythonic AI
В итоге получилась подпись [START] a man is riding a bike in the snow [END]
(«мужчина едет на велосипеде по снегу»). Любопытно, что модель правильно
определила на изображении мужчину, снег и то, что он на чем-то едет. Но для
некоторых изображений модель может выдавать совершенно бессмысленные
подписи.
Для улучшения генерации подписей можно попробовать использовать двунаправленную LSTM. Ниже показана соответствующая функция create_
model():
1. def create_model(vocab_size, max_length):
2.
img_input = tf.keras.Input(shape=(2048,))
3.
x = tf.keras.layers.Dropout(0.5)(img_input)
4.
img_feature = tf.keras.layers.Dense(256, activation='relu')(x)
5.
6.
# Двунаправленная последовательная модель LSTM
7.
txt_input = tf.keras.Input(shape=(max_length,))
8.
x = tf.keras.layers.Embedding(vocab_size, 256, mask_zero=True)
(txt_input)
9.
x = tf.keras.layers.Dropout(0.5)(x)
10.
txt_feature = tf.keras.layers.Bidirectional(tf.keras.layers.
LSTM(128))(x) 11.
12.
# Слияние обеих моделей
13.
x = tf.keras.layers.Add()([img_feature, txt_feature])
14.
x = tf.keras.layers.Dense(256, activation='relu')(x)
15.
outputs = tf.keras.layers.Dense(vocab_size)(x)
16.
17.
model = tf.keras.Model(inputs=[img_input, txt_
input],outputs=outputs) 18.
19.
sparse_cat_loss = tf.keras.losses.SparseCategoricalCrossentro
py(from_logits=True)
20.
model.compile(loss=sparse_cat_loss, optimizer='adam')
21.
22.
return model
Как показано выше, в строке номер 10 мы использовали двунаправленную
обертку для слоя LSTM. Также обратите внимание, что для слоя LSTM задано 128 узлов (нейронов), но из-за двунаправленности общее количество
узлов будет 128*2=256. Это позволит нам добавить выход LSTM к вектору
Генерация подписей к изображениям
■
447
признаков изображения длиной 256. На следующей иллюстрации показан
пример сгенерированной подписи:
Иллюстрация 12.10. Сгенерированная подпись («собака бежит в воздухе»)
Создание модели, основанной на механизме
внимания
Из главы 11 «Создание моделей с механизмом внимания и моделей трансформера» мы узнали о механизме внимания и создали на основе него модель
классификации. Для формирования векторов признаков из текстовых данных можно воспользоваться моделью, основанной на разработанном нами
слое внимания, подобном тому, что рассматривался в главе 11 «Создание моделей с механизмом внимания и моделей трансформера». Ниже показан блок
кода, в котором задается соответствующая функция create_model():
1. class Attention(tf.keras.layers.Layer):
2.
def __init__(self, return_sequences=True):
3.
self.return_sequences = return_sequences
4.
super(Attention,self).__init__()
5.
6.
def build(self, input_shape):
7.
self.W=self.add_weight(shape=(input_shape[1],1),initializer="normal")
448
8.
9.
10.
11.
12.
■
Pythonic AI
self.b=self.add_weight(shape=(input_shape[1],1),initialize
r="zeros")
super(Attention,self).build(input_shape)
def call(self, x):
dot_prod = tf.keras.activations.tanh(tf.
matmul(x,self.W)+self.b)
attention = tf.keras.activations.softmax(dot_prod, axis=1)
out_sequences = x*attention
13.
14.
15.
16.
if self.return_sequences:
17.
return out_sequences
18.
19.
return tf.math.reduce_sum(out_sequences, axis=1)
20.
21.def create_model(vocab_size, max_length):
22.
img_input = tf.keras.Input(shape=(2048,))
23.
x = tf.keras.layers.Dropout(0.5)(img_input)
24.
img_feature = tf.keras.layers.Dense(256, activation='relu')
(x)
25.
26. # Модель с механизмом внимания
27.
txt_input = tf.keras.Input(shape=(max_length,))
28.
x = tf.keras.layers.Embedding(vocab_size, 32, input_
length=max_length)(txt_input)
29.
x = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(128,
return_sequences=True))(x)
30.
txt_feature = Attention(return_sequences=False)(x)
31.
#txt_feature = tf.keras.layers.LSTM(256)(x)
32.
33.
34.
# Слияние обеих моделей
35.
x = tf.keras.layers.Add()([img_feature, txt_feature])
36.
x = tf.keras.layers.Dense(256, activation='relu')(x)
37.
x = tf.keras.layers.Dropout(0.6)(x)
38.
outputs = tf.keras.layers.Dense(vocab_size)(x)
39.
40.
model = tf.keras.Model(inputs=[img_input, txt_
input],outputs=outputs) 41.
Генерация подписей к изображениям
42.
43.
44.
45.
■
449
sparse_cat_loss = tf.keras.losses.SparseCategoricalCrossentro
py(from_logits=True)
model.compile(loss=sparse_cat_loss, optimizer='adam')
return model
Как показано выше, для построения архитектуры модели генерации подписей мы использовали пользовательский слой внимания. Схема архитектуры
модели показана на следующей иллюстрации:
Иллюстрация 12.11. Архитектура модели, основанной на механизме внимания
После того как модель обучена, можно воспользоваться ею для вывода подписей к заданным изображениям, как мы делали это ранее. На следующей иллюстрации показана подпись, сгенерированная для изображения './Flicker8k_
Dataset/1096395242_fc69f0ae5a.jpg':
450
■
Pythonic AI
Иллюстрация 12.12. Сгенерированная подпись к изображению 1096395242_
fc69f0ae5a.jpg («мужчина в красной рубашке»)
Создание модели на основе трансформера
В главе 11 «Создание моделей с механизмом внимания и моделей трансформера» мы разработали трансформер на основе доработанной нами архитектуры. Такой подход можно применить и к разработке модели генерации подписей. Рассмотрим следующий пример пользовательской архитектуры:
1. class TransformerLayer(tf.keras.layers.Layer):
2.
def __init__(self, embed_size, num_heads, ff_units, rate=0.1):
3.
super().__init__()
4.
self.attention = tf.keras.layers.MultiHeadAttention(num_
heads=num_heads, key_dim=embed_size, dropout=rate)
5.
6.
self.feedforward = tf.keras.Sequential()
7.
self.feedforward.add(tf.keras.layers.Dense(ff_
units,activation="relu"))
8.
self.feedforward.add(tf.keras.layers.Dense(embed_size))
9.
10.
self.layernormalization_1 = tf.keras.layers.LayerNormal
ization(epsilon=1e-6)
11.
self.layernormalization_2 = tf.keras.layers.LayerNormal
ization(epsilon=1e-6)
Генерация подписей к изображениям
■
451
12.
13.
def call(self, inputs):
14.
attn_output = self.attention(inputs, inputs)
15.
ln_out = self.layernormalization_1(inputs + attn_
output)
16.
ff_output = self.feedforward(ln_out)
17.
return self.layernormalization_2(ln_out + ff_output)
18.
19. class TokenAndPositionEmbedding(tf.keras.layers.Layer):
20.
def __init__(self, max_seq_len, vocab_size, embedding_dim):
21.
super().__init__()
22.
self.max_seq_len = max_seq_len
23.
self.token_embedding = tf.keras.layers.Embedding(input_
dim=vocab_size, output_dim=embedding_dim)
24.
self.position_embedding = tf.keras.layers.Embedding
(input_dim=max_seq_len, output_dim=embedding_dim)
25.
26.
def call(self, x):
27.
position_tensor = tf.range(start=0, limit=self.max_seq_
len,delta=1)
28.
position_tensor = self.position_embedding(position_
tensor)
29.
x = self.token_embedding(x)
30.
return x + position_tensor
31.
32.
def create_model(vocab_size, max_length):
33.
embed_size = 32
34.
num_heads = 2
35.
ff_units = 32
36.
img_input = tf.keras.Input(shape=(2048,))
37.
x = tf.keras.layers.Dropout(0.5)(img_input)
38.
img_feature = tf.keras.layers.Dense(256,
activation='relu')(x)
39.
40.
# Модель трансформера
41.
txt_input = tf.keras.Input(shape=(max_length,))
42.
embedding_layer = TokenAndPositionEmbedding(max_length,
vocab_size,embed_size)
452
■
Pythonic AI
43.
x = embedding_layer(txt_input)
44.
transformer_block = TransformerLayer(embed_size, num_
heads, ff_units)
45.
x = transformer_block(x)
46.
x = tf.keras.layers.GlobalAveragePooling1D()(x)
47.
x = tf.keras.layers.Dropout(0.1)(x)
48.
x = tf.keras.layers.Dense(256, activation="relu")(x)
49.
txt_feature = tf.keras.layers.Dropout(0.1)(x)
50.
51.
# Слияние обеих моделей
52.
x = tf.keras.layers.Add()([img_feature, txt_feature])
53.
x = tf.keras.layers.Dense(256, activation='relu')(x)
54.
x = tf.keras.layers.Dropout(0.6)(x)
55.
outputs = tf.keras.layers.Dense(vocab_size)(x)
56.
57.
model = tf.keras.Model(inputs=[img_input, txt_
input],outputs=outputs)
58.
59.
sparse_cat_loss = tf.keras.losses.SparseCategoricalCros
sentropy(from_logits=True)
60.
model.compile(loss=sparse_cat_loss, optimizer='adam')
61.
62.
return model
Как показано выше, мы создали пользовательские слои TransformerLayer
и TokenAndPositionEmbedding. В модели генерации подписей они выполняют роль энкодера для обработки входных текстовых последовательностей.
Использование предварительно
обученных моделей генерации подписей
отH uggingF ace
В главе 7 «Создание приложения для чтения текста и изображений» и в главе
11 «Создание моделей с механизмом внимания и моделей трансформера» мы
использовали предварительно обученные модели трансформеров компании
Hugging Face. В данном случае мы тоже воспользуемся для вывода (инференса) предварительно обученными моделями преобразования изображений
Генерация подписей к изображениям
■
453
в текст, которые предоставляет Hugging Face. Для начала нужно установить
библиотеку трансформеров Hugging Face с помощью следующей команды:
1. !pip install transformers
Библиотека трансформеров предлагает класс pipeline (конвейер) — простой
API, используемый для вывода рабочих результатов (инференса). Пользоваться этим API довольно просто, нужно только определить тип задачи и модель. В нашем случае это генерация «из изображения в текст» без специальных примеров (zero-shot) на основе модели BLIP.
Модель BLIP1 (Bootstrapping Language-Image Pre-training), предназначенная
для унификации обработки визуальных данных и генерации текста на основе изображений (Unified Vision-Language Understanding and Generation), была
разработана исследователями из компании Salesforce. В настоящее время эта
модель входит в состав библиотеки Hugging Face Transformers. BLIP — это система визуально-языкового предварительного обучения, которая обрабатывает некачественные (зашумленные) данные из Интернета и улучшает подписи
с помощью фильтрации. Ее генератор (captioner) создает синтетические подписи, а фильтр удаляет шумные. Эта система позволила добиться лучших результатов в некоторых задачах перевода визуальных данных в текстовые.
Рассмотрим фрагмент кода, с помощью которого модель BLIP можно применить для генерации подписей в нашем случае:
1. from transformers import pipeline
2.
3. image = Image.open("./Flicker8k_Dataset/1000268201_693b08cb0e.jpg")
4.
5. captioner = pipeline("image-to-text", model="Salesforce/blipimagecaptioning-base")
6. caption = captioner(image)
7.
8. plt.imshow(image)
9.
10. print("Caption: ", caption[0]["generated_text"])
1
Li, Junnan, Dongxu Li, Caiming Xiong, and Steven Hoi. “Blip: Bootstrapping language-image
pre-training for unified vision-language understanding and generation”. In International
Conference on Machine Learning, стр. 12888–12900. PMLR, 2022.
454
■
Pythonic AI
Как показано выше, мы использовали API конвейера pipeline из библиотеки трансформеров. Задачу мы определили как "image-to-text" («преобразование изображения в текст») и указали модель BLIP. Далее мы
сгенерировали подпись для изображения с заданным путем "./Flicker8k_
Dataset/1000268201_693b08cb0e.jpg". Сгенерированная подпись показана
на следующей иллюстрации:
Иллюстрация 12.13. Сгенерированная подпись к изображению
1000268201_693b08cb0e.jpg
Как видно, для данного изображения была сгенерирована подпись “a little girl
in a pink dress” («маленькая девочка в розовом платье»).
Мы можем попробовать и другую хорошую модель генерации подписей, например, microsoft/git-base, доступную в библиотеке трансформеров Hugging
Face. Эта модель, называемая также моделью GIT1 (Generative Image-to-text
Transformer), реализует кодировщик изображений и декодировщик текста
в рамках одной задачи языкового моделирования. Экземпляр класса pipeline
(конвейера) нужно будет заменить так, как показано в строке кода ниже. Остальное остается таким же, как и в случае с моделью BLIP:
1. captioner = pipeline("image-to-text",model="microsoft/git-base")
1
Jianfeng Wang, Zhengyuan Yang, Xiaowei Hu, Linjie Li, Kevin Lin, Zhe Gan, Zicheng Liu, Ce
Liu, and Lijuan Wang. “Git: A generative image-to-text transformer for vision and language.”
arXiv preprint arX- iv:2205.14100 (2022).
Генерация подписей к изображениям
■
455
Заключение
В последние годы в сфере генерации подписей к изображениям (задача «из
изображения в текст», image-t0-text) удалось достичь значительного прогресса благодаря современным разработкам в области глубокого обучения и компьютерного зрения. В этой главе мы рассмотрели различные методы и способы создания описательных и содержательных подписей к изображениям. Мы
узнали о мультимодальном обучении и о том, как создаются наборы данных
для мультимодального обучения.
Мы разработали модели с использованием слоев LSTM, слоев внимания
и трансформеров, а также воспользовались предварительно обученными моделями.
В главе также подчеркивается важность предварительной обработки данных
для обучения и оценки моделей создания подписей к изображениям.
Генерация текстовых подписей к изображениям способна произвести революцию в таких областях, как поиск изображений, понимание контента и помощь людям с ограниченными возможностями. Эти достижения, позволяющие машинам понимать и описывать визуальный мир, открывают двери для
новых практических приложений и возможностей.
В следующей главе мы познакомимся с генеративно-состязательными сетями (GAN, Generative Adversarial Networks) и их применением.
Основные выводы
• Задача генерации подписей к изображениям подразумевает автоматическое создание описательных и контекстуально значимых подписей
или текстовых пояснений к изображениям.
• Генерация текстовых подписей на основе визуальных данных — это
пример мультимодального обучения, поскольку в этом случае мы имеем дело как с изображениями, так и с текстовыми данными.
• Эффективная генерация подписей к изображениям начинается с извлечения признаков из изображений. Для этого обычно применяются
модели на основе сверточных сетей. Далее используются архитектуры
долгой краткосрочной памяти (LSTM), механизмы внимания и трансформеры. Сэкономить время и ресурсы позволяют предварительно
обученные модели для генерации подписей, такие как предлагаемые
платформой Hugging Face.
456
■
Pythonic AI
Ссылки
• Модель BLIP от Hugging Face: https://huggingface.co/docs/transformers/
main/en/model_doc/blip
• COCO (Common Objects in Context) — еще один широко используемый
набор данных для разработки систем генерации подписей к изображениям: https://cocodataset.org/#home.
Глава 13
Учимся строить
модели GAN
Введение
За последние годы в сфере искусственного интеллекта были достигнуты впечатляющие успехи в области генеративных моделей. Среди различных разработок, привлекших значительное внимание, особенно выделяются так называемые генеративно-состязательные сети (GAN, Generative Adversarial
Networks). GAN являются одним из самых революционных и значимых
инструментов создания реалистичных и высококачественных синтетических
данных.
Концепцию GAN впервые представил в 2014 году коллектив исследователей
во главе с Ианом Гудфеллоу1. Созданные ими сети продемонстрировали замечательные возможности в генерации изображений, музыки, текста и даже
поразительно реалистичного видео. Благодаря этому открылись замечательные перспективы в различных областях, таких как искусство, развлечения
и аугментация данных.
Из этой главы мы узнаем об основных концепциях, лежащих в основе сетей
GAN, а также о компонентах, из которых состоят эти мощные модели. Мы
рассмотрим увлекательное противодействие генератора и дискриминатора,
работающих в тандеме и помогающим сети учиться и совершенствоваться со
временем. Сети GAN учатся самостоятельно генерировать данные, демонстрирующие поразительное сходство с исходным обучающим набором. В результате такого состязательного процесса обучения модель учится выявлять
1
Ian Goodfellow, Jean Pouget-Abadie, Mehdi Mirza, Bing Xu, David Warde-Farley, Sherjil
Ozair, Aaron Courville, and Yoshua Bengio. “Generative adversarial nets.” Advances in neural
information processing systems 27 (2014).
458
■
Pythonic AI
сложные закономерности и распределения в реальных данных, благодаря
чему генерирует новые образцы данных, неотличимые от настоящих. Кроме
того, мы опишем основные проблемы, связанные с сетями GAN, а также лучшие методы преодоления этих проблем.
Итак, давайте отправимся в это увлекательное путешествие и научимся строить GAN-модели, обещающие яркое будущее для генеративного ИИ.
Структура
В этой главе мы рассмотрим следующие темы:
• Генеративные модели.
• Концепция GAN: Обзор.
• Построение модели GAN.
• Вариационный автокодировщик.
• Построение модели вариационного автокодировщика.
Цели
Мы начнем с объяснения основных теоретических концепций, лежащих в основе GAN, включая такие базовые понятия, как генератор и дискриминатор,
а также состязательный процесс обучения (adversarial training process). Рассмотрим интуитивно понятную идею противопоставления этих двух сетей
друг другу, когда генератор стремится создавать синтетические данные, неотличимые от реальных, а дискриминатор пытается правильно классифицировать образцы как реальные или поддельные. Из этой главы мы узнаем
об основных шагах по настройке среды разработки и создании архитектуры нейронной сети для моделей GAN и вариационного автокодировщика
(VAE, Variational Autoencoder) в TensorFlow 2. Кроме того, мы научимся настраивать процесс обучения генеративных моделей с использованием соответствующих функций потерь, оптимизаторов и конвейеров данных.
Генеративные модели
Генеративные модели — весьма увлекательное направление в сфере искусственного интеллекта, расширяющее границы возможностей машин. Под генеративными моделями подразумевается класс моделей машинного обучения,
способных превосходно генерировать новый и оригинальный контент. Они
Учимся строить модели GAN
■
459
учатся на заданном наборе данных, выявляя присутствующие в них закономерности и структуру, а затем используют эти знания для создания новых
экземпляров, очень похожих на обучающие данные. Существуют два популярных типа генеративных моделей — это GAN и VAE. В настоящей главе мы
рассмотрим их и узнаем, чем они различаются в своем подходе к обработке
и генерации данных.
Генеративные модели используются в ряде очень интересных областей. Помимо прочего, они генерируют реалистичные изображения, связный и контекстуально релевантный текст, сочиняют музыку, создают увлекательные
виртуальные миры с эффектом погружения и т. д.
На следующей иллюстрации показаны изображения, полученные с помощью
генеративных сетей. Изображение взято из статьи “Plug and Play Generative
Networks”1, опубликованной Аном Нгуеном и его коллегами:
Иллюстрация 13.1. Изображения, созданные GAN
Генеративные модели обладают огромным творческим потенциалом, но
вместе с тем создают и некоторые этические проблемы. Так, например, генерацией убедительных изображений, видео и текста могут воспользоваться
1
Anh Nguyen, Jeff Clune, Yoshua Bengio, Alexey Dosovitskiy, and Jason Yosinski. “Plug & play
generative networks: Conditional iterative generation of images in latent space.” In Proceedings
of the IEEE conference on computer vision and pattern recognition, pp. 4467–4477. 2017.
460
■
Pythonic AI
злоумышленники в своих преступных целях. В наше время широкого распространения контента, созданного искусственным интеллектом, обществу
приходится решать проблемы подлинности, конфиденциальности и доверия.
Тем не менее несомненно одно — благодаря постоянным исследованиям и достижениям в этой области генеративные модели откроют новые, возможно,
пока еще неизведанные пути создания контента а также способы творческого
развития и инноваций.
Концепция GAN. Обзор
Представьте, что мы хотим создавать реалистичные картины, но мы не художники и не обладаем соответствующими навыками. Поэтому мы нанимаем художника для создания подлинных картин и детектива, который будет
выявлять подделки. Соревнование между ними представляет собой довольно увлекательный процесс, поскольку в ходе противостояния они учатся на
своих ошибках и со временем совершенствуют свои навыки. Художник создает все более качественные картины, чтобы обмануть детектива. Детектив
с каждым разом все лучше отличает настоящие картины от подделок, потому
что учится на предыдущих примерах.
По ходу этого состязания происходит нечто удивительное. Художник достигает такого мастерства в создании хороших картин, что детектив уже не
может определить, настоящие они или поддельные. Художник становится
суперталантливым мастером. Но и детектив не сидел на месте — благодаря
стараниям художника он тоже многому научился. Детектив становится суперумным сыщиком.
Точно так же, по аналогии с двумя противоборствующими сторонами, работает и GAN. В основе GAN лежат два ключевых компонента: сеть-генератор
и сеть-дискриминатор. Задача генератора — создавать синтетические данные, например изображения или текст, а задача дискриминатора — различать реальные данные и сгенерированные образцы. Эти две сети вступают
в конкурентную борьбу, постоянно пытаясь превзойти друг друга. Генератор
формирует синтетические образцы данных, чтобы обмануть дискриминатор
и заставить его классифицировать эти данные как реальные. Но и дискриминатор постепенно улучшает свою способность различать настоящие и поддельные образцы. Благодаря такому состязательному процессу обучения модели GAN со временем выдают все более и более реалистичные результаты.
Как уже упоминалось выше, модель GAN была предложена Яном Гудфеллоу
и его коллегами в качестве инновационного решения проблемы обучения без
Учимся строить модели GAN
■
461
учителя. Они представили концепцию одновременного обучения генератора и дискриминатора как реализацию сценария минимаксной игры. Авторы
продемонстрировали, что сети GAN могут генерировать синтетические образцы, близко напоминающие распределение реальных данных, и при этом
не требуют явной информации о метках (маркерах данных).
GAN отличаются от традиционных генеративных моделей в нескольких аспектах, что дает им уникальные преимущества и возможности. В отличие от
других генеративных моделей, которые опираются на явную вероятностную
модель, GAN неявно изучают распределение данных с помощью состязательного обучения. Это позволяет GAN выявлять сложные закономерности и генерировать более разнообразные результаты. Еще одно ключевое отличие заключается в том, что GAN не требуют явно маркированных данных во время
обучения. В то время как другие модели полагаются на размеченные (маркированные) данные для контролируемого обучения, GAN могут использовать
для обучения немаркированные данные и генерировать образцы, очень похожие на обучающее распределение. По сравнению с другими генеративными
моделями GAN демонстрируют более интерактивный и динамичный процесс
обучения. Генератор и дискриминатор постоянно адаптируются друг к другу
и улучшают свою работу благодаря состязательному взаимодействию, генерируя все более реалистичные результаты.
Архитектура GAN.
Генератор и дискриминатор
Основу сети GAN составляют генератор и дискриминатор, которые работают совместно, генерируя реалистичные синтетические данные и различая реальные и сгенерированные образцы. Схема на иллюстрации 13.2 показывает
взаимодействие между генератором и дискриминатором:
Иллюстрация 13.2. Архитектура GAN
462
■
Pythonic AI
Генератор
Сеть генератора играет важную роль в архитектуре GAN, так как именно она
генерирует синтетические данные, имитирующие распределение реальных
данных. Она принимает на вход вектор случайного шума, часто взятый из
простого распределения, такого как гауссовское или равномерное, и преобразует его в сложный выходной сигнал, напоминающий реальные данные.
Сеть генератора обычно состоит из нескольких слоев и включает в себя полносвязные слои, сверточные слои (для данных в виде изображений) или рекуррентные слои (для последовательных данных). Каждый слой постепенно
преобразует вектор входного шума, улавливая закономерности и структуры
в реальном распределении данных. Чтобы выходные данные попадали в определенный диапазон, на последнем слое сети генератора обычно используется функция активации, например сигмоидальная или функция гиперболического тангенса.
Выбор архитектуры генератора зависит от характера данных и желаемого результата. Часто для увеличения размерности и генерации высокоразмерных
данных, таких как изображения, используются сверточные слои или операции апсемплинга.
Дискриминатор
Сеть дискриминатора работает как бинарный классификатор, различая реальные и сгенерированные образцы. Ее основная задача — правильно классифицировать входные данные и обеспечить обратную связь с генератором
для улучшения результатов его работы.
Как и генератор, сеть дискриминатора состоит из нескольких слоев. Обычно для изображений используются сверточные сети, выявляющие локальные
шаблоны и особенности, тогда как для других типов данных могут использоваться полносвязные слои.
Сеть дискриминатора выдает одно скалярное значение, представляющее вероятность того, что входной образец является настоящим. Для сжатия конечного результата до допустимого диапазона вероятностей используются
функции активации, такие как сигмоидальная или softmax.
Архитектура сетей генератора и дискриминатора может существенно влиять
на производительность и качество модели GAN. Перечислим некоторые ключевые архитектурные факторы и решения:
Учимся строить модели GAN
■
463
• Глубина и сложность. Глубокие сети с несколькими слоями позволяют
лучше выявлять сложные закономерности и структуры данных. Однако чрезмерная глубина сетей может стать причиной трудностей в обучении и появления таких проблем, как проблема исчезающего градиента и схлопывание мод (mode collapse). Очень важно при этом найти
правильный баланс.
• Размер сети. Пропускная способность сети зависит от количества
нейронов или фильтров в каждом слое. Крупная сеть потенциально
способна улавливать самые тонкие детали, но с большей вероятностью может переобучиться. Для снижения вероятности переобучения
применяют такие методы регуляризации, как дропаут или пакетная
нормализация.
• Функции активации. Выбор функций активации влияет на диапазон
и нелинейность сети. Обычно в качестве функции активации выбирают ReLU (Rectified Linear Unit) для скрытых слоев и сигмоидальную
или функцию гиперболического тангенса для выходных слоев. Также
достойны внимания такие недавно представленные функции активации, как LeakyReLU или GELU.
• Регуляризация и нормализация. Такие методы и приемы, как дропаут, пакетная или спектральная нормализация повышают стабильность
обучения и предотвращают схлопывание мод.
Решающее значение для успешной работы GAN имеет выбор обучающих
данных и входных представлений. Чтобы генератор выявил желательные
закономерности (паттерны) и структуры, обучающие данные должны быть
репрезентативными для целевого распределения. Для повышения качества
и разнообразия данных можно применить такие этапы предварительной обработки, как нормализация или аугментация данных. Для входных представлений в качестве отправной точки генератора обычно используются векторы
случайного шума. Они создаются на основе простого распределения, например гауссовского или равномерного. Размер и размерность вектора шума зависят от сложности целевых данных и желательного результата.
Обучение GAN. Состязательное обучение
Основной принцип работы GAN заключается в уникальном и динамичном
процессе, называемом «состязательным обучением». Этот процесс обучения
GAN можно описать как минимаксную игру между генератором и дискриминатором. Генератор стремится минимизировать способность дискриминатора
464
■
Pythonic AI
отличать реальные образцы от сгенерированных, а дискриминатор стремится максимизировать свою точность классификации. Изначально обе сети
инициализируются случайным образом, и обучение начинается с того, что
генератор создает синтетические образцы, а дискриминатор их классифицирует. Затем с помощью градиентов обратной связи дискриминатора параметры обновляются, благодаря чему улучшается способность дискриминатора
различать реальные и сгенерированные образцы.
Далее генератор создает новые образцы на основе обновленных параметров,
а дискриминатор обеспечивает дальнейшую обратную связь. Теперь на основе градиентов дискриминатора обновляются параметры генератора, благодаря чему улучшается его способность генерировать более реалистичные
образцы. Этот итерационный процесс обновления генератора и дискриминатора продолжается до так называемой сходимости (convergence), когда генератор выдает результаты, которые дискриминатор не может отличить от
реальных данных, из-за чего эффективность классификации приближается
к случайному угадыванию.
При обучении GAN широко применяется такая функция потерь, как бинарная кросс-энтропия (BCE, Binary cross-entropy) (бинарная перекрестная
энтропия). Она измеряет разницу между предсказанными вероятностями
и истинными метками (реальными или сгенерированными). BCE побуждает
генератор выдавать результаты, схожие с реальными образцами распределения, а дискриминатор — точно классифицировать реальные и сгенерированные образцы. При обучении GAN пытаются найти тонкий баланс между
эффективностью генератора и эффективностью дискриминатора. Если вперед вырвется генератор, то он будет создавать образцы, слишком похожие на
обучающие данные. И наоборот, если слишком мощным станет дискриминатор, то он начнет с легкостью отличать реальные образцы от сгенерированных, и генератору будет труднее совершенствоваться.
Обучение GAN осложняется некоторыми факторами, препятствующими
стабильности и сходимости. Среди них стоит упомянуть следующие:
• Схлопывание мод. Под схлопыванием мод подразумевается явление,
когда генератор не может передать все разнообразие распределения
реальных данных и выдает только ограниченные их вариации, что
приводит к выдаче повторяющихся результатов. Предотвратить схлопывание мод помогают такие методы, как дискриминация мини-пакетов (mini-batch discrimination) и изменение архитектуры.
• Исчезающие градиенты. В процессе обучения GAN может проявиться так называемая проблема исчезающих градиентов, когда градиенты
Учимся строить модели GAN
■
465
становятся настолько маленькими, что модель перестает обучаться.
Эту проблему решают с помощью альтернативных стратегий обучения, инициализации весов или градиентного штрафа.
• Нестабильность обучения. Процесс обучения GAN может стать нестабильным, когда генератор и дискриминатор постоянно демонстрируют
разные уровни производительности. Стабилизировать процесс обучения помогают такие методы, как сопоставление признаков, дискриминация мини-пакетов и спектральная нормализация.
• Чувствительность к гиперпараметрам. GAN чувствительны к выбору
гиперпараметров, таких как скорость обучения, размер пакетов и тип
регуляризации. Стабильность обучения во многом зависит от тщательной настройки этих гиперпараметров.
Построение GAN-модели
В этом разделе мы построим модель Глубокой сверточной генеративносостязательной сети (DCGAN, Deep Convolutional Generative Adversarial
Network) с помощью библиотеки TensorFlow 2. DCGAN1 — это особый тип
архитектуры GAN, в которой как для генератора, так и для дискриминатора
используются глубокие сверточные сети. DCGAN решают некоторые проблемы более ранних архитектур GAN, такие как схлопывание мод и нестабильное обучение. Ниже перечислены основные характеристики DCGAN:
• Сверточная архитектура. В модели DCGAN полносвязные слои в сетях генератора и дискриминатора заменены на сверточные. Сверточные
слои позволяют сетям эффективно обрабатывать данные изображений, выявлять локальные признаки и пространственные отношения.
• Шаговые свертки и транспонированные свертки. Слои пулинга
в DCGAN заменены на два типа сверточных слоев. Шаговые свертки
используются в дискриминаторе для понижения дискретизации карт
признаков, что позволяет дискриминатору выявлять высокоуровневые
представления изображений. В главе 4 «Проектирование CNN с помощью TensorFlow» мы познакомились с понятием шага. В шаговой свертке значение шага, применяемое во время операции свертки, больше
единицы. Транспонированные свертки (также иногда называемые
свертками с дробным шагом или ошибочно называемые «деконволюциями») используются в генераторе для повышения дискретизации
(«апсемплинга») вектора шума, чтобы на выходе получались похожие
1
Alec Radford, Luke Metz, and Soumith Chintala. “Unsupervised representation learning with
deep convolutional generative adversarial networks.” arXiv preprint arXiv:1511.06434 (2015).
466
■
Pythonic AI
на изображение данные с более высоким разрешением. Транспонированная свертка — это операция, обратная обычной свертке. Для транспонированной свертки устанавливается значение шага, равное 1.
• Пакетная нормализация. В DCGAN широко используется пакетная
нормализация как в сети генератора, так и в сети дискриминатора.
Пакетная нормализация помогает стабилизировать процесс обучения,
нормализуя входы слоев, уменьшая внутренние ковариативные сдвиги
и ускоряя сходимость.
• Функции активации. В дискриминаторе DCGAN обычно используются функции активации, такие как LeakyReLU, помогающие устранить
проблему исчезающего градиента и вносящие нелинейность. В генераторе часто используют функцию активации ReLU, за исключением
выходного слоя, где применяется функция гиперболического тангенса
tanh. В TensorFlow функция LeakyReLU доступна как слой tf.keras.
layers.LeakyReLU.
Примечание. В главе 3 «Создание нашей первой модели нейронной
сети» мы познакомились с функцией активации ReLU. Функция активации Leaky ReLU — это модифицированная версия ReLU. В то время
как функция ReLU обнуляет все отрицательные значения, в функции
Leaky ReLU для отрицательных входных значений вводится небольшая
«утечка», или наклон графика, благодаря чему на выходе получается
небольшое ненулевое значение. Математически эта функция задается
следующим образом:
LeakyReLU(x) = max(a * x, x)
Где a — это небольшая положительная константа, определяющая наклон функции для отрицательных входных значений.
Если на входе нейрон ReLU получает отрицательное значение, его градиент становится равным нулю, из-за чего веса не обновляются, и такая ситуация может привести к «гибели» нейрона. Небольшой наклон
функции Leaky ReLU позволяет сети продолжать обучение и обновлять
соответствующие веса. Благодаря этому повышается стабильность
обучения, а отрицательные признаки, как и положительные, получают
свое представление.
Комбинация сверточных слоев, шаговых сверток, транспонированных сверток и пакетной нормализации в модели DCGAN позволяет эффективно
Учимся строить модели GAN
■
467
генерировать реалистичные изображения с тщательной детализацией и пространственной согласованностью. Модели DCGAN получили широкое распространение и продемонстрировали впечатляющую производительность в различных задачах синтеза изображений, включая генерацию новых изображений,
повышение их разрешения и преобразование одних изображений в другие.
Перед тем как построить DCGAN, импортируем необходимые библиотеки
с помощью следующих команд:
1. import numpy as np
2. import matplotlib.pyplot as plt
3. import tensorflow as tf
Для построения DCGAN мы воспользуемся набором данных Fashion MNIST1 —
эталонным набором данных в области машинного обучения и компьютерного
зрения. В главе 3 «Создание нашей первой модели нейронной сети» мы воспользовались оригинальным набором данных MNIST, состоящим из черно-белых
изображений рукописных цифр (0-9). Набор данных Fashion MNIST включает
в себя черно-белые изображения десяти различных типов предметов одежды
и модных аксессуаров. Всего в нем 60 000 обучающих и 10 000 тестовых изображений, каждое из которых представляет собой квадрат размером 28×28 пикселей. Изображения разделены на различные категории, такие как «футболки/
топы», «брюки», «свитеры», «платья», «пальто», «сандалии», «рубашки», «кроссовки», «сумки» и «ботинки». Каждая категория содержит равное количество
изображений, что позволяет получить сбалансированный набор данных.
Загрузить набор данных Fashion MNIST из библиотеки TensorFlow можно
с помощью функции load_data(). Эта функция загружает данные и возвращает обучающий и тестовый наборы данных в виде кортежей массивов
NumPy. Ниже показана соответствующая строка кода:
1. (train_images, train_labels), (test_images, test_labels) =
tf.keras. datasets.fashion_mnist.load_data()
Так как для генерации изображений с помощью GAN мы будем использовать
только обучающие изображения, то метки и тестовый набор нам не нужны.
Их можно удалить следующим образом:
1. del train_labels, test_images, test_labels
1
Han Xiao, Kashif Rasul, and Roland Vollgraf. “Fashion-mnist: a novel image dataset for
benchmarking machine learning algorithms.” arXiv preprint arXiv:1708.07747 (2017).
468
■
Pythonic AI
Проверив форму массива NumPy под названием train_images, увидим, что
в нем содержится 60 000 изображений размером 28×28, как показано на иллюстрации 13.3:
Иллюстрация 13.3. Форма изображений
Так как изображения представлены в черно-белом формате, то каждое из них
имеет один канал. Как показано выше, информация о размерности канала
отсутствует. Добавим дополнительное измерение для канала, как показано
на иллюстрации 13.4:
Иллюстрация 13.4. Размерность (форма) изображений после добавления измерения
Выше мы добавили дополнительное измерение в конец массива NumPy
с помощью функции expand_dims(). Заодно мы преобразовали тип данных в float. Новая форма массива теперь показывает и размерность канала.
В оригинальной статье о модели DCGAN авторы не применяли никакой предварительной обработки к обучающим изображениями, за исключением приведения значений к диапазону [-1, 1] с помощью функции активации tanh.
Нормализуем значения пикселей, приведя их к тому же диапазону от -1 до 1,
как показано в следующей строке кода:
1. train_images = (train_images - 127.5) / 127.5
Создадим набор данных из обучающей выборки изображений, как показано
в следующем фрагменте кода:
1. BUFFER_SIZE = 60000
2. BATCH_SIZE = 128
3. train_dataset = tf.data.Dataset.from_tensor_slices(train_images).
shuffle(BUFFER_SIZE).batch(BATCH_SIZE).prefetch(1)
Учимся строить модели GAN
■
469
Теперь зададим архитектуру генератора нашей модели GAN. Прежде чем составить код, рассмотрим схему генератора DCGAN, представленную на изображении 13.5, которое взято из оригинальной статьи о DCGAN:
Иллюстрация 13.5. Архитектура DCGAN
Создадим аналогичную архитектуру для генератора, обрабатывающего данные Fashion MNIST. Для этого напишем функцию create_generator(), как
показано в следующем блоке кода:
1. def create_generator():
2.
inputs = tf.keras.Input(shape=(100,))
3.
x = tf.keras.layers.Dense(7*7*256, use_bias=False)(inputs)
4.
x = tf.keras.layers.BatchNormalization()(x)
5.
x = tf.keras.layers.ReLU()(x)
6.
x = tf.keras.layers.Reshape((7, 7, 256))(x)
7.
8.
#block 1
9.
x = tf.keras.layers.Conv2DTranspose(128, kernel_size=(5, 5),
strides=(1, 1), padding='same', use_bias=False)(x)
10.
x = tf.keras.layers.BatchNormalization()(x)
11.
x = tf.keras.layers.ReLU()(x)
12.
13.
#block 2
14.
x = tf.keras.layers.Conv2DTranspose(64, kernel_size=(5,
5),strides=(2, 2), padding='same', use_bias=False)(x)
15.
x = tf.keras.layers.BatchNormalization()(x)
470
16.
■
Pythonic AI
x = tf.keras.layers.ReLU()(x)
17.
18.
#block 3
19.
outputs = tf.keras.layers.Conv2DTranspose(1, kernel_size=(5,
5), strides=(2, 2), padding='same', use_bias=False,
activation='tanh')(x)
20.
model = tf.keras.Model(inputs=inputs, outputs=outputs)
21.
return model
Чтобы понять, как работает написанный выше код, рассмотрим его от конца
к началу. Предполагается, что этот генератор будет создавать изображения,
имеющие форму (28×28×1) на выходе. Как показано в блоке выше, в строке 19
мы задали один фильтр (ядро). Количество ядер определяет количество каналов. Операция Conv2DTranspose с шагом 2 удваивает оба размера (высоту
и ширину) массива. В блоке 3 с помощью операции Conv2DTranspose с шагом
2 генерируется массив размером 28×28, а для этого блок 2 должен сгенерировать массивы, высота и ширина которых равны 14. Аналогичным образом
блок 1 должен генерировать массивы, высота и ширина которых равны 7.
Поскольку в блоке 1 мы не использовали шаг 2 (строка номер 9), размерность
в нем не удваивается. Следовательно, размерность входных данных остается
7×7, как показано в строке номер 3. На входе, в блоке 1 и в блоке 2, используется соответственно 256, 128 и 64 ядер, согласно схеме на иллюстрации 13.5
выше. Следует отметить, что во всех слоях используется функция активации
ReLU, кроме последнего, где применяется функция tanh.
Модель генератора принимает вектор шума размером 100. Затем он передается на полносвязный слой из 7*7*256=12 544 нейронов, где генерируется массив такой же формы (строка номер 3). После пакетной нормализации
и операций ReLU массив приобретает размерность (7×7×256), что в итоге
с помощью операций апсемплинга позволяет генерировать изображения
размером (28×28×1).
Теперь определим функцию для создания подаваемого в генератор вектора
шума, как показано в следующем фрагменте кода:
1. def generate_noise(batch_size, noise_dimension):
2.
noise = tf.random.normal([batch_size, noise_dimension])
3.
return noise
Учимся строить модели GAN
■
471
В показанном выше фрагменте кода используется функция tf.random.
normal(), генерирующая случайные значения типа float из нормального
распределения. По умолчанию нормальное распределение имеет среднее значение 0 и стандартное отклонение 1.
Можно указать этой функции создать вектор шума с размером пакета 1 и размерностью 10. Выходные данные показаны на иллюстрации 13.6:
Иллюстрация 13.6. Сгенерированный шум
Теперь создадим модель генератора с помощью определенной выше функции
create_generator(). Также сформируем вектор случайного шума и подадим
его в генератор для создания изображения. Рассмотрим следующий фрагмент
кода:
1. generator = create_generator()
2.
3. noise = generate_noise(1, 100)
4. gen_image = generator(noise, training=False)
5.
6. plt.imshow(gen_image[0], cmap='gray')
Как показано выше, мы создали модель генератора и случайный шум. Шум
представляет собой один вектор длиной 100. В строке номер 4 мы передали
полученный вектор шума на вход модели генератора и получили сгенерированное изображение. Аргументу training мы задали значение False, указывающее на то, что модель не должна обновлять свои обучаемые параметры на
этом шаге вывода. Для отображения картинки мы воспользовались функцией imshow() из библиотеки matplotlib, а параметр cmap='gray' указывает на
то, что для отображения выбрана цветовая карта в оттенках серого. Сгенерированное на основе полностью шумовых данных изображение показано на
иллюстрации 13.7 ниже:
472
■
Pythonic AI
Иллюстрация 13.7. Изображение, сгенерированное из шума
Теперь зададим модель дискриминатора, представляющую собой бинарный
классификатор. Он будет принимать на вход изображение и классифицировать его как поддельное или настоящее. Ниже показан блок кода функции
create_discriminator():
1. def create_discriminator():
2.
inputs = tf.keras.Input(shape=(28,28,1))
3.
x = tf.keras.layers.Conv2D(64, (5, 5), strides=(2,
2),padding='same')(inputs)
4.
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
5.
x = tf.keras.layers.Dropout(0.4)(x)
6.
7.
x = tf.keras.layers.Conv2D(128, (5, 5), strides=(2, 2),
padding='same')
(x)
8.
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
9.
x = tf.keras.layers.Dropout(0.4)(x)
10.
11.
x = tf.keras.layers.Conv2D(256, (5, 5), strides=(2, 2),
padding='same')(x)
12.
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
Учимся строить модели GAN
13.
■
473
x = tf.keras.layers.Dropout(0.4)(x)
14.
15.
x = tf.keras.layers.Flatten()(x)
16.
outputs = tf.keras.layers.Dense(1)(x)
17.
model = tf.keras.Model(inputs=inputs, outputs=outputs)
18.
return model
Как показано выше, для извлечения признаков из данных изображения дискриминатор использует сверточные слои. Затем он использует плотный слой
с одним нейроном для выполнения бинарной классификации. Отметим, что
после каждого сверточного слоя в этой модели дискриминатора используется
функция активации LeakyReLU с коэффициентом 0,2 наклона отрицательной
части, как и описывалось в оригинальной статье про DCGAN.
Теперь для проверки можно передать изображение, созданное генератором
целиком из шумовых данных, в дискриминатор, который постарается его
классифицировать. Сделаем это с помощью следующего фрагмента кода:
1. discriminator = create_discriminator()
2. prediction = discriminator(generated_image)
Результат показан на иллюстрации 13.8:
Иллюстрация 13.8. Результат предсказания
Теперь нужно определить для модели GAN функцию потерь. В этой модели GAN дискриминатор выполняет бинарную классификацию. Поэтому мы
применим функцию бинарной кросс-энтропии в виде модуля tf.keras.
losses.BinaryCrossentropy. Определим функцию для вычисления потерь,
как показано в следующем фрагменте кода:
1. def bce_loss(y_true, y_pred):
2.
loss_func=tf.keras.losses.BinaryCrossentropy(from_logits=True)
3.
return loss_func(y_true, y_pred)
474
■
Pythonic AI
Как показано выше, функция bce_loss() принимает в качестве входных данных аргументы y_true и y_pred. В последнем слое модели дискриминатора
мы не использовали сигмоидальную функцию активации, поэтому параметру from_logits в BinaryCrossentropy мы задали значение True.
Определим оптимизаторы для генератора и дискриминатора. Как предлагалось в оригинальной статье про DCGAN, воспользуемся оптимизатором Adam
с шагом обучения 0,0002 и экспоненциальной скоростью затухания для оценок
первого момента равной 0,5. Соответствующий фрагмент кода показан ниже:
1. generator_optimizer = tf.keras.optimizers.Adam(learning_rate=2e-4,
beta_1=0.5)
2. discriminator_optimizer = tf.keras.optimizers.Adam(learning_
rate=2e-4, beta_1=0.5)
Перед началом обучения зададим некоторые начальные параметры, как показано в следующих строках кода:
1. EPOCHS = 50
2. noise_dimension = 100
3. num_images_to_generate = 20
4.
5. seed = generate_noise(num_images_to_generate, noise_dimension)
Таким образом мы сгенерируем 20 изображений после 50 эпох обучения. Для генерации изображений мы передадим вектор шума с именем seed. Далее показаны
строки кода процесса обучения, объяснения которых будут приведены ниже:
1. for epoch in range(EPOCHS):
2.
print(epoch)
3.
for real_image_batch in train_dataset:
4.
noise = tf.random.normal([BATCH_SIZE, noise_dimension])
5.
6.
with tf.GradientTape() as gen_tape, tf.GradientTape() as
disc_tape:
7.
generated_images = generator(noise, training=True)
8.
9.
real_preds = discriminator(real_image_batch,
training=True)
Учимся строить модели GAN
10.
■
475
fake_preds = discriminator(generated_images,
training=True)
11.
12.
real_and_fake_preds = tf.concat([real_preds,
fake_preds],axis = 0) 13.
14.
y_true = tf.constant([[1.]] * len(fake_preds))
15.
generator_loss = bce_loss(y_true, fake_preds)
16.
17.
y_true = tf.constant([[1.]] * len(real_preds) +
[[0.]] * len(fake_preds))
18.
discriminator_loss = bce_loss(y_true, real_and_
fake_preds)
19.
20.
gradients_generator = gen_tape.gradient(generator_
loss,generator.trainable_variables)
21.
gradients_discriminator = disc_tape.
gradient(discriminator_loss, discriminator.trainable_
variables)
22.
23.
generator_optimizer.apply_gradients(zip(gradients_
generator, generator.trainable_variables))
24.
discriminator_optimizer.apply_gradients(zip(gradients_
discriminator, discriminator.trainable_variables)) 25.
26.
predictions = generator(seed, training=False)
27.
28.
predictions = generator(seed, training=False)
Обучение проводится в течение заданного количества эпох. Обучающие
данные итерируются пакетами. Для каждого пакета с помощью tf.random.
normal() генерируется случайный шум, имеющий форму размера пакета
и определенной заранее размерности данных.
В строке номер 6 мы создали два контекста GradientTape: один для модели генератора, другой — для модели дискриминатора. Эти контексты будут записывать операции для последующего вычисления градиента во время обучения.
В строке номер 7 мы вызвали модель генератора с шумом в качестве входных
данных и создали пакет сгенерированных изображений. Затем мы дважды
вызвали модель дискриминатора: один раз с реальными изображениями из
476
■
Pythonic AI
обучающей партии и один раз со сгенерированными изображениями. При
этом были вычислены предсказания дискриминатора для обоих типов изображений.
В строке номер 12 мы объединили предсказания дискриминатора для реальных и поддельных изображений, создав тем самым комбинированный тензор.
Нам нужно было вычислить функцию потерь на полученных предсказаниях.
Будучи классификатором, дискриминатор определяет реальные изображения
как 1, а поддельные — как 0. В идеале генератор должен создавать поддельные
изображения, похожие на реальные, которым дискриминатор присваивал бы
значения 1, то есть определял бы их как настоящие. Задача функции потерь
генератора — уменьшить разницу между значениями предсказаний для подделок и единицами, поскольку 1 — это значение для реальных изображений.
Дискриминатор объединяет потери для реальных изображений и потери для
фальшивых изображений, пытаясь увеличить вероятность оценки реальных
изображений в виде единиц и поддельных в виде нулей.
Мы вычислили функцию потерь бинарной кросс-энтропии для генератора
и дискриминатора с помощью функции bce_loss(). После вычисления потерь мы в строках номер 20 и 21 рассчитали градиенты потерь генератора
и дискриминатора относительно их обучаемых переменных (весов) с помощью соответствующих градиентных лент. Эти вычисленные градиенты использовались для обновления обучаемых переменных модели генератора
и дискриминатора, когда градиенты применялись с соответствующими оптимизаторами.
После завершения обработки пакетных данных мы использовали генератор
для создания новых изображений. Для этого в строке номер 26 мы вызвали
модель генератора с заданной ранее переменной seed. Этот шаг выполняется
с параметром training=False, чтобы на генератор не повлияли никакие обновления в процессе обучения. Шаг этот необязателен и нужен только для
того, чтобы распечатать или сохранить выход генератора на этой стадии для
последующей проверки качества изображений в ходе циклов обучения.
Наконец, мы сохранили созданные генератором изображения в переменной
predictions, представляющей выход GAN после обучения.
Вывести на экран сгенерированные изображения можно с помощью следующего блока кода:
1. fig = plt.figure(figsize=(4, 5))
2. for i in range(predictions.shape[0]):
3. plt.subplot(4, 5, i+1)
Учимся строить модели GAN
■
477
4. image = predictions[i,:,:,0] * 127.5 + 127.5
5. plt.imshow(image, cmap='gray')
6. plt.axis('off')
7. plt.show()
Ранее мы нормализовали значения пикселей. В строке номер 4 мы денормализуем сгенерированные изображения перед тем, как выводить их на экран.
Полученные изображения показаны на иллюстрации 13.9:
Иллюстрация 13.9. Сгенерированные изображения
Все показанные на иллюстрации выше изображения сгенерированы моделью
DCGAN и не существуют в реальном наборе данных.
Вариационный автокодировщик
VAE1 — это класс генеративных моделей, которые привлекли особое внимание в области глубокого обучения. Это довольно эффективная структура выявления латентных (скрытых) представлений сложных распределений дан1
Diederik P. Kingma and Max Welling. “Auto-encoding variational bayes.” arXiv preprint
arXiv:1312.6114 (2013).
478
■
Pythonic AI
ных, и модели этого типа нашли применение в различных задачах, включая
генерацию изображений, обнаружение аномалий и сжатие данных.
Прежде чем рассматривать VAE, разберем сначала принцип действия традиционных автокодировщиков. Автокодировщик (автоэнкодер) — это нейронная сеть, обучающаяся сжатому представлению входных данных, как правило, с помощью архитектуры «кодировщик-декодировщик». Кодер отображает
входные данные в низкоразмерное латентное (скрытое) пространство, а декодер восстанавливает исходные данные из этого латентного представления.
Автокодировщики в основном используются для уменьшения размерности
и для сжатия данных, а также для удаления шума и обнаружения аномалий.
Рассмотрим схему на иллюстрации 13.10:
Иллюстрация 13.10. Архитектура VAE
Традиционные автокодировщики эффективны в обучении полезным латентным представлениям, но им не хватает контроля над процессом генерации.
Другими словами, они не предназначены для моделирования распределения
вероятностей, лежащего в основе данных. Такое ограничение затрудняет генерацию новых образцов или выполнение значимой интерполяции в латентном пространстве.
VAE устраняет это ограничение, вводя в структуру автокодировщика вероятностное моделирование. VAE моделирует латентное пространство как распределение вероятностей и тем самым представляет собой мощный инструмент для генерации новых образцов данных из того же распределения, что
и обучающие данные. Модели VAE нашли множество применений в различных областях:
• VAE могут генерировать новые образцы посредством выборки из латентного пространства. Эта способность особенно полезна для создания реалистичных изображений, музыки или текста.
• VAE могут обучаться распределению нормальных данных, что позволяет им выявлять аномальные образцы, которые значительно отклоняются от этого распределения.
Учимся строить модели GAN
■
479
• Обучаясь на неполных данных и восстанавливая недостающие части,
VAE могут заполнять отсутствующие значения в наборах данных, что
делает их ценными для задач предварительной обработки данных.
• VAE могут обучаться общему латентному представлению из разных
областей, что позволяет передавать знания между областями и облегчает такие задачи, как передача стиля изображения.
Архитектура
Архитектура VAE состоит из кодировщика (кодера), декодировщика (декодера) и функции потерь.
Рассмотрим каждый компонент подробнее:
• Кодировщик. Кодировщик принимает входные данные и отображает их в латентное пространство. Обычно он состоит из нескольких
скрытых слоев, постепенно уменьшающих размеры входных данных
до достижения желаемого размера латентного пространства. Данные
изображений можно обрабатывать сверточными слоями. На выходе
кодировщик генерирует среднее значение и дисперсию многомерного
гауссовского (нормального) распределения, описывающие латентное
представление.
• Декодировщик. Декодировщик берет выборку из латентного пространства и отображает ее обратно в исходное пространство данных.
Как и кодировщик, он обычно состоит из нескольких слоев, размеры
которых постепенно увеличиваются, пока не сравняются с исходными
входными размерами. Для изображений можно использоваться транспонированные свертки, выполняющие апсемплинг. На выходе декодер
выдает восстановленную версию входных данных.
• Функция потерь. Функция потерь в VAE состоит из двух компонентов:
вычисления потерь при восстановлении и регуляризирующего члена.
Вычислитель потерь при восстановлении измеряет разницу между
входом и выходом декодировщика, поощряя точность восстановления (реконструкции). Регуляризирующий член нужен для того, чтобы
латентное пространство соответствовало желаемому распределению
(обычно нормальному гауссовскому), он штрафует отклонения от этого распределения.
VAE обеспечивают строгую вероятностную основу для генерации данных,
хотя это иногда приводит к созданию размытых образцов.
480
■
Pythonic AI
Обучение вариационного
автокодировщика
Обучение VAE заключается в оптимизации параметров кодировщика и декодировщика для точного восстановления входных данных и обеспечения соответствия латентного пространства желаемому распределению вероятностей. Процесс обучения можно кратко описать следующим образом:
1. Выборка. На основе входного образца выбирается латентный вектор
из распределения, определяемого выходами кодировщика (средним
значением и дисперсией).
2. Восстановление (реконструкция). Отобранный латентный вектор
поступает в декодировщик, восстанавливающий входные данные.
3. Вычисление потерь. Потери при восстановлении реконструкции вычисляются посредством сравнения выхода декодировщика с исходным
входом. Кроме того, член регуляризации накладывает штраф на отклонение от желаемого распределения.
4. Обратное распространение. Вычисляются градиенты функции потерь относительно параметров модели, и веса кодировщика и декодировщика обновляются с помощью алгоритма оптимизации, такого как
Adam.
Процесс обучения продолжается в течение нескольких итераций или эпох,
пока модель не сойдется, в результате чего создается пара кодировщик-декодировщик, способная генерировать значимые латентные представления
и производить высококачественное восстановление.
Построение модели вариационного
автокодировщика
В этом разделе мы построим модель VAE с помощью библиотеки TensorFlow 2.
Для начала загрузим данные Fashion MNIST, предварительно обработаем их
и создадим из них набор данных так же, как мы делали это ранее для DCGAN.
Вот соответствующие строки кода:
1. import numpy as np
2. import matplotlib.pyplot as plt
3. import tensorflow as tf 4.
Учимся строить модели GAN
■
481
5. train_images, train_labels), (test_images, test_labels) =
tf.keras.datasets.fashion_mnist.load_data()
6.
7. train_images = np.expand_dims(train_images, -1).astype('float32')
8. test_images = np.expand_dims(test_images[:20], -1).
astype('float32')
9.
10. train_images = train_images/255.0
11. test_images = test_images/255.0
12.
13. BUFFER_SIZE = 60000
14. BATCH_SIZE = 128
15. train_dataset = tf.data.Dataset.from_tensor_slices(train_images).
shuffle(BUFFER_SIZE).batch(BATCH_SIZE).prefetch(1)
Как показано выше, на этот раз мы сохраняем тестовые изображения, чтобы позже использовать их в качестве входных данных для восстановления.
В строке номер 8 мы сохранили только 20 образцов из тестовых данных,
которые будем восстанавливать. Также можно заметить, что, в отличие от
DCGAN, здесь мы выполнили нормализацию посредством деления значений
пикселей на 255, поскольку в VAE мы не будем использовать функцию активации tanh.
Кодировщик задается следующим блоком кода:
1. latent_space_dim = 16
2. def create_encoder():
3.
inputs = tf.keras.layers.Input(shape=(28,28,1))
4.
5.
x = tf.keras.layers.Conv2D(32, (3,3), padding="same")(inputs)
6.
x = tf.keras.layers.BatchNormalization()(x)
7.
x = tf.keras.layers.LeakyReLU()(x)
8.
9.
x = tf.keras.layers.Conv2D(64, (3,3), padding="same", strides=2)(x)
10.
x = tf.keras.layers.BatchNormalization()(x)
11.
x = tf.keras.layers.LeakyReLU()(x)
12.
482
■
Pythonic AI
13.
x = tf.keras.layers.Conv2D(64, (3,3), padding="same", strides=2)
(x)
14.
x = tf.keras.layers.BatchNormalization()(x)
15.
x = tf.keras.layers.LeakyReLU()(x)
16.
17.
x = tf.keras.layers.Conv2D(64, (3,3), padding="same")(x)
18.
x = tf.keras.layers.BatchNormalization()(x)
19.
x = tf.keras.layers.LeakyReLU()(x)
20.
21.
x = tf.keras.layers.Flatten()(x)
22.
23.
mean = tf.keras.layers.Dense(units=latent_space_dim)(x)
24.
log_var = tf.keras.layers.Dense(units=latent_space_dim)(x)
25.
26.
def sampling(mean_log_var):
27.
mean, log_var = mean_log_var
28.
epsilon = tf.random.normal(shape=tf.shape(mean))
29.
random_sample = mean + tf.math.exp(log_var/2) * epsilon
30.
return random_sample
31.
32.
outputs = tf.keras.layers.Lambda(sampling)([mean, log_var])
33.
34.
model = tf.keras.Model(inputs=inputs, outputs=[mean, log_
var,outputs])
35.
return model
Как показано выше, в строке номер 1 мы сначала определили размерность
латентного пространства. Кодировщик представляет собой типичную сверточную нейронную сеть (CNN) с тем отличием, что на выходе выполняется
операция выборки. Мы развернули выход последнего сверточного слоя с помощью функции tf.keras.layers.Flatten() и преобразовали двумерные
карты признаков в одномерный вектор. Затем мы добавили два полносвязных слоя для оценки среднего значения и логарифмической дисперсии распределения латентного пространства.
Далее мы определили функцию выборки для выполнения операции выборки,
необходимой для трюка с повторной параметризацей в VAE. Она в качестве
Учимся строить модели GAN
■
483
входных данных принимает среднее значение и логарифмическую дисперсию
и возвращает случайную выборку из латентного пространства.
Эта выборка строится на основе случайного значения из нормального распределения со средним значением 0 и дисперсией 1, которое умножается на
экспоненту половины логарифмической дисперсии и прибавляется к среднему значению mean.
Слой выходов создается с помощью функции tf.keras.layers.Lambda(),
которая оборачивает произвольные выражения в объект Layer. Этот слой вызывает функцию выборки с входными значениями mean и log_var. Данный
слой представляет выборочные значения латентного пространства. В качестве выходов модель возвращает среднее значение, логарифмическую дисперсию и результат выборки.
Теперь зададим декодировщик следующим блоком кода:
1. def create_decoder():
2.
inputs = tf.keras.layers.Input(shape=(latent_space_dim))
3.
x = tf.keras.layers.Dense(units=7*7*64)(inputs)
4.
x = tf.keras.layers.Reshape((7,7,64))(x)
5.
x = tf.keras.layers.Conv2DTranspose(64, (3, 3), padding="same")(x)
6.
x = tf.keras.layers.BatchNormalization()(x)
7.
x = tf.keras.layers.LeakyReLU()(x)
8.
9.
x = tf.keras.layers.Conv2DTranspose(64, (3, 3), padding="same",
strides=2)(x)
10.
x = tf.keras.layers.BatchNormalization()(x)
11.
x = tf.keras.layers.LeakyReLU()(x)
12.
13.
x = tf.keras.layers.Conv2DTranspose(64, (3, 3), padding="same",
strides=2)(x)
14.
x = tf.keras.layers.BatchNormalization()(x)
15.
x = tf.keras.layers.LeakyReLU()(x)
16.
17.
x = tf.keras.layers.Conv2DTranspose(1, (3, 3), padding="same")(x)
18.
outputs = tf.keras.layers.LeakyReLU()(x )
19.
model = tf.keras.Model(inputs=inputs, outputs=outputs)
20.
return model
484
■
Pythonic AI
Как показано выше, декодировщик похож на генерирующую часть модели
GAN, рассмотренной ранее. Мы использовали транспонирующие свертки
для апсемплирования латентного вектора в выходные данные, похожие на
изображения с более высоким разрешением.
Теперь можно создать модели кодировщика и декодировщика и определить
для них оптимизаторы, как показано в следующем фрагменте кода:
1. encoder_model = create_encoder()
2. decoder_model = create_decoder()
3. optimizer_enc = tf.keras.optimizers.Adam(learning_rate=5e-4)
4. optimizer_dec = tf.keras.optimizers.Adam(learning_rate=5e-4)
Теперь определим функцию потерь для VAE. Функция потерь VAE представляет собой сумму двух функций потерь: потерь при восстановлении и расхождения (дивергенции) Кульбака-Лейблера (KL, Kullback-Lebler). Потери
при восстановлении — это просто разница между реальными изображениями, поданными на кодировщик, и изображениями, восстановленными декодировщиком. Она используется для увеличения вероятности того, что сгенерированный выходной сигнал будет похож на входные данные. Расхождение
KL измеряет разницу между одним распределением вероятностей и другим.
Расхождение KL равно нулю, если два распределения одинаковы. В данном
случае расхождение KL — это регуляризирующий член, обеспечивающий
приближение латентного распределения к стандартному нормальному распределению. Рассмотрим следующий фрагмент кода:
1. def vae_loss(y_true, y_pred, mean, log_var):
2.
reconstruction_loss = tf.math.reduce_mean(tf.math.reduce_sum(tf.
keras.losses.binary_crossentropy(y_true, y_pred), axis=(1, 2)))
3.
kl_loss = -0.5 * (1 + log_var - tf.math.square(mean) - tf.math.
exp(log_var))
4.
kl_loss = tf.math.reduce_mean(tf.math.reduce_sum(kl_loss,
axis=1))
5.
return (reconstruction_loss + kl_loss)
В этом фрагменте мы вычислили потери при восстановлении, используя
функцию бинарной кросс-энтропии между исходными реальными изображениями (y_true) и восстановленными изображениями (y_pred). Мы поэлементно суммировали бинарные потери кросс-энтропии по измерениям (1, 2),
Учимся строить модели GAN
■
485
которые соответствуют размерам высоты и ширины входных изображений,
и вычислили среднее значение потерь при восстановлении по всей партии.
Затем мы вычислили расхождение KL между выученным латентным распределением и стандартным нормальным распределением. Мы также просуммировали kl_loss по оси 1 и затем усреднили по всей партии. Функция возвращает сумму потерь при восстановлении и kl_loss. Теперь можно начать
процесс обучения, как показано в следующем блоке кода:
1. EPOCHS=50
2. for epoch in range(EPOCHS):
3.
print(epoch)
4.
for image_batch in train_dataset:
5.
with tf.GradientTape() as enc_tape, tf.GradientTape() as
dec_tape:
6.
mean, log_var, enc_outputs = encoder_model(image_
batch)
7.
reconstruction = decoder_model(enc_outputs)
8.
total_loss = vae_loss(image_batch, reconstruction,
mean, log_var) 9.
10.
gradients_enc = enc_tape.gradient(total_loss, encoder_
model.trainable_variables)
11.
gradients_dec = dec_tape.gradient(total_loss, decoder_
model.trainable_variables)
12.
13.
optimizer_enc.apply_gradients(zip(gradients_enc,
encoder_model. trainable_variables))
14.
optimizer_dec.apply_gradients(zip(gradients_dec,
decoder_model. trainable_variables))
Рассмотренный выше процесс очень похож на процесс обучения модели
DCGAN, который мы описывали ранее. В строке номер 6 модель кодировщика в качестве выхода возвращает среднее значение, логарифмическую дисперсию и результат выборки. В строке номер 7 результат выборки, или латентное пространство, передается в модель декодировщика. В данном случае мы
использовали функцию vae_loss().
486
■
Pythonic AI
После завершения обучения можно подать в модель кодировщика тестовые
изображения, чтобы сгенерировать латентное пространство. Затем мы передадим это латентное представление в модель декодировщика, чтобы получить
восстановленные изображения. Рассмотрим следующий фрагмент кода:
1. mean, log_var, enc_outputs = encoder_model(test_images,
training=False)
2. reconst = decoder_model(enc_outputs, training=False)
Вывести на экран восстановленные изображения можно следующим образом:
1. fig = plt.figure(figsize=(4, 4))
2. for i in range(20):
3. ax = fig.add_subplot(5, 4, i+1)
4. ax.axis('off')
5. ax.imshow(reconst[i, :,:,0]*255.0, cmap = 'gray')
Восстановленные изображения показаны на иллюстрации 13.11:
Иллюстрация 13.11. Восстановленные изображения
Учимся строить модели GAN
■
487
Реальные тестовые изображения можно вывести следующим образом:
1. fig = plt.figure(figsize=(4, 4))
2. for i in range(20):
3. ax = fig.add_subplot(5, 4, i+1)
4. ax.axis('off')
5. ax.imshow(test_images[i, :,:,0]*255, cmap = 'gray')
Тестовые изображения показаны на иллюстрации 13.12:
Иллюстрация 13.12. Реальные тестовые изображения
При сравнении двух последних иллюстраций можно заметить одну из общих
проблем моделей VAE, состоящую в том, что они обычно генерируют размытые изображения. Такая нечеткость возникает из-за характера целевой
функции VAE и процесса выборки латентных переменных. Для улучшения
визуального качества генерируемых изображений можно попробовать воспользоваться более глубокими или широкими нейронными сетями или применить такие методы постобработки, как фильтры повышения резкости или
алгоритмы подавления шума, чтобы улучшить визуальное качество генерируемых изображений.
488
■
Pythonic AI
Важно отметить, что несмотря на склонность VAE к генерации размытых
изображений они все же обладают ценными свойствами, такими как способность обучаться интерпретируемым латентным представлениям и выполнять
контролируемые манипуляции с латентным пространством.
Заключение
В этой главе мы познакомились с моделями GAN и рассмотрели все ключевые компоненты и концепции, связанные с этими моделями нейросетей. Также мы узнали о том, как обучать модели GAN для создания реалистичных
и высококачественных изображений.
На протяжении всей главы мы рассматривали два основных компонента
GAN: генератор и дискриминатор. Мы обсудили архитектуры генератора
и дискриминатора, а также изучили функции потерь, играющие решающую
роль в управлении процессом обучения и оптимизации моделей.
Кроме того, мы затронули тему вариационных кодировщиков (VAE), в которых используется вероятностный подход к генеративному моделированию.
VAE объединяют в себе возможности как кодирующих, так и декодирующих
нейронных сетей, благодаря чему выявляют значимые представления входных данных и генерируют новые образцы посредством выборки из латентного (скрытого) пространства.
Построение моделей GAN — захватывающая и быстро развивающаяся область машинного обучения и компьютерного зрения. Несмотря на сохраняющиеся проблемы, GAN продолжают расширять границы генеративного моделирования. Они обладают огромным потенциалом для применения
в искусстве, в сфере развлечений и во многих других областях.
В следующей главе мы подробнее познакомимся с тем, как модели GAN используются для восстановления (реконструкции) изображений.
Основные выводы
• Назначение генеративных моделей — изучение и имитация базового
распределения данных в целях создания новых реалистичных образцов данных.
• GAN и VAE — две популярные генеративные модели, для каждой из
которых характерен свой подход к обучению и генерации данных.
Учимся строить модели GAN
■
489
• GAN состоят из двух нейронных сетей, генератора и дискриминатора,
реализующих сценарий минимаксной игры для двух игроков. Генератор стремится создавать данные, неотличимые от реальных, а дискриминатор пытается отличить сгенерированные данные от настоящих.
• DCGAN (Deep Convolutional GAN) — это вариант моделей GAN со
сверточными сетями как в генераторе, так и дискриминаторе. При реализации DCGAN нужно задать модели генератора и дискриминатора,
функции потерь и методы обучения GAN.
• VAE — это генеративные модели, включающие в себя схемы автокодировщиков и применяющие принцип вероятностного моделирования.
Их задача — обучаться вероятностному отображению данных в латентном (скрытом) пространстве, что позволяет контролировать генерацию и интерполяцию данных. Мы на практическом примере рассмотрели, как разработать модель VAE для восстановления изображений.
Ссылки
• Иан Гудфеллоу, описание GAN (учебное пособие NIPS 2016): https://
www.youtube.com/watch?v=HGYYEUSm-0Q
• Генеративные модели от OpenAI: https://openai.com/research/generativemodels
• Арифметика свертки: https://github.com/vdumoulin/conv_arithmetic
• Официальная документация по слою TensorFlow Conv2DTranspose:
http s : / / w w w. te ns or f l ow. org / api _ d o c s / py t h on / t f / ke r a s / l aye rs /
Conv2DTranspose
• Официальная документация слоя TensorFlow LeakyReLU:
• https://www.tensorflow.org/api_docs/python/tf/keras/layers/LeakyReLU
Глава 14
Генерация
искусственных лиц
с помощью GAN
Введение
За последнее время в сфере искусственного интеллекта были достигнуты
особые успехи в области компьютерного зрения. Одним из направлений,
вызывающих большой интерес и привлекающих повышенное внимание как
исследователей, так и энтузиастов, является генерация искусственных лиц.
Появление генеративно-состязательных сетей (GAN) ознаменовало собой
новый и многообещающий этап создания высокореалистичных и визуально
привлекательных искусственных лиц.
Это направление обладает огромным потенциалом, оно охватывает широкий
спектр практических приложений в таких сферах, как видеоигры, виртуальная реальность, кинопроизводство, системы распознавания лиц и даже взаимодействие человека и компьютера. Класс моделей глубокого обучения GAN
произвел настоящую революцию в области синтеза изображений, позволив
создавать фотореалистичные изображения, практически неотличимые от
реальных. Благодаря этим моделям был сделан огромный шаг вперед, они
помогли преодолеть многие проблемы и ограничения предыдущих моделей
и методов.
В этой главе мы подробно рассмотрим методы и технические приемы, используемые при создании искусственных лиц с помощью GAN. Мы познакомимся с фундаментальными концепциями и основополагающими принципами,
позволяющими создавать убедительные и разнообразные изображения лиц.
Генерация искусственных лиц с помощью GAN
■
491
Кроме того, мы затронем проблемы, возникающие при создании искусственных лиц. Мы рассмотрим разновидность GAN, называемую условной GAN,
и более подробно опишем ее уникальную способность контролируемого синтеза лиц. Цель этой главы — познакомить читателей с тем, что представляет
собой искусственная генерация лиц и описать основные перспективы, расширяющие границы возможного в сфере компьютерного зрения и взаимодействии человека и компьютера.
Структура
В этой главе будут рассмотрены следующие темы:
• Условные генеративно-состязательные сети.
• Архитектура и обучение cGAN.
• Области применения cGAN.
• Набор данных и их структура.
• Построение модели.
• Обучение модели cGAN.
• Генерация и вывод полученных данных.
Цели
В этой главе описывается реализация условной генеративно-состязательной сети (cGAN, conditional Generative Adversarial Network), предназначенной для создания синтетических изображений лиц определенной возрастной
категории. Цель главы — изучить возможности cGAN в генерации реалистичных и разнообразных изображений лиц с условием в виде возрастных
меток.
В главе описывается концепция cGAN и объясняется отличие cGAN от традиционных GAN. Здесь же мы покажем архитектуру условной GAN на основе библиотеки TensorFlow 2 с сетью генератора и сетью дискриминатора.
Мы приведем обоснования выбранной архитектуры и модификаций, помогающих обработать условие в виде возраста. Кроме того, в этой главе мы
рассмотрим процесс обучения cGAN с использованием соответствующих
функций потерь, оптимизаторов и конвейеров данных, а также покажем, как
использовать обученную cGAN для создания искусственных изображений
лиц заданной возрастной категории.
492
■
Pythonic AI
Условные генеративно
состязательные сети
Одним из важных направлений развития моделей GAN являются модели
cGAN1, реализующие концепцию условной генерации. Из главы 13 «Учимся
строить модели GAN» мы узнали, что стандартная GAN состоит из двух основных компонентов: генератора и дискриминатора. Генератор принимает на
вход случайный шум и генерирует синтетические данные, а дискриминатор
должен различать реальные и фальшивые (поддельные, синтетические) данные. Генератор и дискриминатор обучаются одновременно в минимаксной
игре для двух игроков, в которой генератор пытается обмануть дискриминатор, а дискриминатор стремится правильно классифицировать реальные
и поддельные данные.
Модели сGAN расширяют традиционную архитектуру GAN за счет включения в генератор и дискриминатор дополнительной информации. Эта дополнительная информация, называемая условными переменными (conditioning
variables), позволяет контролировать процесс генерации и получать желаемые результаты. В качестве таких переменных может выступать любая вспомогательная информация, например метки классов, текстовые описания или
входные изображения.
Архитектура и обучение сGAN
Архитектура генератора и дискриминатора в cGAN модифицирована таким
образом, чтобы учитывать условные переменные. Генератор получает в качестве входных данных случайный шум и условные переменные и генерирует образцы на основе предоставленной информации. Дискриминатор,
в свою очередь, принимает реальные или сгенерированные образцы вместе
с соответствующими условными переменными и классифицирует их как подлинные или поддельные. На иллюстрации 14.1 показана схема архитектуры
cGAN, взятая из оригинальной статьи про cGAN.
Цель процесса обучения — оптимизация сетей генератора и дискриминатора.
Генератор стремится генерировать образцы, которые не только реалистичны, но и удовлетворяют определенным условиям. Дискриминатор же учится
различать настоящие и поддельные образцы, учитывая при этом условные
переменные.
1
Mehdi Mirza and Simon Osindero. “Conditional generative adversarial nets.” arXiv preprint
arXiv:1411.1784 (2014).
Генерация искусственных лиц с помощью GAN
■
493
Иллюстрация 14.1. Архитектура cGAN
В результате итерационного процесса сети генератора и дискриминатора повышают свою эффективность и выдают все более подходящие к условиям результаты, которые все лучше напоминают целевое распределение.
Области применения cGANs
На практике модели cGAN применяются для решения множества разных задач, некоторые из них перечислены ниже:
• Перевод изображений в изображения. Модели cGAN отлично справляются с преобразованием изображений из одной категории в изображения другой категории с сохранением определенных признаков.
Например, они могут преобразовывать эскизы в реалистичные изображения, превращать дневные пейзажи в ночные или даже создавать
изображения зебр из изображения лошадей на основе заданных условных переменных.
494
■
Pythonic AI
Генератор принимает на вход изображение из одной категории (допустим,
черно-белые) с условием (карта меток или часть целевого изображения) и генерирует выходное изображение указанной категории. Дискриминатор получает это изображение с тем же условием, что подавалось на генератор, и пытается отличить сгенерированное изображение от реальных изображений из
целевой категории.
Ниже приведены примеры перевода изображений в изображения, взятые из
статьи1 Филиппа Изолы и его коллег:
Иллюстрация 14.2. Перевод изображения в изображение с помощью cGAN
• Семантический синтез изображений. Если настроить генератор на
метки классов или текстовые описания, cGAN позволяет генерировать изображения, соответствующие определенным семантическим
концепциям. Данный метод находит широкое применение в области
компьютерного зрения, включая генерацию изображений различных
категорий объектов или даже создание уникальных композиций на основе текстового ввода. При генерации изображений на основе текста
условная GAN на входе принимает текстовое описание и генерирует
изображение, соответствующее этому описанию. Ниже приведены
1
Phillip Isola, Jun-Yan Zhu, Tinghui Zhou, and Alexei A. Efros. “Image-to-image translation
with conditional adversarial networks.” In Proceedings of the IEEE conference on computer
vision and pattern recognition, стр. 1125–1134. 2017.
Генерация искусственных лиц с помощью GAN
■
495
примеры перевода текста в изображения, взятые из статьи1 Скотта
Рида и его коллег:
Иллюстрация 14.3. Перевод текста в изображение с помощью cGAN
• Интерактивная генерация изображений. cGAN можно использовать
в интерактивных приложениях, где пользователи управляют процессом генерации с помощью условных переменных. Например, пользователи могут по своему усмотрению настраивать такие атрибуты генерируемых лиц, как возраст, пол или эмоции.
• Аугментация данных. Настраивая генератор на определенные атрибуты, cGAN можно использовать для аугментации данных в моделях
машинного обучения. Получая информацию об условиях, генератор
1
Scott Reed, Zeynep Akata, Xinchen Yan, Lajanugen Logeswaran, Bernt Schiele, and Honglak
Lee. “Generative adversarial text to image synthesis.” In International conference on machine
learning, pp. 1060–1069. PMLR, 2016.
496
■
Pythonic AI
создает синтетические образцы данных, а дискриминатор пытается
отличить реальные данные от сгенерированных как на основе самих
данных, так и на основе условий. Информация об условиях для синтетических образцов должна соответствовать желаемым характеристикам для аугментации. Такой процесс помогает разнообразить обучающие данные и повысить устойчивость и обобщающую способность
моделей.
Модели cGAN продемонстрировали замечательные результаты в области
генерации реалистичных изображений по заданному условию. Из этой главы мы узнаем, как разработать условную генеративно-состязательную сеть
для создания искусственных лиц определенной возрастной категории. Благодаря включению в архитектуру генератора и дискриминатора условных
переменных cGAN позволяют точно контролировать генерируемые образцы. По мере дальнейшего развития cGANs стоит ожидать появления еще
более сложных и разнообразных приложений, которые еще сильнее размоют границу между реальным и синтетическим в мире искусственного интеллекта.
Набор данных и их структура
Для разработки модели cGAN, генерирующей искусственные лица, мы воспользуемся набором изображений лиц IMDB-WIKI1. Набор данных IMDBWIKI2 Face Images with Age and Gender Labels (изображений лиц с метками
возраста и пола) Швейцарской высшей технической школы Цюриха широко используется в области компьютерного зрения. Он представляет собой
большую коллекцию изображений лиц и соответствующих меток возраста и пола, что делает его ценным ресурсом для решения различных задач,
таких как оценка возраста, классификация по полу и распознавание лиц.
Домашняя страница набора данных расположена по адресу: https://data.
vision.ee.ethz.ch/cvl/rrothe/imdb-wiki/. Мы будем использовать изображения лиц только из набора Википедии, размер которого составляет почти
1 ГБ. На сайте выложен файл Matlab (.mat), содержащий всю метаинфор1
2
Rasmus Rothe, Radu Timofte, and Luc Van Gool. “Deep expectation of real and apparent age
from a single image without facial landmarks.” International Journal of Computer Vision 126,
no. 2-4 (2018): 144–157.
Rasmus Rothe, Radu Timofte, and Luc Van Gool. “Dex: Deep expectation of apparent age
from a single image.” In Proceedings of the IEEE international conference on computer vision
workshops, стр. 10–15. 2015.
Генерация искусственных лиц с помощью GAN
■
497
мацию об изображениях. Метаинформация включает в себя следующие
данные:
dob: дата рождения (date of birth).
photo_taken: год, когда была сделана фотография.
full_path: путь к файлу изображения.
gender: пол, где 0 — это женщина, 1 — мужчина, NaN — неизвестно.
name: имя знаменитости.
face_location: местоположение лица.
face_score: оценка детектора (чем выше, тем лучше). Inf означает, что на
изображении не было найдено ни одного лица.
second_face_score: оценка детектора лица со второй по величине оценкой.
Эта метка полезна для игнорирования изображений, на которых имеется более одного лица. Она принимает значение NaN, если второе лицо не было
обнаружено.
Набор данных охватывает широкий диапазон возрастов людей, от новорожденных до пожилых, и включает в себя информацию о лицах разных национальностей, полов и происхождения. Изображения взяты с общедоступных
страниц IMDb и Википедии, что обеспечивает богатое и разнообразное представление лиц.
В этом наборе данных каждое изображение лица связано с меткой возраста,
приблизительно указывающей на возраст человека в тот момент, когда было
сделано изображение. Наряду с возрастными метками в набор данных также включены гендерные метки для каждого изображения лица. Эти метки
облегчают задачи гендерной классификации и позволяют исследовать гендерные характеристики изображений лиц. В целом описанный набор данных
представляет собой ценный ресурс, помогающий развивать методы анализа
и генерации изображений лиц.
Предварительная обработка
метаданных
Перед началом работы над приложением генерации лиц импортируем необходимые библиотеки с помощью следующих команд:
498
■
Pythonic AI
1. import numpy as np
2. import pandas as pd 3. import tensorflow as tf 4. import scipy.io
5. from datetime import datetime, timedelta
6. import matplotlib.pyplot as plt
На главной странице общего набора данных о лицах IMDB-WIKI можно
скопировать ссылку на расположение набора WIKI о лицах: https://data.
vision.ee.ethz.ch/cvl/rrothe/imdb-wiki/static/wiki_crop.tar. Набор
данных можно получить с помощью команды wget. Далее необходимо извлечь
данные из файла tar-архива. Ниже показаны соответствующие строки кода:
1. !wget -qq https://data.vision.ee.ethz.ch/cvl/rrothe/imdb-wiki/
static/wiki_crop.tar
2. !tar -xf wiki_crop.tar
После выполнения этих строк мы увидим на левой панели хранилища файлов сеанса каталог wiki_crop.
В каталоге wiki_crop находится файл формата Matlab под названием wiki.
mat, содержащий метаданные файлов изображений. Для чтения файла формата Matlab воспользуемся функцией loadmat() из библиотеки scipy.io, как
показано в следующей строке кода:
1. metadata = scipy.io.loadmat("wiki_crop/wiki.mat")
Как показано выше, переменная metadata хранит содержимое файла wiki.
mat. Данные загружаются в виде словаря, а получить к ним доступ можно
с помощью ключа "wiki". Извлечем значения "dob", "photo_taken", "full_
path", "name", "face_ score" и "second_face_score" и создадим из них объект DataFrame библиотеки pandas так, как показано в блоке кода ниже:
1. df = pd.DataFrame(columns=["dob", "photo_taken", "full_path",
"name", "face_score", "second_face_score"])
2. df["dob"] = metadata["wiki"][0, 0]["dob"][0]
3. df["photo_taken"] = metadata["wiki"][0, 0]["photo_taken"][0]
4. df["full_path"] = ["wiki_crop/"+i[0] for i in metadata["wiki"][0,
0]["full_path"][0]]
5. df["name"] = metadata["wiki"][0, 0]["name"][0]
Генерация искусственных лиц с помощью GAN
■
499
6. df["face_score"] = metadata["wiki"][0, 0]["face_score"][0]
7. df["second_face_score"] = metadata["wiki"][0, 0]["second_face_
score"][0]
Как показано в блоке кода выше, мы извлекли метаданные по ключу wiki. Из
вложенных массивов мы получили значения для вставки в соответствующие
столбцы объекта DataFrame библиотеки pandas. Объект DataFrame упростит
работу с данными.
Нам нужно сохранить изображения с одним лицом. Это означает, что мы будем сохранять только те изображения, у которых метка second_face_score
имеет значении NaN. Это делается следующей строкой кода:
1. df = df[df["second_face_score"].isna()]
Если мы сейчас проверим форму (размерность) объекта DataFrame df с помощью команды df.shape, то увидим, что в нем 58 232 строки.
В наборе имеется несколько изображений, на которых лица не обнаружены,
и для них метка face_score имеет значение -Inf (отрицательная бесконечность). Поскольку отрицательная бесконечность — наименьшее возможное
значение, то это и минимальное значение столбца face_score. Чтобы исключить изображения, на которых лица не обнаружены, нужно выбрать строки
со значением face_score больше минимального. Делается это с помощью
следующей строки кода:
1. df = df[df["face_score"] > min(df["face_score"])]
Теперь рассчитаем возраст человека на фотографии посредством вычитания
даты рождения из даты, когда был сделан снимок. Для этого создадим следующую функцию:
1. def get_age(taken, dob):
2.
birth = datetime.fromordinal(int(dob)) + timedelta(days=dob%1) timedelta(days = 366)
3.
if birth.month < 7:
4.
5.
6.
return taken - birth.year
else:
return taken - birth.year - 1
7. df["age"] = df.apply(lambda x: get_age(x["photo_taken"],
x["dob"]), axis=1)
500
■
Pythonic AI
Как показано выше, функция get_age() вычисляет возраст человека на основе заданного числового представления даты рождения (dob). В строке номер 2 значение dob преобразуется в целое число и из него извлекается часть
даты (год, месяц и день). С помощью выражения dob % 1 мы вычислили
дробную часть dob, представляющую собой время суток (часы, минуты, секунды и т. д.). Для учета високосных лет вычитается timedelta(days=366).
Применив эту функцию get_age() к объекту df, мы создали новый столбец
"age" для хранения возвращаемых значений.
В некоторых столбцах dob и photo_taken указаны неверные значения. Следовательно, некоторые значения возраста вычисляются неверно. Можно отфильтровать df на основе реалистичного диапазона (от 0 до 100) значений
возраста с помощью следующей строки кода:
1. df=df[(df.age<100) & (df.age>0)]
Теперь разделим значения возраста на различные группы. Для этого напишем функцию get_age_group(), как показано ниже:
1. def get_age_group(age):
2.
if 0 < age <= 18:
3.
return 0
4.
elif 19 < age <= 29:
5.
return 1
6.
elif 30 < age <= 39:
7.
return 2
8.
elif 40 < age <= 49:
9.
return 3
10.
elif 50 < age <= 59:
11.
return 4
12.
13.
else:
return 5
14. df["age_group"] = df["age"].map(get_age_group)
В блоке кода выше мы применили функцию get_age_group() к столбцу age
в объекте df и создали новый столбец под названием "age_group" с возвращенными значениями. Мы будем использовать эти возрастные категории
Генерация искусственных лиц с помощью GAN
■
501
в качестве целевой переменной в нашей модели условной GAN. Как показано
выше, в целевой переменной будет шесть категорий: 0, 1, 2, 3, 4 и 5.
Для ускорения обучения можно еще больше сократить объем обучающих
данных и ограничиться только теми изображениями, которые имеют определенное значение face_score. Рассмотрим следующую строку:
1. df = df[df["face_score"] > 3.23]
Здесь мы рассматриваем только те строки, в которых значение face_score
больше 3,23. Это необязательный шаг, который позволяет использовать
меньший набор данных для более быстрого обучения. Этот шаг можно пропустить и продолжить работу с полным набором данных. После выполнения
этого шага в объекте df должно быть 20 104 строки.
Построение модели
Теперь можно приступить к построению модели условной GAN. Сначала определим некоторые константы, которые будут использоваться в компонентах
cGAN. Рассмотрим следующие строки кода:
1. BATCH_SIZE = 128
2. IMAGE_SHAPE = (64,64,3)
3. NUM_OF_CLASSES = 6
4. LATENT_DIM = 100
5. EPOCHS = 50
Как показано выше, в строке номер 3 задано 6 классов, потому что в нашей
целевой переменной age_group есть только шесть возможных категорий.
Дискриминатор
Сеть дискриминатора работает как бинарный классификатор, различая реальные и сгенерированные образцы. Ее основная задача — правильно классифицировать входные данные и обеспечивать обратную связь с генератором для
улучшения его выходных данных. Зададим архитектуру дискриминатора с помощью функции create_discriminator(), как показано в блоке кода ниже:
502
■
Pythonic AI
1. def create_discriminator():
2.
inputs_label = tf.keras.Input(shape=(1,))
3.
x = tf.keras.layers.CategoryEncoding(num_tokens=NUM_OF_CLASSES,
output_mode="one_hot")(inputs_label)
4.
units = IMAGE_SHAPE[0] * IMAGE_SHAPE[1]
5.
x = tf.keras.layers.Dense(units)(x)
6.
labels = tf.keras.layers.Reshape((IMAGE_SHAPE[0], IMAGE_
SHAPE[1],1))(x) 7.
8.
inputs_image = tf.keras.layers.Input(shape=IMAGE_SHAPE)
9.
x = tf.keras.layers.Concatenate()([inputs_image, labels])
10.
x = tf.keras.layers.Conv2D(128, (3,3), strides=(2,2),
padding='same')(x)
11.
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
12.
x = tf.keras.layers.Dropout(0.4)(x)
13.
x = tf.keras.layers.Conv2D(128, (3,3), strides=(2,2),
padding='same')(x)
14.
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
15.
x = tf.keras.layers.Dropout(0.4)(x)
16.
x = tf.keras.layers.Flatten()(x)
17.
outputs = tf.keras.layers.Dense(1, activation='sigmoid')(x)
18.
19.
model = tf.keras.Model([inputs_image, inputs_label], outputs)
20.
opt = tf.keras.optimizers.Adam(learning_rate=0.0002,
beta_1=0.5)
21.
model.compile(loss='binary_crossentropy', optimizer=opt,
metrics=['accuracy'])
22.
return model
Как показано выше, архитектура дискриминатора состоит из сверточных слоев, извлекающих признаки изображения. В качестве дополнительной информации об условии дискриминатор принимает метки. В строке номер 2 данные
о метках подаются на вход в виде одного скаляра. В строке номер 3 выполняется однократное кодирование категорий с помощью слоя tf.keras.layers.
CategoryEncoding. Так, например, поскольку существует всего 6 классов,
метка «3» будет преобразована в [0, 0, 0, 1, 0, 0]. В строке номер 5 эта закодированная метка преобразуется в плотное представление. В строке номер 6
Генерация искусственных лиц с помощью GAN
■
503
плотное представление метки преобразуется в ту же форму, что и входное
изображение, но с дополнительным каналом (1). Это позволяет впоследствии
объединить информацию о метке с данными изображения. Строка номер 8
считывает входные изображения, а строка номер 9 объединяет данные изображения с информацией о метке по координате канала, в результате чего создается совместное представление. Остальные слои стандартны и выполняют операции свертки. Наконец, в строке номер 17 для выполнения бинарной
классификации задан плотный слой, состоящий из одного нейрона.
Генератор
Продолжим задавать архитектуру нашей модели cGAN и перейдем к генератору. В условных GAN генератор вместе с вектором шума принимает дополнительные условные входные данные, такие как метки классов, и генерирует
образцы, принадлежащие к этим определенным классам. Рассмотрим следующий блок кода:
1. def create_generator():
2.
inputs_label = tf.keras.Input(shape=(1,))
3.
x = tf.keras.layers.CategoryEncoding(num_tokens=NUM_OF_CLASSES,
output_mode="one_hot")(inputs_label)
4.
units = 16 * 16
5.
x = tf.keras.layers.Dense(units)(x)
6.
labels = tf.keras.layers.Reshape((16, 16, 1))(x)
7.
8.
inputs_lat = tf.keras.Input(shape=(LATENT_DIM,))
9.
units = 128 * 16 * 16
10.
x = tf.keras.layers.Dense(units)(inputs_lat)
11.
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
12.
latent = tf.keras.layers.Reshape((16, 16, 128))(x)
13.
14.
x = tf.keras.layers.Concatenate()([latent, labels])
15.
x = tf.keras.layers.Conv2DTranspose(128, (4,4),
strides=(2,2), padding='same')(x)
16.
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
17.
x = tf.keras.layers.Conv2DTranspose(128, (4,4),
strides=(2,2), padding='same')(x)
■
504
Pythonic AI
18.
x = tf.keras.layers.LeakyReLU(alpha=0.2)(x)
19.
outputs = tf.keras.layers.Conv2D(3, (7,7), activation='tanh',
padding='same')(x)
20.
model = tf.keras.Model([inputs_lat, inputs_label], outputs)
21.
return model
Как показано выше, генератор имеет два входных слоя:
• inputs_label. Это входной слой для меток классов. Он ожидает одно
целое число, представляющее собой метку класса.
• inputs_lat. Это входной слой для вектора шума (латентного пространства). Он имеет форму, соответствующую размерности вектора шума.
Как и в модели дискриминатора, здесь мы использовали слой CategoryEncoding
для однократного кодирования меток классов в векторы. Далее мы задали
плотный (полносвязный) слой для отображения закодированных векторов
в сетке 16×16 с одним каналом. Эта сетка впоследствии будет объединена
с латентным вектором.
Вектор шума (inputs_lat) пропускается через плотный слой, отображающий его на сетку 16×16 со 128 каналами. Сетки 16×16 из латентного вектора
и метки объединяются по размерности канала, в результате чего получается
сетка 16×16 со 129 каналами.
К объединенным данным применяются транспонированные свертки, которые увеличивают размер входной сетки и одновременно обучаются закономерностям, необходимым для создания реалистичных изображений. Под конец применяется операция свертки для генерирования выхода с 3 каналами
(соответствующими каналам RGB изображения).
Генератор этой архитектуры принимает на вход векторы шума и метки классов
и генерирует синтетические изображения, принадлежащие заданным классам.
Дискриминатор и генератор работают вместе в рамках условной GAN, при
этом цель генератора — научиться генерировать изображения, которые могут
обмануть дискриминатор, классифицирующий их как реальные образцы.
Генерация искусственных лиц с помощью GAN
■
505
Итоговая модель cGAN
Теперь можно объединить заданные выше дискриминатор и генератор и создать общую модель cGAN. Рассмотрим следующий блок кода:
1. def create_cgan(g_model, d_model):
2.
d_model.trainable = False
3.
gen_noise, gen_label = g_model.input
4.
gen_output = g_model.output
5.
gan_output = d_model([gen_output, gen_label])
6.
model = tf.keras.Model([gen_noise, gen_label], gan_output)
7.
opt = tf.keras.optimizers.Adam(learning_rate=0.0002, beta_1=0.5)
8.
model.compile(loss='binary_crossentropy', optimizer=opt)
9.
return model
Этот блок задает модель cGAN, объединяя генератор и дискриминатор. Сначала мы устанавливаем дискриминатор в режим необучаемого. Предполагается, что во время обучения cGAN обновляться будут только веса генератора,
а веса дискриминатора останутся неизменными. Это связано с тем, что перед
обучением генератора мы предварительно обучаем дискриминатор различать настоящие и поддельные образцы.
Сгенерированные изображения вместе с соответствующими метками классов передаются в качестве входных данных для модели дискриминатора. Результатом этой операции будет вероятность, с которой дискриминатор классифицирует сгенерированные изображения как настоящие.
Созданная указанной функцией модель cGAN обучается генерировать синтетические изображения, соответствующие определенным меткам классов.
Благодаря объединению в модели cGAN генератора и дискриминатора, она
учится генерировать реалистичные изображения, соответствующие заданным распределениям классов.
506
■
Pythonic AI
Загрузка набора данных
Ранее мы скачали набор данных лиц WIKI, извлекли из него метаданные
и предварительно обработали, создав на их основе объект DataFrame библиотеки pandas. Теперь нужно задать набор данных TensorFlow и загрузить
данные изображений. Определим две функции для предварительной обработки и загрузки данных изображений в набор данных с помощью следующего блока кода:
1. def preprocess_data(img_path, trainy):
2.
img = tf.io.read_file(img_path)
3.
img = tf.io.decode_jpeg(img, channels=3)
4.
img = tf.image.resize(img, (IMAGE_SHAPE[0], IMAGE_SHAPE[1]))
5.
img = (img - 127.5) / 127.5
6.
return [img, trainy]
7.
8. def create_dataset(images, trainy):
9.
dataset = tf.data.Dataset.from_tensor_slices((images, trainy))
10.
dataset = dataset.map(preprocess_data, num_parallel_calls=tf.
data. AUTOTUNE)
11.
dataset = dataset.shuffle(BATCH_SIZE*8).batch(BATCH_SIZE).
prefetch(tf.data.AUTOTUNE)
12.
return dataset
Определенная выше функция create_dataset() в качестве входных данных
принимает пути к файлам изображений и метки (age_group) и создает набор
данных TensorFlow. К набору данных мы применили функцию preprocess_
data(), которая считывает файл по заданному пути, масштабирует изображение до нужного размера и нормализует значения пикселей до значений от
-1 до 1. Здесь же определены соответствующие значения методов shuffle,
batch и prefetch, применяемых к набору данных.
Генерация искусственных лиц с помощью GAN
■
507
Создание латентных точек
и фальшивых данных
Определим две функции для создания латентных точек и фальшивых (поддельных, синтетических) данных. Эти функции будут использоваться в процессе обучения. Рассмотрим следующий блок кода:
1. def create_latent_points(n_samples):
2.
x_input = np.random.randn(LATENT_DIM * n_samples)
3.
z_input = x_input.reshape(n_samples, LATENT_DIM)
4.
labels = np.random.randint(0, NUM_OF_CLASSES, n_samples)
5.
return [z_input, labels]
6.
7. def create_fake_data(generator):
8.
z_input, labels_input = create_latent_points(BATCH_SIZE)
9.
images = generator.predict([z_input, labels_input])
10.
y = np.zeros((BATCH_SIZE, 1))
11.
return [images, labels_input], y
Этот блок кода содержит две функции, связанные с созданием латентных
точек (случайных векторов шума и соответствующих меток классов) и генерацией фальшивых (поддельных) данных с помощью модели генератора. Функция create_latent_points() создает случайные латентные точки
и случайные метки классов на основе нормальных распределений.
Функция create_fake_data() генерирует фальшивые данные, используя модель генератора cGAN. Генератор принимает на вход векторы шума и метки
классов и генерирует синтетические изображения. Он также создает соответствующий пакет целевых меток в виде массива нулей. В контексте GAN
этот пакет нулевых меток указывает на фальшивые (нереальные) образцы.
В целом эти функции играют важную роль в процессе обучения cGAN и повышают способность генератора создавать более реалистичные образцы.
508
■
Pythonic AI
Обучение модели cGAN
Процесс обучения модели cGAN представляет собой так называемое «состязательное обучение». Ниже показан блок кода, в котором задается функция
такого обучения:
1. def train(g_model, d_model, gan_model, dataset):
2.
3.
4.
5.
for i in range(EPOCHS):
for batch in dataset:
if batch[0].shape[0] < BATCH_SIZE:
continue
6.
X_real = batch[0]
7.
labels_real = batch[1]
8.
y_real = np.ones((BATCH_SIZE, 1))
9.
11.
12.
real_data_loss, _ = d_model.train_on_batch([X_real, labels_
real], y_real) 10.
[X_fake, labels], y_fake = create_fake_data(g_
model)
fake_data_loss, _ = d_model.train_on_batch([X_fake,
labels],y_fake)
13.
14.
15.
[z_input, labels_input] = create_latent_points(BATCH_SIZE)
y_gan = np.ones((BATCH_SIZE, 1))
16.
17.
gan_loss = gan_model.train_on_batch([z_input, labels_input],
y_gan)
18.
print("d1={0:.3f}, d2={1:.3f} g={2:.3f}".format(real_data_
loss, fake_data_loss, gan_loss))
19.
g_model.save('cgan_generator.h5')
В этом блоке кода реализован цикл обучения cGAN. Функция обучения принимает четыре аргумента: модель генератора, модель дискриминатора, модель
cGAN (объединяющую генератор и дискриминатор) и обучающий набор данных, итерируемый по партиям реальных данных. Для заданного количества
эпох обучения функция выполняет итерацию по каждому пакету обучающего набора данных. Если размер пакета меньше заданного BATCH_SIZE, функция пропускает этот пакет. Такое условие обеспечивает постоянство размера
Генерация искусственных лиц с помощью GAN
■
509
пакета. В строке номер 8 создается массив NumPy y_real с единицами, который будет использоваться в качестве целевого вывода для дискриминатора
при обучении на реальных выборках данных. Мы обучали дискриминатор на
реальных образцах данных вместе с их метками. В real_data_loss хранятся
потери дискриминатора на этом шаге обучения, а "_" используется для отбрасывания второго возвращаемого значения (метрики).
Функция create_fake_data() генерирует образцы фальшивых данных с помощью генератора и присваивает этим фальшивым образцам соответствующие метки. Она также создает массив NumPy y_fake с нулями, который будет
использоваться в качестве целевого выхода для дискриминатора при обучении на образцах фальшивых данных.
Затем дискриминатор обучается на образцах фальшивых данных и их метках.
В fake_data_loss хранятся потери дискриминатора на этом шаге обучения.
В строке номер 14 функция create_latent_points() в качестве входных
данных для генератора генерирует случайные латентные точки и соответствующие метки. В строке номер 15 создается массив NumPy с единицами, который будет использоваться в качестве целевого выхода для cGAN во время
обучения.
Далее модель cGAN обучается на случайных латентных точках и метках в качестве входных данных для генератора.
Наконец, по завершении цикла тренировки обученная модель генератора сохраняется в файл с именем "cgan_generator.h5".
Теперь можно вызвать эту заданную функцию обучения для обучения всей
модели cGAN с помощью следующих строк кода:
1. d_model = create_discriminator()
2. g_model = create_generator()
3. gan_model = create_cgan(g_model, d_model)
4. dataset = create_dataset(np.array(df["full_path"]),
np.array(df["age_group"]))
5. train(g_model, d_model, gan_model, dataset)
Как показано выше, мы создали дискриминатор, генератор и GAN-модель. Мы
вызвали функцию create_dataset() для создания набора данных TensorFlow
из файлов изображений. И, наконец, мы вызвали функцию train() с необходимыми входными аргументами для обучения модели.
510
■
Pythonic AI
Генерация и вывод полученных
данных
После завершения процесса обучения генератор можно использовать для создания поддельных лиц заданной целевой возрастной категории. Сгенерируем 36 поддельных лиц и выведем их на экран с помощью следующих команд:
1. model = tf.keras.saving.load_model('cgan_generator.h5')
2. latent_points, labels = create_latent_points(36)
3. labels = np.asarray([x for _ in range(6) for x in range(6)])
4. X = model.predict([latent_points, labels])
5. X = (X + 1) / 2.0
В этом фрагменте кода функция load_model() загружает обученную модель
генератора из сохраненного файла cgan_generator.h5. Далее генератор создает 36 синтетических образцов данных, задавая 36 случайных латентных
точек и указывая соответствующие метки. Эти метки принимают значения
от 0 до 5 и тем самым создают шаблон сетки.
В строке номер 4 загруженная модель-генератор на основе предоставленных
случайных скрытых точек и соответствующих меток генерирует новые образцы данных.
После генерации образцов в строке номер 5 данные масштабируются до диапазона от 0 до 1. Модель генератора cGAN выводит данные в диапазоне от -1
до 1, что типично для моделей GAN. Чтобы привести данные к более стандартному диапазону от 0 до 1, в этой строке выполняется простая операция
масштабирования.
Примечание. Сохранять модели в файл h5 и загружать их из него не
обязательно. Упомянутая выше функция training() вместо сохранения
может возвращать модель g_model, но из-за длительности процесса
обучения сохранять модель в файл просто удобно.
Теперь можно вывести на экран сетку из 36 поддельных лиц с помощью следующих строк кода:
Генерация искусственных лиц с помощью GAN
■
511
1. def create_plot(examples, n):
2.
for i in range(n * n):
3.
plt.subplot(n, n, 1 + i)
4.
plt.axis('off')
5.
plt.imshow(examples[i, :, :])
6.
plt.show()
7.
8. create_plot(X, 6)
Как показано выше, функция create_plot() принимает сгенерированные
образцы. Также ей передается значение n для распределения образцов по сетке. Полученные образцы показаны ниже:
Иллюстрация 14.4. Сгенерированные изображения лиц
Все показанные на иллюстрации выше изображения сгенерированы моделью
cGAN и не существуют в наборе данных. Впрочем, заметно, что эти синтетические лица заданных возрастных категорий довольно нереалистичны.
Для улучшения качества вывода cGAN можно испробовать следующие стратегии:
512
■
Pythonic AI
• Более глубокие и сложные архитектуры генератора и дискриминатора
могут помочь модели выявить более сложные закономерности и детали, что приведет к повышению качества результатов. Полезными могут
оказаться такие методы, как нормализация (пакетная нормализация,
нормализация экземпляров и т. д.) и пропуск связей.
• Вместо стандартной функции потерь с использованием бинарной
кросс-энтропии для дискриминатора, можно рассмотреть использование функции потерь, основанной на сопоставлении признаков. Сопоставление признаков предполагает согласование статистик (среднего
и ковариации) промежуточных признаков реальных и сгенерированных изображений. Благодаря этому повышается стабильность обучения и улучшается качество результатов.
• При обучении дискриминатора можно применять сглаживание меток, то есть присваивать реальным образцам значения чуть меньше 1,
а поддельным — чуть больше 0. Так может снизиться риск того, что
дискриминатор переобучится, и результаты будут получаться более
разнообразными и реалистичными. Можно также попробовать применить одностороннее сглаживание меток, при котором сглаживаются
только настоящие метки, но не поддельные. Дискриминатор будет менее уверен в своих решениях, что побудит генератор создавать более
разнообразные и реалистичные выборки. Ниже приведен один из способов генерации сглаженных меток в TensorFlow:
1. y_real = np.ones((BATCH_SIZE, 1)) * 0.9
2. y_fake = np.zeros((BATCH_SIZE, 1)) + 0.1
• Такие методы аугментации данных, как поворот, масштабирование или
отзеркаливание и реальных, и сгенерированных данных во время обучения повышают разнообразие образцов и улучшают способность модели генерировать более реалистичные и разнообразные результаты.
• Общее качество результатов может улучшить постепенное увеличение
разрешения генерируемых изображений в процессе обучения (прогрессивное наращивание).
• Убедитесь в том, что набор данных для обучения сбалансирован и содержит достаточное количество примеров каждого класса. Из-за нарушений баланса данных генерация может сместиться в сторону доминирующих классов с игнорированием более редких. На следующей
иллюстрации показано распределение изображений в нашем наборе
данных по возрастным группам age_group:
Генерация искусственных лиц с помощью GAN
■
513
Иллюстрация 14.5. Распределение по возрастным группам
• Повысить разнообразие и улучшить качество генерируемых выборок
может сочетание различных методов для обучения нескольких моделей cGAN с последующим усреднением их результатов.
Важно отметить, что эффективность этих методов может зависеть от конкретного набора данных, проблемной области и других факторов. Чтобы
найти оптимальный подход для конкретного приложения cGAN, рекомендуется поэкспериментировать с различными комбинациями этих стратегий
и гиперпараметров.
Заключение
В этой главе мы изучили весьма значительные возможности моделей GAN по
генерации искусственных лиц. В частности, мы сосредоточились на реализации условной GAN, генерирующей синтетические изображения лиц людей
определенной возрастной категории. На протяжении всех глав мы наблюдали за тем, как теоретические концепции последовательно реализуются на
514
■
Pythonic AI
практике, а создание никогда ранее не существовавших лиц стало логичным
итогом нашей работы.
Наше путешествие в мир генерации лиц началось с описания принципов
работы условных GAN и их уникальной способности контролировать генерацию данных с помощью дополнительной входной информации. Достижения в этой области позволяют нам в частности создавать индивидуальные
изображения лиц определенных возрастных категорий, и таким образом
демонстрируют богатые возможности условного синтеза. Мы постарались
разобраться в тонкостях манипулирования данными, их предварительной
обработки, проектирования архитектуры и методов оптимизации, которые
были необходимы для успешного построения нашей модели условной GAN.
В ходе тщательных экспериментов и итераций мы отточили нашу модель генерации синтетических лиц.
Сейчас, в заключительной главе книги, стоит немного задуматься о том, какое глубокое влияние нейронные сети оказывают на всю сферу искусственного интеллекта. На протяжении всех глав этой книги читатели знакомились
с различными передовыми концепциями в этой сфере, начиная с моделей
компьютерного зрения и обработки естественного языка до трансформеров,
создания подписей к изображениям и GAN.
Заканчивая эту книгу, мы как никогда раньше убеждаемся в том, что искусственный интеллект — это постоянно развивающаяся область, полная потрясающих возможностей. ИИ-технологии приносят большую пользу в самых
различных областях, от медицинской диагностики до безграничного творчества. Отмечая успехи, достигнутые в области искусственного интеллекта,
мы должны понимать, что это только начало. Дальнейший путь, несомненно,
будет сопровождаться как впечатляющими прорывами, так и различными
проблемами технического и этического плана. Поскольку искусственный интеллект продолжает формировать наш мир и наше будущее, исследователи,
практики и политики должны объединить свои усилия, чтобы со всей ответственностью проследить за развитием, прозрачностью и справедливостью систем ИИ.
Перед тем как попрощаться с вами, мы высказываем надежду, что эта книга пробудила в вас настоящую страсть и любопытство, заставила искренне
заинтересоваться увлекательной сферой искусственного интеллекта и предоставила вам возможность внести свой собственный вклад в многообещающее
будущее искусственного интеллекта. Также мы надеемся на то, что кем бы вы
ни были — студентами, исследователями или энтузиастами ИИ — знания, полученные из этой книги, послужат вам надежной основой для продолжения
Генерация искусственных лиц с помощью GAN
■
515
вашего путешествия в увлекательный мир искусственного интеллекта. Когда мы перейдем к следующей главе эволюции ИИ, пусть нашими маяками
станут любопытство, жажда творчества и приверженность этическим принципам. Открывающиеся перед нами возможности поистине безграничны,
и будущее ИИ в наших руках.
Основные выводы
• Весьма эффективная структура моделей cGAN позволяет генерировать данные с определенными атрибутами или характеристиками. Эти
модели предусматривают контроль над результатом с помощью информации об условиях, предоставляемой сетям-генераторам и сетямдискриминаторам.
• Архитектура cGAN состоит из генератора и дискриминатора, которые
можно обучать с помощью условных переменных.
• Мы разработали модель cGAN и обучили ее генерировать синтетические изображения лиц.
Ссылки
• Официальная документация слоя CategoryEncoding библиотеки
TensorFlow: https://www.tensorflow.org/api_docs/python/tf/keras/layers/
CategoryEncoding
• Поддельные лица, сгенерированные ИИ: https://thispersondoesnotexist.
com/
Указатель
__init__ (функция), 53
A
AlexNet, 159
Anaconda
страница загрузки, 72
Anaconda Navigator, 72
B
BERT, модель, 392
BLIP, модель, 453
break, оператор, 37
C
continue, оператор, 37
CSV-файл, 259
cоздание приложений
для преобразования
изображений в текст с
помощью TensorFlow 2, 271
cтандартные архитектуры CNN
AlexNet, 159
Inception, 163
LeNet, 158
ResNet, 162
VGGNet, 160
описание, 158
D
DCGAN, модель
о модели, 465
характеристики, 465
DNN, модуль, 237
F
F1-мера, 210
for, цикл, 36
G
GAN, модель
построение, 465
GitHub
использование с Google Colab,
90
ссылка на блокнот, 88
GIT, модель, 454
GloVe (алгоритм векторизации)
использование для эмбеддинга,
304
использование эмбеддингов,
376
об алгоритме, 304
Google Colab
использование, 27
использование с GitHub, 81
о среде, 81
■
Google Диск
подключение, 83
GPT (генеративные
предварительно обученные
трансформеры)
модели от Hugging Face, 414
модели от OpenAI, 416
о моделях, 412
GRU, модель
о модели, 319, 355
построение, 339
H
Hugging Face
использование предварительно
обученных моделей GPT,
414
использование предварительно
обученных моделей
генерации подписей, 452
I
Inception, сеть, 163
IoU, метрика, 206
IoU, расчёт, 207
J
join(), функция, 49
K
Keras, 103
L
LeNet, архитектура, 158
len(), функция, 41
lower(), функция, 48
LSTM, модель
базовая структура, 332
о модели, 319
построение модели, 331
построение с помощью
TensorFlow, 335
M
Matplotlib, библиотека, 68
MNIST, база данных, 105
N
NLTK, библиотека
POS-теггинг (разметка частей
речи), 286
использование для обработки
естественного языка, 276
лемматизация, 284
стемминг, 283
токенизация, 277
удаление стоп-слов, 280
NumPy, библиотека
векторизация, 61
генерация чисел, 62
изменение формы
(размерности) массивов,
58
описание, 56
транспонирование массивов,
60
умножение матриц, 61
O
OCR (оптическое распознавание
символов)
области применения, 250
OCR (оптическое распознавание
текстов)
создание приложения
с помощью Tesseract, 259
OpenAI
использование генеративных
предварительно
обученных
трансформеров (GPT), 416
517
518
■
P
Pandas, 361
PIL, библиотека Python Imaging
Library, 254
Python
в среде Google Colab, 27
объектно-ориентированное
программирование, 52
описание, 26
отступы, 32
переменные, 32
условные выражения, 35
циклы, 36
R
range(), функция
описание, 38
синтаксис, 38
RCNN (региональная
свёрточная нейронная сеть)
модель YOLO, 235
модель YOLO, реализация, 244
о сети, 215, 230
реализация, 235
ReLu, функция, 100, 143, 463
ResNet50, предварительно
обученная модель, 192
ResNet, архитектура, 162
Ridge-регрессия, 147
ROC-кривая, 210
S
spaCy, библиотека
POS-теггинг (разметка частей
речи), 293
вычисление сходства, 299
использование для обработки
естественного языка, 288
лемматизация, 297
разбор зависимостей
(dependency parsing), 295
распознавание именованных
сущностей (NER), 294
токенизация, 291
spaCy, компоненты
language class, 289
StringStore, 289
конвейер обработки (pipeline),
290
модели, 290
объект Doc, 289
объект token, 290
словарь, 289
split(), функция, 49
SSD (Single Shot Detector), 255
SSD (Single Shot Multibox
Detector), модель
использование, 218
описание, 215
преимущества, 217
процесс работы, 216
T
tanh, функция
(гиперболического тангенса),
99
TensorBoard, 127
TensorFlow, слой внимания
для построения архитектуры
RNN, 331
для построения модели LSTM,
335
использование GPU, 155
использование TPU, 155
и эмбеддинг слов, 314
набор данных, 154
описание, 103
построение сетей с помощью
TensorFlow, 153
реализация, 397
■
TensorFlow 2
создание приложений для
преоборазования
изображений в текст, 259
ссылка на файлы моделей, 233
tensorflow_hub, библиотека, 375
TensorFlow, инструментарий
визуализации, 123
Tesseract
использование для создания
OCR-приложения, 259
Tesseract OCR
ограничения, 259
TF-IDF, показатель, 302, 356
TF (частота слов), 302
U
upper(), функция, 48
V
VGG16, предварительно
обученная модель, 192
VGGNet, 160
W
while, цикл, 36
Word2Vec
модель CBOW, 304
модель Skip-gram, 304
Y
YOLO, модель
о модели, 255
описание, 235
реализация, 244
характеристики, 235
А
автоматическое распознавание
речи (ASR), 249
алгоритм обратного
распространения, 102
аннотации истинных данных,
206
арифметические операторы, 32
архитектура CNN
об архитектуре, 137
полносвязный слой, 145
свёрточный слой, 138
слой ReLu, 143
слой пулинга, 143
архитектура GAN
генератор, 462
дискриминатор, 462
об архитектуре, 461
архитектура модели CNN
обучение, 180
оценка, 180
создание, 180
уменьшение переобучения, 181
архитектура трансформера
вектор запроса, 388
вектор значения, 388
вектор ключа, 388
декодер, 390
механизм самовнимания, 389
многоголовое внимание, 391
описание, 388
энкодер, 389
архитектуры RNN
Многие к одному, 325
Многие ко многим, 325
Один ко многим, 324
описание, 324
построение с помощью
TensorFlow, 331
519
520
■
Б
База данных CFPB, 356
бинарная кросс-энтропия, 464
блокноты Colab, 76
блок трансформера
использование предварительно
обученных
трансформеров, 412
использование слоёв из
моделей TensorFlow, 401
реализация, 400
создание пользовательских
слоёв, 406
большая языковая модель
(LLM), 275, 321, 385
Бюро финансовой защиты
потребителей (CFPB), 356
В
вариационный кодировщик
(VAE)
архитектура, 479
обучение, 480
описание, 458, 477
построение модели, 488
вектор запроса, 388
вектор значения, 388
вектор ключа, 388
виртуальная реальность (VR),
424
внутреннее внимание, 389
Г
Генеративно-состязательные
сети (GAN)
обзор, 460
обучение, 463
о сетях, 490
проблемы, 464
сети, 457
генеративные модели, 458
генерация лиц обученной
моделью, 513
генерация подписей к
изображениям
методология и подход, 422
области применения, 424
о генерации, 341
проблемы, 423
генерация последовательности,
349
гистограмма ориентированных
градиентов (HOG), 255
Глубокая нейронная сеть (DNN),
101
графический процессор (GPU),
71, 81, 414
Д
данные, 172, 356, 496
аугментация, 148, 495
загрузка, 176
очистка, 370
предварительная обработка,
176, 370
данные изображений и
текстовые данные
подготовка, 433
построение модели для
генерации подписей, 435
построение модели для
обработки изображений,
433
двунаправленная RNN
генерация подписей к
изображениям, 341
обработка естественного
языка, 341
о сети, 342
распознавание речи, 341
декодер, 390
дополнение (padding), 141
■
дополненная реальность (AR),
424
дропаут, 148
И
индекс Жаккара (коэффициент
сходства), 207
ИНС, модели
использование
последовательного API,
109
использование
функционального API, 113
компиляция, 116
модель подклассов, 115
настройка на обучение, 117
обучение, 116
оценка, 119, 121
построение, 109
построение графика значений
потерь и метрик, 121
предварительная обработка
изображений, 107
интерактивная генерация
изображений, 495
Искусственные нейронные сети
(ИНС)
алгоритм обратного
распространения, 102
глубокая нейронная сеть, 101
модель машинного обучения,
97
оптимизатор, 103
о сетях, 93, 248
основы, 94
функция активации, 97
функция потерь, 103
Искусственный интеллект (ИИ),
26
К
классификация изображений
о классификации, 169
с помощью предварительно
обученных моделей, 186
классификация текстов, 355
кривая точности-полноты
(RP-кривая), 210
Л
логические операторы, 34
локализация, 203
локальная среда, 71
настройка, 75
М
Маскированная языковая
модель (MLL), 392
масштабируемое внимание
на основе скалярного
произведения, 389
метаданные
предварительная обработка,
501
методы обобщения
в случае недообучения, 152
в случае переобучения, 146
о методах обобщения, 145
метрики оценки
F1-мера, 210
IoU, 206
ROC-кривая, 210
кривая точности-полноты
(RP-кривая(, 210
о метриках, 206
средняя точность (AP), 209
усреднённая точность (mAP),
210
механизм самовнимания, 389
Многие к одному, тип
архитектуры RNN, 325
521
522
■
Многие ко многим, тип
архитектуры RNN, 325
многоголовое внимание, 391
модели TensorFlow
загрузка, 130
загрузка во время обучения,
130
загрузка после обучения, 132
использование слоёв моделей,
401
сохранение, 130
сохранение во время обучения,
130
сохранение после обучения,
132
модели подклассов
использование, 115
модели последовательностей
анализ настроения, 320
генерация музыки, 321
генерация подписей к
изображениям, 321
двунаправленная модель GRU,
373
использование предварительно
обученных эмбеддингов,
374
использование предварительно
обученных эмбеддингов
GloVe, 376
машинный перевод, 320
модель LSTM, 371
обучение, 370
описание, 319
построение, 370
распознавание речи, 320
модель CBOW, 304
модель LSTM для создания
подписей, 447
модель генерации подписей
на основе LSTM, 447
на основе механизма
внимания, 450
на основе трансформера, 450
обучение, 439
оценка, 439
модель на основе внимания
создание, 450
модель на основе трансформера
создание, 450, 452
модель обнаружения объектов
модель SSD (Single Shot
Multibox Detector), 215
описание, 215
Н
набор данных
загрузка, 357
набор данных CIFAR
ссылка на скачивание, 170
набор данных Flickr8
валидационный набор, 426
компоненты, 426
обучающий набор, 426
о наборе, 425
тестовый набор, 426
наследование классов, 54
настройка гиперпараметров, 130
недообучение
методы преодоления, 152
решение проблемы, 153
нейрон, 95
нейронная языковая модель
(NNLM), 374
О
область объединения, 208
область пересечения, 207
облачная среда, 71
■
обнаружение объектов
метрики оценки, 206
описание, 202
основные принципы, 204
подавление немаксимумов
(NMS), 211
функции потерь, 206
якорные рамки, 213
обработка данных, 361, 362
обработка данных с помощью
библиотеки Pandas, 361
обработка естественного языка
(NLP)
описание, 274, 276, 341, 353
с помощью NLTK, 276
с помощью spaCy, 288
обратное распространение
ошибки во времени (BPTT),
328, 334
обучение с подкреплением на
основе обратной связи от
человека (RLHF), 413
объектно-ориентированное
программирование
в Python, 52
класс, 52
метод, 53
объект, 52
Один ко многим, тип
архитектуры RNN, 324
одномерная свёрточная
нейронная сеть (1D CNN)
обучение, 379
о сети, 354, 379, 381
построение, 379
преимущества, 380
операторы идентичности, 34
операторы принадлежности, 35
операторы сравнения, 33
отступы, 32
П
пакетная нормализация, 151
перевод изображений
в изображения, 493
переобучение
аугментация данных, 148
дропаут, 148, 181
методы преодоления, 146
обобщение, 146
пакетная нормализация, 151
переопределение архитектуры
модели, 146
регуляризация, 147, 184
уменьшение, 181
подавление немаксимумов
(NMS)
описание, 211
преимущества, 212
процедура, 211
полносвязный слой, 145
полнота, 210
пользовательский слой
внимания
использование, 400
пользовательский слой
трансформера
создание, 406
пользовательское изображение
использование, 199
последовательный API
использование, 109
предварительная обработка
изображений классификация
изображений, 107, 422
предварительно обученная
модель генерации подписей
использование модели Hugging
Face, 452
523
524
■
предварительно обученные
модели
использование для
классификации
изображений, 187
модель ResNet50, 192
модель VGG-16, 187
о моделях, 186
построение модели для
генерации подписей, 435
построение модели для
обработки изображений,
433
предварительно обученные
трансформеры
использование, 412
предварительно обученные
эмбеддинги
использование, 374, 376
предсказанная ограничивающая
рамка, 206
преобразование изображений
в текст
концепция, 248
Р
распознавание именованных
сущностей (NER), 288, 415
распознавание речи, 341
расхождение КульбакаЛейблера, 484
регрессия Lasso, 147
регуляризация
Ridge-регрессия, 147
регрессия Lasso, 147
Рекуррентные нейронные сети
(RNN)
базовая архитектура, 322
о сетях, 252, 319, 387
построение модели RNN, 321
преимущества, 324
С
свёртки с дробным шагом, 465
свёрточная нейронная сеть
(CNN), 136, 252, 327, 482
свёрточный слой
дополнение, 141
о слое, 138
сдвиг, 142
фильтр, 138
ядро, 138
сдвиг (в свёртках), 142
семантический синтез
изображений, 494
Сеть пирамиды признаков
(FPN), 215
сигмоидальная функция, 97
слои
использование слоёв из
официальных моделей
TensorFlow, 401
слой ReLu, 143
слой внимания
использование
пользовательского слоя
внимания, 400
реализация, 393
слой внимания библиотеки
TensorFlow, реализация,
397
слой пулинга
описание, 143
по максимальному значению,
144
по среднему значению, 144
состязательное обучение, 508
среднеквадратичная ошибка
(MSE), 103, 206
средняя абсолютная ошибка
(MAE), 103
средняя абсолютная процентная
ошибка (MAPE), 103
■
средняя точность (AP), 209
стохастический градиентный
спуск (SGD), 103
структуры данных в Python
кортеж, 43
множество, 46
описание, 39
словарь, 44
списки, 42
списки (подробнее), 47
функции, 50
Т
тензорный процессор (TPU), 71,
81, 176, 414
точность, 210
У
условные выражения
оператор elif, 35
оператор else, 35
оператор if, 35
условные генеративносостязательные сети (cGAN)
архитектура, 492
генератор, 503
дискриминатор, 501
загрузка набора данных, 506
области применения, 493
обучение, 492, 508
о сети, 492
построение, 501
построение модели, 505
создание латентных точек, 507
создание фальшивых данных,
507
усреднённая точность (mAP),
210
Ф
функции Python, 50
функциональный API
использование, 113
функция потерь, 103
Ц
центральный процессор (CPU),
81
Ш
шаговые свёртки, 465
Э
эмбеддинги слов
в TensorFlow, 314
высокая размерность, 304
описание, 311
переносимость, 305
плотность, 304
с помощью GloVe, 314
энкодер, 389
Я
языковая модель, 349, 423
якорные рамки, 213
525
Все права защищены. Книга или любая ее часть не может быть
скопирована, воспроизведена в электронной или механической форме,
в виде фотокопии, записи в память ЭВМ, репродукции или каким-либо
иным способом, а также использована в любой информационной системе
без получения разрешения от издателя. Копирование, воспроизведение
и иное использование книги или ее части без согласия издателя
является незаконным и влечет уголовную, административную
и гражданскую ответственность.
Производственно-практическое издание
ПУТЕВОДИТЕЛЬ ПО GPT И AI
Банерджи Ариндам
PYTHONIC AI
РУКОВОДСТВО ДЛЯ НАЧИНАЮЩИХ
ПО СОЗДАНИЮ ПРИЛОЖЕНИЙ ИСКУССТВЕННОГО ИНТЕЛЛЕКТА НА PYTHON
Главный редактор Р. Фасхутдинов
Руководитель направления В. Обручев
Ответственный редактор Л. Салихова
Научный редактор А. Алексеев
Литературный редактор О. Рухлова
Младший редактор А. Лосевский
Художественный редактор В. Новикова
Компьютерная верстка Е. Матусовская
Корректоры Е. Ерошкина, Л. Макарова
Страна происхождения: Российская Федерация
Шы2арушы ел: Ресей Федерациясы
ООО «Издательство «Эксмо»
123308, Россия, г. Москва, ул. Зорге, д. 1, стр. 1, эт. 20, каб. 2013. Тел.: 8 (495) 411-68-86.
Home page: www.eksmo.ru E-mail: info@eksmo.ru
Mндіруші: «Издательство «Эксмо» ЖШR
123308, Ресей, МSскеу Uаласы, Зорге кVшесі, 1-Wй, 1-UXрылыс, 20 Uабат, 2013-каб.
Тел.: 8 (495) 411-68-86. Home page: www.eksmo.ru E-mail: info@eksmo.ru.
Тауар белгісі: «Эксмо»
Интернет-магазин : www.book24.ru
Интернет-магазин : www.book24.kz
Интернет-дкен : www.book24.kz
Импортёр в Республику Казахстан ТОО «РДЦ-Алматы».
RазаUстан Республикасына импорттаушы «РДЦ-Алматы» ЖШС.
12+
Дистрибьютор и представитель по приему претензий на продукцию
в Республике Казахстан: ТОО «РДЦ-Алматы»
ТОО РДЦ Алматы, Алматы, ул. Домбровского, 3«а», литер Б, офис 1.
Дистрибьютор жSне RазаUстан Республикасында Vнімге ша2ымдар
Uабылдау жVніндегі Vкіл: «РДЦ-Алматы» ЖШС.
Алматы U., Домбровский кVш., 3 «а», литер Б, офис 1.
Тел.: 8 (727) 251-59-90/91/92. E-mail: RDC-Almaty@eksmo.kz
Сведения о подтверждении соответствия издания согласно законодательству РФ
о техническом регулировании можно получить на сайте Издательства «Эксмо»:
www.eksmo.ru/certification
ТехникалыU реттеу туралы РФ за`намасына сай басылымны` сSйкестігін растау
туралы мSліметтерді мына адрес бойынша алу2а болады: http://eksmo.ru/certification/
Произведено в Российской Федерации
Ресей Федерациясында Vндірілген
Сертификаттау2а жатпайды
Дата изготовления / Подписано в печать 17.07.2025. Формат 70x1001/16.
Печать офсетная. Усл. печ. л. 42,78.
Тираж
экз. Заказ
Москва. ООО «Торговый Дом «Эксмо»
Адрес: 123308, г. Москва, ул. Зорге, д.1, строение 1.
Телефон: +7 (495) 411-50-74. E-mail: reception@eksmo-sale.ru
По вопросам приобретения книг «Эксмо» зарубежными оптовыми
покупателями обращаться в отдел зарубежных продаж ТД «Эксмо»
E-mail: international@eksmo-sale.ru
International Sales: International wholesale customers should contact
Foreign Sales Department of Trading House «Eksmo» for their orders.
international@eksmo-sale.ru
По вопросам заказа книг корпоративным клиентам, в том числе в специальном
оформлении, обращаться по тел.: +7 (495) 411-68-59, доб. 2151.
E-mail: borodkin.da@eksmo.ru
Оптовая торговля бумажно-беловыми
и канцелярскими товарами для школы и офиса «Канц-Эксмо»:
Компания «Канц-Эксмо»: 142702, Московская обл., Ленинский р-н, г. Видное-2,
Белокаменное ш., д. 1, а/я 5. Тел./факс: +7 (495) 745-28-87 (многоканальный).
e-mail: kanc@eksmo-sale.ru, сайт: www.kanc-eksmo.ru
Филиал «Торгового Дома «Эксмо» в Нижнем Новгороде
Адрес: 603094, г. Нижний Новгород, улица Карпинского, д. 29, бизнес-парк «Грин Плаза»
Телефон: +7 (831) 216-15-91 (92, 93, 94). E-mail: reception@eksmonn.ru
Филиал OOO «Издательство «Эксмо» в г. Санкт-Петербурге
Адрес: 192029, г. Санкт-Петербург, пр. Обуховской обороны, д. 84, лит. «Е»
Телефон: +7 (812) 365-46-03 / 04. E-mail: server@szko.ru
Филиал ООО «Издательство «Эксмо» в г. Екатеринбурге
Адрес: 620024, г. Екатеринбург, ул. Новинская, д. 2щ
Телефон: +7 (343) 272-72-01 (02/03/04/05/06/08)
Филиал ООО «Издательство «Эксмо» в г. Самаре
Адрес: 443052, г. Самара, пр-т Кирова, д. 75/1, лит. «Е»
Телефон: +7 (846) 207-55-50. E-mail: RDC-samara@mail.ru
Филиал ООО «Издательство «Эксмо» в г. Ростове-на-Дону
Адрес: 344023, г. Ростов-на-Дону, ул. Страны Советов, 44А
Телефон: +7(863) 303-62-10. E-mail: info@rnd.eksmo.ru
Филиал ООО «Издательство «Эксмо» в г. Новосибирске
Адрес: 630015, г. Новосибирск, Комбинатский пер., д. 3
Телефон: +7(383) 289-91-42. E-mail: eksmo-nsk@yandex.ru
Обособленное подразделение в г. Хабаровске
Фактический адрес: 680000, г. Хабаровск, ул. Фрунзе, 22, оф. 703
Почтовый адрес: 680020, г. Хабаровск, А/Я 1006
Телефон: (4212) 910-120, 910-211. E-mail: eksmo-khv@mail.ru
Республика Беларусь: ООО «ЭКСМО АСТ Си энд Си»
Центр оптово-розничных продаж Cash&Carry в г. Минске
Адрес: 220014, Республика Беларусь, г. Минск, проспект Жукова, 44, пом. 1-17, ТЦ «Outleto»
Телефон: +375 17 251-40-23; +375 44 581-81-92
Режим работы: с 10.00 до 22.00. E-mail: exmoast@yandex.by
Казахстан: «РДЦ Алматы»
Адрес: 050039, г. Алматы, ул. Домбровского, 3А
Телефон: +7 (727) 251-58-12, 251-59-90 (91,92,99). E-mail: RDC-Almaty@eksmo.kz
Полный ассортимент продукции ООО «Издательство «Эксмо» можно приобрести в книжных
магазинах «Читай-город» и заказать в интернет-магазине: www.chitai-gorod.ru.
Телефон единой справочной службы: 8 (800) 444-8-444. Звонок по России бесплатный.
Интернет-магазин ООО «Издательство «Эксмо»
www.eksmo.ru
Розничная продажа книг с доставкой по всему миру.
Тел.: +7 (495) 745-89-14. E-mail: imarket@eksmo-sale.ru