Русский
Русский
English
Статистика
Реклама

Tf-idf

Ранжирование текстов по похожести на опорные тексты при помощи модели TF-IDF в реализации GENSM

12.02.2021 10:14:08 | Автор: admin

Бывает так, что критерии поиска текстов слишком сложны, чтобы обойтись регулярными выражениями. В таких случаях на помощь приходит ML. Если из списка текстов выбрать самый подходящий для нас, можно выяснить похожесть всех остальных текстов на этот. Похожесть(similarity) это численная мера, чем выше тем более текст похож, поэтому при сортировке по убыванию по этому параметру мы увидим наиболее подходящие нам тексты из выборки.

В качестве примера возьмем любой набор текстов. Здесьhttp://study.mokoron.com/ можно скачать небольшое csv с твитами. В реальной работе это могут быть разного рода комментарии, ответы от техподдержки, запросы пользователей. Так или иначе, импортировав все нужные библиотеки, загрузим в pandas наш список текстов и взглянем на первые из них:

import pandas as pdimport refrom gensim import corpora,models,similaritiesfrom gensim.utils import tokenizedf = pd.read_csv('positive.csv',sep=";",names = [1,2,3,"text",4,5,6,7,8,9,10,11])[["text"]]list(df.head(5)["text"].values)['@first_timee хоть я и школота, но поверь, у нас то же самое :D общество профилирующий предмет типа)', 'Да, все-таки он немного похож на него. Но мой мальчик все равно лучше:D', 'RT @KatiaCheh: Ну ты идиотка) я испугалась за тебя!!!', 'RT @digger2912: "Кто то в углу сидит и погибает от голода, а мы ещё 2 порции взяли, хотя уже и так жрать не хотим" :DD http://t.co/GqG6iuE2',

Стандартный и привычный способ поиска нужных нам текстов регулярные выражения. Они очень быстро выполняются даже на гигантских датасетах и отлично послужат нам в качестве фильтров. Для примера выберем случайные слова из тех, что мы только что видели и превратим их в регулярки:

regex_queries = ["школ.*","голод.*","страш.*",'[^а-яА-Я]ми[^а-яА-Я]'] for word in regex_queries:    df[word] = df["text"].str.count(word,flags=re.IGNORECASE)

Звездочка с точкой .* показывает что после нужного нам фрагмента может быть любой набор символов. [^а-яА-Я] означает любой символ, который не является русской буквой. Вообще для создания регулярных выражений удобно использовать сайт regex. Метод str.count библиотеки pandas применяет регулярное выражение массово на весь датасет, выдавая количество найденных регулярок. Флаг re.IGNORECASE это часть конфигурации библиотеки регулярных выражений regex, заставляющая ее искать вне зависимости от того, заглавные в тексте буквы или строчные.

Результат выполнения выглядит так:

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

Для непосредственного поиска похожих текстов стоит использовать реализацию doc2bow из библиотеки genism, поскольку помимо нужных нам моделей она предоставляет множество других полезных функций, например токенизацию со встроенной лемматизацией, что может быть полезно в случае, если предварительно обученные модели для лемматизации использовать нельзя.

Первым делом выделим тексты, по которым будет проводиться сравнение, далее называя их опорные тексты. Скорее всего, после первого этапа поисков этот список расширится, поскольку модель предложит дополнительные варианты подходящих текстов. В качестве примера просто возьмем первые 5 твитов, главное помнить, что эти тексты так же должны быть в общем наборе.

texts_to_compare = list(df.head(5)["text"])['@first_timee хоть я и школота, но поверь, у нас то же самое :D общество профилирующий предмет типа)', 'Да, все-таки он немного похож на него. Но мой мальчик все равно лучше:D', 'RT @KatiaCheh: Ну ты идиотка) я испугалась за тебя!!!', 'RT @digger2912: "Кто то в углу сидит и погибает от голода, а мы ещё 2 порции взяли, хотя уже и так жрать не хотим" :DD http://t.co/GqG6iuE2', '@irina_dyshkant Вот что значит страшилка :D\nНо блин,посмотрев все части,у тебя создастся ощущение,что авторы курили что-то :D']

Теперь токенизируем все тексты в нашем датасете. На этом этапе осуществляется обработка текстов, который можно провести множеством способов при помощи множества библиотек. Самые распространённые способы включают в себя:

  • Удаление стоп слов, таких как а, и, но и прочее. Обычно для этого используются заранее собранные словари. Можно выполнить библиотекой NLTK

  • Понижение регистра слов до строчных. Большинство библиотек и чистый python сам по себе могут это делать. Некоторые, могут еще и удалить диакритические знаки, например gensim.

  • Лемматизация, то есть приведение слова к словарной форме. Сложность этого действия зависит от языка, для русского языка этот процесс не прост и требует специальных библиотек. pymystem3 подойдет, но важно всегда контролировать качество.

  • Стемминг, или обрезка слов до корня. Это более экстремальный вариант лемматизации, который чайник может превратить в чай. Выполняется если потеря смысла не страшна. Подходящая библиотека pymorphy и написанные под нее скрипты для русского языка в духе Стеммера Портера.

def tokenize_in_df(strin):    try:        return list(tokenize(strin,lowercase=True, deacc=True,))    except:        return ""df["tokens"] = df["text"].apply(tokenize_in_df)df.head(5)["tokens"].valuesarray([list(['first_timee', 'хоть', 'я', 'и', 'школота', 'но', 'поверь', 'у', 'нас', 'то', 'же', 'самое', 'd', 'общество', 'профилирующии', 'предмет', 'типа']),       list(['да', 'все', 'таки', 'он', 'немного', 'похож', 'на', 'него', 'но', 'мои', 'мальчик', 'все', 'равно', 'лучше', 'd']),       list(['rt', 'katiacheh', 'ну', 'ты', 'идиотка', 'я', 'испугалась', 'за', 'тебя']),       list(['rt', 'digger', 'кто', 'то', 'в', 'углу', 'сидит', 'и', 'погибает', 'от', 'голода', 'а', 'мы', 'еще', 'порции', 'взяли', 'хотя', 'уже', 'и', 'так', 'жрать', 'не', 'хотим', 'dd', 'http', 't', 'co', 'gqg', 'iue']),       list(['irina_dyshkant', 'вот', 'что', 'значит', 'страшилка', 'd', 'но', 'блин', 'посмотрев', 'все', 'части', 'у', 'тебя', 'создастся', 'ощущение', 'что', 'авторы', 'курили', 'что', 'то', 'd'])],      dtype=object)

В нашем примере мы применили только приведение к строчным буквам и удаление ударений в параметрах функции gensim.tokenize: lowercase=True, deacc=True.

Создадим словарь слов, которые есть во всем нашем наборе текстов:

dictionary = corpora.Dictionary(df["tokens"])feature_cnt = len(dictionary.token2id)dictionary.token2id{'d': 0, 'first_timee': 1, 'же': 2, 'и': 3, 'нас': 4, 'но': 5, 'общество': 6, 'поверь': 7, 'предмет': 8, 'профилирующии': 9, 'самое': 10, 'типа': 11, 'то': 12, 'у': 13, 'хоть': 14, 'школота': 15, 'я': 16, 'все': 17, 'да': 18, 'лучше': 19,

Каждое новое слово получает свой номер. Для дальнейшего использования номера слов в словаре походят намного лучше, чем сами слова. Теперь нужно создать корпус, превратив наши токенизированные тексты в векторы (называются bow bag of words мешок слов). Вектор в данном случае список пар значений номер слова в словаре : количество таких слов в отдельном тексте.

corpus = [dictionary.doc2bow(text) for text in df["tokens"]]corpus[[(0, 1),  (1, 1),  (2, 1),  (3, 3),  (4, 1),  (5, 1),  (6, 1),  (7, 1),  (8, 1),  (9, 2),  (10, 1),  (11, 1),  (12, 1),  (13, 1),  (14, 2),  (15, 1),  (16, 1),

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

Именно этим займется модель tf-idf. Сама по себе аббревиатура TF-IDF расшифровывается как TF term frequency, IDF inverse document frequency, то есть отношение частоты употребления слова в отдельном тексте к частоте употребления слова во всех документах. Построенная на основе такой меры модель прекрасно подходит для поиска похожих текстов, поскольку позволяет сравнивать совокупные меры текстов между собой, строя матрицу похожести.

tfidf = models.TfidfModel(corpus)index = similarities.SparseMatrixSimilarity(tfidf[corpus],num_features = feature_cnt)

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

for text in texts_to_compare:    kw_vector = dictionary.doc2bow(tokenize(text))    df[text] = index[tfidf[kw_vector]]

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

df["sum"] = 0for text in texts_to_compare:    df["sum"] = df["sum"]+df[text]for word in regex_queries:    df["sum"] = df["sum"]+df[word]/5  

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

df["sum"].value_counts(bins=5)(-0.0022700000000000003, 0.254]    113040(0.254, 0.508]                       1829(0.508, 0.762]                         31(0.762, 1.016]                          7(1.016, 1.269]                          4

На этом этапе python уже не нужен, продолжать работать удобнее в excel:

df[df["sum"]>0.250].to_excel("похожие тексты.xlsx")

Проверка результата и поиск дополнительных текстов и слов для улучшения алгоритма комфортно проходит в Excel, за счет использования фильтров и сортировок.

Вот пример, отсортируем результат по похожести на самый первый опорный текст (он, разумеется, окажется на самом первом месте при сортировке):

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

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

Ссылка на код

Подробнее..

ANYKS Spell-checker

20.09.2020 22:11:04 | Автор: admin
image

Здравствуйте, это моя третья статья на хабре, ранее я писал статью о языковой модели ALM. Сейчас, я хочу познакомить вас с системой исправления опечаток ASC (реализованной на основе ALM).

Да, систем исправления опечаток существует огромное количество, у всех есть свои сильные и слабые стороны, из открытых систем я могу выделить одну наиболее перспективную JamSpell, с ней и будем сравнивать. Есть ещё подобная система от DeepPavlov, про которую многие могут подумать, но я с ней так и не подружился.

Список возможностей:


  1. Исправление ошибок в словах с разницей до 4-х дистанций по Левенштейну.
  2. Исправление опечаток в словах (вставка, удаление, замещение, перестановка) символов.
  3. Ёфикация с учётом контекста.
  4. Простановка регистра первой буквы слова, для (имён собственных и названий) с учётом контекста.
  5. Разбиение объединённых слов на отдельные слова, с учётом контекста.
  6. Выполнение анализа текста без корректировки исходного текста.
  7. Поиск в тексте наличия (ошибок, опечаток, неверного контекста).


Поддерживаемые операционные системы:


  • MacOS X
  • FreeBSD
  • Linux


Написана система на С++11, есть порт для Python3

Готовые словари


Название Размер (Гб) Оперативная память (Гб) Размер N-грамм Язык
wittenbell-3-big.asc 1.97 15.6 3 RU
wittenbell-3-middle.asc 1.24 9.7 3 RU
mkneserney-3-middle.asc 1.33 9.7 3 RU
wittenbell-3-single.asc 0.772 5.14 3 RU
wittenbell-5-single.asc 1.37 10.7 5 RU

Тестирование


Для проверки работы системы использовались данные соревнованияисправления опечаток 2016 года от Dialog21. Для тестирования использовался обученный бинарный словарь:wittenbell-3-middle.asc
Проводимый тест Precision Recall FMeasure
Режим исправления опечаток 76.97 62.71 69.11
Режим исправления ошибок 73.72 60.53 66.48

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

Материалы использовавшиеся в тестировании


  • test.txt- Текст для тестирования
  • correct.txt- Текст корректных вариантов
  • evaluate.py- Скрипт Python3 для расчёта результатов коррекции


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

Для сравнения возьмём систему исправления опечаток, которую я упоминал выше JamSpell.

ASC vs JamSpell


Установка
ASC
$ git clone --recursive https://github.com/anyks/asc.git$ cd ./asc$ mkdir ./build$ cd ./build$ cmake ..$ make

JamSpell
$ git clone https://github.com/bakwc/JamSpell.git$ cd ./JamSpell$ mkdir ./build$ cd ./build$ cmake ..$ make


Обучение
ASC

train.json
{  "ext": "txt",  "size": 3,  "alter": {"е":"ё"},  "debug": 1,  "threads": 0,  "method": "train",  "allow-unk": true,  "reset-unk": true,  "confidence": true,  "interpolate": true,  "mixed-dicts": true,  "only-token-words": true,  "locale": "en_US.UTF-8",  "smoothing": "wittenbell",  "pilots": ["а","у","в","о","с","к","б","и","я","э","a","i","o","e","g"],  "corpus": "./texts/correct.txt",  "w-bin": "./dictionary/3-middle.asc",  "w-vocab": "./train/lm.vocab",  "w-arpa": "./train/lm.arpa",  "mix-restwords": "./similars/letters.txt",  "alphabet": "абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz",  "bin-code": "ru",  "bin-name": "Russian",  "bin-author": "You name",  "bin-copyright": "You company LLC",  "bin-contacts": "site: https://example.com, e-mail: info@example.com",  "bin-lictype": "MIT",  "bin-lictext": "... License text ...",  "embedding-size": 28,  "embedding": {      "а": 0, "б": 1, "в": 2, "г": 3, "д": 4, "е": 5,      "ё": 5, "ж": 6, "з": 7, "и": 8, "й": 8, "к": 9,      "л": 10, "м": 11, "н": 12, "о": 0, "п": 13, "р": 14,      "с": 15, "т": 16, "у": 17, "ф": 18, "х": 19, "ц": 20,      "ч": 21, "ш": 21, "щ": 21, "ъ": 22, "ы": 23, "ь": 22,      "э": 5, "ю": 24, "я": 25, "<": 26, ">": 26, "~": 26,      "-": 26, "+": 26, "=": 26, "*": 26, "/": 26, ":": 26,      "%": 26, "|": 26, "^": 26, "&": 26, "#": 26, "'": 26,      "\\": 26, "0": 27, "1": 27, "2": 27, "3": 27, "4": 27,      "5": 27, "6": 27, "7": 27, "8": 27, "9": 27, "a": 0,      "b": 2, "c": 15, "d": 4, "e": 5, "f": 18, "g": 3,      "h": 12, "i": 8, "j": 6, "k": 9, "l": 10, "m": 11,      "n": 12, "o": 0, "p": 14, "q": 13, "r": 14, "s": 15,      "t": 16, "u": 24, "v": 21, "w": 22, "x": 19, "y": 17, "z": 7  }}

$ ./asc -r-json ./train.json

Приведу также пример на языке Python3
import ascasc.setSize(3)asc.setAlmV2()asc.setThreads(0)asc.setLocale("en_US.UTF-8")asc.setOption(asc.options_t.uppers)asc.setOption(asc.options_t.allowUnk)asc.setOption(asc.options_t.resetUnk)asc.setOption(asc.options_t.mixDicts)asc.setOption(asc.options_t.tokenWords)asc.setOption(asc.options_t.confidence)asc.setOption(asc.options_t.interpolate)asc.setAlphabet("абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz")asc.setPilots(["а","у","в","о","с","к","б","и","я","э","a","i","o","e","g"])asc.setSubstitutes({'p':'р','c':'с','o':'о','t':'т','k':'к','e':'е','a':'а','h':'н','x':'х','b':'в','m':'м'})def statusArpa1(status):    print("Build arpa", status)def statusArpa2(status):    print("Write arpa", status)def statusVocab(status):    print("Write vocab", status)def statusIndex(text, status):    print(text, status)def status(text, status):    print(text, status)asc.collectCorpus("./texts/correct.txt", asc.smoothing_t.wittenBell, 0.0, False, False, status)asc.buildArpa(statusArpa1)asc.writeArpa("./train/lm.arpa", statusArpa2)asc.writeVocab("./train/lm.vocab", statusVocab)asc.setCode("RU")asc.setLictype("MIT")asc.setName("Russian")asc.setAuthor("You name")asc.setCopyright("You company LLC")asc.setLictext("... License text ...")asc.setContacts("site: https://example.com, e-mail: info@example.com")asc.setEmbedding({     "а": 0, "б": 1, "в": 2, "г": 3, "д": 4, "е": 5,     "ё": 5, "ж": 6, "з": 7, "и": 8, "й": 8, "к": 9,     "л": 10, "м": 11, "н": 12, "о": 0, "п": 13, "р": 14,     "с": 15, "т": 16, "у": 17, "ф": 18, "х": 19, "ц": 20,     "ч": 21, "ш": 21, "щ": 21, "ъ": 22, "ы": 23, "ь": 22,     "э": 5, "ю": 24, "я": 25, "<": 26, ">": 26, "~": 26,     "-": 26, "+": 26, "=": 26, "*": 26, "/": 26, ":": 26,     "%": 26, "|": 26, "^": 26, "&": 26, "#": 26, "'": 26,     "\\": 26, "0": 27, "1": 27, "2": 27, "3": 27, "4": 27,     "5": 27, "6": 27, "7": 27, "8": 27, "9": 27, "a": 0,     "b": 2, "c": 15, "d": 4, "e": 5, "f": 18, "g": 3,     "h": 12, "i": 8, "j": 6, "k": 9, "l": 10, "m": 11,     "n": 12, "o": 0, "p": 14, "q": 13, "r": 14, "s": 15,     "t": 16, "u": 24, "v": 21, "w": 22, "x": 19, "y": 17, "z": 7}, 28)asc.saveIndex("./dictionary/3-middle.asc", "", 128, statusIndex)

JamSpell

$ ./main/jamspell train ../test_data/alphabet_ru.txt ../test_data/correct.txt ./model.bin


Тестирование
ASC

spell.json
{    "debug": 1,    "threads": 0,    "method": "spell",    "spell-verbose": true,    "confidence": true,    "mixed-dicts": true,    "asc-split": true,    "asc-alter": true,    "asc-esplit": true,    "asc-rsplit": true,    "asc-uppers": true,    "asc-hyphen": true,    "asc-wordrep": true,    "r-text": "./texts/test.txt",    "w-text": "./texts/output.txt",    "r-bin": "./dictionary/3-middle.asc"}

$ ./asc -r-json ./spell.json

Пример на языке Python3
import ascasc.setAlmV2()asc.setThreads(0)asc.setOption(asc.options_t.uppers)asc.setOption(asc.options_t.ascSplit)asc.setOption(asc.options_t.ascAlter)asc.setOption(asc.options_t.ascESplit)asc.setOption(asc.options_t.ascRSplit)asc.setOption(asc.options_t.ascUppers)asc.setOption(asc.options_t.ascHyphen)asc.setOption(asc.options_t.ascWordRep)asc.setOption(asc.options_t.mixDicts)asc.setOption(asc.options_t.confidence)def status(text, status):    print(text, status)asc.loadIndex("./dictionary/3-middle.asc", "", status)f1 = open('./texts/test.txt')f2 = open('./texts/output.txt', 'w')for line in f1.readlines():    res = asc.spell(line)    f2.write("%s\n" % res[0])f2.close()f1.close()

JamSpell

Так-как версия для Python у меня не собралась, пришлось написать небольшое приложение на C++
#include <fstream>#include <iostream>#include <jamspell/spell_corrector.hpp>// Если используется BOOST#ifdef USE_BOOST_CONVERT#include <boost/locale/encoding_utf.hpp>// Если нужно использовать стандартную библиотеку#else#include <codecvt>#endifusing namespace std;/** * convert Метод конвертирования строки utf-8 в строку * @param  str строка utf-8 для конвертирования * @return     обычная строка */const string convert(const wstring & str){// Результат работы функцииstring result = "";// Если строка переданаif(!str.empty()){// Если используется BOOST#ifdef USE_BOOST_CONVERT// Объявляем конвертерusing boost::locale::conv::utf_to_utf;// Выполняем конвертирование в utf-8 строкуresult = utf_to_utf <char> (str.c_str(), str.c_str() + str.size());// Если нужно использовать стандартную библиотеку#else// Устанавливаем тип для конвертера UTF-8using convert_type = codecvt_utf8 <wchar_t, 0x10ffff, little_endian>;// Объявляем конвертерwstring_convert <convert_type, wchar_t> conv;// wstring_convert <codecvt_utf8 <wchar_t>> conv;// Выполняем конвертирование в utf-8 строкуresult = conv.to_bytes(str);#endif}// Выводим результатreturn result;}/** * convert Метод конвертирования строки в строку utf-8 * @param  str строка для конвертирования * @return     строка в utf-8 */const wstring convert(const string & str){// Результат работы функцииwstring result = L"";// Если строка переданаif(!str.empty()){// Если используется BOOST#ifdef USE_BOOST_CONVERT// Объявляем конвертерusing boost::locale::conv::utf_to_utf;// Выполняем конвертирование в utf-8 строкуresult = utf_to_utf <wchar_t> (str.c_str(), str.c_str() + str.size());// Если нужно использовать стандартную библиотеку#else// Объявляем конвертер// wstring_convert <codecvt_utf8 <wchar_t>> conv;wstring_convert <codecvt_utf8_utf16 <wchar_t, 0x10ffff, little_endian>> conv;// Выполняем конвертирование в utf-8 строкуresult = conv.from_bytes(str);#endif}// Выводим результатreturn result;}/** * safeGetline Функция извлечения строки из текста * @param  is файловый поток * @param  t  строка для извлечения текста * @return    файловый поток */istream & safeGetline(istream & is, string & t){// Очищаем строкуt.clear();istream::sentry se(is, true);streambuf * sb = is.rdbuf();for(;;){int c = sb->sbumpc();switch(c){ case '\n': return is;case '\r':if(sb->sgetc() == '\n') sb->sbumpc();return is;case streambuf::traits_type::eof():if(t.empty()) is.setstate(ios::eofbit);return is;default: t += (char) c;}}}/*** main Главная функция приложения*/int main(){// Создаём корректорNJamSpell::TSpellCorrector corrector;// Загружаем модель обученияcorrector.LoadLangModel("model.bin");// Открываем файл на чтениеifstream file1("./test_data/test.txt", ios::in);// Если файл открытif(file1.is_open()){// Строка чтения из файлаstring line = "", res = "";// Открываем файл на чтениеofstream file2("./test_data/output.txt", ios::out);// Если файл открытif(file2.is_open()){// Считываем до тех пор пока все удачноwhile(file1.good()){// Считываем строку из файлаsafeGetline(file1, line);// Если текст получен, выполняем коррекциюif(!line.empty()){// Получаем исправленный текстres = convert(corrector.FixFragment(convert(line)));// Если текст получен, записываем его в файлif(!res.empty()){// Добавляем перенос строкиres.append("\n");// Записываем результат в файлfile2.write(res.c_str(), res.size());}}}// Закрываем файлfile2.close();}// Закрываем файлfile1.close();}    return 0;}

Компилируем и запускаем
$ g++ -std=c++11 -I../JamSpell -L./build/jamspell -L./build/contrib/cityhash -L./build/contrib/phf -ljamspell_lib -lcityhash -lphf ./test.cpp -o ./bin/test$ ./bin/test


Результаты


Получение результатов
$ python3 evaluate.py ./texts/test.txt ./texts/correct.txt ./texts/output.txt


ASC
Precision Recall FMeasure
92.13 82.51 87.05

JamSpell
Precision Recall FMeasure
77.87 63.36 69.87

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

Принцип обучения который предлагаю я


  1. Собираем языковую модель на грязных данных
  2. Удаляем все редко встречающиеся слова и N-граммы в собранной языковой модели
  3. Добавляем одиночные слова для более правильной работы системы исправления опечаток.
  4. Собираем бинарный словарь

Приступим


Предположим, что у нас есть несколько корпусов разной тематики, логичнее обучить их отдельно, потом объединить.

Сборка корпуса с помощью ALM
collect.json
{"size": 3,"debug": 1,"threads": 0,"ext": "txt","method": "train","allow-unk": true,"mixed-dicts": true,"only-token-words": true,"smoothing": "wittenbell","locale": "en_US.UTF-8","w-abbr": "./output/alm.abbr","w-map": "./output/alm.map","w-vocab": "./output/alm.vocab","w-words": "./output/words.txt","corpus": "./texts/corpus","abbrs": "./abbrs/abbrs.txt","goodwords": "./texts/whitelist/words.txt","badwords": "./texts/blacklist/garbage.txt","mix-restwords": "./texts/similars/letters.txt","alphabet": "абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz"}

$ ./alm -r-json ./collect.json

  • size Мы собираем N-граммы длиной 3
  • debug Выводим индикатор выполнения сбора данных
  • threads Для сборки используем все доступные ядра
  • ext Указываем расширение файлов в каталоге которые пригодны для обучения
  • allow-unk Разрешаем хранить токенunkв языковой модели
  • mixed-dicts Разрешаем исправлять слова с замещёнными буквами из других языков
  • only-token-words Собираем не целиком N-граммы как есть а только последовательности нормальных слов
  • smoothing Используем алгоритм сглаживания wittenbell (на данном этапе он не применяется, но какой-то алгоритм сглаживания указать нужно)
  • locale Устанавливаем локаль окружения (можно не указывать)
  • w-abbr Сохраняем собранные суффиксы цифровых аббревиатур
  • w-map Сохраняем карту последовательности как промежуточный результат
  • w-vocab Сохраняем собранный словарь
  • w-words Сохраняем список собранных уникальных слов (на всякий случай)
  • corpus Используем для сборки каталог с текстовыми данными корпуса
  • abbrs Используем в обучении, общеупотребимые аббревиатуры, такие как (США, ФСБ, КГБ ...)
  • goodwords Используем заранее подготовленный белый список слов
  • badwords Используем заранее подготовленный чёрный список слов
  • mix-restwords Используем файл с похожими символами разных языков
  • alphabet Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)

Версия на Python
import alm# Мы собираем N-граммы длиной 3alm.setSize(3)# Для сборки используем все доступные ядраalm.setThreads(0)# Устанавливаем локаль окружения (можно не указывать)alm.setLocale("en_US.UTF-8")# Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)alm.setAlphabet("абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz")# Устанавливаем похожие символы разных языковalm.setSubstitutes({'p':'р','c':'с','o':'о','t':'т','k':'к','e':'е','a':'а','h':'н','x':'х','b':'в','m':'м'})# Разрешаем хранить токен <unk> в языковой моделиalm.setOption(alm.options_t.allowUnk)# Разрешаем исправлять слова с замещёнными буквами из других языковalm.setOption(alm.options_t.mixDicts)# Собираем не целиком N-граммы  как есть а только последовательности нормальных словalm.setOption(alm.options_t.tokenWords)# Используем алгоритм сглаживания wittenbell (на данном этапе он не применяется, но какой-то алгоритм сглаживания указать нужно)alm.init(alm.smoothing_t.wittenBell)# Используем в обучении, общеупотребимые аббревиатуры, такие как (США, ФСБ, КГБ ...)f = open('./abbrs/abbrs.txt')for abbr in f.readlines():    abbr = abbr.replace("\n", "")    alm.addAbbr(abbr)f.close()# Используем заранее подготовленный белый список словf = open('./texts/whitelist/words.txt')for word in f.readlines():    word = word.replace("\n", "")    alm.addGoodword(word)f.close()# Используем заранее подготовленный чёрный список словf = open('./texts/blacklist/garbage.txt')for word in f.readlines():    word = word.replace("\n", "")    alm.addBadword(word)f.close()def status(text, status):    print(text, status)def statusWords(status):    print("Write words", status)def statusVocab(status):    print("Write vocab", status)def statusMap(status):    print("Write map", status)def statusSuffix(status):    print("Write suffix", status)# Выполняем сборку языковой моделиalm.collectCorpus("./texts/corpus", status)# Выполняем сохранение списка собранных уникальных словalm.writeWords("./output/words.txt", statusWords)# Выполняем сохранение словаряalm.writeVocab("./output/alm.vocab", statusVocab)# Выполняем сохранение карты последовательностиalm.writeMap("./output/alm.map", statusMap)# Выполняем сохранение списка суффиксов цифровых аббревиатурalm.writeSuffix("./output/alm.abbr", statusSuffix)

Таким образом, мы собираем все наши корпуса

Прунинг собранного корпуса с помощью ALM
prune.json
{    "size": 3,    "debug": 1,    "allow-unk": true,    "method": "vprune",    "vprune-wltf": -15.0,    "locale": "en_US.UTF-8",    "smoothing": "wittenbell",    "r-map": "./corpus1/alm.map",    "r-vocab": "./corpus1/alm.vocab",    "w-map": "./output/alm.map",    "w-vocab": "./output/alm.vocab",    "goodwords": "./texts/whitelist/words.txt",    "badwords": "./texts/blacklist/garbage.txt",    "alphabet": "абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz"}

$ ./alm -r-json ./prune.json

  • size Мы используем N-граммы длиной 3
  • debug Выводим индикатор выполнения прунинга словаря
  • allow-unk Разрешаем хранить токенunkв языковой модели
  • vprune-wltf Минимально-разрешённый вес слова в словаре (все, что ниже удаляется)
  • locale Устанавливаем локаль окружения (можно не указывать)
  • smoothing Используем алгоритм сглаживания wittenbell (на данном этапе он не применяется, но какой-то алгоритм сглаживания указать нужно)
  • r-map Кара последовательности собранная на предыдущем этапе
  • r-vocab Словарь собранный на предыдущем этапе
  • w-map Сохраняем карту последовательности как промежуточный результат
  • w-vocab Сохраняем собранный словарь
  • goodwords Используем заранее подготовленный белый список слов
  • badwords Используем заранее подготовленный чёрный список слов
  • alphabet Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)

Версия на Python
import alm# Мы собираем N-граммы длиной 3alm.setSize(3)# Для сборки используем все доступные ядраalm.setThreads(0)# Устанавливаем локаль окружения (можно не указывать)alm.setLocale("en_US.UTF-8")# Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)alm.setAlphabet("абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz")# Разрешаем хранить токен <unk> в языковой моделиalm.setOption(alm.options_t.allowUnk)# Используем алгоритм сглаживания wittenbell (на данном этапе он не применяется, но какой-то алгоритм сглаживания указать нужно)alm.init(alm.smoothing_t.wittenBell)# Используем заранее подготовленный белый список словf = open('./texts/whitelist/words.txt')for word in f.readlines():    word = word.replace("\n", "")    alm.addGoodword(word)f.close()# Используем заранее подготовленный чёрный список словf = open('./texts/blacklist/garbage.txt')for word in f.readlines():    word = word.replace("\n", "")    alm.addBadword(word)f.close()def statusPrune(status):    print("Prune data", status)def statusReadVocab(text, status):    print("Read vocab", text, status)def statusWriteVocab(status):    print("Write vocab", status)def statusReadMap(text, status):    print("Read map", text, status)def statusWriteMap(status):    print("Write map", status)# Выполняем загрузкусловаряalm.readVocab("./corpus1/alm.vocab", statusReadVocab)# Выполняем загрузку карты последовательностиalm.readMap("./corpus1/alm.map", statusReadMap)# Выполняем прунинг словаряalm.pruneVocab(-15.0, 0, 0, statusPrune)# Выполняем сохранение словаряalm.writeVocab("./output/alm.vocab", statusWriteVocab)# Выполняем сохранение карты последовательностиalm.writeMap("./output/alm.map", statusWriteMap)


Объединение собранных данных с помощью ALM
merge.json
{    "size": 3,    "debug": 1,    "allow-unk": true,    "method": "merge",    "mixed-dicts": "true",    "locale": "en_US.UTF-8",    "smoothing": "wittenbell",    "r-words": "./texts/words",    "r-map": "./corpus1",    "r-vocab": "./corpus1",    "w-map": "./output/alm.map",    "w-vocab": "./output/alm.vocab",    "goodwords": "./texts/whitelist/words.txt",    "badwords": "./texts/blacklist/garbage.txt",    "mix-restwords": "./texts/similars/letters.txt",    "alphabet": "абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz"}

$ ./alm -r-json ./merge.json

  • size Мы используем N-граммы длиной 3
  • debug Выводим индикатор выполнения загрузки данных
  • allow-unk Разрешаем хранить токенunkв языковой модели
  • mixed-dicts Разрешаем исправлять слова с замещёнными буквами из других языков
  • locale Устанавливаем локаль окружения (можно не указывать)
  • smoothing Используем алгоритм сглаживания wittenbell (на данном этапе он не применяется, но какой-то алгоритм сглаживания указать нужно)
  • r-words Указываем каталог или файл с словами которые нужно добавить в словарь
  • r-map Указываем каталог с файлами карт последовательности, собранных и пропруненных на предыдущих этапах
  • r-vocab Указываем каталог с файлами словарей, собранных и пропруненных на предыдущих этапах
  • w-map Сохраняем карту последовательности как промежуточный результат
  • w-vocab Сохраняем собранный словарь
  • goodwords Используем заранее подготовленный белый список слов
  • badwords Используем заранее подготовленный чёрный список слов
  • alphabet Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)

Версия на Python
import alm# Мы собираем N-граммы длиной 3alm.setSize(3)# Для сборки используем все доступные ядраalm.setThreads(0)# Устанавливаем локаль окружения (можно не указывать)alm.setLocale("en_US.UTF-8")# Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)alm.setAlphabet("абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz")# Устанавливаем похожие символы разных языковalm.setSubstitutes({'p':'р','c':'с','o':'о','t':'т','k':'к','e':'е','a':'а','h':'н','x':'х','b':'в','m':'м'})# Разрешаем хранить токен <unk> в языковой моделиalm.setOption(alm.options_t.allowUnk)# Разрешаем исправлять слова с замещёнными буквами из других языковalm.setOption(alm.options_t.mixDicts)# Используем алгоритм сглаживания wittenbell (на данном этапе он не применяется, но какой-то алгоритм сглаживания указать нужно)alm.init(alm.smoothing_t.wittenBell)# Используем заранее подготовленный белый список словf = open('./texts/whitelist/words.txt')for word in f.readlines():    word = word.replace("\n", "")    alm.addGoodword(word)f.close()# Используем заранее подготовленный чёрный список словf = open('./texts/blacklist/garbage.txt')for word in f.readlines():    word = word.replace("\n", "")    alm.addBadword(word)f.close()# Используем файл с словами которые нужно добавить в словарьf = open('./texts/words.txt')for word in f.readlines():    word = word.replace("\n", "")    alm.addWord(word)f.close()def statusReadVocab(text, status):    print("Read vocab", text, status)def statusWriteVocab(status):    print("Write vocab", status)def statusReadMap(text, status):    print("Read map", text, status)def statusWriteMap(status):    print("Write map", status)# Выполняем загрузку словаряalm.readVocab("./corpus1", statusReadVocab)# Выполняем загрузку карты последовательностиalm.readMap("./corpus1", statusReadMap)# Выполняем сохранение словаряalm.writeVocab("./output/alm.vocab", statusWriteVocab)# Выполняем сохранение карты последовательностиalm.writeMap("./output/alm.map", statusWriteMap)


Обучение языковой модели с помощью ALM
train.json
{    "size": 3,    "debug": 1,    "allow-unk": true,    "reset-unk": true,    "interpolate": true,    "method": "train",    "locale": "en_US.UTF-8",    "smoothing": "wittenbell",    "r-map": "./output/alm.map",    "r-vocab": "./output/alm.vocab",    "w-arpa": "./output/alm.arpa",    "w-words": "./output/words.txt",    "alphabet": "абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz"}

$ ./alm -r-json ./train.json

  • size Мы используем N-граммы длиной 3
  • debug Выводим индикатор обучения языковой модели
  • allow-unk Разрешаем хранить токенunkв языковой модели
  • reset-unk Выполняем сброс значения частоты, дляunkтокена в языковой модели
  • interpolate Выполнять интерполяцию при расчётах частот
  • locale Устанавливаем локаль окружения (можно не указывать)
  • smoothing Используем алгоритм сглаживания wittenbell
  • r-map Указываем файл карты последовательности, собранной на предыдущих этапах
  • r-vocab Указываем файл словаря, собранного на предыдущих этапах
  • w-arpa Указываем адрес файла ARPA, для сохранения
  • w-words Указываем адрес файла, для сохранения уникальных слов (на всякий случай)
  • alphabet Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)

Версия на Python
import alm# Мы собираем N-граммы длиной 3alm.setSize(3)# Для сборки используем все доступные ядраalm.setThreads(0)# Устанавливаем локаль окружения (можно не указывать)alm.setLocale("en_US.UTF-8")# Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)alm.setAlphabet("абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz")# Устанавливаем похожие символы разных языковalm.setSubstitutes({'p':'р','c':'с','o':'о','t':'т','k':'к','e':'е','a':'а','h':'н','x':'х','b':'в','m':'м'})# Разрешаем хранить токен <unk> в языковой моделиalm.setOption(alm.options_t.allowUnk)# Выполняем сброс значения частоты токена <unk> в языковой моделиalm.setOption(alm.options_t.resetUnk)# Разрешаем исправлять слова с замещёнными буквами из других языковalm.setOption(alm.options_t.mixDicts)# Разрешаем выполнять интерполяцию при расчётахalm.setOption(alm.options_t.interpolate)# Используем алгоритм сглаживания wittenbell (на данном этапе он не применяется, но какой-то алгоритм сглаживания указать нужно)alm.init(alm.smoothing_t.wittenBell)def statusReadVocab(text, status):    print("Read vocab", text, status)def statusReadMap(text, status):    print("Read map", text, status)def statusBuildArpa(status):    print("Build ARPA", status)def statusWriteMap(status):    print("Write map", status)def statusWriteArpa(status):    print("Write ARPA", status)def statusWords(status):    print("Write words", status)# Выполняем загрузку словаряalm.readVocab("./output/alm.vocab", statusReadVocab)# Выполняем загрузку карты последовательностиalm.readMap("./output/alm.map", statusReadMap)# Выполняем расчёты частот языковой моделиalm.buildArpa(statusBuildArpa)# Выполняем запись языковой модели в файл ARPAalm.writeArpa("./output/alm.arpa", statusWriteArpa)# Выполняем сохранение словаряalm.writeWords("./output/words.txt", statusWords)


Обучение spell-checker ASC
train.json
{"size": 3,"debug": 1,"threads": 0,"confidence": true,"mixed-dicts": true,"method": "train","alter": {"е":"ё"},"locale": "en_US.UTF-8","smoothing": "wittenbell","pilots": ["а","у","в","о","с","к","б","и","я","э","a","i","o","e","g"],"w-bin": "./dictionary/3-single.asc","r-abbr": "./output/alm.abbr","r-vocab": "./output/alm.vocab","r-arpa": "./output/alm.arpa","abbrs": "./texts/abbrs/abbrs.txt","goodwords": "./texts/whitelist/words.txt","badwords": "./texts/blacklist/garbage.txt","alters": "./texts/alters/yoficator.txt","upwords": "./texts/words/upp","mix-restwords": "./texts/similars/letters.txt","alphabet": "абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz","bin-code": "ru","bin-name": "Russian","bin-author": "You name","bin-copyright": "You company LLC","bin-contacts": "site: https://example.com, e-mail: info@example.com","bin-lictype": "MIT","bin-lictext": "... License text ...","embedding-size": 28,"embedding": {    "а": 0, "б": 1, "в": 2, "г": 3, "д": 4, "е": 5,    "ё": 5, "ж": 6, "з": 7, "и": 8, "й": 8, "к": 9,    "л": 10, "м": 11, "н": 12, "о": 0, "п": 13, "р": 14,    "с": 15, "т": 16, "у": 17, "ф": 18, "х": 19, "ц": 20,    "ч": 21, "ш": 21, "щ": 21, "ъ": 22, "ы": 23, "ь": 22,    "э": 5, "ю": 24, "я": 25, "<": 26, ">": 26, "~": 26,    "-": 26, "+": 26, "=": 26, "*": 26, "/": 26, ":": 26,    "%": 26, "|": 26, "^": 26, "&": 26, "#": 26, "'": 26,    "\\": 26, "0": 27, "1": 27, "2": 27, "3": 27, "4": 27,    "5": 27, "6": 27, "7": 27, "8": 27, "9": 27, "a": 0,    "b": 2, "c": 15, "d": 4, "e": 5, "f": 18, "g": 3,    "h": 12, "i": 8, "j": 6, "k": 9, "l": 10, "m": 11,    "n": 12, "o": 0, "p": 14, "q": 13, "r": 14, "s": 15,    "t": 16, "u": 24, "v": 21, "w": 22, "x": 19, "y": 17, "z": 7}}

$ ./asc -r-json ./train.json

  • size Мы используем N-граммы длиной 3
  • debug Выводим индикатор обучения опечаточника
  • threads Для сборки используем все доступные ядра
  • confidence Разрешаем загружать данные из ARPA так-как они есть, без перетокенизации
  • mixed-dicts Разрешаем исправлять слова с замещёнными буквами из других языков
  • alter Альтернативные буквы (буквы которые замещают другие буквы в словаре, в нашем случае, это буква Ё)
  • locale Устанавливаем локаль окружения (можно не указывать)
  • smoothing Используем алгоритм сглаживания wittenbell (на данном этапе он не применяется, но какой-то алгоритм сглаживания указать нужно)
  • pilots Устанавливаем список пилотных слов (слова состоящие из одной буквы)
  • w-bin Устанавливаем адрес для сохранения бинарного контейнера
  • r-abbr Указываем каталог с файлами, собранных суффиксов цифровых аббревиатур на предыдущих этапах
  • r-vocab Указываем файл словаря, собранного на предыдущих этапах
  • r-arpa Указываем файл ARPA, собранный на предыдущем этапе
  • abbrs Используем в обучении, общеупотребимые аббревиатуры, такие как (США, ФСБ, КГБ ...)
  • goodwords Используем заранее подготовленный белый список слов
  • badwords Используем заранее подготовленный чёрный список слов
  • alters Используем файл со словами содержащими альтернативные буквы, которые используются всегда однозначно (синтаксис файла аналогичен списку похожих букв в разных алфавитах)
  • upwords Используем файл со списком слов, которые всегда употребляются с заглавной буквы (названия, имена, фамилии...)
  • mix-restwords Используем файл с похожими символами разных языков
  • alphabet Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)
  • bin-code Устанавливаем код языка в словаре
  • bin-name Устанавливаем название словаря
  • bin-author Устанавливаем имя автора словаря
  • bin-copyright Устанавливаем копирайт словаря
  • bin-contacts Устанавливаем контактные данные автора словаря
  • bin-lictype Устанавливаем тип лицензии словаря
  • bin-lictext Устанавливаем текст лицензии словаря
  • embedding-size Устанавливаем размер блока внутреннего эмбеддинга
  • embedding Устанавливаем параметры блока внутреннего эмбеддинга (не обязательно, влияет на точность подбора кандидатов)

Версия на Python
import asc# Мы собираем N-граммы длиной 3asc.setSize(3)# Для сборки используем все доступные ядраasc.setThreads(0)# Устанавливаем локаль окружения (можно не указывать)asc.setLocale("en_US.UTF-8")# Разрешаем исправлять регистр у слов в начале предложенийasc.setOption(asc.options_t.uppers)# Разрешаем хранить токен <unk> в языковой моделиasc.setOption(asc.options_t.allowUnk)# Выполняем сброс значения частоты токена <unk> в языковой моделиasc.setOption(asc.options_t.resetUnk)# Разрешаем исправлять слова с замещенными буквами из других языковasc.setOption(asc.options_t.mixDicts)# Разрешаем загружать данные из ARPA так-как они есть, без перетокенизацииasc.setOption(asc.options_t.confidence)# Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)asc.setAlphabet("абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz")# Указываем список пилотных слов (слова которые состоят из одной буквы)asc.setPilots(["а","у","в","о","с","к","б","и","я","э","a","i","o","e","g"])# Устанавливаем похожие символы разных языковasc.setSubstitutes({'p':'р','c':'с','o':'о','t':'т','k':'к','e':'е','a':'а','h':'н','x':'х','b':'в','m':'м'})# Загружаем файл заранее подготовленный белый список словf = open('./texts/whitelist/words.txt')for word in f.readlines():    word = word.replace("\n", "")    asc.addGoodword(word)f.close()# Загружаем файл заранее подготовленный чёрный список словf = open('./texts/blacklist/garbage.txt')for word in f.readlines():    word = word.replace("\n", "")    asc.addBadword(word)f.close()# Загружаем файл суффиксов цифровых аббревиатурf = open('./output/alm.abbr')for word in f.readlines():    word = word.replace("\n", "")    asc.addSuffix(word)f.close()# Загружаем файл общеупотребимые аббревиатуры, такие как (США, ФСБ, КГБ ...)f = open('./texts/abbrs/abbrs.txt')for abbr in f.readlines():    abbr = abbr.replace("\n", "")    asc.addAbbr(abbr)f.close()# Загружаем файл со списком слов, которые всегда употребляются с заглавной буквы (названия, имена, фамилии...)f = open('./texts/words/upp/words.txt')for word in f.readlines():    word = word.replace("\n", "")    asc.addUWord(word)f.close()# Устанавливаем альтернативную буквуasc.addAlt("е", "ё")# Загружаем файл со словами содержащими альтернативные буквы, которые используются всегда однозначно (синтаксис файла аналогичен списку похожих букв в разных алфавитах)f = open('./texts/alters/yoficator.txt')for words in f.readlines():    words = words.replace("\n", "")    words = words.split('\t')    asc.addAlt(words[0], words[1])f.close()def statusIndex(text, status):    print(text, status)def statusBuildIndex(status):    print("Build index", status)def statusArpa(status):    print("Read arpa", status)def statusVocab(status):    print("Read vocab", status)# Выполняем загрузку данные языковой модели из файла ARPAasc.readArpa("./output/alm.arpa", statusArpa)# Выполняем загрузку словаряasc.readVocab("./output/alm.vocab", statusVocab)# Устанавливаем код языка в словареasc.setCode("RU")# Устанавливаем тип лицензии словаряasc.setLictype("MIT")# Устанавливаем название словаряasc.setName("Russian")# Устанавливаем имя автора словаряasc.setAuthor("You name")# Устанавливаем копирайт словаряasc.setCopyright("You company LLC")# Устанавливаем текст лицензии словаряasc.setLictext("... License text ...")# Устанавливаем контактные данные автора словаряasc.setContacts("site: https://example.com, e-mail: info@example.com")# Устанавливаем параметры блока внутреннего эмбеддинга (не обязательно, влияет на точность подбора кандидатов)asc.setEmbedding({    "а": 0, "б": 1, "в": 2, "г": 3, "д": 4, "е": 5,    "ё": 5, "ж": 6, "з": 7, "и": 8, "й": 8, "к": 9,    "л": 10, "м": 11, "н": 12, "о": 0, "п": 13, "р": 14,    "с": 15, "т": 16, "у": 17, "ф": 18, "х": 19, "ц": 20,    "ч": 21, "ш": 21, "щ": 21, "ъ": 22, "ы": 23, "ь": 22,    "э": 5, "ю": 24, "я": 25, "<": 26, ">": 26, "~": 26,    "-": 26, "+": 26, "=": 26, "*": 26, "/": 26, ":": 26,    "%": 26, "|": 26, "^": 26, "&": 26, "#": 26, "'": 26,    "\\": 26, "0": 27, "1": 27, "2": 27, "3": 27, "4": 27,    "5": 27, "6": 27, "7": 27, "8": 27, "9": 27, "a": 0,    "b": 2, "c": 15, "d": 4, "e": 5, "f": 18, "g": 3,    "h": 12, "i": 8, "j": 6, "k": 9, "l": 10, "m": 11,    "n": 12, "o": 0, "p": 14, "q": 13, "r": 14, "s": 15,    "t": 16, "u": 24, "v": 21, "w": 22, "x": 19, "y": 17, "z": 7}, 28)# Выполняем сборку индекса бинарного словаряasc.buildIndex(statusBuildIndex)# Выполняем сохранение индекса бинарного словаряasc.saveIndex("./dictionary/3-middle.asc", "", 128, statusIndex)


Я понимаю, что не каждый человек сможет обучить свой бинарный словарь, на это нужны текстовые корпуса и значительные вычислительные ресурсы. По этому ASC способная работать с одним лишь файлом ARPA в качестве основного словаря.

Пример работы
spell.json
{    "ad": 13,    "cw": 38120,    "debug": 1,    "threads": 0,    "method": "spell",    "alter": {"е":"ё"},    "asc-split": true,    "asc-alter": true,    "confidence": true,    "asc-esplit": true,    "asc-rsplit": true,    "asc-uppers": true,    "asc-hyphen": true,    "mixed-dicts": true,    "asc-wordrep": true,    "spell-verbose": true,    "r-text": "./texts/test.txt",    "w-text": "./texts/output.txt",    "upwords": "./texts/words/upp",    "r-arpa": "./dictionary/alm.arpa",    "r-abbr": "./dictionary/alm.abbr",    "abbrs": "./texts/abbrs/abbrs.txt",    "alters": "./texts/alters/yoficator.txt",    "mix-restwords": "./similars/letters.txt",    "goodwords": "./texts/whitelist/words.txt",    "badwords": "./texts/blacklist/garbage.txt",    "pilots": ["а","у","в","о","с","к","б","и","я","э","a","i","o","e","g"],    "alphabet": "абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz",    "embedding-size": 28,    "embedding": {        "а": 0, "б": 1, "в": 2, "г": 3, "д": 4, "е": 5,        "ё": 5, "ж": 6, "з": 7, "и": 8, "й": 8, "к": 9,        "л": 10, "м": 11, "н": 12, "о": 0, "п": 13, "р": 14,        "с": 15, "т": 16, "у": 17, "ф": 18, "х": 19, "ц": 20,        "ч": 21, "ш": 21, "щ": 21, "ъ": 22, "ы": 23, "ь": 22,        "э": 5, "ю": 24, "я": 25, "<": 26, ">": 26, "~": 26,        "-": 26, "+": 26, "=": 26, "*": 26, "/": 26, ":": 26,        "%": 26, "|": 26, "^": 26, "&": 26, "#": 26, "'": 26,        "\\": 26, "0": 27, "1": 27, "2": 27, "3": 27, "4": 27,        "5": 27, "6": 27, "7": 27, "8": 27, "9": 27, "a": 0,        "b": 2, "c": 15, "d": 4, "e": 5, "f": 18, "g": 3,        "h": 12, "i": 8, "j": 6, "k": 9, "l": 10, "m": 11,        "n": 12, "o": 0, "p": 14, "q": 13, "r": 14, "s": 15,        "t": 16, "u": 24, "v": 21, "w": 22, "x": 19, "y": 17, "z": 7    }}

$ ./asc -r-json ./spell.json

Версия на Python
import asc# Для сборки используем все доступные ядраasc.setThreads(0)# Разрешаем исправлять регистр у слов в начале предложенийasc.setOption(asc.options_t.uppers)# Разрешаем выполнять сплитыasc.setOption(asc.options_t.ascSplit)# Разрешаем выполнять Ёфикациюasc.setOption(asc.options_t.ascAlter)# Разрешаем выполнять сплит слов с ошибкамиasc.setOption(asc.options_t.ascESplit)# Разрешаем удалять лишние пробелы между словамиasc.setOption(asc.options_t.ascRSplit)# Разрешаем выполнять корректировку регистров словasc.setOption(asc.options_t.ascUppers)# Разрешаем выполнять сплит по дефисамasc.setOption(asc.options_t.ascHyphen)# Разрешаем удалять повторяющиеся словаasc.setOption(asc.options_t.ascWordRep)# Разрешаем исправлять слова с замещенными буквами из других языковasc.setOption(asc.options_t.mixDicts)# Разрешаем загружать данные из ARPA так-как они есть, без перетокенизацииasc.setOption(asc.options_t.confidence)# Указываем алфавит используемый при обучении (алфавит всегда должен быть указан один и тот же)asc.setAlphabet("абвгдеёжзийклмнопрстуфхцчшщъыьэюяabcdefghijklmnopqrstuvwxyz")# Указываем список пилотных слов (слова которые состоят из одной буквы)asc.setPilots(["а","у","в","о","с","к","б","и","я","э","a","i","o","e","g"])# Устанавливаем похожие символы разных языковasc.setSubstitutes({'p':'р','c':'с','o':'о','t':'т','k':'к','e':'е','a':'а','h':'н','x':'х','b':'в','m':'м'})# Загружаем файл заранее подготовленный белый список словf = open('./texts/whitelist/words.txt')for word in f.readlines():    word = word.replace("\n", "")    asc.addGoodword(word)f.close()# Загружаем файл заранее подготовленный чёрный список словf = open('./texts/blacklist/garbage.txt')for word in f.readlines():    word = word.replace("\n", "")    asc.addBadword(word)f.close()# Загружаем файл суффиксов цифровых аббревиатурf = open('./output/alm.abbr')for word in f.readlines():    word = word.replace("\n", "")    asc.addSuffix(word)f.close()# Загружаем файл общеупотребимые аббревиатуры, такие как (США, ФСБ, КГБ ...)f = open('./texts/abbrs/abbrs.txt')for abbr in f.readlines():    abbr = abbr.replace("\n", "")    asc.addAbbr(abbr)f.close()# Загружаем файл со списком слов, которые всегда употребляются с заглавной буквы (названия, имена, фамилии...)f = open('./texts/words/upp/words.txt')for word in f.readlines():    word = word.replace("\n", "")    asc.addUWord(word)f.close()# Устанавливаем альтернативную буквуasc.addAlt("е", "ё")# Загружаем файл со словами содержащими альтернативные буквы, которые используются всегда однозначно (синтаксис файла аналогичен списку похожих букв в разных алфавитах)f = open('./texts/alters/yoficator.txt')for words in f.readlines():    words = words.replace("\n", "")    words = words.split('\t')    asc.addAlt(words[0], words[1])f.close()def statusArpa(status):    print("Read arpa", status)def statusIndex(status):    print("Build index", status)# Выполняем загрузку данные языковой модели из файла ARPAasc.readArpa("./dictionary/alm.arpa", statusArpa)# Устанавливаем характеристики словаря (38120 слов полученных при обучении и 13 документов используемых в обучении)asc.setAdCw(38120, 13)# Устанавливаем параметры блока внутреннего эмбеддинга (не обязательно, влияет на точность подбора кандидатов)asc.setEmbedding({    "а": 0, "б": 1, "в": 2, "г": 3, "д": 4, "е": 5,    "ё": 5, "ж": 6, "з": 7, "и": 8, "й": 8, "к": 9,    "л": 10, "м": 11, "н": 12, "о": 0, "п": 13, "р": 14,    "с": 15, "т": 16, "у": 17, "ф": 18, "х": 19, "ц": 20,    "ч": 21, "ш": 21, "щ": 21, "ъ": 22, "ы": 23, "ь": 22,    "э": 5, "ю": 24, "я": 25, "<": 26, ">": 26, "~": 26,    "-": 26, "+": 26, "=": 26, "*": 26, "/": 26, ":": 26,    "%": 26, "|": 26, "^": 26, "&": 26, "#": 26, "'": 26,    "\\": 26, "0": 27, "1": 27, "2": 27, "3": 27, "4": 27,    "5": 27, "6": 27, "7": 27, "8": 27, "9": 27, "a": 0,    "b": 2, "c": 15, "d": 4, "e": 5, "f": 18, "g": 3,    "h": 12, "i": 8, "j": 6, "k": 9, "l": 10, "m": 11,    "n": 12, "o": 0, "p": 14, "q": 13, "r": 14, "s": 15,    "t": 16, "u": 24, "v": 21, "w": 22, "x": 19, "y": 17, "z": 7}, 28)# Выполняем сборку индекса бинарного словаряasc.buildIndex(statusIndex)f1 = open('./texts/test.txt')f2 = open('./texts/output.txt', 'w')for line in f1.readlines():    res = asc.spell(line)    f2.write("%s\n" % res[0])f2.close()f1.close()



P.S. Для тех, кто не хочет вообще ничего собирать и обучать, я поднял web версию ASC. Нужно также учитывать то, что система исправления опечаток это не всезнающая система и скормить туда весь русский язык невозможно. Исправлять любые тексты ASC не будет, под каждую тематику нужно обучать отдельно.
Подробнее..

Кластеризация и классификация больших Текстовых данных с помощью М.О. на Java. Статья 3 АрхитектураРезультаты

24.01.2021 14:08:22 | Автор: admin

Привет, Хабр! Сегодня будет заключительная часть темы Кластеризация и классификация больших Текстовых данных с помощью машинного обучения на Java. Данная статья является продолжениемпервой и второй статьи.

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

Архитектуры системы можно разделить на две основные части: веб приложение и программное обеспечение кластеризации и классификации данных

Алгоритм программного обеспечение для машинного обучение состоит из 3 основных частей:

  1. обработка естественного языка;

    1. токенизация;

    2. лемматизация;

    3. стоп-листинг;

    4. частота слов;

  2. методы кластеризации ;

    1. TF-IDF ;

    2. SVD;

    3. нахождение кластерных групп;

  3. методы классификации Aylien API.

Обработка естественного языка

Алгоритм начинается с чтение любых текстовых данных. Так как система у нас электронная библиотеку, то и книги в основном в формате pdf. Реализация и детали обработки NLP можно почитать тут.

Ниже приводим сравнение при запуске алгоритмов Лемматизации и Стеммитизации:

Общее количество слов: 4173415Количество слов после приминение Лемматизации: 88547Количество слов после приминение Стеммитизации: 82294

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

characterize, design, space, render, robot, face, alisa, kalegina, university, washington, seattle, washington, grace, schroeder, university, washington, seattle, washington, aidan, allchin, lakeside, also, il, school, seattle, washington, keara, berlin, macalester, college, saint, paul, minnesota, kearaberlingmailcom, maya, cakmak, university, washington, seattle, washington, abstract, face, critical, establish, agency, social, robot, building, expressive, mechanical, face, costly, difficult, robot, build, year, face, ren, der, screen, great, flexibility, robot, face, open, design, space, tablish, robot, character, perceive, property, despite, prevalence, robot, render, face, systematic, exploration, design, space, work, aim, fill, gap, conduct, survey, identify, robot, render, face, code, term, property, statistics

а стеммитизация обрезает окончание и в некоторых случаях удаляет нужные буквы, теряя основной смысл слово:

character, design, space, render, robot, face, alisa, kalegina, univers, washington, seattl, washington, grace, schroeder, univers, washington, seattl, washington, grsuwedu, aidan, allchin, lakesid, also, il, school, seattl, washington, keara, berlin, macalest, colleg, saint, paul, minnesota, kearaberlingmailcom, maya, cakmak, univers, washington, seattl, washington, abstract, face, critic, establish, agenc, social, robot, build, express, mechan, face, cost, difficult, mani, robot, built, year, face, ren, dere, screen, great, flexibl, robot, face, open, design, space, tablish, robot, charact, perceiv, properti, despit, preval, robot, render, face, systemat, explor, design, space, work, aim, fill, gap, conduct, survey, identifi, robot, render, face, code, term, properti, statist, common, pattern, observ, data, set, face, conduct, survey, understand, peopl, percep, tion, render, robot, face, identifi, impact, differ, face, featur, survey, result, indic, prefer, vari, level, realism, detail, robot, facecharacter, design, space, render, robot, face, alisa, kalegina, univers, washington, seattl, washington, grace, schroeder, univers, washington, seattl, washington, grsuwedu, aidan, allchin, lakesid, also, il, school, seattl, washington, keara, berlin, macalest, colleg, saint, paul, minnesota, kearaberlingmailcom, maya, cakmak, univers, washington, seattl, washington, abstract, face, critic, establish, agenc, social, robot, build, express, mechan, face, cost, difficult, mani, robot, built, year, face, ren, dere, screen, great, flexibl, robot, face, open, design, space, tablish, robot, charact, perceiv, properti, despit, preval, robot, render, face, systemat, explor, design, space, work, aim, fill, gap, conduct, survey, identifi, robot, render, face, code, term, properti, statist, common, pattern, observ, data, set, face, conduct, survey, understand, peopl, percep, tion, render, robot, face, identifi, impact, differ, face, featur, survey, result, indic, prefer, vari, level, realism, detail, robot, face

Методы кластеризации

Для применения алгоритма tf-idf нужно подсчитать сколько раз слово встречается в каждом документе. Можно использовать HashMap, где ключ - слово, значение - кол-во.

После этого нужно построит матрицу документы-слова:

Далее по формуле вычисляем tf-idf:

Следующий этап, использование метода сингулярного разложение, где на вход приходит результат tf-idf. Пример выходных данных алгоритма сингулярного разложение:

-0.0031139399383999997 0.023330604746 -1.3650204652799997E-4-0.038380206566 0.00104373247064 0.056140327901-0.006980774822399999 0.073057418689 -0.0035209342337999996-0.0047152503238 0.0017397257449 0.024816828582999998-0.005195951771999999 0.03189764447 -5.9991080912E-4-0.008568593700999999 0.114337675179 -0.0088221197958-0.00337365927 0.022604474721999997 -1.1457816390099999E-4-0.03938283525 -0.0012682796482399999 0.0023486548592-0.034341362795999995 -0.00111758118864 0.0036010404917-0.0039026609385999994 0.0016699372352999998 0.021206653766000002-0.0079418490394 0.003116062838 0.072380311755-0.007021828444599999 0.0036496566028 0.07869801528199999-0.0030219410092 0.018637386319 0.00102082843809-0.0042041069026 0.023621439238999998 0.0022947637053-0.0061050946438 0.00114796066823 0.018477825284-0.0065708646563999995 0.0022944737838999996 0.035902813761-0.037790461814 -0.0015372596281999999 0.008878823611899999-0.13264545848599998 -0.0144908102251 -0.033606397957999995-0.016229093174 1.41831464625E-4 0.005181988760999999-0.024075296507999996 -8.708131965899999E-4 0.0034344653516999997

Матрицу SVD можно использовать как координаты в трехмерном пространстве.

После применение сингулярного разложение, нужно записать результат в базу данных для дальнейшей обработки. Так как уже упоминалась что выходные данные это координаты, то нужно записать эти данные в трехмерном пространстве. OrientDB поддерживает графовые базы данных, и данная поддержка и является целью использование именно OrientDB как основной базы данных. OrientDB поддерживает только двухмерные графическое пространство, но это не помешает, так как трехмерная пространство нужно только для вычислении, для графических целей можно использовать и двухмерное пространство. Все данные каждого документа хранится в объекте вершин. Данные вершины записываются в базу.

Теперь нужно применить данную операцию и для терминов, то есть слов.

Последний этап метода кластеризации найти кластерные группы. Так как у нас уже есть трехмерная пространство, где хранятся точки документов и терминов в виде вершин, то нужно соединить эти документы и слова использовав схожий метод кластеризации DBSCAN. Для определения расстояние между документом и словом используется Евклидовое расстояние. А радиус можно определить по формуле ниже. В данном примере и при тестировании используется r=0.007. Так как в пространстве находится 562 документов и более 80.000 тысяч слов, то они расположены близко. При большом радиусе алгоритм будет связывать термин и документ в один кластер, которые не должны быть в одной группе.

r=max(D)/n

где max(D) это дистанция между документом и самой дальней точкой термина, то есть максимальная дистанция документа в пространстве. n - это количество документов в пространстве

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

После этого нужно всего лишь соединить вершины документов, которые имеют общие вершины терминов. Для соединения документов нужно чтобы общее число терминов было больше 4-х. Формула определение общего сила слов (в данном случае > nt)

nt=N/S

N это количество кластерных групп термин - документов, S это количество связей в семантическом пространстве.

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

Методы классификации Aylien API

Для классификации в инструменте Aylien API всего лишь нужно передать любой текст. API вернет ответ в виде json объекта, где внутри есть категории классификации. Можно было бы отправлять весь текст каждого документа в одной группе кластеров через API и получить категории классификации. Для примера рассмотрим 9 групп кластеров, которые состоят из статьи про ИТ технологии. Все тексты документов каждой группы записываются в массив и отправляют запрос POST через API:

String queryText = "select  DocText from documents where clusters = '" + cluster + "'";   OResultSet resultSet = database.query(queryText);   while (resultSet.hasNext()) {   OResult result = resultSet.next();   String textDoc = result.toString().replaceAll("[\\<||\\>||\\{||\\}]", "").replaceAll("doctext:", "")   .toLowerCase();   keywords.add(textDoc.replaceAll("\\n", ""));   }   ClassifyByTaxonomyParams.Builder classifyByTaxonomybuilder    = ClassifyByTaxonomyParams.newBuilder();   classifyByTaxonomybuilder.setText(keywords.toString());   classifyByTaxonomybuilder.setTaxonomy(ClassifyByTaxonomyParams.StandardTaxonomy.IAB_QAG);   TaxonomyClassifications response = client.classifyByTaxonomy(classifyByTaxonomybuilder.build());   for (TaxonomyCategory c : response.getCategories()) {   clusterUpdate.add(c.getLabel());   }

После успешного получение ответа от сервиса методам GET, данные группы обновляются:

На этом этапе алгоритм кластеризации и классификации закончен. Все эти данные записываются в базу данных для дальнейшей обработки и использование в веб интерфейсе.

Так же изучался и подход применение классификации без использования метода кластеризации. Результат очень сильно отличался. Так как если алгоритм не знает группы кластеров, то метод классификации будет классифицировать каждый документ отдельно. И предметы для каждого документа может быть различным и не обобщенным. Так для эксперимента, классифицируем каждый документ и находим предмет. Но для сравнения оставим кластерные группы, которые не будут влиять на саму классификацию:

Разработка веб-интерфейса

Цель разработки веб-интерфейса наглядный вид результата использование алгоритма кластеризации и классификации. Это дает пользователю удобный интерфейс не только увидеть сам результат, но и в дальнейшем использовать эти данные для нужд. Так же разработка веб-интерфейса показывает, что данный метод можно успешно использовать для онлайн библиотек. Веб приложение было написано с использованием Фреймворка Vaadin Flow:

В данном приложении есть следующие функции:

  • Документы, разделенные по предметам методом кластеризации и классификации.

  • Поиск по ключевым словам.

  • Поиск по хэш-тегам.

  • Весь список документов в базе данных, где есть возможность поиска по ИД документа, наименованию документа, предметам кластеров, ключевым словам и по хэш-тэгам.

  • Возможность скачивание файла.

Список документов классификации по предмету Technology & Computing:

Список документов найденные по ключевым словам:

Табличный список всех документов:

Заключение

В работе был подробно рассмотрен сама концепция машинного обучение, для понимания цели использование методов или алгоритмов машинного обучения. Подробно описаны актуальные и известные методы и алгоритмы машинного обучения для решения цели и задачи работы. Так как задачи кластеризации используется для разных областей и предметов, в данной исследовательской работе было выбрано цель автоматизация процесса классификации текстовых данных, которые считаются сложнее чем обычные задачи классификации других данных. Алгоритм описанный и разработанный в данной исследовательской работе можно применять на большие количества текстовых документов на английском языке. Хотя алгоритм не сильно подвязан на язык текста. Для использования алгоритма для других языков нужно изменить алгоритмы обработки естественного языка. Алгоритм включает в себе две основных методов: кластеризация текста и классификация групп кластеров.

Разработка алгоритма кластеризации, который включают в себе последовательное применение алгоритмов лемматизации, токенизации, стоп-листниг, tf-idf, сингулярного разложение. Первые три метода относится к методу обработки естественного языка, данные методы можно изменить под язык обрабатываемого текста. Для нахождение кластерных групп используется алгоритм на основе метода DBSCAN и использование Евклидового расстояние для определения расстояние между объектами. При исследовании было доказано что точность кластеризации зависит от отношения количества кластеров к количеству объектов в одном кластере. Количество кластеров определяется радиусом каждого документа, а количество объектов в одном кластере определяется средним количеством общих объектов, в данном случае слов или терминов. Алгоритм кластеризации описанный в работе можно использовать не только для классификации групп, а и для других целей, таких как нахождение ассоциативных правил, нахождение групп документов, которые схожи по смысловому тексту и т.д.

В результате исследование, было предложено использование NoSQL базы данных, о именно OrinetDB, который поддерживает все 4 модели NoSQL. Данный тип базы данных очень хорошо подходит для хранения результатов алгоритма кластеризации, так как данный результат является не реляционным. Стоит отметить что OrientDB очень удобен для хранения, обработки и визуализации хранимых данных.

Для классификации кластерных используется Aylien API, который использует подход классификации по таксономии и на базе кодов. В результате исследовании кластерные группы были разделены по предметным областям, который включает в себя более 100 контентной категории. Данный метод можно заменить и другими, более специфическими алгоритмами машинного обучение, таких как метод опорных векторов, метод k-ближайших, нейронную сеть. Но так как данные методы требуют большое количество данных который используется для построения модели, в данной работе было использована готовая модель классификации.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru