Вступление

В России модно грустить. Популярная культура под разными соусами толкает нам депресняк: всякие майки "Россия для грустных", фестивали "боль", подборки "Russian doomer music" на ютубе и так далее. Рунет завален однотипными постпанком, панельками и серостью, которые тем не менее довольно много людей видят чем-то родным и уютным, ощущают частью своего культурного кода. Мне стало интересно, сможет ли ИИ если не победить то хотя бы возглавить эту моду и затронуть струны душ рашн думеров. Ведь что может быть более бессмысленно и тоскливо чем нейросеть, рисующая гипертрофированно-хтоническую Россию? Так появился проект нейроссия.

Собираем данные

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

Примеры скачанных фотографий

Чтобы выкачать фотки, лайки и комменты я воспользовался python-библиотечкой vk_api. Этот модуль позволяет работать с методами api вконтакта, а также обрабатывать капчу и двухфакторную аутентификацию. В качестве примера приведу скрипт скачивающий фоточки из последнего поста на стене сообщества:

Качаем картинки из последнего поста на стене группы
import os
import vk_api
import urllib.request

def captcha_handler(captcha):
    print (f"url: {captcha.get_url()}\n")
    key = input("Enter captcha code: ")
    return captcha.try_again(key)

def auth_handler():
    code = input("Enter 2FA code: ")
    return (code, True)

def save_post_pictures(post, imgfolder, imres):
    pictures = []
    
    if not os.path.exists(imgfolder):
        os.makedirs(imgfolder)
    
    for attachment in post['attachments']:
        if attachment['type'] == 'photo':
            photo = attachment['photo']
            for size in photo['sizes']:
                if size['type'] == imres:
                    url = size['url']
                    filename = ('_'.join(url.split('/')[-2:])).split('?')[0]
                    urllib.request.urlretrieve(url, os.path.join(imgfolder, filename))
                    pictures.append(filename)
                    
    return pictures

phone = input("phone ")
password = input("password ")
domain = 'plattenbauten' # vk.com/plattenbauten

sess = vk_api.VkApi(phone, password, captcha_handler=captcha_handler, auth_handler=auth_handler)
sess.auth()
api = sess.get_api()

posts = api.wall.get(domain=domain, count=1)['items']
save_post_pictures(posts[0], domain, 'z') # https://vk.com/dev/photo_sizes - z максимальный размер

Так я накачал чуть больше чем 100к фоточек из нескольких пабликов. Дальше встал вопрос, как их отфильтровать. Для фильтрации я использовал детектор EfficientDet (чтобы выкинуть из датасета фотки с людьми, машинами, котиками и.т.д, оставив только пейзажи), а также совсем чуть-чуть NLP в лице NLTK чтобы обработать подписи к фотографиям - сделать стемминг слов и найти по тегам то что не нашёл детектор. В итоге осталось около 10к фотографий.

Обучаем стайлган

Я использовал архитектуру Stylegan2 с аугментациями дискриминатора. Аугментации (повороты, отзеркаливания, кропы) позволяют получать чуть лучшие результаты на маленьких датасетах. Вот в этом репозитории оригинальный код, а вот здесь - моя наколеночная модификация для запуска в Google Colab и генерации видео по ключевым кадрам (об этом дальше).

У этой реализации стайлгана есть одна особенность - на вход ему нужны данные в формате .tfrecord (как их получить из картинок описано в readme). А особенность заключается в том, что картинки записываются в tfrecord-ы без сжатия, что в условиях работы на Colab может быть критично. Так, например, датасет из 10 тысяч картинок разрешения 1024х1024, будучи сконвертированным в .tfrecord забьёт диск Колаба под завязку. Говорят, что есть модификация stylegan2 которая использует сжатие в .tfrecord-ах, но я её пока не проверял.

Отчего так в России берёзы шумят?

Обучение на Google Colab в бесплатной версии идёт со скоростью примерно полчаса на эпоху для разрешения 1024х1024. Когда уже на первых эпохах на генерируемых сеткой картинках проступила родная цветовая гамма, вспомнился мем про "отчего так в России берёзы шумят".

Несколько эпох спустя

Генерируем видео

В какой-то момент проект из гиковских прикольчиков превратился в прикольчики творческие. Возникла идея привлечь к этому друзей-музыкантов и сделать генеративное видео с генеративной музыкой (впоследствие трансформировавшаяся в вариативный стрим на платформе mubert). Хотелось чтобы переходы в видео были хоть немного осмысленными и согласующимися с музыкой.

Латентный вектор это промежуточное сильно сжатое представление картинки, в котором скрыты все выученные нейросетью свойства. Наверное, можно было заморочиться и найти в латентном векторе (размерности 512) компоненты отвечающие за тот или иной параметр картинки (чтобы например плавно менять освещение, сохраняя структуру картинки), но я решил что проще сгенерировать видео по ключевым кадрам. Если плавно переходить от вектора соответствующего одной картинке (ключевому кадру) к вектору соответствующему другой, то картинки также будут плавно перетекать одна в другую. Для этого я поменял несколько строк в скрипте generate.py так, чтобы на вход он принимал папку с латентными векторами выбранных картинок, и список, в котором эти картинки должны появляться в видео.

В заключение

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

Ещё картиночки

Ссылки

Нейроссия в ВК

Нейросеть Stylegan2-ada

Python-обёртка api вконтакте