6 заметок с тегом

Python

Определение языка текста на Python

Для задач NLP бывает полезно предварительно определить язык текста с которым мы сейчас работаем.

Например, это может пригодиться в случае, если:

  1. Какая-то наша модель умеет работать корректно только с определенным набором языков
  2. Для каждого языка у нас есть отдельная модель
  3. Текст на разных языках нужно по разному подготавливать: выбрать нужный стеммер или токенайзер — особенно важно для китайского и японского языков.

В работе я использую для решения этой задачи три библиотеки: fastText от Facebook, Compact Language Detector v3 от Google и langdetect. У каждой из них свои преимущества и недостатки связанные с размерами моделей, скоростью работы и точностью. Но, в целом, судя по опыту, точнее всего работает именно fastText.

Для задачи определения языка у fastText есть две готовые модели: побольше, на 126 мб и поменьше, на 917 кб. Вторая будет менее точная, но обе поддерживают одинаковое количество языков — 176 штук.

Качаем обе и посмотрим как с ними работать:


wget https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin
wget https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.ftz

Загружаем обе модели:


import fastText

model_big = fastText.load_model('./lid.176.bin')
model_small = fastText.load_model('./lid.176.ftz')

Пробуем в работе:


print(model.predict(["hi"]))
print(model_small.predict(["hi"]))

И получаем довольно странный результат:


([['__label__ca']], [array([0.5109927], dtype=float32)])
([['__label__en']], [array([0.12450418], dtype=float32)])

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


print(model.predict(["hi there, human"]))
print(model_small.predict(["hi there, human"]))

И получаем вполне приемлемый результат:


([['__label__en']], [array([0.84252757], dtype=float32)])
([['__label__en']], [array([0.83792776], dtype=float32)])

Когда использовать какую модель из двух? Это зависит от желаемой точности и скорости работы. Если важнее точность, то можно использовать большую модель, а если скорость, то маленькую. Главное, если мы применяем определение языка в пайплайне обучения, например, классификатора спама, использовать, по возможности, ту же самую модель и в продакшне. А то итоговое качество может сильно хромать.

 Нет комментариев    25   2 мес   fastText   NLP   Python

Ошибка при работе с библиотекой implicit на GPU

Работаю тут с библиотекой implicit для питона, настроил всю инфраструктуру CUDA, прописал все пути, но все равно ловил странное исключение, при попытке использования ALS на GPU:

No CUDA extension has been built, can't train on GPU

Чтож, идем читать исходники als.py:


if not implicit.gpu.HAS_CUDA:
    raise ValueError("No CUDA extension has been built, can't train on GPU.")

Очевидно, HAS_CUDA == False, но с чего это вдруг? Находим инициализацию этой переменной в __init__.py:


try:
    import cupy  # noqa

    from ._cuda import *  # noqa

    HAS_CUDA = True
except ImportError:
    HAS_CUDA = False

Файл _cuda — локальный, значит остается только проверить наличие cupy на рабочей машине. И правда, все дело было именно в её отсутствии. Ну хоть сообщить то об этом нормально можно было? :(

Устанавливаем по  инструкции, запускаем fit на ALS и вуаля, все работает. На RTX 3090 прирост скорости обучения, по сравнению с Xeon W-2265, у меня был примерно в 20 раз, на разреженной матрице размерности 300000х100000.

 Нет комментариев    19   3 мес   CUDA   GPU   Python

Ошибка при сборке Python биндинга для библиотеки Facebook StarSpace

При сборке Python биндинга крутой библиотеки StarSpace получил такую ошибку:


Traceback (most recent call last):
  File "test.py", line 1, in 
    import starwrap as sw
ImportError: /www/home/mntlp/Starspace/python/test/starwrap.so: undefined symbol: _Py_ZeroStruct

Возникает она потому, что я в данный момент работаю под anaconda, а биндинг собрался под другую версию Python. Пофиксить несложно. Открываем CMakeLists.txt и в начала файла, после строки


project(starspace)

устанавливаем пути к библиотекам и инклудам нужного нам питона:


set(PYTHON_LIBRARY "/home/user/anaconda3/lib")
set(PYTHON_INCLUDE_DIR "/home/user/anaconda3/include/python3.6m/")

А если работаем под виртуальной средой в anaconda, то вместо предыдущих путей нужно указать путь через нее:


set(PYTHON_LIBRARY "/home/user/anaconda3/envs/env_name/lib")
set(PYTHON_INCLUDE_DIR "/home/user/anaconda3/envs/env_name/include/python3.6m")

Дальше:


cd ./build
cmake --build .
cd -
cp ./build/starwrap.so ./test
cd test
python3 test.py

В общем, все как было в build.sh до возникновения ошибки.

 Нет комментариев    37   7 мес   Facebook Research   Python   StarSpace

Обрабатываем некорректно сохраненные в лог JSON данные

С месяц назад, при записи JSON данных в лог, забыл отформатировать их json.dumps из питоновского dict’a в нормальный вид, а просто записал его при форматной печатью, преобразовав dict в str. Само собой, получил кучу данных в неудобоваримом виде, эх.

Как исправить? Стандартный json.loads теперь не воспримет такую строку как корректный JSON, т. к. с точки зрения формата она не валидна. Можно решить эту проблему при помощи функции ast.literal_eval.

Она безопасно преобразует строку, содержащую символы или стандартные структуры питона в нужный элемент или объект. В нашем случае, строковое представление словаря в сам словарь. А значит, так можно пробежаться по всем логам и преобразовать их в корректный вид.

Пример работы:


import ast
import json

# Наш преобразованный в строку dict()
s = "{'a': 'Text with \\'quotes\\''}"
j = json.loads(s)
# Получаем ошибку парсинга из-за одинарных кавычек
>> json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

# Парсим строку в dict
j = ast.literal_eval(s)
print(j)
>> {'a': "Text with 'quotes'"}
# Теперь уже дампим данные корректно
print(json.dumps(j))
>>{"a": "Text with 'quotes'"}

 Нет комментариев    24   7 мес   Python   Лайфхак

Шпаргалка по формам/алгоритмам нормализации Unicode текста

Алгоритмы нормализации в Unicode нужны для преобразования внутренней структуры текста, чтобы потом с ним было проще работать. Например, можно заменить несколько символов одним, убрать все диакритические знаки из текста и даже преобразовать похожие буквы в их аналоги.

Всего есть четыре таких алгоритма: NFD, NFC, NFKD, NFKC. Каждый в отдельности можно запускать на одной и той же строке много раз, и результат от этого никак не изменится. То есть они идемпотентны.

Работать с Unicode текстом будем при помощи страндартной Python библиотеки unicodedata.

1. NFD (Normalization Form Canonical Decomposition) или форма нормализации D.

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

Получившаяся разложенная последовательность сортируется в некотором порядке, пока не очень понял в каком именно.

for c in unicodedata.normalize("NFD", "ё"):
    print("'%s': %s" % (c, unicodedata.name(c)))

>> 'е': CYRILLIC SMALL LETTER IE
>> '̈': COMBINING DIAERESIS

Так можно быстро подчистить всю диакритику в тексте, удаляя все ненужные группы символов из строки и оставляя только буквы.

2. NFC (Normalization Form Canonical Composition) или форма нормализации C.

Сначала выполняет NFD декомпозицию, а затем комбинирует полученные простые символы в составные. NFD декомпозиция тут нужна, чтобы разбить уже частично комбинированные символы на простые составляющие для последующей сортировки и обратной сборки.

for c in unicodedata.normalize("NFC", "ё"):
    print("'%s': %s" % (c, unicodedata.name(c)))

>> 'ё': CYRILLIC SMALL LETTER IO

Так можно быстро «приклеить» всю диакритику к буквам и получить из двух символов CYRILLIC SMALL LETTER IE и COMBINING DIAERESIS один CYRILLIC SMALL LETTER IO.

3. NFKD (Normalization Form Compatibility Decomposition) или форма нормализации KD.

Алгоритм, который выполняет NFD декомпозицию и заменяет похожие символы совместимыми аналогами, например, дробь ’¼’ заменяется на строку символов «1/4».

s = '⑲ ⁹ ¼'
print(unicodedata.normalize("NFKD", s))

>> 1 19 9 1/4

for c in list(s):
    print("'%s': %s - '%s'" % (c, unicodedata.name(c), unicodedata.normalize("NFKD", c)))

>> '⑲': CIRCLED NUMBER NINETEEN - '19'
>> ' ': SPACE - ' '
>> '⁹': SUPERSCRIPT NINE - '9'
>> ' ': SPACE - ' '
>> '¼': VULGAR FRACTION ONE QUARTER - '1⁄4'

После такой нормализации можно легко делать фильтрацию текста регэкспами, если его пытались усложнить и замаскировать от этого заменой похожих символов.

4. NFKC (Normalization Form Compatibility Composition) или форма нормализации KC.

Сначала выполняется совместимое разложение NFKD, а затем символы собираются вместе согласно NFC. Аналогично работе с NFC, можно быстро склеить всю диакритику с буквами, приведенными к некоторому базовому виду.

Проблемы NFKD и NFKC

При всем удобстве алгоритмов NFKD и NFKC, они не приводят некоторую часть визуально похожих символов к совместимым аналогам. Например, группу Negative Circled Number * из блока Enclosed Alphanumerics вполне можно привести к числам, но этого не происходит:

unicodedata.normalize("NFKC", "⓫ ⓯")

>> '⓫ ⓯'

А значит, если хочется сделать качественное приведение похожих символов, необходимое для многих задач, придется повозиться над своими таблицами замен.

 Нет комментариев    22   11 мес   Python   Unicode
Ранее Ctrl + ↓