[Natural Language Processing, Алгоритмы, Изучение языков, Искусственный интеллект, Машинное обучение] ANYKS Spell-checker
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Здравствуйте, это моя третья статья на хабре, ранее я писал статью о языковой модели ALM. Сейчас, я хочу познакомить вас с системой исправления опечаток ASC (реализованной на основе ALM).
Да, систем исправления опечаток существует огромное количество, у всех есть свои сильные и слабые стороны, из открытых систем я могу выделить одну наиболее перспективную JamSpell, с ней и будем сравнивать. Есть ещё подобная система от DeepPavlov, про которую многие могут подумать, но я с ней так и не подружился.
Список возможностей:
- Исправление ошибок в словах с разницей до 4-х дистанций по Левенштейну.
- Исправление опечаток в словах (вставка, удаление, замещение, перестановка) символов.
- Ёфикация с учётом контекста.
- Простановка регистра первой буквы слова, для (имён собственных и названий) с учётом контекста.
- Разбиение объединённых слов на отдельные слова, с учётом контекста.
- Выполнение анализа текста без корректировки исходного текста.
- Поиск в тексте наличия (ошибок, опечаток, неверного контекста).
Поддерживаемые операционные системы:
- 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
Установка
SPL
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
Обучение
SPL
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 asc
asc.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
Тестирование
SPL
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 asc
asc.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>
#endif
using 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-8
using 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
Результаты
Получение результатов
SPL
$ 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 — обучение на грязных данных. В отрытом доступе найти текстовые корпуса без ошибок и опечаток практически не реально. Исправлять руками терабайты данных не хватит жизни, а работать с этим как-то надо.
Принцип обучения который предлагаю я
- Собираем языковую модель на грязных данных
- Удаляем все редко встречающиеся слова и N-граммы в собранной языковой модели
- Добавляем одиночные слова для более правильной работы системы исправления опечаток.
- Собираем бинарный словарь
Приступим
Предположим, что у нас есть несколько корпусов разной тематики, логичнее обучить их отдельно, потом объединить.
Сборка корпуса с помощью ALM
SPL
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-граммы длиной 3
alm.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
SPL
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-граммы длиной 3
alm.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
SPL
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-граммы длиной 3
alm.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
SPL
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-граммы длиной 3
alm.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)
# Выполняем запись языковой модели в файл ARPA
alm.writeArpa("./output/alm.arpa", statusWriteArpa)
# Выполняем сохранение словаря
alm.writeWords("./output/words.txt", statusWords)
Обучение spell-checker ASC
SPL
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-граммы длиной 3
asc.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)
# Выполняем загрузку данные языковой модели из файла ARPA
asc.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 в качестве основного словаря.
Пример работы
SPL
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)
# Выполняем загрузку данные языковой модели из файла ARPA
asc.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 не будет, под каждую тематику нужно обучать отдельно.
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, PostgreSQL, Программирование, Алгоритмы] Immutable Trie: найди то, не знаю что, но быстро, и не мусори
- [IT-компании, Законодательство в IT, Социальные сети и сообщества] Администрация США одобрила сделку по продаже части бизнеса TikTok компаниям Oracle и Walmart
- Выпуск Vue.js 3.0.0, фреймворка для создания пользовательских интерфейсов
- [JavaScript, Node.JS] Подключение и настройка TradingView графиков
- [Big Data, Искусственный интеллект, Машинное обучение] Временные сверточные сети – революция в мире временных рядов (перевод)
- [ReactJS, JavaScript] React 17: Ничего нового? (перевод)
- [Google Cloud Platform, Python, R, Профессиональная литература] Учимся обращаться к данным и запрашивать их при помощи Google BigQuery. С примерами на Python и R (перевод)
- [JavaScript, Node.JS] Выбираем финансовые графики для своего приложения
- [Информационная безопасность, Программирование, CTF] Мамкины хацкеры или мой путь в CTF
- [Google Cloud Platform, Облачные сервисы] Анонсируем Google Cloud Next OnAir EMEA
Теги для поиска: #_natural_language_processing, #_algoritmy (Алгоритмы), #_izuchenie_jazykov (Изучение языков), #_iskusstvennyj_intellekt (Искусственный интеллект), #_mashinnoe_obuchenie (Машинное обучение), #_spellchecker, #_alm, #_asc, #_anyks, #_language_model, #_spellchecker, #_tf, #_tfidf, #_idf, #_natural_language_processing, #_algoritmy (
Алгоритмы
), #_izuchenie_jazykov (
Изучение языков
), #_iskusstvennyj_intellekt (
Искусственный интеллект
), #_mashinnoe_obuchenie (
Машинное обучение
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:24
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Здравствуйте, это моя третья статья на хабре, ранее я писал статью о языковой модели ALM. Сейчас, я хочу познакомить вас с системой исправления опечаток ASC (реализованной на основе ALM). Да, систем исправления опечаток существует огромное количество, у всех есть свои сильные и слабые стороны, из открытых систем я могу выделить одну наиболее перспективную JamSpell, с ней и будем сравнивать. Есть ещё подобная система от DeepPavlov, про которую многие могут подумать, но я с ней так и не подружился. Список возможностей:
Поддерживаемые операционные системы:
Написана система на С++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 Думаю, излишне добавлять другие данные, при желании каждый может повторить тест, все используемые материалы в тестировании прикладываю ниже. Материалы использовавшиеся в тестировании
Теперь, интересно сравнить, работу самих систем исправления опечаток в равных условиях, обучим два разных опечаточника на одних и тех же текстовых данных и проведём тест. Для сравнения возьмём систему исправления опечаток, которую я упоминал выше JamSpell. ASC vs JamSpell УстановкаSPLASC
$ 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 ОбучениеSPLASC
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 asc
asc.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
ТестированиеSPLASC
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 asc
asc.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> #endif using 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-8 using 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 Результаты Получение результатовSPL$ 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 — обучение на грязных данных. В отрытом доступе найти текстовые корпуса без ошибок и опечаток практически не реально. Исправлять руками терабайты данных не хватит жизни, а работать с этим как-то надо. Принцип обучения который предлагаю я
Приступим Предположим, что у нас есть несколько корпусов разной тематики, логичнее обучить их отдельно, потом объединить. Сборка корпуса с помощью ALMSPLcollect.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
Версия на Python import alm
# Мы собираем N-граммы длиной 3 alm.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) Таким образом, мы собираем все наши корпуса Прунинг собранного корпуса с помощью ALMSPLprune.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
Версия на Python import alm
# Мы собираем N-граммы длиной 3 alm.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) Объединение собранных данных с помощью ALMSPLmerge.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
Версия на Python import alm
# Мы собираем N-граммы длиной 3 alm.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) Обучение языковой модели с помощью ALMSPLtrain.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
Версия на Python import alm
# Мы собираем N-граммы длиной 3 alm.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) # Выполняем запись языковой модели в файл ARPA alm.writeArpa("./output/alm.arpa", statusWriteArpa) # Выполняем сохранение словаря alm.writeWords("./output/words.txt", statusWords) Обучение spell-checker ASCSPLtrain.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
Версия на Python import asc
# Мы собираем N-граммы длиной 3 asc.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) # Выполняем загрузку данные языковой модели из файла ARPA asc.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 в качестве основного словаря. Пример работыSPLspell.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) # Выполняем загрузку данные языковой модели из файла ARPA asc.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 не будет, под каждую тематику нужно обучать отдельно. =========== Источник: habr.com =========== Похожие новости:
Алгоритмы ), #_izuchenie_jazykov ( Изучение языков ), #_iskusstvennyj_intellekt ( Искусственный интеллект ), #_mashinnoe_obuchenie ( Машинное обучение ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:24
Часовой пояс: UTC + 5