ANYKS Spell-checker

  • Tutorial
image

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

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

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


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


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


  • MacOS X
  • FreeBSD
  • Linux

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

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


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

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


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

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

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


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


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

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

ASC vs JamSpell


Установка
ASC

$ git clone --recursive https://github.com/anyks/asc.git
$ cd ./asc

$ mkdir ./build
$ cd ./build

$ cmake ..
$ make

JamSpell

$ git clone https://github.com/bakwc/JamSpell.git
$ cd ./JamSpell

$ mkdir ./build
$ cd ./build

$ cmake ..
$ make


Обучение
ASC

train.json

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

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

Приведу также пример на языке Python3

import 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

{
    "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 — обучение на грязных данных. В отрытом доступе найти текстовые корпуса без ошибок и опечаток практически не реально. Исправлять руками терабайты данных не хватит жизни, а работать с этим как-то надо.

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


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

Приступим


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

Сборка корпуса с помощью ALM
collect.json

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

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

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

Версия на Python
import alm

# Мы собираем N-граммы длиной 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

{
    "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

{
    "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

{
    "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

{
	"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

{
    "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 не будет, под каждую тематику нужно обучать отдельно.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 2

    0
    прям хорошо получилось!
    а у вас есть идеи, почему он не исправляет (и как исправлять) следующие сочетания слов:
    сколько сутки идет дождь
    получилась посчитать
    он снул на диване (уснул)
    он снул руку в реку (сунул)
    онснулрукувреку (он сунул руку в реку)
      +1
      При разработке систем исправлений опечаток, главная проблема — не ломать верные слова, опечаточник всех слов не знает. Приходится внедрять механизмы защиты. Если анализатор текста встречает слова которые существуют, он их исправлять не будет (даже если контекст не верный). При неверном контексте будет сделана только пометка, что контекст не верен.

      1. сколько сутки идет дождь — Все слова верные, контекст не верен, если нажать кнопку проверить то этот контекст будет выделен синим
      2. получилась посчитать — Аналогично пункту 1
      3. он снул на диване — тоже самое, словно «снул» существует и означает умирать, засыпать.
      4. онснулрукувреку — Сплит слов с ошибками не гарантирован, может просплитить а может и нет, здесь множество факторов влияет. Если ввести верный контекст то сплитит правильно: онсунулрукувреку => он сунул руку в реку

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

    Самое читаемое