Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Здравствуйте, это моя третья статья на хабре, ранее я писал статью о языковой модели 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
Установка
ASC
JamSpell
$ 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
Приведу также пример на языке Python3
JamSpell
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
Тестирование
ASC
spell.json
Пример на языке Python3
JamSpell
Так-как версия для Python у меня не собралась, пришлось написать небольшое приложение на C++
Компилируем и запускаем
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
Результаты
Получение результатов
$ 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
collect.json
Версия на Python
Таким образом, мы собираем все наши корпуса
{
"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
prune.json
Версия на Python
{
"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
merge.json
Версия на Python
{
"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
train.json
Версия на Python
{
"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
train.json
Версия на Python
{
"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 в качестве основного словаря.
Пример работы
spell.json
Версия на Python
{
"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 не будет, под каждую тематику нужно обучать отдельно.