
Forte позволяет пользователям разбивать сложные проблемы на составные конвейеры и обеспечивает взаимодействие между задачами посредством унифицированного формата данных. С Forte легко составить любой конвейер NLP, независимо от разнородности данных и процессов, в виде модульной и легко редактируемой системы. Цель Forte, иногда называемая «Lego Learning», состоит в том, чтобы исключить все усилия, связанные с созданием связующего кода, при этом упростив процессы и облегчив совместную работу.
В этом руководстве Гектор Лю, руководитель отдела разработки в Petuum, научит вас создавать систему машинного перевода с помощью Forte, мощного инструмента из проекта с открытым исходным кодом CASL. Мы создадим настраиваемую систему управления машинным переводом, которая будет достаточно надежной и гибкой, чтобы обрабатывать запросы на новые функции, экономя время специалиста по обработке данных или инженера по машинному обучению и улучшая процесс создания и обновления сложного конвейера.
Мы демонстрируем, как наше компонуемое решение приносит нам следующие значения:
- Легко отлаживать
- Легко адаптироваться к изменениям
- Низкое трение между средами
- Легко сотрудничать
Из этого руководства вы узнаете:
1 — Как читать данные из источника
- Как создать простой конвейер НЛП
- Как поддерживать и хранить входные данные
2 — Как обрабатывать данные в конвейере
- Как выполнить сегментацию предложений
- Как аннотировать и запрашивать данные
- Как перевести входной текст с помощью предварительно обученной модели
- Как управлять несколькими объектами данных
3 — Как обрабатывать новые практические запросы
- Как обрабатывать такие структуры, как данные HTML
- Как выбрать один объект данных для обработки
- Как заменить модель перевода услугами удаленного перевода
- Как сохранить и загрузить пайплайн
Выполните следующую команду, чтобы установить все необходимые зависимости для этого руководства:
# It is recommended to install these in command line !pip install forte==0.2.0 forte.nltk requests # for certain environment, you may run into troubles installing transformers, such as requiring Rust # some workaround here: https://github.com/huggingface/transformers/issues/2831#issuecomment-600141935 might help !pip install transformers==4.16.2 # you may want to try different pytorch version depending on your platform # if you cannot install pytorch, try locate your problem at https://github.com/pytorch/pytorch/issues !pip install torch==1.11.0 # for certain environment, the installation may fail # some workaround here: https://github.com/google/sentencepiece/issues/378#issuecomment-1145399969 might help !pip install sentencepiece
1 — Как читать данные из источника
Как создать простую воронку: начните с Reader
В этом разделе вы узнаете:
- Что такое ридер и зачем он нужен
- Как составить простой пайплайн с готовым ридером
from forte import Pipeline from forte.data.readers import TerminalReader pipeline: Pipeline = Pipeline()
Всем конвейерам требуется считыватель для чтения и анализа входных данных. Чтобы заставить наш конвейер читать запросы из пользовательского терминала командной строки, используйте класс TerminalReader, предоставленный Forte. TerminalReader преобразует запрос пользователя в объект DataPack, который представляет собой унифицированный формат данных для НЛП, упрощающий объединение различных инструментов НЛП в виде процессоров Forte.
pipeline.set_reader(TerminalReader())
Чтобы запустить конвейер, состоящий из одного TerminalReader, вызовите process_dataset, который вернет итератор объектов DataPack. Вторая строка в следующем фрагменте кода извлекает первый пользовательский запрос из TerminalReader.
pipeline.initialize() datapack = next(pipeline.process_dataset()) print(datapack.text)
Как поддерживать и хранить входные данные: DataPack
В этом разделе вы узнаете:
- Что такое объект DataPack и зачем он нужен
Forte помогает демистифицировать происхождение данных и повысить отслеживаемость того, как данные проходят по конвейеру и как генерируются функции для взаимодействия данных с моделью. Подобно грузовому кораблю, который загружает и перевозит товары из одного порта в другой, пакет данных несет информацию при прохождении каждого модуля и обновляет состояния онтологии по пути.
Пакет данных и мультимодальность:
DataPack поддерживает не только текстовые данные, но также аудиоданные и данные изображений.
2 — Как обрабатывать данные в конвейере
Как выполнить сегментацию предложений: добавить в конвейер готовый процессор Forte
В этом разделе вы узнаете:
- Что такое процессор и зачем он нужен
- Как добавить готовый процессор в конвейер
Процессор Forte принимает пакеты данных в качестве входных данных, обрабатывает их и сохраняет выходные данные в пакетах данных. Все процессоры, которые мы собираемся использовать в этом разделе, являются PackProcessors, которые ожидают ровно один DataPack в качестве входных данных и сохраняют свои выходные данные обратно в тот же DataPack. Следующие две строки кода показывают, как встроенный процессор NLTKSentenceSegmenter добавляется в наш конвейер.
from fortex.nltk.nltk_processors import NLTKSentenceSegmenter pipeline.add(NLTKSentenceSegmenter())
Когда мы запускаем конвейер, процессор NLTKSentenceSegmenter разделит пользовательский запрос на предложения и сохранит их обратно в DataPack, созданный TerminalReader. Фрагмент кода ниже показывает, как получить все предложения из первого запроса.
from ft.onto.base_ontology import Sentence
pipeline.initialize()
for sent in next(pipeline.process_dataset()).get(Sentence):
print(sent.text)
Как аннотировать и запрашивать данные: онтология
В этом разделе вы узнаете:
- Что такое система онтологий и зачем она нужна
- Как написать индивидуальную онтологию и как ее использовать
Sentence — это предопределенная онтология, предоставленная Forte, и она используется NLTKSentenceSegmenter для аннотирования каждого предложения в тексте. Forte построен на основе системы Ontology, которая определяет отношения между аннотациями НЛП, например, отношения между словами и документами или между двумя словами. Это ядро Forte. Онтологию можно указать в формате JSON. Предоставляются инструменты для преобразования онтологии в производственный код (Python).
Мы также можем определить индивидуальные онтологии:
from dataclasses import dataclass
from forte.data.ontology.top import Annotation
from typing import Optional
@dataclass
class Article(Annotation):
language: Optional[str]
def __init__(self, pack, begin: int, end: int):
super().__init__(pack, begin, end)
Ниже приведен простой пример, показывающий, как мы можем запрашивать предложения с помощью только что созданной нами новой онтологии:
from forte.data import DataPack
sentences = [
"Do you want to get better at making delicious BBQ?",
"You will have the opportunity, put this on your calendar now.",
"Thursday, September 22nd join World Class BBQ Champion, Tony Balay from Lonestar Smoke Rangers."
]
datapack: DataPack = DataPack()
# Add sentences to the DataPack and annotate them
for sentence in sentences:
datapack.set_text(datapack.text + sentence)
datapack.add_entry(
Sentence(datapack, len(datapack.text) - len(sentence), len(datapack.text))
)
# Annotate the whole text with Article
article: Article = Article(datapack, 0, len(datapack.text))
article.language = "en"
datapack.add_entry(article)
for article in datapack.get(Article):
print(f"Article (language - {article.language}):")
for sentence in article.get(Sentence):
print(sentence.text)
В нашем предыдущем примере у нас есть следующее наследование онтологий. Предложение и статья наследуются от аннотации, которая используется для представления текстовых данных. В статье у нас есть поле language для представления языка текста.
На самом деле, мы поддерживаем не только текстовую онтологию, но и аудио, изображения и ссылки, которые представляют отношения между двумя записями.
Annotation наследуется всеми текстовыми записями, которые обычно имеют диапазон для извлечения частичного текста из полного текста.
Article, как показано в нашем предыдущем примере, наследует аннотацию и содержит полеlanguageдля различения английского и немецкого языков. В примере с одним пакетом данных английская статья содержит фрагмент английского текста в пакете данных. Точно так же немецкая статья имеет часть немецкого текста в DataPack.Sentenceв нашем примере используется для разбивки статьи, и мы передаем предложения в конвейер машинного перевода.
AudioAnnotation наследуется всеми аудиозаписями, которые обычно имеют аудиодиапазон для извлечения частичного аудио из полного аудио.
Recordingявляется примером подклассаAudioAnnotationи имеет дополнительное полеrecording_class, обозначающее классы, к которым принадлежит аудио.
ImageAnnotation наследуется всеми записями изображений, которые обычно имеют индекс полезной нагрузки, указывающий на загруженный массив изображений.
BoundingBoxявляется примером подклассаImageAnnotation. Как видно из рисунка, он имеет больше отношений наследования, чем другие классы онтологий, из-за природы объектов CV. Преимущество онтологии Forte в том, что она поддерживает сложное наследование, и пользователи могут наследовать существующую онтологию и добавлять новые функции онтологии для своих нужд.
Link наследуется всеми подобными ссылкам записями, у которых есть родитель и дочерний элемент.
RelationLinkявляется примером подклассаLinkи имеет атрибут класса, определяющий тип отношения.
Как перевести входной текст с помощью предварительно обученной модели: создание процессора машинного перевода
В этом разделе вы узнаете:
- Основы процесса машинного перевода
- Как завернуть предварительно обученную модель машинного перевода в процессор Forte
Перевод преобразует последовательность текста с одного языка на другой. В этом руководстве мы будем использовать модель Huggingface Transformer для преобразования входных данных, которая состоит из нескольких шагов, включая токенизацию подслов, встраивание входных данных, вывод модели, декодирование и т. д.
В Forte у нас есть общий класс PackProcessor, который объединяет компоненты и поведения, связанные с моделью и выводом, для обработки DataPack. Поэтому нам нужно создать класс, который наследует общий метод от PackProcessor. Затем у нас есть определение класса class MachineTranslationProcessor(PackProcessor).
from forte.data import DataPack
from forte.data.readers import StringReader
from forte.processors.base import PackProcessor
from transformers import T5Tokenizer, T5ForConditionalGeneration
class MachineTranslationProcessor(PackProcessor):
"""
Translate the input text and output to a file.
"""
def initialize(self, resources, configs):
super().initialize(resources, configs)
# Initialize the tokenizer and model
model_name: str = self.configs.pretrained_model
self.tokenizer = T5Tokenizer.from_pretrained(model_name)
self.model = T5ForConditionalGeneration.from_pretrained(model_name)
self.task_prefix = "translate English to German: "
self.tokenizer.padding_side = "left"
self.tokenizer.pad_token = self.tokenizer.eos_token
def _process(self, input_pack: DataPack):
# en2de machine translation
inputs = self.tokenizer([
self.task_prefix + sentence.text
for sentence in input_pack.get(Sentence)
], return_tensors="pt", padding=True)
output_sequences = self.model.generate(
input_ids=inputs["input_ids"],
attention_mask=inputs["attention_mask"],
do_sample=False,
)
output = ''.join(self.tokenizer.batch_decode(
output_sequences, skip_special_tokens=True
))
src_article: Article = Article(input_pack, 0, len(input_pack.text))
src_article.language = "en"
input_pack.set_text(input_pack.text + '\n\n' + output)
tgt_article: Article = Article(input_pack, len(input_pack.text) - len(output), len(input_pack.text))
tgt_article.language = "de"
@classmethod
def default_configs(cls):
return {
"pretrained_model": "t5-small"
}
Инициализация необходимых компонентов:
- Пользователям необходимо рассмотреть возможность инициализации всех необходимых компонентов NLP для задачи логического вывода, таких как токенизатор и модель.
- Пользователям также необходимо указать всю конфигурацию в
configs, объекте, похожем на словарь, который указывает конфигурации всех компонентов, таких как название модели.
Операции MT на DataPack:
- После инициализации у нас уже есть необходимые компоненты НЛП. Нам необходимо рассмотреть несколько вариантов поведения машинного перевода на основе Forte DataPack.
Текстовые данные предварительной обработки:
- Получить текстовые данные из DataPack (учитывая, что он уже считывает данные из источника данных).
- Поскольку T5 имеет лучшую производительность при задании задачи, мы также хотим включить подсказку в наши данные.
- Токенизация, которая преобразует входной текст в последовательности токенов и идентификаторов токенов.
- Сгенерируйте выходные последовательности из модели.
- Декодируйте идентификаторы выходных токенов в предложения с помощью токенизатора.
Общий метод обработки DataPack — _process(self, input_pack: DataPack). Он должен токенизировать входной текст, использовать класс модели для вывода, декодировать идентификаторы выходных токенов и, наконец, записывать выходные данные в целевой файл.
Теперь мы можем добавить его в конвейер и запустить задачу машинного перевода.
input_string: str = ' '.join(sentences)
pipeline: Pipeline = Pipeline[DataPack]()
pipeline.set_reader(StringReader())
pipeline.add(NLTKSentenceSegmenter())
pipeline.add(MachineTranslationProcessor())
pipeline.initialize()
for datapack in pipeline.process_dataset([input_string]):
for article in datapack.get(Article):
print([f"\nArticle (language - {article.language}): {article.text}"])
Онтология в DataPack:
Здесь мы приводим иллюстрацию, чтобы пользователи могли лучше понять внутреннюю память DataPack. Как мы видим, текстовые данные, предложения и статьи хранятся как диапазон в Annotations. Их текстовые данные могут быть легко и эффективно извлечены по их промежуткам.
Как управлять несколькими объектами данных: MultiPack, лучший способ хранения исходного и целевого текста
В этом разделе вы узнаете:
- Что такое мультипак и зачем он нужен
- Как использовать мультипак
Вышеупомянутый шаг выводит DataPack, который хорош для хранения данных об одном конкретном фрагменте текста. Сложный конвейер, подобный тому, который мы строим сейчас, может потребовать передачи нескольких пакетов данных по конвейеру, и здесь может помочь MultiPack. MultiPack управляет набором пакетов данных, которые можно индексировать по именам.
MultiPackBoxer — это простой процессор Forte, который преобразует DataPack в MultiPack, делая его единственным DataPack. Имя можно указать через конфиг. Мы используем его для упаковки DataPack, содержащего исходное предложение.

from forte.data import MultiPack
from forte.processors.base import MultiPackProcessor
from forte.data.caster import MultiPackBoxer
class MachineTranslationMPProcessor(MultiPackProcessor):
"""
Translate the input text and output to a file.
"""
def initialize(self, resources, configs):
super().initialize(resources, configs)
# Initialize the tokenizer and model
model_name: str = self.configs.pretrained_model
self.tokenizer = T5Tokenizer.from_pretrained(model_name)
self.model = T5ForConditionalGeneration.from_pretrained(model_name)
self.task_prefix = "translate English to German: "
self.tokenizer.padding_side = "left"
self.tokenizer.pad_token = self.tokenizer.eos_token
def _process(self, input_pack: MultiPack):
source_pack: DataPack = input_pack.get_pack("source")
target_pack: DataPack = input_pack.add_pack("target")
# en2de machine translation
inputs = self.tokenizer([
self.task_prefix + sentence.text
for sentence in source_pack.get(Sentence)
], return_tensors="pt", padding=True)
output_sequences = self.model.generate(
input_ids=inputs["input_ids"],
attention_mask=inputs["attention_mask"],
do_sample=False,
)
# Annotate the source article
src_article: Article = Article(source_pack, 0, len(source_pack.text))
src_article.language = "en"
# Annotate each sentence
for output in self.tokenizer.batch_decode(
output_sequences, skip_special_tokens=True
):
target_pack.set_text(target_pack.text + output)
text_length: int = len(target_pack.text)
Sentence(target_pack, text_length - len(output), text_length)
# Annotate the target article
tgt_article: Article = Article(target_pack, 0, len(target_pack.text))
tgt_article.language = "de"
@classmethod
def default_configs(cls):
return {
"pretrained_model": "t5-small",
}
Затем MachineTranslationMPProcessor записывает выходное предложение в целевой DataPack.
Теперь давайте попробуем создать новый конвейер, использующий MultiPack для управления текстом на разных языках.
nlp: Pipeline = Pipeline[DataPack]()
nlp.set_reader(StringReader())
nlp.add(NLTKSentenceSegmenter())
nlp.add(MultiPackBoxer(), config={"pack_name": "source"})
nlp.add(MachineTranslationMPProcessor(), config={
"pretrained_model": "t5-small"
})
nlp.initialize()
for multipack in nlp.process_dataset([input_string]):
for pack_name in ("source", "target"):
for article in multipack.get_pack(pack_name).get(Article):
print(f"\nArticle (language - {article.language}): ")
for sentence in article.get(Sentence):
print(sentence.text)
Онтология в MultiPack:
Для сравнения вот иллюстрация внутренней памяти MultiPack. Мы видим, что MultiPack упаковывает один исходный DataPack и один целевой DataPack. Диапазоны статей основаны на двух отдельных текстах DataPack.
3 — Как обрабатывать новые практические запросы
Как работать со структурами, такими как данные HTML
В этом разделе вы узнаете
- Как построить систему управления переводами
- Как сохранить структуру, подобную HTML, в машинном переводе
- Как выбрать конкретный DataPack из MultiPack для обработки
На предыдущем шаге входная строка представляла собой простой абзац, состоящий из нескольких предложений. Однако во многих случаях нам может понадобиться обрабатывать данные со структурной информацией, такой как HTML или XML. Когда ввод представляет собой строку необработанных данных HTML, описанный выше конвейер машинного перевода может работать не так, как ожидалось:
html_input: str = """
<!DOCTYPE html>
<html>
<head><title>Beginners BBQ Class.</title></head>
<body>
<p>Do you want to get better at making delicious BBQ? You will have the opportunity, put this on your calendar now. Thursday, September 22nd join World Class BBQ Champion, Tony Balay from Lonestar Smoke Rangers.</p>
</body>
</html>
"""
nlp.initialize()
for multipack in nlp.process_dataset([html_input]):
print("Source Text: " + multipack.get_pack("source").text)
print("\nTarget Text: " + multipack.get_pack("target").text)
Мы видим, что исходная структура HTML нарушена в переведенном выводе.
Чтобы обрабатывать структурированные данные, такие как HTML, нам потребуется обновить текущий дизайн конвейера. К счастью, конвейеры Forte очень модульны, мы можем просто вставить два новых процессора, не обновляя предыдущий конвейер.
Сначала нам нужен очиститель HTML для анализа всех тегов HTML из входной строки. На рисунке ниже показан эффект удаления тегов.
После завершения перевода нам также потребуется восстановить структуру HTML из неструктурированного вывода перевода. На рисунке ниже показано, как заменить одно исходное предложение одним целевым предложением, если целевое предложение готово.
from forte.data import NameMatchSelector
from forte.data.readers.html_reader import ForteHTMLParser
class HTMLTagCleaner(MultiPackProcessor):
def initialize(self, resources, configs):
super().initialize(resources, configs)
self._parser = ForteHTMLParser()
def _process(self, input_pack: MultiPack):
raw_pack: DataPack = input_pack.get_pack("raw")
source_pack: DataPack = input_pack.add_pack("source")
self._parser.feed(raw_pack.text)
cleaned_text: str = raw_pack.text
for span, _ in self._parser.spans:
cleaned_text = cleaned_text.replace(
raw_pack.text[span.begin:span.end], ''
)
source_pack.set_text(cleaned_text)
class HTMLTagRecovery(MultiPackProcessor):
def _process(self, input_pack: MultiPack):
raw_pack: DataPack = input_pack.get_pack("raw")
source_pack: DataPack = input_pack.get_pack("source")
target_pack: DataPack = input_pack.get_pack("target")
result_pack: DataPack = input_pack.add_pack("result")
result_text: str = raw_pack.text
for sent_src, sent_tgt in zip(source_pack.get(Sentence), target_pack.get(Sentence)):
result_text = result_text.replace(sent_src.text, sent_tgt.text)
result_pack.set_text(result_text)
Теперь мы можем создать систему управления переводами, вставив два представленных выше процессора в наш предыдущий конвейер машинного перевода.
# Pipeline with HTML handling
pipeline: Pipeline = Pipeline[DataPack]()
pipeline.set_reader(StringReader())
pipeline.add(MultiPackBoxer(), config={"pack_name": "raw"})
pipeline.add(HTMLTagCleaner())
pipeline.add(
NLTKSentenceSegmenter(),
selector=NameMatchSelector(),
selector_config={"select_name": "source"}
)
pipeline.add(MachineTranslationMPProcessor(), config={
"pretrained_model": "t5-small"
})
pipeline.add(HTMLTagRecovery())
pipeline.initialize()
for multipack in pipeline.process_dataset([html_input]):
print(multipack.get_pack("raw").text)
print(multipack.get_pack("result").text)
Селектор:
В приведенном выше фрагменте кода мы используем NameMatchSelector для выбора одного конкретного DataPack из MultiPack на основе его ссылочного имени select_name. Это позволяет NLTKSentenceSegmenter обрабатывать только указанный DataPack.
Как заменить модель перевода службами удаленного перевода: заменить нашу модель машинного перевода API онлайн-перевода
В этом разделе вы узнаете:
- Как использовать другую службу перевода
Forte также позволяет нам обновлять модель перевода и беспрепятственно интегрировать ее в исходный конвейер. Например, если мы хотим передать задачу перевода онлайн-сервису, все, что нам нужно сделать, — это обновить процессор перевода. Нет необходимости изменять другие компоненты в конвейере.
# You can get your own API key by following the instructions in https://docs.microsoft.com/en-us/azure/cognitive-services/translator/ api_key = input("Enter your API key here:") import requests import uuid class OnlineMachineTranslationMPProcessor(MultiPackProcessor): """ Translate the input text and output to a file use online translator api. """ def initialize(self, resources, configs): super().initialize(resources, configs) self.url = configs.endpoint + configs.path self.from_lang = configs.from_lang self.to_lang = configs.to_lang self.subscription_key = configs.subscription_key self.subscription_region = configs.subscription_region def _process(self, input_pack: MultiPack): source_pack: DataPack = input_pack.get_pack("source") target_pack: DataPack = input_pack.add_pack("target") params = { 'api-version': '3.0', 'from': 'en', 'to': ['de'] } # Build request headers = { 'Ocp-Apim-Subscription-Key': self.subscription_key, 'Ocp-Apim-Subscription-Region': self.subscription_region, 'Content-type': 'application/json', 'X-ClientTraceId': str(uuid.uuid4()) } # You can pass more than one object in body. body = [{ 'text': source_pack.text }] request = requests.post(self.url, params=params, headers=headers, json=body) result = request.json() target_pack.set_text("".join( [trans['text'] for trans in result[0]["translations"]] ) ) @classmethod def default_configs(cls): return { "from_lang" : 'en', "to_lang": 'de', "endpoint" : 'https://api.cognitive.microsofttranslator.com/', "path" : '/translate', "subscription_key": None, "subscription_region" : "westus2", 'X-ClientTraceId': str(uuid.uuid4()) } nlp: Pipeline = Pipeline[DataPack]() nlp.set_reader(StringReader()) nlp.add(NLTKSentenceSegmenter()) nlp.add(MultiPackBoxer(), config={"pack_name": "source"}) nlp.add(OnlineMachineTranslationMPProcessor(), config={ "from_lang" : 'en', "to_lang": 'de', "endpoint" : 'https://api.cognitive.microsofttranslator.com/', "path" : '/translate', "subscription_key": api_key, "subscription_region" : "westus2", 'X-ClientTraceId': str(uuid.uuid4()) }) nlp.initialize() for multipack in nlp.process_dataset([input_string]): print("Source Text: " + multipack.get_pack("source").text) print("\nTarget Text: " + multipack.get_pack("target").text)
Как сохранить и загрузить конвейер: сохраните весь конвейер с помощью save()
В этом разделе вы узнаете
- Как экспортировать и импортировать пайплайн Forte
Forte также позволяет нам сохранять пайплайн на диск. Он сериализует весь конвейер и генерирует промежуточное представление, которое может быть загружено позже, возможно, на другом компьютере.
import os
save_path: str = os.path.join(os.path.dirname(os.path.abspath('')), "pipeline.yml")
nlp.save(save_path)
with open(save_path, 'r') as f:
print(f.read())
Теперь, когда конвейер сохранен, мы можем попытаться повторно загрузить конвейер, чтобы проверить, работает ли он по-прежнему должным образом.
new_nlp: Pipeline = Pipeline()
new_nlp.init_from_config_path(save_path)
new_nlp.initialize()
for multipack in new_nlp.process_dataset([input_string]):
print("Source Text: " + multipack.get_pack("source").text)
print("\nTarget Text: " + multipack.get_pack("target").text)
Теперь вы можете построить систему машинного перевода с помощью Forte!
Forte является родной частью платформы Petuum, открытой и совместимой системы, которая позволяет вам создавать собственную платформу для управления проектами машинного обучения. Forte вносит непосредственный вклад в систему универсальных конвейеров Petuum, которая расширяет возможности Forte с текстовыми данными для всех структурированных и неструктурированных типов данных в мультимодальном режиме, а также предоставляет варианты переносимости конвейеров машинного обучения для упрощения развертывания. Чтобы узнать больше о Petuum и подать заявку на нашу частную бета-версию, посетите: www.petuum.com