Привет, Хабр! Сегодня расскажем вам о том, как создавать собственные токенизаторы с SpaCy. Да-да, тот самый SpaCy, который мы все знаем и любим.

Зачем нам свой токенизатор?

Согласитесь, стандартные токенизаторы хороши, но иногда требуется что-то особенное. Например, разбивать текст на токены по специфическим правилам или обрабатывать экзотические языки программирования (да-да, я смотрю на тебя, Brainfuck).

Начнем с основ

Установка SpaCy

Если вы еще не установили SpaCy, самое время это сделать:

pip install spacy

И не забудьте загрузить модель языка:

python -m spacy download en_core_web_sm

Первый пример

Посмотрим, как работает стандартный токенизатор:

import spacy

nlp = spacy.load('en_core_web_sm')
doc = nlp("Hello, world! How are you?")
for token in doc:
    print(token.text)

Вывод:

Hello
,
world
!
How
are
you
?

Все отлично, но что, если нам нужно, чтобы "Hello, world!" было одним токеном?

Создаем простой токенизатор

Правила токенизации

SpaCy позволяет нам добавлять исключения в токенизатор. Создадим правило, которое объединит "Hello, world!" в один токен.

from spacy.tokenizer import Tokenizer
from spacy.lang.en import English

nlp = English()

# Определяем правило исключения
special_cases = {"Hello, world!": [{"ORTH": "Hello, world!"}]}

# Добавляем правило в токенизатор
nlp.tokenizer.add_special_case("Hello, world!", special_cases["Hello, world!"])

doc = nlp("Hello, world! How are you?")
for token in doc:
    print(token.text)

Вывод:

Hello, world!
How
are
you
?

Теперь "Hello, world!" рассматривается как один токен.

Переходим к более сложным вещам

Пользовательский токенизатор на основе регулярных выражений

Иногда стандартных методов недостаточно, и нужно создать токенизатор с нуля.

import re
from spacy.tokenizer import Tokenizer
from spacy.lang.en import English

nlp = English()

# Определяем свою функцию токенизации
def custom_tokenizer(nlp):
    prefix_re = re.compile(r'''^[\[\("']''')
    suffix_re = re.compile(r'''[\]\)"']$''')
    infix_re = re.compile(r'''[-~]''')
    simple_url_re = re.compile(r'''^https?://''')

    return Tokenizer(nlp.vocab, prefix_search=prefix_re.search,
                     suffix_search=suffix_re.search,
                     infix_finditer=infix_re.finditer,
                     token_match=simple_url_re.match)

nlp.tokenizer = custom_tokenizer(nlp)

doc = nlp("Check out https://example.com and don't forget to say 'Hello-world'!")
for token in doc:
    print(token.text)

Вывод:

Check
out
https://example.com
and
do
n't
forget
to
say
'
Hello
-
world
'
!

Здесь настроили префиксы, суффиксы и инфиксы с помощью регулярных выражений.

Кастомный токенизатор с правилами

Tokenizer.tokens_from_list

Предположим, хочется разбивать текст по пробелам, но сохранять эмодзи как отдельные токены.

from spacy.tokenizer import Tokenizer

def custom_tokenizer(nlp):
    def tokenize(text):
        tokens = text.split()
        return Doc(nlp.vocab, words=tokens)
    return Tokenizer(nlp.vocab, token_match=tokenize)

nlp.tokenizer = custom_tokenizer(nlp)

doc = nlp("I love Python ?")
for token in doc:
    print(token.text)

Вывод:

I
love
Python
?

Использовали функцию token_match, чтобы определить свои правила токенизации.

Пример применения

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

import re
from spacy.tokenizer import Tokenizer
from spacy.lang.en import English

nlp = English()

# Регулярное выражение для хештегов и упоминаний
hashtag_mention_re = re.compile(r'''^[@#]\w+''')

def custom_tokenizer(nlp):
    return Tokenizer(nlp.vocab, token_match=hashtag_mention_re.match)

nlp.tokenizer = custom_tokenizer(nlp)

doc = nlp("@elonmusk i love cats #cats!")
for token in doc:
    print(token.text)

Вывод:

@elonmusk
#cats

Ой, кажется, мы что-то сделали не так. Исправим это, добавив стандартные правила.

def custom_tokenizer(nlp):
    return Tokenizer(nlp.vocab, token_match=hashtag_mention_re.match,
                     rules=nlp.Defaults.tokenizer_exceptions,
                     prefix_search=nlp.tokenizer.prefix_search,
                     suffix_search=nlp.tokenizer.suffix_search,
                     infix_finditer=nlp.tokenizer.infix_finditer)

nlp.tokenizer = custom_tokenizer(nlp)

doc = nlp("@elonmusk i love cats #cats!")
for token in doc:
    print(token.text)

Вывод:

@elonmusk
i
love
#cats
!

Теперь все работает как надо!

Создание класса токенизатора

Если нужно более сложное поведение, можно создать свой класс токенизатора.

from spacy.tokenizer import Tokenizer
from spacy.tokens import Doc
from spacy.lang.en import English

class MyTokenizer(Tokenizer):
    def __call__(self, text):
        words = text.split()
        return Doc(self.vocab, words=words)

nlp = English()
nlp.tokenizer = MyTokenizer(nlp.vocab)

doc = nlp("Custom tokenizer class in action!")
for token in doc:
    print(token.text)

Вывод:

Custom
tokenizer
class
in
action!

Обработка специальных случаев

Токенизация чисел и единиц измерения

Предположим, нужно, чтобы "100kg" было одним токеном.

import re
from spacy.tokenizer import Tokenizer
from spacy.lang.en import English

nlp = English()

# Регулярное выражение для чисел с единицами
number_unit_re = re.compile(r'''^\d+(kg|lb|cm|mm)$''')

def custom_tokenizer(nlp):
    return Tokenizer(nlp.vocab, token_match=number_unit_re.match,
                     rules=nlp.Defaults.tokenizer_exceptions,
                     prefix_search=nlp.tokenizer.prefix_search,
                     suffix_search=nlp.tokenizer.suffix_search,
                     infix_finditer=nlp.tokenizer.infix_finditer)

nlp.tokenizer = custom_tokenizer(nlp)

doc = nlp("He lifted 100kg weight.")
for token in doc:
    print(token.text)

Вывод:

He
lifted
100kg
weight
.

Заключение

Если у вас возникнут вопросы или вы захотите поделиться своими знаниями, пишите в комментариях.

Также, пользуясь случаем, напоминаю, что 20 ноября в рамках курса по NLP пройдет бесплатный открытый урок. На уроке участники узнают, что такое языковые модели, как их использовать для решения NLP-задач, а также изучат подходы по обучению больших языковых моделей, таких как ChatGPT. Записаться на урок можно по ссылке.

Комментарии (1)


  1. ENick
    14.11.2024 20:49

    Спасибо, интересно, а русский язык для spacyрабочий?

    Какой векторизатор дружит с spacy?