Ну, отдохнули и хватит. С возвращением!
В предыдущих сериях мы с вами собрали данные и обучили свою первую модель.
Затем, ужаснувшись результатам, обучили еще с десяток.
Самое время показать наше творение миру!
Экспорт модели
Для начала пересохраним модель генератора в подходящий формат, чтобы нам не пришлось тащить декларации классов на хостинг.
Создадим небольшой файлик с расширением *.py и скопируем в него код из-под спойлера ниже.
Пусть это будет jit.py:
# Заменяем path на путь к понравившейся итерации модели # Нам нужен файл *G_A.pth - генератор фото -> комикс # output_path - имя файла для экспортируемой модели # может быть любым с расширением *.jit, главное - не забыть, куда мы его сохранили path= '/checkpoints/resnet9_nowd_nodo_128to400_c8/60_net_G_A.pth' output_path ='/checkpoints/resnet9_nowd_nodo_128to400_c8/resnet9_nowd_nodo_128to400_c8_160-50-60_1.jit' import torch from torch import nn class ResnetGenerator(nn.Module): """Resnet-based generator that consists of Resnet blocks between a few downsampling/upsampling operations. We adapt Torch code and idea from Justin Johnson's neural style transfer project(https://github.com/jcjohnson/fast-neural-style) """ def __init__(self, input_nc, output_nc, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False, n_blocks=6, padding_type='reflect'): """Construct a Resnet-based generator Parameters: input_nc (int) -- the number of channels in input images output_nc (int) -- the number of channels in output images ngf (int) -- the number of filters in the last conv layer norm_layer -- normalization layer use_dropout (bool) -- if use dropout layers n_blocks (int) -- the number of ResNet blocks padding_type (str) -- the name of padding layer in conv layers: reflect | replicate | zero """ assert(n_blocks >= 0) super(ResnetGenerator, self).__init__() if type(norm_layer) == functools.partial: use_bias = norm_layer.func == nn.InstanceNorm2d else: use_bias = norm_layer == nn.InstanceNorm2d model = [nn.ReflectionPad2d(3), nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0, bias=use_bias), norm_layer(ngf), nn.ReLU(True)] n_downsampling = 2 for i in range(n_downsampling): # add downsampling layers mult = 2 ** i model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=1, bias=use_bias), norm_layer(ngf * mult * 2), nn.ReLU(True)] mult = 2 ** n_downsampling for i in range(n_blocks): # add ResNet blocks model += [ResnetBlock(ngf * mult, padding_type=padding_type, norm_layer=norm_layer, use_dropout=use_dropout, use_bias=use_bias)] for i in range(n_downsampling): # add upsampling layers mult = 2 ** (n_downsampling - i) model += [nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2), kernel_size=3, stride=2, padding=1, output_padding=1, bias=use_bias), norm_layer(int(ngf * mult / 2)), nn.ReLU(True)] model += [nn.ReflectionPad2d(3)] model += [nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0)] model += [nn.Tanh()] self.model = nn.Sequential(*model) def forward(self, input): """Standard forward""" return self.model(input) class ResnetBlock(nn.Module): """Define a Resnet block""" def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias): """Initialize the Resnet block A resnet block is a conv block with skip connections We construct a conv block with build_conv_block function, and implement skip connections in <forward> function. Original Resnet paper: https://arxiv.org/pdf/1512.03385.pdf """ super(ResnetBlock, self).__init__() self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, use_dropout, use_bias) def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias): """Construct a convolutional block. Parameters: dim (int) -- the number of channels in the conv layer. padding_type (str) -- the name of padding layer: reflect | replicate | zero norm_layer -- normalization layer use_dropout (bool) -- if use dropout layers. use_bias (bool) -- if the conv layer uses bias or not Returns a conv block (with a conv layer, a normalization layer, and a non-linearity layer (ReLU)) """ conv_block = [] p = 0 if padding_type == 'reflect': conv_block += [nn.ReflectionPad2d(1)] elif padding_type == 'replicate': conv_block += [nn.ReplicationPad2d(1)] elif padding_type == 'zero': p = 1 else: raise NotImplementedError('padding [%s] is not implemented' % padding_type) conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim), nn.ReLU(True)] if use_dropout: conv_block += [nn.Dropout(0.5)] p = 0 if padding_type == 'reflect': conv_block += [nn.ReflectionPad2d(1)] elif padding_type == 'replicate': conv_block += [nn.ReplicationPad2d(1)] elif padding_type == 'zero': p = 1 else: raise NotImplementedError('padding [%s] is not implemented' % padding_type) conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim)] return nn.Sequential(*conv_block) def forward(self, x): """Forward function (with skip connections)""" out = x + self.conv_block(x) # add skip connections return out import functools norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False) model = ResnetGenerator(3,3,64, norm_layer=norm_layer, use_dropout=False, n_blocks=9) model.load_state_dict(torch.load(path)) model.eval() model.cuda() model.half() trace_input = torch.ones(1,3,256,256).cuda() model = model.eval() jit_model = torch.jit.trace(model.float(), trace_input) torch.jit.save(jit_model, output_path)
Заменяем переменные на свои:
- path — путь к понравившейся итерации модели.
Нам нужен файл *G_A.pth — генератор фото -> комикс. - output_path — имя файла для экспортируемой модели, может быть любым, с расширением *.jit, главное — не забыть, куда мы его сохранили.
Не обязательно экспортировать генератор из последней эпохи обучения, возьмите тот, результаты которого нравятся вам больше всего.
Далее идем в консоль, переходим в папку с нашим файлом и пишем:
python jit.py
Voila! Модель готова к знакомству с внешним миром.
Документация: https://pytorch.org/docs/stable/jit.html
Если быть кратким, экспорт в jit позволяет сериализовать модель и не тащить за собой среду python, все зависимости и внешние модули, которые она могла использовать. Кроме torch, разумеется.
Хоть мы и будем хостить ее в python-среде, jit-модели можно использовать в самостоятельных приложениях.
Выбор хостинга
Буду предельно откровенен и сразу признаюсь: мой внутренний deep learning enthusiast погиб где-то на втором часу изучения возможностей хостинга с поддержкой GPU. Так что если кто-то подскажет мне недорогой Serverless GPU хостинг, я буду более чем признателен.
Платить за полноценный сервер для своих экспериментов я не планировал, поэтому искал только serverless решения.
После мучительных поползновений по многостраничным тарифным планам гугла и амазона мой выбор пал на algorithmia.com
Причин несколько:
-
Web IDE — идеальный вариант для чайников, хоть и жутко медленный, так как для проверки приходится ждать окончания билда. Вне рамок этого туториала я бы рекомендовал тестировать все локально, так как большинство ошибок возникает на этапе загрузки и сохранения файлов.
-
Минимальное количество опций — сложно умереть от преждевременной старости, не дочитав до конца список вариантов.
-
Ну и последний аргумент — за почти уже полгода экспериментов я до сих пор не потратил бесплатный стартовый баланс.
Хоть сейчас при регистрации персонального аккаунта и дают меньше, чем прошлым летом, этого все равно должно хватить на некоторое время, и уж точно на все наши эксперименты. В конце месяца накидывают еще кредитов, что не раз спасало меня от потенциального разорения.
Из минусов стоит отметить, что видеокарты там — только старые Tesla K80 на 12GB RAM, что накладывает соответствующие ограничения. В любом случае, пока мы доберемся до продакшена, мы уже будем понимать, что нам нужно от сервера.
Деплой модели
Ну что, в бой!
Регистрация
Идем на https://algorithmia.com/signup и регистрируемся. Не уверен, что есть разница, какую профессию\тип аккаунта выбирать, но если вы найдете золотоносное комбо, дающее максимум кредитов, обязательно дайте знать в комментах!
Загрузка модели
После регистрации мы окажемся в своем профиле.
Нам нужно создать папки для модели и картинок, которые она сгенерирует.
Для этого выбираем Data Sources в меню слева.
Кликаем New Data Source -> Hosted Data Collection
Назовем папку “My Models”.
В результате нас должно перекинуть на страницу со списком наших папок.
Создадим еще одну папку: New Collection -> “photo2comics_out”
Самое время загрузить нашу свежеэкспортированную модель!
Переходим в папку My Models и перетаскиваем файл с моделью в браузер, либо выбираем Upload Files из меню.
Теперь скопируем ссылку на нашу модель, она пригодится нам ниже. Для этого кликнем на троеточие справа от имени файла.
С данными покончено, переходим к непосредственно алгоритму.
Алгоритм
Возвращаемся в профиль по клику на Home в меню слева.
Далее кликаем Create New -> Algorithm и выбираем имя нашего алгоритма. Остальные опции заполняем как на картинке ниже.

Нажимаем Create New Algorithm и выбираем WebIDE в появившемся окошке.
Если вы случайно закрыли попап, исходный код можно открыть, нажав Source Code в меню нашего алгоритма.
Удаляем шаблонный код и вставляем наш:
import Algorithmia import torch import torchvision import torchvision.transforms as transforms import cv2 from torch import * import uuid import gc import requests import numpy as np client = Algorithmia.client() # Скачиваем модель по ссылке file_path, сохраняем на наш виртуальный сервер # под именем model_file и загружаем ее. def load_model(): file_path = "{ ссылка на нашу загруженную модель }" model_file = client.file(file_path).getFile().name model = torch.jit.load(model_file).half().cuda() return model model = load_model().eval() torch.cuda.empty_cache() torch.cuda.ipc_collect() torch.backends.cudnn.benchmark = True # Скачиваем картинку по ссылке, уменьшаем до нужного размера, # преобразуем в тензор и нормализуем, # т.к. модель обучалась на нормализованных картинках def preprocessing(image_path, max_size): response = requests.get(image_path) response = response.content nparr = np.frombuffer(response, np.uint8) img_res = cv2.imdecode(nparr, cv2.IMREAD_COLOR) img_res = cv2.cvtColor(img_res, cv2.COLOR_BGR2RGB) x = img_res.shape[0] y = img_res.shape[1] #if image is bigger than the target max_size, downscale it if x>max_size and x<=y: y = y*(max_size/x) x = max_size if y>max_size and y<x: x = x*(max_size/y) y = max_size size = (int(y),int(x)) img_res = cv2.resize(img_res,size) t = Tensor(img_res/255.)[None,:] t = t.permute(0,3,1,2).half().cuda() # standartize t = (t-0.5)/0.5 return(t) def predict(input): gc.collect() with torch.no_grad(): res = model(input).detach() return res # Денормализуем картинку обратно, сохраняем со случайным именем # и загружаем в наше хранилище def save_file(res, file_uri): #de-standartize res = (res*0.5)+0.5 tempfile = "/tmp/"+str(uuid.uuid4())+".jpg" torchvision.utils.save_image(res,tempfile) client.file(file_uri).putFile(tempfile) # API calls will begin at the apply() method, with the request body passed as 'input' # For more details, see algorithmia.com/developers/algorithm-development/languages def apply(input): processed_data= preprocessing(input["in"], input["size"]) res = predict(processed_data) save_file(res, input["out"]) input = None res = None processed_data = None gc.collect() return "Success"
Не забудьте вставить ссылку на загруженную модель. Ее мы скопировали в предыдущем разделе, когда загружали модель.
Находясь в WebIDE, справа вверху кликаем на DEPENDENCIES и заменяем текст на список наших зависимостей:
algorithmia>=1.0.0,<2.0 opencv-python six torch==1.3.0 torchvision numpy
Версия torch должна быть такой же или более новой, чем та, на которой мы сохраняли модель. В противном случаем могут быть ошибки при импорте jit модели.
Нажимаем SAVE, BUILD и ждем завершения билда. Как только в консоли внизу появится сообщение об успешном билде, можем проверить работоспособность модели, отправив в консоль тестовый запрос:
{"in":"https://cdn3.sportngin.com/attachments/photo/9226/3971/JABC-9u_medium.JPG", "out":"data://username/photo2comics_out/test.jpg", "size":512}
Где {username} — ваш логин. Если все прошло успешно, в консоли появится “Success”, а в папке, которую мы указали (в данном случае — photo2comics_out), появится сгенерированное изображение.
Итоги
Поздравляю, мы дешево и сердито задеплоили нашу скромную модель!
В следующем выпуске мы с вами подружим модель с телеграм-ботом и, наконец, зарелизим уже все это добро.
Если вам не терпится опробовать модель в деле, вы всегда можете ознакомится с официальной документацией: https://algorithmia.com/developers/api/
Ну а чтобы скоротать время до следующей статьи, можете потыкать некоторых ботов, модели для которых я захостил на Алгоритмии:
@selfie2animebot — Превращает селфи в аниме
@pimpmyresbot — Увеличивает разрешение х2 (максимум до 1400х1400)
@photozoombot — Создает 3д-зум видео из одного фото
@photo2comicsbot — Собственно, виновник торжества
Не забывайте делиться результатами, идеями и возникшими проблемами в комментариях.
На этом на сегодня все. До новых встреч!
ссылка на оригинал статьи https://habr.com/ru/post/485824/
Добавить комментарий