company_banner

Microsoft ♥️ Python

    Традиционно считается, что Microsoft хорошо поддерживает языки программирования на платформе .NET: C# или F#. Но это не совсем так — облако Azure поддерживает целый спектр языков, среди которых Python занимает почетное место. А если речь заходит о машинном обучении, то Python здесь любимчик.



    Какие сервисы в Microsoft ориентированы на Python, как их использовать и почему Microsoft и Python вместе навсегда, расскажет Дмитрий Сошников (@shwars).

    Дмитрий Сошников работает в Microsoft 13 лет, 10 из которых — технологическим евангелистом. В Microsoft Дмитрий защищает пользователей продуктов для разработчиков от разработчиков продуктов в роли Cloud Developer Advocate. Когда возникают проблемы с продуктами компании, ему можно пожаловаться (идеально в виде issue на GitHub). Дмитрий не только посочувствует, но и передаст жалобу разработчикам компании.



    Изначально в Microsoft я занимался популяризацией .NET среди студентов и разработчиков компании. Поэтому меня больше знают в .NET-сообществе.

    Ещё студентом и аспирантом писал на Java, позже полюбил C#, а ещё позже перешел на F# и стал преподавать функциональное программирование в МФТИ. Разработал популярные онлайн-курсы по C# и F# на русском языке и написал книгу по F# — первую на русском языке. Организовал больше 20 студенческих хакатонов по всей России, курировал конкурс Imagine Cup в нашей стране. От хакатонов есть результат, например, российские команды дважды стали победителями мирового чемпионата в 2014 и 2015 годах. 

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

    Для ML-проектов пришлось пересесть с .NET-языков на Python. Это было не очень приятно, до тех пор, пока мы не научились писать на Python слегка специфичным образом, разработав собственную библиотеку mPyPl. Она упрощает разработку на Python за счет функциональных конвейеров данных.

    Код на mPyPl похож на то, как устроены пайплайны в том же самом F#, и выглядит примерно так:

    import mPyPl as mp
    
    images = (
    mp.get_files('images',ext='.jpg')
    | mp.as_field('filename')
    | mp.apply('filename','image’, lambda x: imread(x))
    | mp.apply('filename','date', get_date)
    | mp.apply(['image','date'],'result’, lambda x: imprint(x[0],x[1]))
    | mp.select_field('result')
    | mp.as_list)

    Проводить предобработку данных таким образом одно удовольствие. «Ленивые» вычисления позволяют не загружать все данные в память, а обрабатывать по мере необходимости. Наша команда из 10-15 человек перешла на такой стиль и осталась довольна. 

    Science Art


    Моя дочь любит искусство и занимается в художественной школе. Я пытаюсь заинтересовать её программированием, поэтому стал внимательно изучать пересечение программирования, компьютерных технологий и искусства — Science Art. Например, вот несколько работ в этом стиле. 



    Изображение справа сгенерировано генеративно-состязательной сетью. Изображение в центре — портрет, совмещённый из нескольких фотографий. Метод совмещения называется «People Blending» или «когнитивный портрет». Подробнее прочитать про то, может ли ИИ создавать искусство, можно в моей статье на хабре.

    Примечание. Если нравится генеративное искусство — подписывайтесь на инстаграм @art_of_artifical. Мы стараемся периодически выкладывать новые картины, сгенерированные нейросетью.

    Когнитивный портрет


    Основан на когнитивных сервисах Microsoft — предобученных нейросетевых моделях, доступных в облаке. Модели работают с разными возможностями ИИ и доступны в облаке. Список доступных сервисов есть на сайте Microsoft. Все возможности сервисов доступны по API. 



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



    Можно добавить свою фотографию для распознавания. Например, у моей фотографии в описании появляется «Dmitri Soshnikov holding a sign». Приятно, что разработчики Microsoft меня знают и заложили возможность распознавать моё лицо.

    Ещё один из когнитивных сервисов — Face API. Выделяет ключевые точки лица — «face landmarks». 



    Как сделать такой портрет


    Для построения когнитивного портрета берём много фотографий, ищем на них ключевые точки и «совмещаем» с помощью аффинных преобразований.

    def affine_transform(img,attrs):
        mc_x = (attrs['mouth_left']['x’]+attrs['mouth_right']['x'])/2.0
        mc_y = (attrs['mouth_left']['y’]+attrs['mouth_right']['y'])/2.0
    
        tr = cv2.getAffineTransform(np.float32([
           (attrs['pupil_left']['x’], attrs['pupil_left'][‘y’],
           (attrs['pupil_right']['x’], attrs['pupil_right']['y']),
           (mc_x,mc_y)]), target_triangle)
    
        return cv2.warpAffine(img,tr,(size,size))

    Весь код для рисования есть в репозиторий на GitHub с примерами и инструкцией. Кроме простого портрета, там есть ещё несколько интересных вариантов. 



    Самый простой способ построить свой портрет — клонировать репозиторий в Azure Notebooks. В клоне будет файл CognitivePortrait.ipynb, который содержит основной код для отрисовки портрета с подробным описанием. Также ранее на хабре я уже описывал этот процесс

    Вам понадобится триальный ключ.



    Он находится по ссылке и будет действовать неделю. Если создать бесплатный облачный аккаунт, и в нём потом объект Face API, то такой ключ будет работать год.

    Используем Python SDK: один вызов функции позволяет извлечь из фотографии все опорные точки. Дальше аффинные преобразования приводят все точки к фиксированным координатам ключевых точек на лице.
     
    Рисуйте свой портрет или придумывайте новую технику. Я экспериментировал с Биллом Гейтсом. Если вдруг вас посетит вдохновение и захочется придумать что-то своё — присылайте pull request!



    Бот


    Когда говорят о Microsoft, обычно вспоминают.NET или Visual Studio Code, а кто-то до сих пор ругается на Windows Vista. Но самое лучшее, что есть у Microsoft — это облако Microsoft Azure.



    В нём очень много разных сервисов, и очень сложно знать всё обо всех, особенно учитывая, что постоянно появляются новые. Я остановлюсь на тех, что связаны с Python, и на наиболее полезных. 

    Рассмотрим пример. Попробуем создать чат-бота, который генерирует когнитивные портреты из фотографий разных людей. Бот будет работать в Telegram: мы отправляем фотографию, она смешивается (как описано выше) с 10 последними фото, которые бот получил до вас, и возвращаем пользователю результирующий портрет.

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

    • Основа — Bot Framework. Позволяет писать ботов для разных каналов связи. С помощью Bot Framework связываем веб-приложение с Telegram, Facebook или Slack. Один код отвечает за взаимодействие с пользователем в разных каналах. 
    • На стороне сервера используем Azure Functions. Это код, который выполняется, а мы не думаем о том, какой сервер это делает. Такой подход называется «serverless».
    • Все фотографии будут храниться в облачном хранилище.

    Создаём бота


    Для начала инсталлируем библиотеку botbuilder-core:

    pip install botbuilder-core
    pip install asyncio

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

    pip install cookiecutter

    Проверяем, что установлен правильно.

    $ cookiecutter --help

    В командной строке вводим команду:

    cookiecutter
    https://github.com/microsoft/botbuilder-python/releases/download/Templates/
    echo.zip

    Команда создаёт бот Echo на основе шаблона на Python. Вводим имя, например, «echo-bot» и описание — «A bot that echoes back user response».



    Логику бота (bot.py) меняем по необходимости в файле on_message_activity. Например, переведем фразу «You said» на «Вы сказали».

    async def on_message_activity(self, turn_context: TurnContext):
    await turn_context.send_activity(f"You said '{ turn_context.activity.text }'")

    Копируем последние четыре цифры в адресе в последней строке (обычно это 3978) для следующего шага. Теперь мы готовы запустить бота.

    Примечание: подробно, как создать, тестировать, отладить и развернуть бота, описано в инструкции.

    Запускаем бота


    В окне терминала переходим в папку echo-bot и установим пакеты, которые требуются для запуска.

    pip install -r requirements.txt

    Запускаем приложение (локально):

    python app.py

    Чтобы проверить работоспособность бота, запускаем Bot Emulator. Он позволит общаться с ботом, не привязывая его к Telegram. Удобно использовать на первых порах.

    Запускаем эмулятор и нажимаем «Open Bot». В открывшемся окне в URL-адрес добавляем сохранённые цифры.



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

    Going Serverless


    Добавляем логику для обработки фотографий, которая будет реализована как Azure Functions. Azure Functions — это модель программирования. Позволяет писать функции, которые срабатывают на события в облаке.

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

    У Azure Function два режима тарификации. В первом случае функция работает на выделенной виртуальной машине, а во втором мы не думаем, что, где и как выполняется — облако само об этом позаботится. Именно такую функцию мы и будем использовать.

    Логику бота опишем Azure Function, которая реагирует на HTTP-запрос. Будем использовать шаблон HTTP-триггер.

    Создаём Azure Function.

    func init coportrait --python

    Вызываем функцию coportrait.

    cd coportrait

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

    func new --name pdraw --template "HTTP trigger"

    Запускаем локально.

    func start

    Появится локальный адрес для вызова функции. Если перейдём по ссылке, то попадем на «временный» сайт.



    Основная страница сайта выглядит как заглушка. Внутри сайта работает наш API — нужно лишь добавить к адресу сайта /api/pdraw (в соответствии с названием нашей функции). Вызываем API и проверяем, что всё работает.



    Анатомия Azure Function


    В Azure Function есть две важные части. Первая — Python-функция __init__.py. Эта функция получает аргументы и возвращает ответ.

    def main(req:func.HttpRequest) -> func.HttpResponse:
        logging.info('Execution begins…')
    
        name = req.params.get('name')
        
        if name:
            return func.HttpResponse(f"Hello {name}!")
        else:
            return func.HttpResponse(
                "Need name query parameter",
                status_code=400)

    Вторая — файл function.json, который описывает, какие данные поступают на вход и выход. Это называется интеграции.

    {
        "scriptFile": "__init__.py",
        "bindings": [
            {
                "authLevel": "function",
                "type": "httpTrigger",
                "direction": "in",
                "name": "req",
                "methods": [
                    "get",
                    "post"
                ]},
            {
                "type": "http",
                "direction": "out",
                "name": "$return"
    }]}

    На вход подается запрос, в котором используются методы GET и POST, и функция срабатывает по HTTP-триггеру. Входы и выходы могут быть разными. Например, как выход, может использоваться файл в хранилище, чтобы функция работала невидимо внутри облака, выполняя определённые действия. С помощью Azure Function возможно построить всю микросервисную архитектуру нашего приложения.

    Алгоритм Azure Function


    Что будет делать наша Azure Function?

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

    Как работать с хранилищем, подробно описано в документации, а если коротко — создаем объект BlockBlobService.

    blob = BlockBlobService(account_name=acct_name, account_key=acct_key)
    
    body = req.get_body()
    
    sec_p = int((end_date-datetime.datetime.now()).total_seconds())
    name = f"{sec_p:09d}-"+time.strftime('%Y%m%d-%H%M%S')

    Дальше создаём blob из последовательности байт.

    blob.create_blob_from_bytes("cin",name,body)

    Последовательность байтов из запроса (фотографию) помещаем в хранилище.

    img = imdecode(body)

    Есть один нюанс — мы смешиваем фотографии с десятью последними. Поэтому у нас должна быть возможность запросить последние 10 файлов в blob.

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

    Можно поступить и хитрее. Список всех файлов из blob выдаётся в алфавитном порядке, поэтому мне нужно придумать такую сортировку файлов, которая бы помещала последние файлы в алфавитном порядке вначале. Подумав не очень долго, придумал считать число секунд до 2021 года. Это число с лидирующими нулями вставляю в начало имени файла: все файлы сортируются в обратном порядке и я могу получить последние 10 фотографий. При этом возникнет «проблема 2021 года», но пока предпочитаю её игнорировать.

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

    cogface = cf.FaceClient(endpoint,CognitiveServicesCredentials(key))
    res = cogface.face.detect_with_stream(io.BytesIO(body),
        return_face_landmarks=True)

    Если на картинке найдено лицо — применяем аффинное преобразование.

    if res is not None and len(res)>0:
        tr = affine_transform(img,res[0].face_landmarks.as_dict())

    Трансформированное изображение сохраняем в другое blob-хранилище — cmapped

    _,body = cv2.imencode('.jpg',tr)
    blob.create_blob_from_bytes("cmapped",name,body.tobytes())

    В завершение работы, Azure Function формирует результат: запрашивает из blob 10 последних изображений и усредняет, получая единое изображение. Запрашиваем:

    imgs = [ imdecode(blob.get_blob_to_bytes("cmapped",x.name).content)
        for x in itertools.islice(blob.list_blobs("cmapped"),10) ]

    Берем cmapped — содержимое blob, и применяем хитрую функцию itertools.islice. Получаем из blob изображения, декодируем с помощью OpenCV и у нас готов список изображений. Превращаем его в np.array и усредняем по первой оси с изображениями. Также помним, что изначально массив целочисленный, а для усреднения нам нужно его привести к float и обратно.

    imgs = np.array(imgs).astype(np.float32)/255.0
    res = (np.average(imgs,axis=0)*255.0).astype(np.uint8)

    На выходе получаем усредненное изображение, которое записываем в ещё одно хранилище — финальное.

    b = cv2.imencode('.jpg',res)[1]
    r = blob.create_blob_from_bytes("out",f"{name}.jpg",b.tobytes())

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

    return func.HttpResponse(f"https://{act}.blob.core.windows.net/out/{name}.jpg")

    Публикуем Azure Function в облако


    Теперь создаём Azure Function и помещаем в облако. Для этого удобно использовать Azure CLI, которая позволяет проводить все операции с помощью командной строки.

    az functionapp create --resource-group <group>
        --os-type Linux
        --consumption-plan-location westeurope
        --runtime python --runtime-version 3.7 --functions-version 2
        --name <APP_NAME> --storage-account <STORAGE_NAME>

    Примечание: полная инструкция по развертыванию в документации.

    Но мне больше нравится работать визуально на портале. Я так лучше понимаю, что происходит. На портале находим приложение Function App и создаём его — нажимаем «Create».



    Настраиваем: указываем название, выбираем «Опубликовать код» и ОС Linux. План — «Serverless»: будем платить только за число вызовов функции, без выделенной машины. При этом Azure будет обеспечивать нам автоматическое масштабирование.



    Создали функцию в облаке — теперь она доступна как ресурс. Пока это только «контейнер» для других функций. Чтобы их загрузить, публикуем функцию в облако:

    $ func azure functionapp publish pdraw

    Для этой функции также нужно написать requirements.txt. Если мы использовали OpenCV, обязательно это указываем.

    Пакеты разворачиваются в облаке и, в конечном итоге, функция начинает работать удаленно. Используя Postman, можем проверить, как она выполняется в интернете — для этого достаточно изменить адрес вызова.

    Если вдруг что-то не заработало — на портале можно посмотреть логи выполнения функции — какие ошибки она выдает, когда выполняется в облаке. Например, если забыли указать пакеты в requirements.txt, это сразу будет видно.

    Когда функция выполняется, в терминале копируем URL.



    URL содержит специальный код, который нужно указать, чтобы функция правильно вызвалась. Код передаем вместе с URL — это «ключ» для дополнительной безопасности. 



    Привязываем бот к сервису


    Добавим код в функцию on_message_activity внутри бота.

    api_url="https://coportrait.azurewebsites.net/api/pdraw?code=..."
    
    async def on_message_activity(self, turn_context: TurnContext):
        a = tc.activity
        if a.attachments is not None and len(a.attachments)>0:
            url = a.attachments[0].content_url
            r = requests.get(url)
            res = requests.post(api_url,data=r.content)
            url = res.text
            message = MessageFactory.attachment(
                CardFactory.hero_card(
                    HeroCard(title="Here is your cognitive portrait",
                    images=[CardImage(url=url)])))
            await tc.send_activity(message)
        else:
            await tc.send_activity(f"Please attach a photograph")

    Наш бот теперь умный — будет обрабатывать сообщения от пользователя. Как он это делает:

    • ищет a.attachments;
    • если они есть, берет URL первого;
    • c URL считывает бинарный объект (картинку) и передает Azure Function;
    • вызывает функцию и получает в ответ URL — res.text;
    • возвращает hero_card, чтобы показать в боте картинку;
    • для этого hero_card передает URL картинки;
    • если вложений нет, бот пишет сообщение с просьбой добавить фотографию.

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

    Публикация бота в облако


    Возможно, это самый сложный этап. Чаще ботов пишут на Node.js или C#, поэтому для этих языков есть много документации, примеров и поддержка на портале. Для Python ситуация чуть хуже, но разобраться можно.

    Вот подробная инструкция по публикации ботов в облако. Если кратко, то для бота мы должны создать два приложения:

    • веб-приложение — в нём будет выполняться наш бот;
    • объект, который будет связывать приложение с Telegram.

    Первое, что мы делаем — создаем в Azure Directory приложение с названием «blenderbot» и передаем ему пароль для аутентификации. 

    az ad app create --display-name "blenderbot" --password "%PASSWD%" --available-to-other-tenants

    Когда приложение создаётся, оно на выходе выдает некоторый appID.



    Берём его из JSON-вывода и запоминаем в переменную.



    Следующей командой разворачиваем одновременно веб-приложение и объект, который связан с Telegram.



    В этом помогает технология deploymentTemplates — позволяет одной командой создать внутри облака сразу несколько объектов. deploymentTemplates идёт в комплекте с кодом бота, который мы раньше сгенерировали через cookiecutter. Это удобно — вызываем deploymentTemplates и он создаёт в облаке всё, что нужно.

    Дальше создаём zip-архив директории со всеми файлами и requirements.txt,…

    bot.zip

    … вызываем Azure Deploy и всё помещаем в облако.

    az webapp deployment source config-zip

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

    Что в облаке


    В результате в облаке появился объект «blenderbot».



    Чтобы проверить, что всё работает, выбираем раздел «Test in Web Chat» и можем поговорить с ботом прямо через интерфейс. Теперь свяжем его с Telegram:

    • создаём бота в Telegram, начав беседу c системным ботом @botfather (примечание: документация от Telegram, как это сделать);
    • даём имя;
    • от Telegram получаем access token;
    • копируем токен и добавляем его в настройки нашего бота в панели управления Azure;
    • находим бота в контактах и общаемся. 



    Экспериментируйте


    Мы запустили бота Telegram, по пути изучив облачные веб-технологии Microsoft, которые поддерживают Python. 


    Подключите @peopleblenderbot в Telegram, но помните, что все фотографии оседают в хранилище и показываются другим пользователям. А если хотите поэкспериментировать с разными новыми алгоритмами рисования — экспериментируйте и присылайте pull requests!

    В облаке Microsoft есть место всем языкам и технологиям, а Python занимает там почётное место!

    Это была расшифровка доклада Дмитрия с весенней онлайн-конференции Moscow Python Conf 2020. Следующая конференция пройдет осенью и будет называться Python Live 2020. «Live» — потому что это онлайн-конференция о Python с упором на живое общение и обсуждение. Нас ждёт четыре дня концентрированного опыта (с 14 по 18 сентября) от разработчиков Яндекса, Мэйл.ру, Microsoft (в том числе и Дмитрия), Parallels, Lamoda и других компаний, общение с core-девелоперами по ключевым вопросам развитию языка, и приглашённые спикеры из смежных областей, например, Go и Erlang.

    Программа конференции практически готова и скоро её можно будет посмотреть на сайте Python Live 2020. Пока подписывайтесь на рассылку: отправим письмо когда опубликуем полную программу конференции, а еще будем присылать анонсы и расшифровки интересных докладов с прошлых конференций. Также бронируйте билеты — 1 июля цена повысится.
    Microsoft
    Microsoft — мировой лидер в области ПО и ИТ-услуг

    Комментарии 5

      +2
      Привет. Судя по всему я единственный, кто прочитал этот длинный маркетинговый текст.
      1. По тексту встречаются f-strings, значит у вас питон старше 3.7. В таком случае (да вообще уже в любом случае) `pip install asyncio` делать НЕ надо, это builtin библиотека.
      2. `if a.attachments is not None and len(a.attachments)>0` — типичный код человека, пришедшего из мира C# :) if a.attachents более чем достаточно, оно проверит и на неравенство с None и если это контейнер — на то что он не пустой.
      3. К чему тут было упоминание вашей библиотеки я не понял, но по ней тоже очень чувствуется, что люди пришли из мира энтерпрайза и теперь им хочется усложнить все пайплайнами.
        0

        Точно не единственный, я тоже это прочитал до конца. За замечания спасибо! Не очень понял, почему текст маркетинговый: я в докладе старался показать, как сделать конкретную поделку, какие сервисы выбрать и как использовать для этого.
        Насчет пайплайнов, вопрос интересный: если мне нужно, к примеру, взять набор картинок из директории, применить к ним ряд преобразований (масштабирование, поворот, что-то ещё из компьютерного зрения) и потом это подать на вход нейросети — как это сделать кошерно, Pythonic way? В keras, например, есть ImageDataGenerator, но он решает одну конкретную задачу. Если я хочу например формировать входные данные для тренировки сиамской сети, или triplet loss — мне нужно делать свой DataGenerator на основе существующего. В то же время функциональный подход позволяет строить композицию простых шагов обработки, и из них собирать требуемую функциональность как из конструктора. Я беседовал с людьми на ряде питоновских конференций, и кажется такой подход выглядит вполне симпатичным.

          0
          Такие штуки в питоне делаются на итераторах и генераторах. Если же очень хочется принести именно пайплайны — стоило посмотреть на их реализацию в sklearn.pipeline
            0

            Наши пайплайны — это и есть генераторы, поверх которых навешен синтаксис пайплайнов из библиотеки Pipe и набор функций, позволяющих функционально описывать операции над полями структур, проходящих через цепочки обработчиков. Образно говоря, мы сделали для Python то, чем является LINQ для классического C#

              0
              pythonic и unpythonic это же про синтаксис и оформление, а не про то, что внутри. Скрывать генераторы с помощью неявного синтаксиса впрямую противоречит второму коану питона:
              www.python.org/dev/peps/pep-0020

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

      Самое читаемое