вступление
Это часть серии сообщений в блогах, посвященных внедрению искусственного интеллекта. Если вас интересует предыстория этой истории или ее развитие:
#1) Как парсить локальные результаты Google с помощью искусственного интеллекта?
#2) Реальный пример машинного обучения на Rails
#3) Советы и сравнения по обучению ИИ
#4) Машинное обучение парсингу с помощью Rails
#5) Реализация моделей ONNX в Rails
На этой неделе мы поговорим о том, как преобразовать файл pth
в файл onnx
, чтобы использовать обученную модель в производстве для улучшения синтаксического анализа SerpApi's Google Local Results Scraper API. Затем мы будем использовать гем ONNX Runtime Ruby
для запуска файла onnx
. Вот ментальная карта процесса, на которую можно ссылаться:
I — Необходимые переменные для преобразования в ONNX
Чтобы преобразовать файл pth
в файл onnx
, нам нужно несколько вещей для переноса состояния модели. Во-первых, нам нужно создать модель в коде транслятора. Это означает, что нам потребуются необходимые входные данные для запуска нашей модели. Давайте посмотрим, что нужно для построения модели:
def initialize(vocab_size, embed_dim, num_class) super() @embedding = Torch::NN::EmbeddingBag.new(vocab_size, embed_dim, sparse: true) @fc = Torch::NN::Linear.new(embed_dim, num_class) init_weights end
В этом случае мы сохраним переменные vocab_size
, embed_dim
и nun_class
и запишем их в файл json для чтения из другого файла.
def initialize(vocab_size, embed_dim, num_class) super() @embedding = Torch::NN::EmbeddingBag.new(vocab_size, embed_dim, sparse: true) @fc = Torch::NN::Linear.new(embed_dim, num_class) init_weights end
Нам также нужен пример входных данных или входных данных для имитации пересылки модели. Наша функция переадресации выглядит следующим образом:
def forward(text, offsets) embedded = @embedding.call(text, offsets: offsets) @fc.call(embedded) end
Итак, нам нужен пример text
и offsets
:
def self.save_model_inputs text, offsets path = "ml/google/local_pack/predict_value/trained_models/translator/translator.json" data = File.read(path) data = JSON.parse(data) data['text'] = text.to_a data['offsets'] = offsets.to_a File.write(path, JSON.pretty_generate(data)) end
Все эти функции интегрированы в тренировочный файл. Вот результат созданного файла JSON:
{ "vocab_size": 24439, "embed_dim": 128, "nun_class": 9, "text": [ 2, 143, 3, 12211, 140, 144, 6259, 2, ... }, "offsets": [ 0, 7, 14, 29, 44, 51, 56, 63, 70, 77, 80, 83, ... ] }
Наконец, поскольку мы используем модель n-gram
, нам нужен словарь, который использует модель:
def self.save_vocab vocab path = "ml/google/local_pack/predict_value/trained_models/n_gram_value_predictor_vocab.json" data = JSON.parse(vocab.to_json) File.write(path, JSON.pretty_generate(data)) end
Вот результирующий файл JSON, содержащий различные аспекты vocabulary
, используемые в обученной модели. Нас будет интересовать именно ключ stoi
.
{ "freqs": { "mcdonald": 2, "'": 334, "s": 309, "mcdonald '": 1, "' s": 293, "starbucks": 5, "goza": 1, "espresso": 43, ... }, "itos": [ "<unk>", "<pad>", "(", ")", ",", "in", "·", "· in", "+1", ... ], "unk_index": 0, "stoi": { "<unk>": 0, "<pad>": 1, "(": 2, ")": 3, ",": 4, "in": 5, "·": 6, "· in": 7, "+1": 8, "united": 9, "states": 10, "united states": 11, ... }, "vectors": null }
II — Преобразование в ONNX
На стороне ruby
отсутствует поддержка преобразования pth
в onnx
. К счастью, у нас есть план по обучению моделей локально и использованию обученных моделей в виде ONNX
файлов в производственной среде. Это не мешает нам использовать файл python
для процесса преобразования. Фактически, именно поэтому мы использовали файл JSON для хранения preliminary variables
для преобразования. Эта часть требует выполнения PyTorch
и python
. Вот требования:
import torch import torch.nn as nn import torch.nn.init as init import json
Нам также нужно воссоздать модель в python
. Воссоздать совсем не сложно. Вот модель в ruby
:
class GLocalNet < Torch::NN::Module def initialize(vocab_size, embed_dim, num_class) super() @embedding = Torch::NN::EmbeddingBag.new(vocab_size, embed_dim, sparse: true) @fc = Torch::NN::Linear.new(embed_dim, num_class) init_weights end def init_weights initrange = 0.5 @embedding.weight.data.uniform!(-initrange, initrange) @fc.weight.data.uniform!(-initrange, initrange) @fc.bias.data.zero! end def forward(text, offsets) embedded = @embedding.call(text, offsets: offsets) @fc.call(embedded) end end
Итак, вот его реконструкция в python
:
class GLocalNet(nn.Module): def __init__(self, vocab_size, embed_dim, num_class): super(GLocalNet, self).__init__() self.embedding = nn.EmbeddingBag(vocab_size, embed_dim, sparse=True) self.fc = nn.Linear(embed_dim, num_class) self.init_weights() def init_weights(self): initrange = 0.5 self.embedding.weight.data.uniform_(-initrange, initrange) self.fc.weight.data.uniform_(-initrange, initrange) self.fc.bias.data.zero_() def forward(self, text, offsets): embedded = self.embedding(text, offsets) return self.fc(embedded)
После этого вызовем нужные переменные из ранее созданного и заполненного JSON-файла:
json_path = "ml/google/local_pack/predict_value/trained_models/translator/translator.json" f = open(json_path) data = json.load(f) #Constructors vocab_size = data['vocab_size'] embed_dim = data['embed_dim'] nun_class = data['nun_class'] #Inputs text = torch.tensor(data['text']) offsets = torch.tensor(data['offsets']) def forward(self, text, offsets): embedded = self.embedding(text, offsets) return self.fc(embedded)
У PyTorch
отличный модуль, onnx.export
. Задаем необходимые параметры для конвертации.
file_path = "ml/google/local_pack/predict_value/trained_models/n_gram_value_predictor.pth" model = GLocalNet(vocab_size, embed_dim, nun_class) model.load_state_dict(torch.load(file_path)) model.eval() torch.onnx.export( model, (text, offsets), "ml/google/local_pack/predict_value/trained_models/n_gram_value_predictor.onnx", export_params=True, opset_version=11, do_constant_folding=True, input_names = ['text', 'offsets'], output_names = ['label'], dynamic_axes={ 'text' : {0 : 'batch_size'}, 'offsets': {0 : 'batch_size'}, 'label' : {0 : 'batch_size'} })
Вот разбивка процесса преобразования:
model
: модель, которую мы загрузили ранее.(text, offsets)
: пример входных данных."ml/google/local_pack/predict_value/trained_models/n_gram_value_predictor.onnx"
: путь для сохранения модели onnx
.export_params=True
: параметр для хранения весов обученных параметров.opset_version=11
: onnx
версия использовать. Мы выбрали 11
для поддержки EmbeddingBag
, который находится в модели.do_constant_folding=True
: параметр для выполнения свертывания констант для оптимизации.input_names = ['text', 'offsets']
: входные имена, которые будут использоваться при вызове преобразованной модели onnx
.output_names = ['label']
: выходные данные имена, которые будут использоваться при вызове конвертированной модели onnx
.dynamic_axes={'text':{0:'batch_size'}, 'offsets': {0:'batch_size'}, 'label':{0:'batch_size'}}
: параметр для сохранения динамики входов и выходов.
III — Реализация ONNX в Ruby on Rails
Мне посчастливилось понять, что итератор словаря для torchtext-ruby
не зависит от самого torch
. Чтобы реализовать модель для production
целей, у нас не может быть больших файлов для такого решения. Так что с небольшой настройкой я смог создать загрузчик ввода, который берет текст и переводит его в массив, используя vocab
, который мы сохранили ранее в training
.
def create_input text def ngrams_iterator(token_list, ngrams) return enum_for(:ngrams_iterator, token_list, ngrams) unless block_given? get_ngrams = lambda do |n| (token_list.size - n + 1).times.map { |i| token_list[i...(i + n)] } end token_list.each do |x| yield x end 2.upto(ngrams) do |n| get_ngrams.call(n).each do |x| yield x.join(" ") end end end def basic_english_normalize(line) line = line.downcase @patterns_dict.each do |pattern_re, replaced_str| line.sub!(pattern_re, replaced_str) end line.split end def vocab_operation vocab, token if vocab[token].present? vocab[token] else vocab[token] = vocab.values.last + 1 vocab[token] end end _patterns = [%r{\'}, %r{\"}, %r{\.}, %r{<br \/>}, %r{,}, %r{\(}, %r{\)}, %r{\!}, %r{\?}, %r{\;}, %r{\:}, %r{\s+}] _replacements = [" \' ", "", " . ", " ", " , ", " ( ", " ) ", " ! ", " ? ", " ", " ", " "] @patterns_dict = _patterns.zip(_replacements) vocab_path = "ml/google/local_pack/predict_value/trained_models/n_gram_value_predictor_vocab.json" data = File.read(vocab_path) data = JSON.parse(data) vocab = data['stoi'] ngrams = 2 new_vocab = [] arr = ngrams_iterator(basic_english_normalize(text), ngrams).map { |token| vocab_operation vocab, token } arr end
Вот функция для предсказания метки из строки:
def get_prediction text local_pack_label = { 0 => "title", 1 => "rating", 2 => "reviews", 3 => "type", 4 => "phone", 5 => "address", 6 => "hours", 7 => "price", 8 => "description" } path = "ml/google/local_pack/predict_value/trained_models/n_gram_value_predictor.onnx" model = OnnxRuntime::Model.new(path) output = model.predict(text: create_input(text), offsets: [0]) result = local_pack_label[output['label'][0].find_index(output['label'][0].max)] result.to_sym end
get_prediction
команда:
- берет
text
, - преобразует его в массив, используя
vocab
, - загружает его в модель с помощью
ONNX file
, - получает массив
result
, состоящий из различных чисел с плавающей запятой, представляющих вероятность меток по порядку, - берет индекс максимальной вероятности и запускает его в хэше
local_pack_label
, - предсказывает его
label
и возвращает его какsymbol
.
IV — Путь вперед
Эту вспомогательную функцию можно напрямую реализовать в наших парсерах. На следующей неделе мы поговорим о расширении словаря для n-грамм, реализации этой функции в наших парсерах и сравнении результатов JSON
с использованием parser enhanced with predictive model
и traditional parser
. Мы также упомянем вариант использования Machine Learning
на Rspec
.
Я хотел бы поблагодарить смелых и блестящих людей из SerpApi за всю их поддержку, особенно в эти trying times
. Также я благодарен читателю за внимание.
Благодарности:
* Использованные драгоценные камни:
torch.rb, https://github.com/ankane/torch.rb
torchtext-ruby, https://github.com/ ankane/torchtext-ruby
onnxruntime-ruby, https://github.com/ankane/onnxruntime-ruby
* Используемые библиотеки C++:
LibTorch 1.10.2, Linux, CUDA 10.2, cxx11 ABI, https://pytorch.org/get-started/locally/
* Материалы перепрофилированы из:
Документация, https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial .html
Учебник, https://docs.microsoft.com/en-us/windows/ai/windows-ml/tutorials/pytorch-convert-model
Первоначально опубликовано на https://serpapi.com 2 марта 2022 г.