Самообновляющиеся нейростикеры

Ссылка на телеграмовский стикерпак.

Когда я увидел, что сделали в process.studio, я сразу понял, что невозможно из этого не сделать стикерпак для телеграма. Вопрос лишь в том, кто успеет первым. А тут ещё в студии Лебедева вышел самообновляющийся генеративный стикерпак Стикератор.

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

Злые: Злые

Приунывшие: Приунывшие

В шоке: В шоке

Счастливые, насколько это возможно: Счастливые, насколько это возможно

Опыта с нейросетями у меня никакого не было, поэтому мне помогал Саша Мохов, за что ему большое спасибо. Он помог выбрать инструмент и помогал советом в тупиковых ситуациях.

Картинки для обучения сети скачал с Эмоджипедии:

import lxml.html
from lxml.cssselect import CSSSelector as css
import requests
from urllib.request import urlretrieve


response = requests.get('https://emojipedia.org/people/')
tree = lxml.html.fromstring(response.content)
links = css('.emoji-list>li>a')(tree)

for l in links:
    url = l.get('href')[1:]
    if url == 'serious-face-with-symbols-covering-mouth/':
        # stops downloading on specific emoji
        break
    print(url)
    response = requests.get('https://emojipedia.org/people/'+url)
    tree2 = lxml.html.fromstring(response.content)
    imgs = css('.vendor-image>img')(tree2)
    index = 0
    for i in imgs:
        index += 1
        img_url = i.get('src')
        filename = img_url.split('/')[-1].split('.')
        filename[0] += '_'+str(index)
        filename = '.'.join(filename)
        print('       - '+img_url)
        urlretrieve(img_url, '/Users/z/Downloads/Neuroji_images/'+filename)

Сеть обучил на бесплатном Google colab. Это офигенный сервис, можно запускать тяжёлые вычисления на серверах гугла и не греть свой ноутбук. Да и быстрее выходит. Результат обучения сохранил как два файла: структура нейросети и веса её связей.

model_17500.h5
model_17500.json

Модель делается долго, но её нужно сделать всего один раз, после этого для получения смайликов нужно совсем немножко ресурсов.

Смайлики генерятся другим питоновским скриптом. Он при запуске загружает модель нейросети, потом по таймеру создаёт стикеры и заливает их в пак с помощью либы python-telegram-bot:

%matplotlib inline

import telegram
from telegram.ext import Updater, CommandHandler

from PIL import Image, ImageDraw
from random import randrange
from io import BytesIO

import matplotlib.pyplot as plt
import numpy as np

from keras.layers import Dense, Flatten, Reshape
from keras.layers.advanced_activations import LeakyReLU
from keras.models import Sequential
from keras.optimizers import Adam
from keras.models import model_from_json

import time
import sys

def sample_images(number=1):
    global bot
    global model
    
    print("Hello, World!")
    
    z = np.random.normal(0, 1, (number, z_dim))

    gen_imgs = model.predict(z)
    print('Generated {} images'.format(number))
    gen_imgs = 0.5 * gen_imgs + 0.5

    clear()

    for i in range(number):
        im_ar = (gen_imgs[i, :, :, :]) * 256.
        pil_im = Image.fromarray(im_ar.astype('uint8'), 'RGBA')
        pil_im = pil_im.resize((512,512), Image.NEAREST)
        bio = BytesIO()
        bio.name = 'image.png'
        pil_im.save(bio, 'PNG')
        bio.seek(0)
        bot.add_sticker_to_set(user_id = 115178271, 
                               name = 'neuroji_by_neuroji_bot',
                               png_sticker = bio,
                               emojis = '😶')
    bot.send_message(chat_id='@zbottesting', text='{} neurojis added!'.format(number))

def clear():
    global bot
    stickers = bot.get_sticker_set('neuroji_by_neuroji_bot').stickers
    for s in stickers:
        bot.delete_sticker_from_set(s.file_id)

z_dim = 100 # Size of the noise vector, used as input to the Generator
number = 80

# load json and create model
json_file = open('model_17500.json', 'r')
model_json = json_file.read()
json_file.close()
model = model_from_json(model_json)
model.load_weights("model_17500.h5")
print("Loaded model from disk")

bot = telegram.Bot('851927713:Aa0hGqlACKcGD-deGbTX5BPYdfv3biNywRE')  #FIXME paste your token here
updater = Updater(bot = bot, use_context=True)
j = updater.job_queue

updater.dispatcher.add_handler(CommandHandler('clear', clear))

def recurring_job():
    try:
        sample_images(number)
    except: # catch *all* exceptions
        e = sys.exc_info()[0]
        bot.send_message(chat_id='@zbottesting', text='{}'.format(e))
    time.sleep(60*5)
    recurring_job()
    
recurring_job()

Рядом должны лежать файлы обученной модели:

model_17500.h5
model_17500.json

Этот код крутится на самом дешёвом сервере DigitalOcean. Причём я смешно запускаю: не просто питоновский файл, а целый jupyter notebook, на котором уже питоновский скрипт работает. Не уверен, что кроме меня так кто-то делает, но мне удобно, я к юпитеру привык.

Запускаю код так. Сперва подключаюсь к серверу по SSH:

ssh -L 8080:localhost:8080 root@64.225.28.54

Запускаю на удалённом сервере Jupyter notebook. Чтобы он продолжал работать после закрытия терминала, надо запускать его хитро:

nohup jupyter nonohup jupyter notebook --allow-root --port 8080 &

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



Ссылка на телеграмовский стикерпак.

Клёво было бы сделать естественный отбор, чтобы самые популярные выживали. Но, кажется, АПИ телеграма не отдаёт статистику об отдельных стикерах.

UPD: Время от времени вылезали ошибки и приходилось перезапускать скрипт в юпитере. Поэтому переделал по-нормальному: теперь слегка переделанный скрипт запускается по крону. Для этого открыл редактор crontab -e и добавил строчку

*/5 * * * * /usr/bin/python3 /root/cron-neuroji.py

Выбрать нужный интервал запуска можно на crontab.guru