автордың кітабын онлайн тегін оқу Python для хакеров. Нетривиальные задачи и проекты
Переводчик Д. Брайт
Ли Воган
Python для хакеров. Нетривиальные задачи и проекты. — СПб.: Питер, 2023.
ISBN 978-5-4461-2968-3
© ООО Издательство "Питер", 2023
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
Посвящается моему дяде, Кеннету П. Вогану. Ты всегда и везде нес свет
Об авторе
Ли Воган — программист, поклонник поп-культуры, консультант, автор книги «Impractical Python Projects»1 (No Starch Press, 2018). За десятилетия работы научным руководителем в компании ExxonMobil он занимался проектированием и анализом компьютерных моделей, разрабатывал и тестировал программное обеспечение и, кроме того, обучал геофизиков и инженеров.
Обе свои книги, «Непрактичный Python» и «Python для хакеров», он написал для тех, кто самостоятельно изучает Python и хочет отточить свое мастерство, выполняя увлекательные и нетривиальные проекты.
1 «Непрактичный Python. Занимательные проекты для тех, кто хочет поумнеть».
«Непрактичный Python. Занимательные проекты для тех, кто хочет поумнеть».
Ли Воган — программист, поклонник поп-культуры, консультант, автор книги «Impractical Python Projects»1 (No Starch Press, 2018). За десятилетия работы научным руководителем в компании ExxonMobil он занимался проектированием и анализом компьютерных моделей, разрабатывал и тестировал программное обеспечение и, кроме того, обучал геофизиков и инженеров.
О научных редакторах
Крис Крен (Chris Kren) окончил Университет Южной Алабамы со степенью магистра в сфере информационных систем. Сейчас он занимается кибербезопасностью и часто использует Python для отчетов, анализа данных и автоматизации.
Эрик Тодд Мортенсон (Eric Todd Mortenson) получил докторскую степень по математике в Висконсинском университете в Мадисоне. Он занимался исследованиями и преподаванием в Университете штата Пенсильвания, Университете Квинсленда, а также в Математическом институте Макса Планка. Сейчас он работает доцентом на факультете математики и компьютерных наук в Санкт-Петербургском государственном университете.
Благодарности
Команда No Starch Press работала над книгой во время пандемии — и совершила очередной трудовой подвиг. Их можно смело назвать профессионалами, которым нет равных, и эта книга появилась на свет лишь благодаря их усилиям. Выражаю всем сотрудникам свою глубочайшую признательность и уважение.
Также благодарю Криса Крена и Эрика Эвенчика (Eric Evenchick) за их ревью кода, Джозефа Б. Пола (Joseph B. Paul), Сару и Лору Воган — за их энтузиазм в косплее, а также Ханну Воган — за полезные фотографии.
Отдельное спасибо Эрику Т. Мортенсону за его подробную научную рецензию, содержащую множество полезных идей и дополнений. Эрик предложил добавить главу, посвященную байесовскому правилу, и предоставил множество практических проектов, а также дополнительных задач, включая применение к байесовским моделям методов Монте-Карло, подведение итогов романа по главам, моделирование взаимосвязей между Луной и «Аполлоном-8», просмотр Марса в 3D, вычисление кривой блеска для экзопланеты, обладающей луной, и некоторые другие. Благодаря его усилиям эта книга стала намного лучше.
В завершение выражаю благодарность участникам с ресурса stackoverflow.com. Одна из лучших особенностей Python заключается в его обширном и многогранном сообществе пользователей. Неважно, какой вопрос у вас возникнет, кто-нибудь на него обязательно ответит. Неважно, насколько странную задачу вы решаете, кто-нибудь наверняка уже делал что-то подобное. И всех этих людей вы можете найти на Stack Overflow.
От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах.
Введение
Если вы освоили основы работы с Python, то уже готовы писать полноценные программы для решения настоящих задач. В книге «Python для хакеров. Нетривиальные задачи и проекты» вы напишете такие программы, чтобы победить в лунной гонке на «Аполлоне-8», помочь Клайду Томбо открыть Плутон, выбрать посадочные места на Марсе, обнаружить экзопланеты, отправить суперсекретные сообщения друзьям, сразиться с ужасными мутантами, спасти моряков после кораблекрушения, убежать от зомби и сделать еще много другого — и все это с помощью языка программирования Python. Вы будете применять мощные техники компьютерного зрения, обработку естественного языка и научные модули, такие как OpenCV, NLTK, NumPy, pandas, matplotlib, а также многие другие библиотеки, созданные для облегчения жизни программистов.
Для кого эта книга?
Можно рассматривать эту книгу как пособие по Python для второкурсников. Это не руководство по основам языка, а возможность продолжить обучение, работая над реальными проектами. Таким образом, вам не придется тратить деньги и место на полке, только чтобы освежить в памяти уже известные принципы. Но я все равно буду объяснять каждый этап проекта, давать подробные инструкции по использованию библиотек и модулей, включая их установку.
Эти проекты заинтересуют всех, кто хочет использовать программирование для экспериментов, проверки теорий, моделирования природных явлений или просто для развлечения. По мере выполнения проектов вы будете накапливать знания о библиотеках Python и модулях, а также узнаете новые полезные приемы, функции и техники. Мы не будем зацикливаться на отдельных фрагментах кода; вместо этого вы научитесь создавать полноценные программы для решения реальных задач, используя реальные данные.
Почему Python?
Python — это высокоуровневый интерпретируемый язык общего назначения. Он свободно распространяемый, интерактивный и совместимый со всеми ведущими платформами, а также микроконтроллерами, например с Raspberry Pi. Python поддерживает и функциональное, и объектно-ориентированное программирование, а также способен взаимодействовать с кодом, написанным на других языках, например на C++.
Поскольку Python вполне доступен для начинающих и полезен для экспертов, он широко применяется в школах, университетах, крупных корпорациях, финансовых учреждениях и практически во всех областях науки. Сегодня этот язык наиболее популярен для машинного обучения, в областях, связанных с наукой о данных и искусственным интеллектом.
План книги
Итак, краткий обзор глав этой книги. Вам не обязательно изучать их все последовательно, но я буду объяснять новые модули и техники более подробно при их первом упоминании.
• Глава 1. Спасение моряков с помощью теоремы Байеса. Используем теорему Байеса, чтобы эффективно направить береговую охрану для поиска и спасения моряков у мыса Python. Набираемся опыта в применении OpenCV, NumPy и модуля itertools.
• Глава 2. Установление авторства с помощью стилометрии. Используем обработку естественного языка для определения автора романа «Затерянный мир» — был ли это сэр Артур Конан Дойл или же Герберт Джордж Уэллс? Практикуемся в работе с NLTK, matplotlib и такими стилометрическими техниками, как стоп-слова, части речи, лексическое богатство и коэффициент Жаккара.
• Глава 3. Суммаризация текста с помощью обработки естественного языка. Делаем скрапинг известных речей из интернета и автоматическое обобщение их важных моментов. Преобразуем текст романа в аннотацию для рекламы или промоматериала. Расширяем навыки работы с BeautifulSoup, Requests, regex, NLTK, Collections, wordcloud и matplotlib.
• Глава 4. Отправка суперсекретных сообщений с помощью книжного шифра. Делимся невзламываемыми шифрами с друзьями путем цифрового воссоздания метода «одноразового блокнота», использованного в шпионском романе Кена Фоллетта «Ключ к Ребекке». Учимся работать с модулем Collections.
• Глава 5. Поиск Плутона. Воссоздаем блинк-компаратор, с помощью которого Клайд Томбо открыл Плутон в 1930 году. Затем используем современное компьютерное зрение для автоматического поиска и отслеживания слаборазличимых транзиентов, таких как кометы и астероиды, перемещающихся относительно звездного поля. Получаем опыт работы с OpenCV и NumPy.
• Глава 6. Победа в лунной гонке с помощью «Аполлона-8». Принимаем участие в приключении и помогаем США победить в лунной гонке, первыми достигнув Луны на корабле «Аполлон-8». Составляем и реализуем грамотный план обратного полета, который в ретроспективе убедил NASA отправиться в полет на год раньше и по факту нанес удар по советской космической программе. Набираемся опыта в использовании модуля turtle.
• Глава 7. Выбор мест высадки на Марсе. Оцениваем потенциальные места посадки для марсохода на основе реальных задач миссии. Отображаем предполагаемые точки на карте Марса вместе с их сводной статистикой. Совершенствуем навыки работы с OpenCV, Python Imaging Library, NumPy и tkinter.
• Глава 8. Обнаружение далеких экзопланет. Моделируем проход экзопланеты на фоне ее солнца, отображаем график итоговых изменений относительной яркости и оцениваем диаметр этой планеты. В завершение симулируем прямое наблюдение экзопланеты с помощью нового телескопа Джеймса Уэбба, включая оценку длительности ее дня. Используем OpenCV, NumPy и matplotlib.
• Глава 9. Как различить своих и чужих. Программируем роботизированную пушку-стража на визуальное распознавание космических пехотинцев и злых мутантов. Применяем OpenCV, NumPy, playsound, pyttsx и datetime.
• Глава 10. Ограничение доступа по принципу распознавания лиц. Реализуем ограничение доступа в секретную лабораторию через распознавание лиц. Используем OpenCV, NumPy, playsound, pyttsx и datetime.
• Глава 11. Создание интерактивной карты побега от зомби. Создаем карту плотности популяции, которая поможет выжившим в ТВ-шоу «Ходячие мертвецы» выбраться из Атланты на безопасную территорию Запада США. Совершенствуемся в работе с pandas, bokeh, holoviews и webbrowser.
• Глава 12. Находимся ли мы в компьютерной симуляции? Определяем способ для симулированных существ — возможно, это мы сами — отыскать свидетельства того, что они живут в компьютерной симуляции. Используем для этого turtle, statistics и perf_counter.
Каждая глава завершается как минимум одним практическим или усложненным проектом. Их решения вы найдете в приложении или онлайн. Учтите, что эти решения не единственные и не обязательно лучшие. Так что, возможно, вам удастся придумать что-то более эффективное.
Что же касается усложненных проектов, то здесь все зависит только от вас. В них я реализую принцип «плыви или тони», который здорово помогает в обучении. Надеюсь, моя книга сможет мотивировать вас на создание собственных проектов, а такие задачи играют роль триггеров, которые взбудоражат ваше воображение.
Можете скачать весь код книги, включая решения к практическим проектам, с сайта https://nostarch.com/real-world-python/. Там же я публикую список опечаток и всевозможные будущие обновления.
Невозможно написать подобную книгу без недочетов. Если вы обнаружите, что в книге что-то не так, пожалуйста, отправьте описание проблемы издателю по адресу errata@nostarch.com. Мы будем вносить все необходимые правки в список опечаток и включим исправление в переиздания, а вы получите вечное признание и славу.
Версия Python, платформа и IDE
Все проекты этой книги я создавал на Python v3.7.2 в Microsoft Windows 10. Если вы используете другую операционную систему, то это не проблема: там, где необходимо, я предлагаю совместимые модули для других платформ.
Примеры кода в книге взяты либо из текстового редактора Python IDLE, либо из интерактивной оболочки. IDLE (Integrated Development and Learning Environment) означает «интегрированная среда разработки и обучения». Это та же интегрированная среда разработки (IDE), но с добавленной L, которая делает акроним созвучным фамилии актера Eric Idle, участника творческой группы Monty Python. Интерактивная оболочка, также называемая интерпретатором, — окно, позволяющее вам мгновенно выполнять команды и тестировать код, не создавая файл.
У IDLE множество недостатков, например отсутствует нумерация строк, но при этом она бесплатна и связана с Python, что дает каждому доступ к ней. Вы можете без проблем использовать любую IDE по своему желанию. Среди наиболее популярных могу назвать Visual Studio Code, Atom, Geany (произносится «джини»), PyCharm и Sublime Text. Они работают в разных операционных системах, включая Linux, macOS и Windows. Еще одна IDE, PyScripter, работает только в Windows. Подробный список доступных редакторов Python и совместимых платформ вы найдете на https://wiki.python.org/moin/PythonEditors/.
Установка Python
Вы можете установить Python на свою машину через дистрибутив. Если же вы решите это сделать напрямую, то инструкции для вашей операционной системы вы найдете на https://www.python.org/downloads/. На машинах с Linux и macOS Python уже обычно предустановлен. С каждой новой версией языка некоторые возможности в него добавляются, а другие исключаются, поэтому я рекомендую обновить вашу версию, если она ниже v3.6.
Щелчок на кнопке скачивания на сайте Python (рис. 1) по умолчанию устанавливает 32-битный Python.
Рис. 1. Страница скачивания Python.org с удобной кнопкой для платформы Windows
Если же вам нужна 64-битная версия, то промотайте страницу вниз до списка конкретных версий (рис. 2) и щелкните на ссылке с тем же номером версии.
Откроется окно, показанное на рис. 3. Здесь щелкните по 64-битному исполняемому файлу, который запустит мастер установки. Следуйте инструкциям и соглашайтесь с настройками по умолчанию.
Для реализации некоторых проектов в этой книге требуются нестандартные библиотеки, которые придется устанавливать отдельно. Это несложно, но можно все упростить, установив дистрибутив Python, который эффективно загружает и управляет сотнями библиотек Python. Это как все покупки сделать в одном магазине. Менеджеры пакетов в таких дистрибутивах будут автоматически находить и скачивать последние версии, включая все необходимые зависимости.
Рис. 2. Список версий на странице скачивания Python.org
Рис. 3. Список файлов для Python 3.8.2 на Python.org
К популярным дистрибутивам относится Anaconda от Continuum Analytics. Можете скачать его с https://www.anaconda.com/. Еще один интересный дистрибутив — Enthought Canopy, хотя бесплатной является лишь его базовая версия. Независимо от того, установите вы библиотеки Python отдельно или через дистрибутив, с проработкой проектов из книги не должно возникнуть проблем.
Запуск Python
После установки Python должен отобразиться в списке приложений операционной системы. При его запуске появится окно оболочки (показано на заднем плане рис. 4). Можете использовать эту интерактивную среду для запуска и тестирования сниппетов кода. Однако для написания более крупных программ вы будете использовать текстовый редактор, который позволяет сохранять код (рис. 4, на переднем плане).
Рис. 4. Окно оболочки Python (задний план) и текстовый редактор (передний план)
Для создания нового файла в редакторе IDLE щелкните File
Программу Python также можно запускать, указав ее имя в PowerShell или Terminal. Для этого потребуется перейти в каталог, где эта программа расположена. Например, если вы не запустили Windows PowerShell из правильного каталога, то вам потребуется изменить путь каталога при помощи команды cd (рис. 5).
Рис. 5. Изменение каталогов и запуск программы Python в Windows PowerShell
Подробности можно узнать на https://pythonbasics.org/execute-python-scripts/.
Использование виртуальной среды
Ну и последнее. Вы можете устанавливать зависимости для каждой главы в отдельной виртуальной среде. В Python виртуальная среда — это самостоятельное дерево каталогов, включающее установку Python и ряд дополнительных пакетов. Такие среды полезны при работе с несколькими версиями Python, так как некоторые библиотеки могут быть совместимы с одной версией, но не с другими. Кроме того, это дает возможность работать с проектами, требующими разные версии одной библиотеки. Раздельное их хранение избавляет от проблем совместимости.
Проекты из книги не требуют использования виртуальных сред, и если вы будете следовать моим инструкциям, то установите необходимые библиотеки для всей системы. Однако если вам действительно нужно изолировать пакеты от операционной системы, то рассмотрите возможность установки отдельной виртуальной среды для каждой главы книги (подробнее — на https://docs.python.org/3.8/library/venv.html#module-venv и https://docs.python.org/3/tutorial/venv.html).
Вперед!
Многие проекты для этой книги созданы на основе статистических и научных принципов, которые известны уже сотню лет, но плохо реализуются вручную. Но с появлением персональных компьютеров в 1975 году наши возможности хранить информацию, обрабатывать ее и делиться ею возросли на много порядков.
За 200 000 лет истории человечества только живущим в последние 45 лет дана привилегия использовать эти магические устройства и реализовывать немыслимые когда-то идеи.
Так воспользуемся же этой возможностью по максимуму! На следующих страницах вы с легкостью решите задачи, которые не давали покоя гениям прошлого. Вы прикоснетесь к некоторым из удивительнейших возможностей, которые появились лишь недавно. И возможно, вы даже начнете предвидеть грядущие открытия.
1. Спасение моряков с помощью теоремы Байеса
Где-то в 1740 году британский пресвитерианский священник Томас Байес решил математически доказать существование Бога. Его гениальное решение, ныне известное как теорема Байеса (она же формула Байеса), сейчас считается одной из наиболее успешных статистических концепций всех времен. Однако на протяжении 200 лет ее по большей части игнорировали, потому что сложные математические вычисления было очень трудно производить вручную. Для того чтобы в полной мере оценить потенциал теоремы Байеса, потребовалось изобрести компьютер. Теперь благодаря нашим быстрым процессорам она стала ключевым элементом в науке о данных и в сфере машинного обучения.
Поскольку правило Байеса дает математически верный способ учитывать новые данные и пересчитывать оценку вероятности события, оно применяется практически во всех областях деятельности человека, начиная со взлома кодов и прогноза, кто победит на президентских выборах, и заканчивая расчетом того, как увеличится число инфарктов при повышении холестерина. Перечень областей, в которых применяется теорема Байеса, с легкостью займет целую главу книги. Но так как для нас важнее всего спасение жизни людей, мы рассмотрим, как использовать эту теорему для помощи потерявшимся морякам.
В этой главе мы создадим игру-симуляцию поисково-спасательной операции. Игроки, действующие от лица спасателей береговой охраны, будут использовать теорему Байеса, чтобы принять решения, которые позволят как можно быстрее обнаружить моряков. Мы начнем с известных инструментов из области data science и компьютерного зрения — библиотеки Open Source Computer Vision Library (OpenCV) и NumPy.
Теорема Байеса
Теорема Байеса помогает исследователям определить вероятность какого-либо события при условии, что произошло другое взаимосвязанное с ним событие. Как сказал великий французский математик Лаплас: «Вероятность причины с учетом события пропорциональна вероятности события с учетом его причины». Базовая формула такова:
Здесь A — это гипотеза, а В — это данные. P(A/B) означает вероятность А с учетом В. Предположим, нам известно, что конкретный тест на некий вид рака не всегда точен и может давать ложноположительные результаты, которые указывают, что рак у пациента есть, хотя на самом деле его нет.
Выражение Байеса в таком случае выглядит так:
Начальная вероятность будет основана на клинических исследованиях. Например, 800 из 1000 человек, болеющих раком, могут получить положительный результат, а 100 из 1000 — ошибочный. На основе показателей заболеваемости общая вероятность наличия у конкретного человека рака может составлять всего 50 из 10 000. Итак, если общая вероятность наличия рака мала, а общая вероятность получения положительного теста относительно высока, то вероятность наличия рака при положительном тесте снижается. Если в исследованиях зафиксирована частота ошибочных результатов тестирования, то правило Байеса может скорректировать оценку измерений.
Теперь, когда принцип применения теоремы ясен, взгляните на рис. 1.1, где показаны различные варианты, следующие из теоремы Байеса, на примере заболевания раком.
Рис. 1.1. Теорема Байеса: определение терминов на примере заболевания раком
Рассмотрим следующий пример: женщина потеряла где-то дома свои очки для чтения. Она помнит, что последний раз была в них, когда находилась в рабочем кабинете, поэтому первым делом идет искать в кабинет. Очки ей найти не удается, но она видит чайную чашку и вспоминает, что ходила на кухню. В этот момент ей надо сделать выбор: более тщательно обыскать кабинет или же пойти поискать на кухню. Она решает отправиться на кухню. Таким образом, она, сама не зная того, приняла байесовское решение.
В кабинет она отправилась, так как считала, что вероятность обнаружения очков там наиболее высока. На языке Байеса эта начальная вероятность обнаружения очков в кабинете называется априорной. После беглого осмотра кабинета она изменила свое решение на основе двух новых элементов информации: ей не удалось сразу обнаружить очки, но она увидела чашку. Этот фактор называется байесовским выводом — когда по мере появления дополнительных фактов вычисляется новая апостериорная оценка (P(A/B) на рис. 1.1).
Представим, что женщина решила использовать при поиске теорему Байеса. Она определила вероятность нахождения очков в кабинете или на кухне и эффективность своих поисков в этих двух помещениях. Теперь вместо интуитивных ощущений ее решения опираются на математическую модель, которую можно постепенно обновлять, если дальнейшие поиски не дадут результата.
На рис. 1.2 показаны вероятности поиска очков.
Рис. 1.2. Начальные вероятности того, что очки находятся в каждом помещении, и эффективности поиска (слева) в сравнении с обновленными целевыми вероятностями (справа)
Левая схема показывает начальную ситуацию. Правая — ситуацию после применения правила Байеса. Предположим, что изначально шанс найти очки в кабинете составлял 85 %, а на кухне — 10 %. Другим комнатам присвоена вероятность 1 %, потому что правило Байеса не может обновлять целевые вероятности, равные нулю (к тому же всегда есть небольшой шанс, что женщина оставила очки в одной из этих комнат).
На левой схеме числа после слэша показывают вероятность эффективности поиска (search effectiveness probability, SEP), то есть оценку эффективности осмотра области. Так как женщина обыскала пока только кабинет, это значение для всех остальных комнат равно нулю. После байесовского вывода (обнаружения чашки) женщина может повторно вычислить вероятности на основе результатов поиска (показано справа). Теперь кухня становится наиболее предпочтительным местом для поиска, но при этом также возрастает вероятность и для других комнат.
Человеческая интуиция подсказывает: если что-то лежит не там, где мы рассчитываем, то шансы на то, что оно находится где-то в другом месте, возрастают. Теорема Байеса это учитывает, в связи с чем повышается вероятность того, что очки находятся в какой-то из других комнат. Однако это может произойти, только если изначально была вероятность их нахождения в другой комнате.
Формула для вычисления вероятности того, что очки находятся в заданной комнате, с учетом эффективности поиска выглядит так:
Здесь G — это вероятность того, что очки находятся в комнате, E —эффективность поиска, а Pprior — априорная, или начальная, оценка вероятности до получения новых данных.
Вы можете получить обновленную вероятность того, что очки находятся в кабинете, подставив целевую вероятность и вероятность поиска в уравнение таким образом:
Как видите, простая математика может быстро стать неподъемной, если делать вычисления вручную. К счастью, мы живем в чудесную компьютерную эпоху, что позволяет поручить всю нудную работу Python.
Проект #1. Поиск и спасение
Сейчас вы напишете на Python программу, которая использует теорему Байеса для поиска одинокого моряка, пропавшего у мыса Python. Как руководитель поисково-спасательных операций береговой охраны вы уже опросили его жену и определили, когда и где моряка засекли в последний раз — после этого уже прошло шесть часов. Он сообщил по радио, что покидает тонущий корабль, но нет никаких данных о том, находится ли он в спасательной лодке или дрейфует в море. Воды у мыса теплые, но если он находится в воде, то примерно через 12 часов наступит переохлаждение организма. При условии, что он одет в спасательный жилет и ему благоволит судьба, у него есть шансы продержаться три дня.
У мыса Python сложные океанические течения (рис. 1.3), сейчас дует юго-западный ветер. Видимость хорошая, но волнение почти не позволяет разглядеть человеческую голову.
Рис. 1.3. Океанические течения у мыса Python
В реальной жизни вы передали бы всю информацию в систему оптимального планирования поисково-спасательных операций (Search and Rescue Optimal Planning System, SAROPS) береговой охраны. Это программное обеспечение учитывает такие факторы, как ветер, приливы и течения, находится ли тело в воде или на лодке и пр. На основе этих данных программа генерирует прямоугольные области поиска, вычисляет начальные вероятности нахождения моряка в каждой из них и составляет график наиболее эффективных траекторий полетов.
Мы предположим, что SAROPS определила три области поиска. Все, что нужно сделать, — это написать программу, которая применяет правило Байеса. При этом ресурсов у вас хватает на поиск в двух из трех областей за день. Вам нужно решить, как эти ресурсы распределить. Это очень трудно, но у вас есть мощный помощник — теорема Байеса.
Задача
Создать поисково-спасательную игру, в которой теорема Байеса используется для определения следующего шага при выполнении поиска.
Стратегия
Поиск моряка подобен поиску очков в предыдущем примере. Вы начнете с базовых целевых вероятностей его местоположения и будете обновлять их по мере получения результатов поиска. Если вы достигнете высокой эффективности поиска в области, но ничего не найдете, то вероятность того, что моряк находится в другой области, увеличится.
Здесь, как и в реальной жизни, есть два варианта, когда все может пойти не так: тщательный поиск в области, не увенчавшийся успехом, или плохой поиск, при котором усилия целого дня затрачиваются впустую. Если приравнять это к показателям эффективности поиска, то в первом случае вы можете получить SEP, равный 0.85, но не произвести поиск моряка в оставшихся 15 % области. Во втором же случае SEP будет 0.2 и у вас останутся неисследованными 80 % области.
Можете представить себе дилемму, которая возникает у руководителей реальных спасательных операций? Послушаете ли вы собственную интуицию, проигнорировав Байеса? Будете ли придерживаться чистой холодной логики Байеса, так как верите, что это наилучший вариант? А может, будете действовать целесообразно и подстрахуете свою карьеру и репутацию, строго следуя правилу Байеса, даже когда в нем не уверены?
В помощь игроку вы, используя библиотеку OpenCV, создадите интерфейс для работы с программой. Несмотря на то что интерфейс может быть простым, например в виде встроенного в оболочку меню, вам также понадобится карта мыса и областей поиска. На карте вы будете отмечать последнее известное местоположение моряка, а также место его обнаружения. Библиотека OpenCV является отличным ресурсом для этой игры, потому что позволяет использовать изображения, а также добавлять текст и рисунки.
Установка библиотек Python
OpenCV — это крупнейшая в мире библиотека компьютерного зрения. Компьютерное зрение — область глубокого обучения, позволяющая машинам видеть, распознавать и обрабатывать изображения, подобно человеку. Зародилась OpenCV в 1999 году как инициатива Intel Research, а теперь поддерживается OpenCV Foundation, некоммерческой организацией, предоставляющей программное обеспечение бесплатно.
Написана эта библиотека на C++, но есть и поддержка других языков, а именно Python и Java. Несмотря на то что в первую очередь она предназначена для приложений компьютерного зрения, работающих в реальном времени, OpenCV также включает стандартные инструменты по работе с изображениями, подобные используемым в Python Imaging Library. На момент написания книги текущая версия библиотеки — OpenCV 4.1.
Для выполнения числовых и научных вычислений в Python этой библиотеке требуются пакеты Numerical Python (NumPy) и SciPy. OpenCV рассматривает изображения как трехмерные массивы NumPy (рис. 1.4). Это дает ей максимальную функциональную совместимость с другими научными библиотеками Python.
Рис. 1.4. Визуальное представление трехканального массива цветного изображения
OpenCV хранит свойства в виде строк, столбцов и каналов. «Форма» изображения с рис. 1.4 будет выражена кортежем из трех элементов (4, 5, 3). Каждый стек ячеек, например 0-20-40 или 19-39-59, представляет один пиксель. Числа показывают значения интенсивности каждого цветового канала для заданного пикселя.
Поскольку для многих проектов в книге нам потребуются библиотеки NumPy и matplotlib, сейчас самое время их установить.
Это можно сделать многими способами. Один из них — использование SciPy, открытой библиотеки Python, применяемой для научных и технических вычислений (подробнее — на https://scipy.org/index.html).
В качестве альтернативы: если вы собираетесь анализировать множество данных и выполнять чертежи для собственных задач, то можете скачать и использовать бесплатные дистрибутивы Python, например Anaconda или Enthought Canopy, которые работают с Windows, Linux и macOS. Эти дистрибутивы избавят вас от необходимости поиска и установки нужных версий всех необходимых научных библиотек, таких как NumPy, SciPy и т.д. Список подобных дистрибутивов вместе со ссылками на их сайты можно найти здесь: https://scipy.org/install.html.
Установка NumPy и других научных библиотек с помощью pip
Если вы хотите установить эти продукты напрямую, то используйте pip (Preferred Installer Program), систему управления библиотеками, упрощающую установку ПО для Python (подробнее — на https://docs.python.org/3/installing/). Windows и macOS версии Python, начиная с 3.4, содержат pip по умолчанию. Пользователи Linux могут установить pip отдельно. Для установки или обновления pip обратитесь к инструкциям на странице https://pip.pypa.io/en/stable/installation/ или найдите онлайн-руководство по инсталляции pip для вашей операционной системы.
Я использовал pip для установки научных пакетов, используя инструкции с https://scipy.org/install.html. Так как matplotlib требует несколько зависимостей, их также нужно установить. Для Windows выполните приведенную ниже команду (Python 3) с помощью PowerShell, запущенной (через Shift-правый клик) из каталога с текущей установкой Python:
$ python -m pip install --user numpy scipy matplotlib ipython jupyter pandas sympy nose
Если у вас установлены и Python 2, и Python 3, то вместо python используйте python3. Чтобы убедиться в том, что NumPy был установлен и стал доступен для OpenCV, откройте оболочку Python и введите:
>>> import numpy
Если ошибки не возникнет, то можно устанавливать OpenCV.
Установка OpenCV через pip
Инструкции по установке OpenCV вы найдете на https://pypi.org/project/opencv-python/. Для инсталляции OpenCV в стандартных средах (Windows, macOS и почти всех дистрибутивах GNU/Linux) введите в PowerShell или терминале следующее:
pip install opencv-contrib-python
либо
python -m pip install opencv-contrib-python
Если у вас установлено несколько версий Python (например, версии 2.7 и 3.7), то нужно указать ту версию, которую вы хотите использовать.
py -3.7 -m pip install --user opencv-contrib-python
Если в качестве посредника установки вы используете Anaconda, то можете выполнить эту команду:
conda install opencv
Чтобы убедиться в корректной установке, введите в оболочке:
>>> import cv2
Отсутствие ошибок означает, что все в порядке. Если же ошибка возникнет, то обратитесь к списку устранения неполадок по ссылке https://pypi.org/project/opencv-python/.
Код для теоремы Байеса
Программа bayes.py, которую вы напишете в этом разделе, симулирует поиск пропавшего моряка в трех смежных областях. Она будет отображать карту, выводить меню вариантов поиска, произвольно выбирать местоположение моряка и либо показывать его при успешном нахождении, либо выполнять байесовский вывод для вероятностей нахождения в каждой области. Код вместе с изображением карты (cape_python.png) можете скачать с https://nostarch.com/real-world-python/.
Импорт модулей
В листинге 1.1 программа bayes.py начинается с импорта необходимых модулей и присваивания ряда констант. Действия этих модулей мы рассмотрим при их реализации в коде.
Листинг 1.1. Импорт модулей и присваивание констант, используемых в программе bayes.py
bayes.py, part 1
import sys
import random
import itertools
import numpy as np
import cv2 as cv
MAP_FILE = 'cape_python.png'
SA1_CORNERS = (130, 265, 180, 315) # (UL-X, UL-Y, LR-X, LR-Y)
SA2_CORNERS = (80, 255, 130, 305) # (UL-X, UL-Y, LR-X, LR-Y)
SA3_CORNERS = (105, 205, 155, 255) # (UL-X, UL-Y, LR-X, LR-Y)
При импорте модулей в программу желательно упорядочить их так: модули стандартной библиотеки (Standard Library) Python, затем сторонние модули, а затем пользовательские. Модуль sys включает команды для операционной системы, такие как выход. Модуль random позволяет генерировать псевдослучайные числа. Модуль itertools помогает в работе с циклами. Наконец, numpy и cv2 импортируют библиотеки NumPy и OpenCV соответственно. Также можно присвоить им сокращенные имена (np, cv) для последующего сокращения написания кода.
Далее выполняется присваивание констант. Согласно руководству по стилю PEP8 (https://www.python.org/dev/peps/pep-0008/), имена констант следует указывать прописными буквами. Это не делает переменные неизменяемыми, но предупреждает других разработчиков, что менять их нельзя.
Карта для вымышленного мыса Python находится в файле изображения cape_python.png (рис. 1.5). Присвойте этот файл постоянной переменной MAP_FILE.
Рис. 1.5. Черно-белая карта мыса Python (cape_python.png)
Области поиска будут наноситься на эту карту в виде прямоугольников. OpenCV определит каждый такой прямоугольник по номеру пикселя в его угловых точках, так что переменную для хранения этих четырех точек нужно создать в виде кортежа. Требуемый порядок: верхний левый x, верхний левый y, нижний правый x и нижний правый y. Для представления «области поиска» используйте в имени переменной SA (search area).
Определение класса Search
Класс — это тип данных в объектно-ориентированном программировании (ООП). ООП — альтернатива функциональному/процедурному программированию. Оно особенно эффективно для больших сложных программ, так как не только производит код, который проще обновлять, поддерживать и использовать повторно, но также снижает его повторяемость. ООП строится вокруг структур данных, известных как объекты, которые состоят из данных, методов и их взаимодействий. В этом качестве оно отлично подходит для игровых программ, где обычно используются взаимодействующие объекты, например космические корабли и астероиды.
Класс — это шаблон, из которого можно создать несколько объектов. Например, у вас может быть класс, создающий линкоры в игре про Вторую мировую войну. Каждый линкор будет наследовать определенные постоянные характеристики, например тоннаж, крейсерскую скорость, уровень топлива, уровень повреждений, вооружение и т.д. При этом каждому объекту линкора также можно задать уникальные характеристики, например индивидуальное имя. После создания, или инстанцирования, отдельные характеристики каждого линкора начнут меняться в зависимости от того, сколько топлива сжигается, сколько корабль получает повреждений, сколько использует боеприпасов и т.д.
В bayes.py вы будете использовать класс в качестве шаблона для создания поисково-спасательной миссии, охватывающей три области поиска. В листинге 1.2 определен класс Search, который задает схему игры.
Листинг 1.2. Определение класса Search и метода init ()
bayes.py, part 2
class Search():
"""Байесовская игра "Поиск и спасение" с 3 областями поиска."""
def __init__(self, name):
self.name = name
❶ self.img = cv.imread(MAP_FILE, cv.IMREAD_COLOR)
if self.img is None:
print('Could not load map file {}'.format(MAP_FILE),
file=sys.stderr)
sys.exit(1)
❷ self.area_actual = 0
self.sailor_actual = [0, 0] # "локальные" координаты в области поиска
❸ self.sa1 = self.img[SA1_CORNERS[1] : SA1_CORNERS[3],
SA1_CORNERS[0] : SA1_CORNERS[2]]
self.sa2 = self.img[SA2_CORNERS[1] : SA2_CORNERS[3],
SA2_CORNERS[0] : SA2_CORNERS[2]]
self.sa3 = self.img[SA3_CORNERS[1] : SA3_CORNERS[3],
SA3_CORNERS[0] : SA3_CORNERS[2]]
❹ self.p1 = 0.2
self.p2 = 0.5
self.p3 = 0.3
self.sep1 = 0
self.sep2 = 0
self.sep3 = 0
Начнем с определения класса Search. Согласно PEP8, первая буква в имени класса должна быть прописной.
Далее определяется метод, устанавливающий начальные значения атрибутов объекта. В ООП атрибут — это именованное значение, связанное с объектом. Если объектом является человек, то атрибутом может быть его вес или цвет глаз. Методы — это атрибуты, одновременно являющиеся функциями, которым при выполнении передается ссылка на их экземпляр. Метод _init_() является особой встроенной функцией, которую Python вызывает автоматически при создании нового объекта. Он привязывает атрибуты каждого создаваемого экземпляра класса. В этом случае передаются два аргумента: self и имя, которое вы хотите использовать для объекта.
Параметр self — это ссылка на экземпляр создаваемого класса, то есть такого, для которого был вызван метод, технически называемый экземпляром контекста. Например, если вы создадите линкор с именем Missouri, тогда параметром self этого объекта станет Missouri и вы сможете вызывать для него метод, например для выстрела из тяжелых орудий, с помощью точечной нотации: Missouri.fire_big_guns(). Давая объектам уникальные имена при инстанцировании, вы сохраняете область видимости атрибутов каждого объекта отдельно от других. В этом случае повреждения, полученные одним из линкоров, не повлияют на остальной флот.
Это хорошая практика — перечислять все начальные значения атрибутов объекта под методом _init_(). Таким образом, пользователи увидят все ключевые атрибуты объекта, которые будут применяться далее в различных методах, а ваш код станет более читаемым и легким в обновлении. В листинге 1.2 это атрибуты self, например self.name.
Атрибуты, присвоенные self, будут вести себя аналогично глобальным переменным в процедурном программировании. Методы в классе смогут обращаться к ним напрямую, не требуя аргументов. Так как эти атрибуты «ограждены» ширмой класса, помех для их использования не возникает, как в случае с реальными глобальными переменными, которые присваиваются внутри глобальной области видимости и изменяются внутри локальных областей отдельных функций.
Далее присваиваем переменную MAP_FILE атрибуту self.img при помощи метода OpenCV imread() ❶. Картинка MAP_FILE черно-белая, но вам нужно добавить в нее цвет. Поэтому используем ImreadFlag как cv.IMREAD_COLOR для загрузки изображения в цветном режиме. Это настроит три цветовых канала (B, G, R) для дальнейшего использования.
Если файла изображения не существует (или пользователь ввел неверное имя файла), OpenCV выдаст не совсем понятную ошибку (NoneType object is not subscriptable). Для ее обработки используется проверка условием, которая определяет отсутствие self.img, то есть равно ли оно None. Если да, то выводится сообщение об ошибке, после чего используется модуль sys для выхода из программы. Передача в него кода выхода 1 указывает, что программа завершилась ошибкой. Если установить file=stder, то будет использован стандартный красный цвет текста для сообщения об ошибке в окне интерпретатора Python, но не в других окнах, например PowerShell.
Далее присваиваем два атрибута фактического местоположения моряка при его нахождении. Первый хранит количество областей поиска ❷, а второй — точную локацию (x,y). Пока что присвоенные значения будут плейсхолдерами. В дальнейшем мы определим метод для случайного выбора конечных значений. Обратите внимание, что для координат местоположения используется список, поскольку нам нужен изменяемый контейнер.
Изображение карты загружается в виде массива. Массив — это коллекция объектов одного типа, имеющая фиксированный размер. Массивы представляют собой эффективные с точки зрения использования памяти контейнеры, обеспечивающие быстрые числовые операции и эффективную логику адресации. Среди концепций, делающих NumPy особенно мощным инструментом, можно выделить векторизацию. Ее суть — в замене явных циклов более быстрыми выражениями массивов. Как правило, операции применяются к массивам целиком, а не к их отдельным частям. При использовании NumPy внутреннее выполнение циклов передается эффективным функциям С или Fortran, которые работают быстрее стандартных техник Python.
Чтобы иметь возможность работать с локальными координатами внутри области поиска, мы создаем из массива подмассив ❸. Обратите внимание, что для этого применяется индексация. Сначала предоставляется диапазон от верхнего левого значения y до нижнего правого y, а затем от верхнего левого x до нижнего правого x. Это особенность NumPy. Чтобы привыкнуть к ней, потребуется время, в том числе и потому, что для многих привычнее декартова система координат, где сначала идет x, а потом y.
Теперь повторим эту процедуру для следующих двух областей поиска, после чего установим предварительные вероятности нахождения моряка в каждой из них ❹. В реальности их бы предоставила программа SAROPS. Очевидно, p1 представляет область 1, p2 — область 2 и т.д. В заключение для SEP устанавливаются атрибуты-плейсхолдеры.
Рисуем карту
Внутри класса Search с помощью функциональности OpenCV мы создадим метод, отображающий базовую карту. Эта карта будет включать области поиска, масштабный отрезок и последнее известное местоположение моряка (рис. 1.6).
Рис. 1.6. Начальный игровой экран (базовая карта) для bayes.py
В листинге 1.3 определяется метод draw_map(), отображающий начальную карту.
Листинг 1.3. Определение метода для отображения базовой карты
bayes.py, part 3
def draw_map(self, last_known):
"""Отображаем базовую карту с масштабом, последними известными
координатами xy и областями поиска."""
cv.line(self.img, (20, 370), (70, 370), (0, 0, 0), 2)
cv.putText(self.img, '0', (8, 370), cv.FONT_HERSHEY_PLAIN, 1, (0, 0, 0))
cv.putText(self.img, '50 Nautical Miles', (71, 370),
cv.FONT_HERSHEY_PLAIN, 1, (0, 0, 0))
❶ cv.rectangle(self.img, (SA1_CORNERS[0], SA1_CORNERS[1]),
(SA1_CORNERS[2], SA1_CORNERS[3]), (0, 0, 0), 1)
cv.putText(self.img, '1',
(SA1_CORNERS[0] + 3, SA1_CORNERS[1] + 15),
cv.FONT_HERSHEY_PLAIN, 1, 0)
cv.rectangle(self.img, (SA2_CORNERS[0], SA2_CORNERS[1]),
(SA2_CORNERS[2], SA2_CORNERS[3]), (0, 0, 0), 1)
cv.putText(self.img, '2',
(SA2_CORNERS[0] + 3, SA2_CORNERS[1] + 15),
cv.FONT_HERSHEY_PLAIN, 1, 0)
cv.rectangle(self.img, (SA3_CORNERS[0], SA3_CORNERS[1]),
(SA3_CORNERS[2], SA3_CORNERS[3]), (0, 0, 0), 1)
cv.putText(self.img, '3',
(SA3_CORNERS[0] + 3, SA3_CORNERS[1] + 15),
cv.FONT_HERSHEY_PLAIN, 1, 0)
❷ cv.putText(self.img, '+', (last_known),
cv.FONT_HERSHEY_PLAIN, 1, (0, 0, 255))
cv.putText(self.img, '+ = Last Known Position', (274, 355),
cv.FONT_HERSHEY_PLAIN, 1, (0, 0, 255))
cv.putText(self.img, '* = Actual Position', (275, 370),
cv.FONT_HERSHEY_PLAIN, 1, (255, 0, 0))
❽ cv.imshow('Search Area', self.img)
cv.moveWindow('Search Area', 750, 10)
cv.waitKey(500)
Определяем метод draw_map() с параметрами self и last_known, указывающими последнее известное местоположение. Далее используем метод OpenCV line()для нанесения масштабного отрезка. В качестве аргументов передаем ему изображение базовой карты, кортеж из левых и правых координат (x, y), кортеж цвета отрезка и ширину отрезка.
Используем метод putText(), чтобы создать
