Список работ

Бартеньев О. В.

Подготовка данных для автоматического определения ударений

Содержание

Введение

Приводятся программы подготовки данных, необходимые обучения нейронных сетей, классифицирующих слова и определяющих ударения. Подготовка реализуется в несколько шагов; за выполнения каждого шага отвечает отдельная программа. Общие для пошаговых программ процедуры и функции и глобальные переменные даны в приложении. Язык программирования Python.
Сведения об ударениях в словах русского языка приведены в грамматическом словаре Зализняка А. А. [1]. Электронная версия словаря содержит около 85000 слов (основ) и около 2400000 их форм – склонений существительных, прилагательных, местоимений, числительных и спряжений глаголов.
Пример. Заяц, зайцы, зайца, зайцев, зайцу, зайцам, зайца, зайцев, зайцем, зайцами, зайце, зайцах.
В словаре [1] в каждом слове подчеркнута ударная гласная: заяц.
В словах с двумя ударениями подчеркнуты обе ударных гласных. Гласная с побочным ударением выделена курсивом: автопоезд.
Наличие такой информация позволяет использовать [1] при разработке программ автоматического определения ударений. Задача автоматического определения ударений может быть решена несколькими способами, например, следующими:
1) перенос словаря [1] в базу данных с указанием в отдельном поле для каждого слова номера ударного слова;
2) составление правил определения ударений [2];
3) проектирование нейронной сети (НС), определяющей ударения.
Программы, реализующие два последние способа, позволяют определять ударения в новых словах, то есть отсутствующих в словаре. Вероятность ошибки определения ударного слога оценивается при тестировании программы на множестве новых слов, в которых специалистами указаны ударные слоги (гласные).
В работе решается задача подготовки данных для НС, определяющих ударения в словах с одним и двумя непереходящими ударениями (в словаре [1] нет слов с числом ударений более 2).

1. Задача подготовки данных

При определении ударений в множестве русских слов выделяются следующие 4 класса:
С0 – слова с переходящими ударениями;
С1 – слова с одним непереходящим ударением;
С2 – слова с двумя непереходящими ударениями;
С3 – служебные части речи, которые могут употребляться без ударений.
В работе рассматриваются задачи подготовки данных для поиска ударений в словах классов С1 и С2.
Множество слов с одним непереходящим ударением делится на 12 подмножеств (групп): в группе Gns находятся слова с ns слогами. (В словаре [1] нет слов с числом слогов более 12.) Для каждой группы слов проектируется и обучается отдельная НС. На вход НС, работающей с группой слов Gns поступает слово в виде вектора с кодами слогов этого слова, например, вектор (99, 3, 144, 6, 8) – это коды слогов слова биохимия. В этом слове 5 слогов: би-о-хи-ми-я; код слога – это его номер в словаре слогов группы слов G5. На выходном слое НС этой группы 5 нейронов. Обученная НС должна предсказывать номер ударного слога: если ударным является слог i, то при правильном прогнозе на выходе i-го нейрона выходного слоя окажется наибольшее значение. В противном случае прогноз НС будет ошибочным. Иными словами, задача определение ударений решается как задача классификации, в которой класс слов с одним непереходящим ударением под номером i – это подмножество слов, в которых ударение падает на слог i.
Аналогичен механизм определения двух непереходящих ударений.
На вход НС, определяющей одно непереходящее ударение, не должны подаваться слова без ударений (ударений не имеют некоторые служебные части речи), слова с переходящими ударениями и слова с двумя непереходящими ударениями. То есть, прежде чем начать определять ударения, слово нужно отнести к классу Сm (m = 0, ..., 3).
В классе С3 всего 286 слов (см. разд. 12). Они хранятся в программе, и принадлежность слова классу устанавливается в результате поиска слова в С3.
Принадлежность слова классу Сm (m = 0, ..., 2) решается независимо для каждой группы, формируемой по словам множества С0+С1+С2: в группу Gns включаются все слова этого множества с числом слогов ns.
Таким образом, для определения ударений в словах классов С1 и С2 необходимо сформировать обучающие и проверочные множества для следующих типов НС:
- классификатор слов, относящий слово к одному из классов С0-С2;
- определитель одного непереходящего ударения;
- определитель двух непереходящих ударений.
Классификация слов выполняется на множестве С0+С1+С2 в группах слов Gns (ns = 2, ..., 12).
Определение одного ударения выполняется в группах слов Gns (ns = 2, ..., 12) класса С1.
Определение двух ударений выполняется в группах слов Gns (ns = 3, ..., 12) класса С2.
Таким образом, необходимо подготовить наборы данных для 32 НС. Эта задача решается за несколько описываемых далее шагов. Источником данных является словарь [1], в котором слова и их формы размещены на связанных html-страницах. Список слов доступен в алфавитном указателе. При выборе слова осуществляется переход на страницу с его формами. В принципе, можно получать необходимые сведения о словах и ударениях, непосредственно обращаясь к html-страницам словаря. В работе, однако, для увеличения скорости обработки данных выполнен перенос словаря в текстовые файлы.

2. Перенос словаря в текстовые файлы

Программы переноса словаря [1] в текстовые файлы, как и все другие, составлены на Python. Перенос данных выполняется в два этапа. На первом формируются файлы а.txt, б.txt, ..., я.txt, содержащие слова. На втором – файлы а_2_0.txt, б_2_0.txt, ..., я_2_0.txt, с формами слов.
Первый этап обеспечивает следующий код:

# Адреса страниц алфавитного указателя словаря [1]
u0 = 'https://gufo.me/dict/zaliznyak?letter='
un = 'https://gufo.me/dict/zaliznyak?page='
dd = '&letter='
pn = 500
for let in list_abc:
    print('БУКВА:', let)
    fn = path + let + t
    all_words = []
    for m in range(pn):
        if m == 0:
            url = u0 + let
        else:
            url = un + str(m + 1) + dd + let
        print(url)
        response = requests.get(url) # Получаем Response
        if response.status_code == 404:
            print('АДРЕС НЕ НАЙДЕН')
            response.close()
            break
        #response.encoding = 'utf8'
        txt = response.text
        response.close()
        p = txt.find('<li>')
        txt = txt[p:]
        p = txt.find('</article>')
        txt = txt[:p]
        txt = preprocess_s(txt)
        txt = txt.split()
        print('Добавлено слов:', len(txt))
        for word in txt:
            if word != '-':
                all_words.append(word)
    add_to_txt_f(all_words, fn)
    print('Число слов в файле:', len(all_words))

Программа читает записи html-страницы, выбирая в них слова, удаляя имеющиеся в их написании html-теги (функция preprocess_s). Сформированный список слов сохраняет в файл процедура add_to_txt_f).
Число слов в сформированных файлах:

а.txt - 2508
б.txt - 3592
в.txt - 5161
г.txt - 2567
д.txt - 3336
е.txt - 229
ё.txt - 21
ж.txt - 640
з.txt - 3876
и.txt - 2137
й.txt - 18
к.txt - 5393
л.txt - 1771
м.txt - 3584
н.txt - 4706
о.txt - 5630
п.txt - 14365
р.txt - 4485
с.txt - 8365
т.txt - 2954
у.txt - 2249
ф.txt - 1436
х.txt - 999
ц.txt - 485
ч.txt - 1010
ш.txt - 1227
щ.txt - 183
э.txt - 1093
ю.txt - 119
я.txt - 312
Всего: 84451

Второй этап чтения словаря [1] и формирования файлов с парадигмами слов выполняет следующая программа:

u0 = 'https://gufo.me/dict/zaliznyak/'
#https://gufo.me/dict/zaliznyak/нареветься
ap = "'"
ye = 'ё'
let_p = 'п'
dd = ''
dd2 = '_2'
for let in list_abc:
    print('БУКВА:', let)
    fn = path + let + dd + t
    fn2_0 = path + let + dd + dd2 + '_0' + t
    fn2 = path + let + dd + dd2 + t
    all_words = read_txt_f(fn)
    all_words2 = []
    lst_p = []
    for word in all_words:
        #word = 'нареветься'
        word = word.replace('\n', '')
        if len(word) == 1: continue
        w0 = word[0]
        url = u0 + word
        response = requests.get(url)
        if response.status_code == 404:
            print('НЕ НАЙДЕНО СЛОВО:', word)
        else:
            txt = response.text
            response.close()
            p = txt.find('<p>')
            txt = txt[p:]
            p = txt.find('<div')
            txt = txt[:p].rstrip()
            txt = txt.replace('<p><span>', '')
            txt = txt.replace(',', '')
            txt = txt.replace('</span></p>', '')
            txt = txt.replace('<u>', "'")
            txt = txt.replace('</u>', '')
            txt = txt.replace('<em>', '')
            txt = txt.replace('</em>', '')
            txt = txt.replace('<strong>1</strong>. ', '')
            txt = txt.replace('<strong>2</strong>. ', '')
            txt = txt.lower()
            txt = txt.split('\n')
            print(word, '-', len(txt))
            for word2 in txt:
                if word2[0] == w0:
                    all_words2.append(word2)
    add_to_txt_f(all_words2, fn2_0)

Программа читает сформированный на первом этапе текстовый файл со словами (процедура read_txt_f), а затем для текущего слова формирует адрес страницы с парадигмами этого слова, читает эту страницу, удаляя ненужную его html-разметку. Список с формами слов, имеющихся в файле, например а.txt, сохраняется в файл а_2_0.txt.
Число записей в сформированных файлах:

а_2_0.txt - 50358
б_2_0.txt - 88743
в_2_0.txt - 172915
г_2_0.txt - 55554
д_2_0.txt - 99633
е_2_0.txt - 6023
ё_2_0.txt - 778
ж_2_0.txt - 16320
з_2_0.txt - 143224
и_2_0.txt - 68756
й_2_0.txt - 350
к_2_0.txt - 114677
л_2_0.txt - 37125
м_2_0.txt - 77895
н_2_0.txt - 143360
о_2_0.txt - 199387
п_2_0.txt - 481478
р_2_0.txt - 137243
с_2_0.txt - 212950
т_2_0.txt - 71076
у_2_0.txt - 73550
ф_2_0.txt - 24796
х_2_0.txt - 20862
ц_2_0.txt - 9430
ч_2_0.txt - 21132
ш_2_0.txt - 24168
щ_2_0.txt - 4749
э_2_0.txt - 17152
ю_2_0.txt - 2205
я_2_0.txt - 5826
Всего: 2381715

Ударения в сформированных файлах обозначаются как апостроф: абаж'ур, 'аэрофотогр'афия.
Для последующей обработки данные берутся из файлов ?_2_0.txt.

3. Сокращение и переформатирование словаря

Перенесенный в текстовые файлы словарь содержит омографы – слова с одинаковым написанием. В задаче определения ударений нужны омографы достаточно иметь по одному экземпляру омографа с разными ударными слогами; лишние экземпляры омографов удаляются. Результат записывается в файл ?_2.txt, например, в а_2.txt.
Кроме того, удаляются слова, не имеющие ударений. В большом числе случаев присутствие таких слов объясняется ошибками при составлении электронной версии словаря, например, адажио или естественно. Слова без ударений переносятся в файл acc_no.txt.
Вдобавок словоформы с приставкой "по" находятся в том же файле, что и основа, например, слова 'ярче и по'ярче окажутся после переноса данных из [1] в файле я_2_0.txt. Все слова, начинающиеся с буквы "п" переносятся в файл п_2.txt.
Кроме того, в [1] в словах с ударной букой "ё" эта буква не помечается ударной. В файлах ?_2.txt перед этой буквой ставится апостроф, поскольку за редкими исключениями эта гласная находится под ударением.
Пример исключения: Кёнигсб'ерг.
Перечисленные действия выполняет следующий код:

# Ставит знак ударения перед ё, если его там нет
def treat_ye(ye, word):
    if ye in word:
        pye = word.find(ye)
        if word[pye - 1] != ap:
            word = word[:pye] + ap + word[pye:]
    return word
def treat_let(dd_0, dd_2, ap, let, let_p, lst_p, acc_no):
    print('БУКВА:', let)
    fn_0 = path + let + dd_0
    fn_2 = path + let + dd_2
    all_words0 = read_txt_f(fn_0)
    n_words0 = len(all_words0)
    print('Число строк в источнике:', n_words0)
    all_words0 = [word.replace('\n', '') for word in all_words0]
    all_words0 = [treat_ye(ye, word) for word in all_words0]
    if let == let_p:
        print('Добавляем слова в файл со словами на букву', let_p, len(lst_p))
        all_words0.extend(lst_p)
    all_words0 = [word for word in all_words0 if len(word) > 0 and word[0] != '-']
    print('Число слов в обработанном источнике:', len(all_words0))
    dict_wrds2 = {}
    no_acc = n_skiped = 0
    for word in all_words0:
        let_1 = word[0]
        if let_1 == let_p and let != let_p:
            lst_p.append(word)
        else:
            if let_1 == ap: let_1 = word[1]
            if let_1 != let:
                n_skiped += 1
                continue
            k = dict_wrds2.get(word)
            k = 1 if k is None else (k + 1)
            dict_wrds2.update({word : k})
    all_words2 = [itm[0] + ' ' + str(itm[1]) for itm in dict_wrds2.items()]
    for word in all_words2:
        n_a = word.count(ap)
        if n_a == 0:
            no_acc += 1
            acc_no.append(word)
    add_to_txt_f(all_words2, fn_2)
    print('Число слов после удаления повторов:', len(all_words2))
    print('Число слов без ударений в ?_2.txt-файлах:', no_acc)
    print('Пропущено слов:', n_skiped)
    return n_words0, no_acc, n_skiped
# Подсчет числа слов (основ)
n_word_base_all = 0
for let in list_abc:
    fn_b = path + let + t
    all_words_base = read_txt_f(fn_b)
    n_word_base_all += len(all_words_base)
print('Всего основ:', n_word_base_all) # 84'451
ap = "'"
ye = 'ё'
fn_acc_no = path + 'acc_no' + t
let_p = 'п'
dd_0 = '_2_0' + t
dd_2 = '_2' + t
n_words0_all = no_acc_all = n_skiped_all = 0
lst_p = []
acc_no = []
for let in list_abc:
    if let == let_p: continue
    n_words0, no_acc, n_skiped = treat_let(dd_0, dd_2, ap, let, let_p, lst_p, acc_no)
    n_words0_all += n_words0
    no_acc_all += no_acc
    n_skiped_all += n_skiped
# Обработка буквы 'п', помимо удаления повторов, добавляем слова на 'п'
# из ?_2_0-файлов других букв
n_words0, no_acc, n_skiped = treat_let(dd_0, dd_2, ap, let_p, let_p, lst_p, acc_no)
n_words0_all += n_words0
no_acc_all += no_acc
n_skiped_all += n_skiped
print('Всего слов в источниках:', n_words0_all) # 2'381'715
print('Всего слов без ударений:', no_acc_all) # 2'872
# Слово пропускается, если оно не начинается с текущей буквы
print('Всего пропущено слов:', n_skiped_all) # 387
add_to_txt_f(acc_no, fn_acc_no)
# Сохраняем слова без ударений в строках по 30 слов в каждой
fn_acc_no_30 = path + 'acc_no_30' + t
acc_no_30 = []
n = 0
s = ''
for w in acc_no:
    w = w.split()
    w = w[0]
    if n == 0:
        s = w
        n = 1
    elif n == 31:
        acc_no_30.append(s)
        s = w
        n = 0
    else:
        n += 1
        s += (' ' + w)
add_to_txt_f(acc_no_30, fn_acc_no_30)

В файлах ?_2.txt после слова указывается число его повторов в файле ?_2_0.txt (омографы с разными ударными слогами рассматриваются при формировании ?_2.txt как разные слова). Фрагмент файла а_2.txt:

абисс'инская 1
абисс'инское 3
абисс'инские 2
абисс'инского 3
абисс'инской 4

Число записей в файлах ?_2.txt:

а_2.txt - 31346
б_2.txt - 50994
в_2.txt - 98902
г_2.txt - 32797
д_2.txt - 56945
е_2.txt - 3473
ё_2.txt - 388
ж_2.txt - 9197
з_2.txt - 81599
и_2.txt - 39450
й_2.txt - 203
к_2.txt - 67941
л_2.txt - 21654
м_2.txt - 44981
н_2.txt - 80788
о_2.txt - 114104
п_2.txt - 305377
р_2.txt - 78991
с_2.txt - 122700
т_2.txt - 40748
у_2.txt - 41881
ф_2.txt - 15366
х_2.txt - 12331
ц_2.txt - 5542
ч_2.txt - 12059
ш_2.txt - 14104
щ_2.txt - 2563
э_2.txt - 10674
ю_2.txt - 1321
я_2.txt - 3340
Всего: 1401759

Число слов, перенесенных в файл п_2.txt из других файлов: 23915.
Число слов, не с непроставленными ударениями (файл acc_no.txt): 2872.

4. Слова с переходящим ударением

Определение ударений в словах с переходящим ударением, например, з'амок и зам'ок или 'автозав'одская и 'автозаводск'ая, требует привлечение контекста. В словах с неподвижным ударением контекст избыточен. Поэтому слова с переходящим ударением отделяются от прочих слов и сохраняются в файл acc_dif.txt. Это выполняет следующая программа:

# Создание файла acc_dif.txt со списком слов с переходящим ударением
# Поиск омографов с разными ударными слогами пыполняется по файлам ?_2.txt.
# Найденные слова заносятся в список, а затем сохраняются в файл acc_dif.txt.
# Пример строки файла: 'автозав'одская 'автозаводск'ая 2 автозаводская
ap = "'"
dict_dif = {}
fn_acc_dif = path + 'acc_dif' + t
dd_2 = '_2' + t
n_words2_all = 0
for let in list_abc:
    all_words2 = prep_all_words2(let, dd_2, app = False)
    n_words2_all += len(all_words2)
    for word in all_words2:
        wrd = word.replace(ap, '')
        hist = dict_dif.get(wrd)
        hist = word if hist is None else (hist + ' ' + word)
        dict_dif.update({wrd:hist})
lst_dif = []
n_dif = 0
n_k2 = 0
n_k3 = 0
for itm in dict_dif.items():
    hist = itm[1].split()
    k = len(hist)
    if k > 1:
        n_dif += 1
        if k == 2:
            n_k2 += 1
        else:
            n_k3 += 1
        lst_dif.append(itm[1] + ' ' + str(k) + ' ' + hist[0].replace(ap, ''))
add_to_txt_f(lst_dif, fn_acc_dif)
print('Всего слов в ?_2-файлах:', n_words2_all) # 1'401'759
print('Всего омографов с переходящим ударением:', n_dif) # 15'036
print('Всего омографов c двумя повторам:', n_k2) # 14'895
print('Всего омографов c тремя повторам:', n_k3) # 14

Всего слов в файле acc_dif.txt: 15036.

5. Слова с двумя непереходящими ударениями

В словаре [1] имеются слова с двумя ударениями. В большей части из них ударения являются непереходящими. При автоматическом определении ударений такие слова следует выделить в отдельный класс. Эти действия реализует следующий код:

# Создание acc_two.txt с перечнем слов с несколькми непереходящими ударениями
# Так же слова с нескольки, но переходящими ударениями могут быть в acc_dif.txt
ap = "'"
fn_acc_two = path + 'acc_two' + t
fn_dif = path + 'acc_dif' + t
acc_dif = read_txt_f(fn_dif)
# Словарь слов с одинаковым написанием, но с разными вариантами ударений.
# Они не будут включены в acc_two.txt
dict_dif = treat_acc(ap, acc_dif)
dd_2 = '_2' + t
acc_two = []
for let in list_abc:
    all_words2 = prep_all_words2(let, dd_2, app = False)
    for word in all_words2:
        n_a = word.count(ap)
        if n_a > 1:
            if dict_dif.get(word) is None:
                wrd = word
                acc_pos = ''
                for k in range(n_a):
                    n1, wrd = find_n1(ap, vowels, wrd)
                    acc_pos += n1 + ','
                acc_two.append(word + ' ' + acc_pos[:-1])
print('Всего слов с числом ударений более 1:', len(acc_two)) # 46'155
add_to_txt_f(acc_two, fn_acc_two)

Всего слов в acc_two.txt: 46155.
В каждой строке файла указывается слово с расставленными ударениями и номера ударных слогов:

'яхт-кл'уб 1,2

Заметим, что слова с несколькими, но переходящими, ударениями могут быть в и acc_dif.txt.

6. Файлы с номерами ударных слогов

В файлах ?_2.txt после слова указывается число его повторов в файле ?_2_0.txt, например:

вавил'онск 1
ваг'он 2
ваг'оновож'атый 1

Файлы ?_3.txt формируются по ?_2.txt и содержат после слова номер ударного слога, в случае одного ударения, номера ударных слогов (через запятую) в случае двух ударных слогов, например:

ваг'он 2
ваг'оновож'атый 2,5

После слова без ударений следует 0:

век 0

Файлы ?_3.txt создает следующая программа:

# Формирование файлов ?_3.txt
ap = "'"
b = ' '
n_words_all_2 = 0
dd_2 = '_2' + t
dd_3 = '_3' + t
for let in list_abc:
    all_words2 = prep_all_words2(let, dd_2, app = False)
    all_words3 = []
    for word3 in all_words2:
        n_words_all_2 += 1
        n1 = n2 = 0
        n_a = word3.count(ap)
        if n_a == 0:
            word3 += ' 0'
        elif n_a == 1:
            n1, _ = find_n1(ap, vowels, word3)
            word3 += (b + n1)
        elif n_a == 2:
            n1, word_3 = find_n1(ap, vowels, word3)
            n2, _ = find_n1(ap, vowels, word_3)
            word3 += (b + n1 + ',' + n2)
        elif n_a > 2:
            print('СЛОВО С ЧИСЛОМ УДАРЕНИЙ > 2:', word3)
        all_words3.append(word3)
    print('Число слов в файле-источнике:', len(all_words2))
    fn_3 = path + let + dd_3
    add_to_txt_f(all_words3, fn_3)
print('Всего слов в источниках:', n_words_all_2) # 1'401'759

В файлах ?_3.txt те же слова, что и в файлах ?_2.txt.

7. Деление слов на слоги. Словарь слогов множества слов С0+С1+С2

При классификации слов, отнесении слова к одному из классов С0-С2, слово представляется в виде слогов, а точнее – в виде вектора их числовых кодов. Поскольку классификация выполняется независимо для каждой группы, то необходимо иметь словари слогов групп множества слов V = С0+С1+С2. Составление таких словарей выполняет приводимая ниже программа. Словари записываются в файлы syl_v_[ns].txt (на место [ns] ставится число слогов в словах группы).
Кроме того, программа формирует словарь слогов множества слов V без деления этого множества на группы (файл syl_w.txt). В каждой строке файла слог, частота применения слога, номера слогов, в которых слог встречается, например:

ю 193104 1,10,11,12,2,3,4,5,6,7,8,9

Вдобавок формируется файл syl_w_max.txt. Состав файла рассмотрим на примере первой строки файла:

а 11 агрометеорологическая 56 31325

В приведенной строке:
а – буква алфавита;
11 – наибольшее число слогов в словах на букву а;
агрометеорологическая – пример слова с наибольшим числом слогов;
56 – число слов с наибольшим числом слогов;
31325 – число слов на букву а в С0+С1+С2.
Аналогичные данные приводятся и для других букв русского алфавита (кроме букв ъ, ы и ь, поскольку слов на эти буквы в словаре [1] нет).
Формирование syl_v-файлов выполняет следующая программа:

# Создание файлов syl_v_i.txt
##word = 'тау' # та-у
##word = 'звезда' # зве-зда
##word = 'хорошая' # ['хо', 'ро', 'ша', 'я']
##word = 'корова' # ['ко', 'ро', 'ва']
##word = 'конер' # ['ко', 'нер']
##word = 'конверт' # кон-верт
##word = 'концерт' # кон-церт
##word = 'добро' # до-бро
##word = 'томный' # то-мный
##word = 'колба' # кол-ба
##word = 'тайна' # тай-на
##word = 'аудиенция' # а-у-ди-ен-ци-я
##word = 'крестец' # кре-стец
##word = 'поаймачнее' # ['по', 'ай', 'ма', 'чне', 'е']
##word = 'денационализировавшаяся' # ['де', 'на', 'ци', 'о', 'на', 'ли', 'зи', 'ро', 'ва', 'вша', 'я', 'ся']
##word = 'авиасъёмками' # ['а', 'ви', 'асъ', 'ём', 'ка', 'ми']
##word = 'темно-оранжев' # ['те', 'мно', '-о', 'ран', 'жев']
##word = 'быстротекуще' # ['бы', 'стро', 'те', 'ку', 'ще']
##word = 'подкова' # ['по', 'дко', 'ва']
##word = 'биохимия' # ['би', 'о', 'хи', 'ми', 'я']
##word = 'быстроходная' # ['бы', 'стро', 'хо', 'дна', 'я']
##word = 'бездна' # ['бе', 'здна']
##word_syls = by_syllables(word)
##print(word_syls)
##sys.exit()
print('Формирование словарей слогов для НС типа V - классификатор слов')
# Список с максимальным числом слогов (по буквам)
lst_max = [11, 10, 11, 11, 12, 10, 6, 9, 11, 11, 7, 11, 10, 10, 11, 11, 12, 12, 11, 10, 10, 10, 10, 9, 11, 9, 8, 11, 8, 9]
n_words_all_2 = 0
dict_syl = {}
fn_syl = path + 'syl_w' + t # Имена файлов-приемников
fn_syl_max = path + 'syl_w_max' + t
rng = range(12)
lst_dict_syl = [{} for k in rng]
lst_n_words = [0 for k in rng]
lst_fn = [path + 'syl_v_' + str(k + 1) + t for k in rng]
dd_2 = '_2' + t
lst_syl_max = []
k_max = -1
for let in list_abc:
    k_max += 1
    syl_max_0 = lst_max[k_max]
    print('Задано маскимальное число слогов:', syl_max_0)
    all_words2 = prep_all_words2(let, dd_2, app = True)
    n_words = len(all_words2)
    n_words_all_2 += n_words
    n_syl_max = syl_max = 0
    word_max = ''
    for word in all_words2:
        n_syl_d = find_n_vowels(word) - 1 # Число слогов - 1
        # В слове может быть дефис: агар-агар. Дефис оставляем
        word_syls = by_syllables(word)
        syl_cur = len(word_syls)
        if syl_cur > syl_max:
            word_max = word
            syl_max = syl_cur
        if syl_cur == syl_max_0:
            n_syl_max += 1
        n_syl = 0
        for syl in word_syls:
            n_syl += 1
            treat_dict_syl(syl, n_syl, dict_syl)
            treat_dict_syl(syl, n_syl, lst_dict_syl[n_syl_d])
        lst_n_words[n_syl_d] += 1
    if syl_max != syl_max_0:
        print('Ошибка: syl_max != syl_max_0')
        sys.exit()
    lst_syl_max.append(let + ' ' + str(syl_max) + ' ' + word_max
                     + ' ' + str(n_syl_max) + ' ' + str(n_words))
add_to_txt_f(lst_syl_max, fn_syl_max)
save_dict(n_words_all_2, dict_syl, fn_syl)
for n_2 in rng:
    n_wrds_cur = lst_n_words[n_2]
    n_21 = n_2 + 1
    save_dict(n_wrds_cur, lst_dict_syl[n_2], lst_fn[n_2])

Программа написана на основании правил слогоделения, приведенных в [3, 4].
Три первые строки файла syl_v_3.txt – словаря слогов слов числом гласных 3:

по 19222 1,2,3
о 16047 1,2,3
на 11941 1,2,3

После слога указываются частота его употребления в словах группы и номера слогов, в которых текущий слог встречается.
Размеры словарей слогов групп множества С0+С1+С2:

syl_v_1.txt - 3672
syl_v_2.txt - 10274
syl_v_3.txt - 9603
syl_v_4.txt - 7393
syl_v_5.txt - 5127
syl_v_6.txt - 3399
syl_v_7.txt - 2211
syl_v_8.txt - 1345
syl_v_9.txt - 688
syl_v_10.txt - 306
syl_v_11.txt - 152
syl_v_12.txt - 54
Всего: 44224

Размер словаря слогов (файл syl_w.txt) множества С0+С1+С2: 15231 (число уникальных слогов в словах словаря [1]).
Состав файла syl_w_max.txt:

а 11 агрометеорологическая 56 31346
б 10 библиотековедениями 25 50994
в 11 воздухонепроницаемостями 9 98902
г 11 гидрометеорологическая 8 32797
д 12 денационализирующаяся 32 56945
е 10 европеизирующаяся 16 3473
ё 6 ёрничающая 8 388
ж 9 женоненавистничествами 3 9197
з 11 заинтересовывающаяся 8 81599
и 11 интернационализациею 10 39450
й 7 йотированиями 1 203
к 11 коллоидообразованиями 1 67941
л 10 литературоведениями 1 21654
м 10 малоупотребительностями 18 44981
н 11 национализирующаяся 24 80788
о 11 откомандировывающаяся 18 114104
п 12 перебаллотировывающаяся 40 305377
р 12 радиометеорологическая 25 78991
с 11 самоопрокидывающаяся 17 122700
т 10 телефонизированиями 4 40748
у 10 увековечивающаяся 58 41881
ф 10 фитопалеонтологиею 2 15366
х 10 христианизирующаяся 16 12331
ц 9 ценообразованиями 1 5542
ч 11 человеконенавистничествами 1 12059
ш 9 шаблонизирующаяся 18 14104
щ 8 щепетильничающая 8 2563
э 11 электромашиностроениями 2 10674
ю 8 южноамериканцами 9 1321
я 9 яфетидологическая 8 3340

8. Число ударений на слоги в словах класса С1

Прогноз номера ударного слога, выполненный НС, можно дополнить прогнозом, основанным на статистических правилах. В файл acc_syl.txt нижеприводимая программа для каждой группы слов множества С0+С1+С2 формирует и записывает сведения о суммарном числе ударений на слоги слов группы:

1 3589: 1-3589
2 67143: 1-38948 2-28195
3 266133: 1-70862 2-143093 3-52178
4 404302: 1-51414 2-168372 3-168327 4-16189
5 327592: 1-18873 2-91266 3-160166 4-53995 5-3292
6 174594: 1-5603 2-28180 3-70620 4-58270 5-11294 6-627
7 74919: 1-1408 2-14663 3-19917 4-23254 5-13442 6-2179 7-56
8 26144: 1-36 2-6653 3-5592 4-5676 5-5538 6-2303 7-329 8-17
9 6535: 1-16 2-57 3-2171 4-1675 5-1366 6-895 7-316 8-13 9-26
10 1385: 3-8 4-392 5-602 6-194 7-155 8-24 10-10
11 307: 5-113 6-121 7-55 8-18
12 89: 6-24 7-65

Рассмотрим содержимое файла на примере группы слов с числом слогов 3 (3-я строка файла):
3 – число слогов в словах группы;
266133 – число слов в группе;
1-70862 – слог 1 и число ударений на слог 1 в словах группы;
2-143093 – слог 2 и число ударений на слог 2 в словах группы;
3-52178 – слог 3 и число ударений на слог 3 в словах группы.
Таким образом, в группе 3 чаще всего ударение приходится на 2-й слог группы.
Наиболее часто ударяемые слоги в разных группах:

G2 – 1;
G3 – 2;
G4 – 2 и 3;
G5 – 3;
G6 – 3;
G7 – 3 и 4;
G8 – 2;
G9 – 3;
G10 – 5;
G11 – 5 и 6;
G12 – 7.

Формирование файла acc_syl.txt:

# Создание файла acc_syl.txt с числом записей 12
dict_syl = {}
for ns in range(1, 13):
    dict_syl.update({ns : [0 for m in range(ns + 1)]})
fn_acc_syl = path + 'acc_syl' + t
dd_3 = '_3' + t; b = ' '; c = ','
n_words_all = 0
for let in list_abc:
    print('БУКВА:', let)
    fn_3 = path + let + dd_3
    all_words3 = prep_words_syls(fn_3)
    for word in all_words3:
        n_syl = find_n_vowels(word) # Число слогов
        p = word.find(b)
        pos_a = word[p + 1:]
        lst_pos_a = pos_a.split(sep = c) # Список с номерами ударных слогов
        lst_pos_a = [int(pos_a) for pos_a in lst_pos_a]
        i_acc = lst_pos_a[0]
        if len(lst_pos_a) == 1 and i_acc > 0:
            n_words_all += 1
            lst_i = dict_syl.get(n_syl)
            lst_i[0] += 1
            lst_i[i_acc] += 1
lst = []
for itm in dict_syl.items():
    ns = itm[0]
    lst_dict = itm[1]
    acc_syl = str(ns) + b + str(lst_dict[0]) + ': '
    for k in range(1, ns + 1):
        acc_syl += ('' if lst_dict[k] == 0 else (str(k) + '-' + str(lst_dict[k]) + b))
    lst.append(acc_syl.rstrip())
add_to_txt_f(lst, fn_acc_syl)
print('Число слов с одним ударным слогом:', n_words_all) # 1'352'732

9. Число ударений на слоги в словах класса С2

Аналогично файлу acc_syl.txt формируется файл acc_syl_two.txt, содержащий частоту употребления пар ударных слогов в каждой группе слов класса С2 (слова с двумя непереходящими ударениями):

# Создание файла acc_syl_two.txt с числом записей 11.
# Содержит частоту употребления пар ударных слогов в каждой группе слов
# с двумя непереходящими ударениями (класс С2)
fn_acc_syl_two = path + 'acc_syl_two' + t
b = ' '
n_words_all = 0
lst = []
rng = range(2, 13) # (2, 13)
for n_syl in rng:
    fw = make_faws('2', n_syl, 'words', path, t)
    all_words = prep_words_syls(fw, app = False)
    dict_classes = {}
    for word in all_words:
        n_words_all += 1
        p = word.find(b)
        pos_a = word[p + 1:]
        k = dict_classes.get(pos_a)
        k = 1 if k is None else (k + 1)
        dict_classes.update({pos_a : k})
    lst_acc = list(dict_classes.items()) # Список частот пар ударных слогов
    lst_acc.sort(key = lambda i: i[0])
    acc_syl_2 = '{} {}: {} - '.format(n_syl, len(all_words), len(lst_acc))
    for itm in lst_acc:
        acc_syl_2 += (itm[0] + '-' + str(itm[1]) + b)
    lst.append(acc_syl_2.rstrip())
add_to_txt_f(lst, fn_acc_syl_two)
print('Число слов с двумя непереходящими ударениями:', n_words_all) # 45'651
# Пример строки файла (число слогов, слов, классов частоты классов):
# 4 4519: 6 - 1,2-1047 1,3-2760 1,4-439 2,3-190 2,4-72 3,4-11

Состав файла acc_syl_two.txt:

2 88: 1 - 1,2-88
3 1306: 3 - 1,2-1030 1,3-256 2,3-20
4 4519: 6 - 1,2-1047 1,3-2760 1,4-439 2,3-190 2,4-72 3,4-11
5 10571: 10 - 1,2-318 1,3-2767 1,4-5571 1,5-450 2,3-190 2,4-900 2,5-104 3,4-240 3,5-28 4,5-3
6 12899: 14 - 1,2-6 1,3-868 1,4-5326 1,5-3398 1,6-80 2,3-72 2,4-899 2,5-1560 2,6-65 3,4-318 3,5-252 3,6-23 4,5-31 5,6-1
7 9638: 17 - 1,3-78 1,4-1427 1,5-3878 1,6-906 1,7-10 2,4-287 2,5-1761 2,6-609 2,7-10 3,4-46 3,5-246 3,6-319 3,7-10 4,5-36 4,6-12 4,7-1 5,7-2
8 4844: 17 - 1,3-16 1,4-161 1,5-1393 1,6-1298 1,7-192 1,8-1 2,4-40 2,5-418 2,6-697 2,7-110 3,5-40 3,6-356 3,7-74 4,5-5 4,6-22 4,7-18 4,8-3
9 1433: 17 - 1,4-48 1,5-45 1,6-469 1,7-227 1,8-23 2,4-8 2,5-32 2,6-214 2,7-129 2,8-12 3,5-5 3,6-68 3,7-71 3,8-1 4,6-23 4,7-34 4,8-24
10 276: 17 - 1,5-2 1,6-16 1,7-76 1,8-48 1,9-1 2,5-13 2,6-10 2,7-19 2,8-31 2,9-1 3,5-1 3,7-32 3,8-6 4,6-2 4,7-4 4,8-13 7,10-1
11 69: 10 - 1,6-1 1,7-1 1,8-34 1,9-8 2,6-1 2,8-10 2,9-1 3,7-9 3,8-2 7,11-2
12 8: 1 - 1,9-8

В файле через запятую указаны номера пар ударных слогов, а вслед – частота этой пары в словах рассматриваемой группы. Начало каждой строки файла формируется так же, как в файле acc_syl.txt.

10. Частота ударений на слоги. Словари групп слов классов С0-С2

Если ранее рассмотренный файл acc_syl.txt показывает, сколько раз ударение подает на i-й слог в словах группы, то acc-файлы, формируемые приводимой ниже программой, содержат в каждой строке слог и число ударений на этот слог в словах заданной группы (файлы acc_1.txt, ..., acc_12.txt) или множества V = С0+С1+С2 (файл acc.txt). Файлы упорядочены по числу ударений на слог.
Наиболее частотные слоги V и групп этого множества:

V = С0+С1+С2: ва 39847
G1: аб 1
G2: вы 410
G3: вы 6217
G4: вы 10839
G5: ва 12368
G6: ва 10137
G7: ва 5117
G8: ва 1783
G9: зи 608
G10: зи 344
G11: зи 111
G12: зи 65

После слога указывается число ударений, которые приходятся на этот слог в словах V или группы.
Девять наиболее частотных слогов множества V (после слога указывается, сколько раз ударение приходится на слог):

ва 39847
вы 29462
ти 23488
ли 19726
ро 19395
ка 18495
ни 17889
ра 17052
та 16307

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

# Создание acc-файлов и файлов words_0_i, word_1_i, words_2_i
def treat_dict_cur(acc, dict_cur, pos_a = None):
    k = dict_cur.get(acc)
    k = 1 if k is None else (k + 1)
    dict_cur.update({acc : k})
def save_dict_wrds(dict_cur, fn_cur):
    lst_wrd = list(dict_cur.items())
    lst_wrd = [itm[0] + ' ' + str(itm[1]) for itm in lst_wrd]
    add_to_txt_f(lst_wrd, fn_cur)
    return len(lst_wrd)
ap = "'"
# Словарь слов с одинаковым написанием, но с разными вариантами ударений
# Они не будут включены в words_1_i.txt и words_2_i.txt
fn_dif = path + 'acc_dif' + t
acc_dif = read_txt_f(fn_dif)
dict_dif = treat_acc(ap, acc_dif)
# Словарь слов с несколькими ударениями. Они не будут включены в words_1_i
fn_two = path + 'acc_two' + t
acc_two = read_txt_f(fn_two)
dict_two = treat_acc(ap, acc_two)
dict_acc = {}
fn_acc = path + 'acc' + t
n_words_all = 0
rng = range(12)
lst_dict_acc = [{} for k in rng]
lst_dict_words1 = [{} for k in rng]
lst_dict_words0 = [{} for k in rng]
lst_dict_words2 = [{} for k in rng]
lst_n_words = [0 for k in rng]
lst_fn = [path + 'acc_' + str(k + 1) + t for k in rng]
lst_fw0 = [make_faws('0', k + 1, 'words', path, t) for k in rng]
lst_fw1 = [make_faws('1', k + 1, 'words', path, t) for k in rng]
lst_fw2 = [make_faws('2', k + 1, 'words', path, t) for k in rng]
dd_3 = '_3' + t
b = ' '
c = ','
for let in list_abc:
    print('БУКВА:', let)
    fn_3 = path + let + dd_3
    all_words3 = prep_words_syls(fn_3)
    for word_0 in all_words3:
        p = word_0.find(b)
        pos_a = word_0[p + 1:]
        word = word_0[:p]
        n_syl_d = find_n_vowels(word) - 1 # Число слогов - 1
        lst_pos_a = pos_a.split(sep = c) # Список с номерами слогов под ударением
        lst_pos_a = [int(pos_a) - 1 for pos_a in lst_pos_a]
        if lst_pos_a[0] > -1:
            n_words_all += 1
            word_syls = by_syllables(word)
            one_acc = True # Флаг слова с одним непереходящим ударением
            if dict_dif.get(word) is not None:
                treat_dict_cur(word, lst_dict_words0[n_syl_d])
                one_acc = False
            # Слово с двумя непереходящими ударениями
            elif dict_two.get(word) is not None:
                lst_dict_words2[n_syl_d].update({word : pos_a})
                one_acc = False
            for pos_a in lst_pos_a:
                acc_syl = word_syls[pos_a] # Слог под ударением
                treat_dict_cur(acc_syl, dict_acc)
                treat_dict_cur(acc_syl, lst_dict_acc[n_syl_d])
                if one_acc: # Слова с одним неподвижным ударением
                    dict_cur = lst_dict_words1[n_syl_d]
                    acc_pos = dict_cur.get(word)
                    if acc_pos is None:
                        dict_cur.update({word : pos_a + 1})
                    lst_n_words[n_syl_d] += 1
save_dict(n_words_all, dict_acc, fn_acc)
for n_2 in rng:
    n_wrds_cur = lst_n_words[n_2]
    n_21 = n_2 + 1
    save_dict(n_wrds_cur, lst_dict_acc[n_2], lst_fn[n_2])
n_wrds0 = n_wrds1 = n_wrds2 = 0
for n_2 in rng:
    n_wrds0 += save_dict_wrds(lst_dict_words0[n_2], lst_fw0[n_2])
    n_wrds1 += save_dict_wrds(lst_dict_words1[n_2], lst_fw1[n_2])
    n_wrds2 += save_dict_wrds(lst_dict_words2[n_2], lst_fw2[n_2])
dd = 'Всего слов:'
print(dd, n_words_all, '(с ударениями)')
dd = dd[:-1] + ' с'
print(dd, 'одинаковым написанием, но с разными ударными слогами:', n_wrds0)
print(dd, 'одним непереходящим ударением:', n_wrds1)
print(dd, 'несколькими непереходящими ударениями:', n_wrds2)

Группа слов Gns класса Сm сохраняется в файл words_[m]_[ns].txt ([m] и [ns] заменяются соответственно номером класса и числом слогов в словах группы, например, words_1_5.txt содержит слова с одним непереходящим ударением с числом слогов 5.
Число слов в группах класса С0:

words_0_1.txt - 74
words_0_2.txt - 3004
words_0_3.txt - 4992
words_0_4.txt - 4778
words_0_5.txt - 1540
words_0_6.txt - 517
words_0_7.txt - 105
words_0_8.txt - 26
words_0_9.txt - 0
words_0_10.txt - 0
words_0_11.txt - 0
words_0_12.txt - 0
Всего: 15036

Число слов в группах класса С1:

words_1_1.txt - 3515
words_1_2.txt - 61611
words_1_3.txt - 256333
words_1_4.txt - 394888
words_1_5.txt - 324692
words_1_6.txt - 173701
words_1_7.txt - 74749
words_1_8.txt - 26094
words_1_9.txt - 6535
words_1_10.txt - 1385
words_1_11.txt - 307
words_1_12.txt - 89
Всего: 1323899

Число слов в группах класса С2:

words_2_1.txt - 0
words_2_2.txt - 88
words_2_3.txt - 1306
words_2_4.txt - 4519
words_2_5.txt - 10571
words_2_6.txt - 12899
words_2_7.txt - 9638
words_2_8.txt - 4844
words_2_9.txt - 1433
words_2_10.txt - 276
words_2_11.txt - 69
words_2_12.txt - 8
Всего: 45651

Words-файлах в каждой строке слово, после которого следует:
- в случае С0 – число повторов слова в словаре [1];
- в случае С1 – номер ударного слога, например, абдоминальный 4;
- в случае С2 – номера ударных слогов, разделенные запятой, например, авиабаза 1,4.
Замечание. Всего в классах С1 и С2, построенных по словарю [1] и содержащих слова с непереходящими ударениями, находится (|С1| + |С2|) / |V| = 98,91 % слов, где V = С0 + С1 + С2.

11. Словари слогов групп слов классов С0-С2

При использовании в задаче определения ударений модели слова в виде слогов необходимо иметь возможность представить слог в виде числа – числового кода. Значение кода слога должно быть уникальным. Такому требованию удовлетворяет, например, номер кода в словаре слогов. При определении ударений в группе слов с одинаковым числом слогов такие словари должны быть построены для каждой группы.
Приводимая далее программа создает словари слогов для групп слов классов С0-С2 (описание классов см. во Введении). В каждом классе группа слов Gns образуется из слов числом слогов ns. Сформированный словарь группы Gns класса Сm (m = 0, ..., 2) сортируется по частоте применения слога в группе и сохраняется в текстовый файл syl_[m]_[ns].txt (на место [m] и [ns] ставятся соответственно номер класса и число слогов в словах группы – номер группы).

# Создаем syl_ClsType_ns.txt - словари слогов words_ClsType_ns.txt
# Создает словари слогов групп слов
for ClsType in ['0', '1', '2']:
    print('Вид классификаторов:', ClsType)
    n_words_all_ClsType = 0
    for ns in range(1, 13):
        dict_syl_ClsType = {}
        fn_syl_ClsType = make_faws(ClsType, ns, 'syl', path, t) # Файл-приемник
        fn_ClsType = make_faws(ClsType, ns, 'words', path, t) # Файл-источник
        all_words_ClsType = prep_words_syls(fn_ClsType, app = False, split = True)
        n_words_ClsType = len(all_words_ClsType)
        n_words_all_ClsType += n_words_ClsType
        for word in all_words_ClsType:
            word_syls = by_syllables(word)
            for syl in word_syls:
                n = dict_syl_ClsType.get(syl)
                if n is None: n = 0
                dict_syl_ClsType.update({syl : n + 1})
        save_dict(n_words_ClsType, dict_syl_ClsType, fn_syl_ClsType)
    print('Всего слов в группах для НС типа {}: {}'.format(ClsType, n_words_all_ClsType))

Три первые строки файла syl_1_3.txt – словаря слогов слов класса С1 с числом гласных 3:

по 18236
о 15289
на 11345

После слога указывается частота его употребления в словах группы.
Аналогично устроены и другие словари слогов групп слов классов С0-С2.
Размеры словарей слогов класса С0:

syl_0_1.txt - 74
syl_0_2.txt - 1223
syl_0_3.txt - 1234
syl_0_4.txt - 915
syl_0_5.txt - 453
syl_0_6.txt - 243
syl_0_7.txt - 105
syl_0_8.txt - 41
syl_0_9.txt - 0
syl_0_10.txt - 0
syl_0_11.txt - 0
syl_0_12.txt - 0
Всего: 4288

Размеры словарей слогов групп класса С1:

syl_1_1.txt - 3515
syl_1_2.txt - 10110
syl_1_3.txt - 9433
syl_1_4.txt - 7117
syl_1_5.txt - 4826
syl_1_6.txt - 3108
syl_1_7.txt - 1948
syl_1_8.txt - 1138
syl_1_9.txt - 546
syl_1_10.txt - 232
syl_1_11.txt - 120
syl_1_12.txt - 46
Всего: 42139

Размеры словарей слогов групп класса С2:

syl_2_1.txt - 0
syl_2_2.txt - 145
syl_2_3.txt - 989
syl_2_4.txt - 1666
syl_2_5.txt - 1834
syl_2_6.txt - 1550
syl_2_7.txt - 1174
syl_2_8.txt - 775
syl_2_9.txt - 419
syl_2_10.txt - 201
syl_2_11.txt - 83
syl_2_12.txt - 19
Всего: 8855

12. Пересечение множеств слогов

Пересечение множеств слогов – поиск общих слогов пары множеств Si и Sj, где Si – это множество i-х слогов слов заданной группы слогов, а Sj – множество j-х слогов той же группы. Эти вычисления выполняются с целью получения дополнительной информации о словах выбранной группы:

ClsType = '1'
print('Определение пересечения каждой пары множеств слогов слов класса', ClsType)
rng = range(2, 9) # (2, 9)
for n_syl in rng:
    rng_syl = range(n_syl)
    s_n_syl = str(n_syl)
    fw = make_faws(ClsType, n_syl, 'words', path, t)
    all_words = prep_words_syls(fw, app = False)
    # Список множеств слогов: множество первых слогов, вторых и так далее
    lst_sets = [set() for k in rng_syl]
    for word in all_words:
        word = word.split()
        acc = int(word[1]) - 1
        word = word[0]
        lst_syls = by_syllables(word) # Список слогов слова
        for k in rng_syl:
            syl_k = lst_syls[k]
            lst_sets[k].add(syl_k)
    print('Число слогов:', n_syl)
    print('Всего слов:', len(all_words))
    print('Номер слога / Число уникальных слогов:')
    s = ''
    for k in rng_syl:
        s += (', ' + str(k + 1) + '/' + str(len(lst_sets[k])))
    print(s[2:])
    print('Пара множеств слогов / Число слогов в пересечении этой пары:')
    s = ''
    for k in range(n_syl - 1):
        set_k = lst_sets[k]
        for m in range(k + 1, n_syl):
            set_int = set_k.intersection(lst_sets[m])
            n_cr = len(set_int)
            if n_cr > 0:
                s += ('; ' + '(' + str(k) + ',' + str(m) + ')' + '/' + str(n_cr))
    if s == '':
        print('Число слогов в пересечении всех пар словарей слогов: 0')
    else:
        print(s[2:])

Сведения для класса С1:

Число слогов: 2
Всего слов: 61611
Номер слога / Число уникальных слогов:
1/2590, 2/8864
Пара множеств слогов / Число слогов в пересечении этой пары:
(0,1)/1344
Читаем файл G:/AM/НС/poems/accents/words_1_3.txt
Число слогов: 3
Всего слов: 256333
Номер слога / Число уникальных слогов:
1/2006, 2/4911, 3/6033
Пара множеств слогов / Число слогов в пересечении этой пары:
(0,1)/1277; (0,2)/882; (1,2)/2131
Читаем файл G:/AM/НС/poems/accents/words_1_4.txt
Число слогов: 4
Всего слов: 394888
Номер слога / Число уникальных слогов:
1/1633, 2/3546, 3/3283, 4/3308
Пара множеств слогов / Число слогов в пересечении этой пары:
(0,1)/1181; (0,2)/924; (0,3)/563; (1,2)/1900; (1,3)/934; (2,3)/1388
Читаем файл G:/AM/НС/poems/accents/words_1_5.txt
Число слогов: 5
Всего слов: 324692
Номер слога / Число уникальных слогов:
1/1143, 2/2566, 3/2127, 4/1744, 5/1687
Пара множеств слогов / Число слогов в пересечении этой пары:
(0,1)/870; (0,2)/705; (0,3)/516; (0,4)/300; (1,2)/1579; (1,3)/935; (1,4)/474; (2,3)/1083; (2,4)/486; (3,4)/666
Читаем файл G:/AM/НС/poems/accents/words_1_6.txt
Число слогов: 6
Всего слов: 173701
Номер слога / Число уникальных слогов:
1/782, 2/1673, 3/1450, 4/1072, 5/892, 6/801
Пара множеств слогов / Число слогов в пересечении этой пары:
(0,1)/578; (0,2)/502; (0,3)/416; (0,4)/270; (0,5)/156; (1,2)/1049; (1,3)/706; (1,4)/450; (1,5)/241; (2,3)/833; (2,4)/492; (2,5)/249; (3,4)/532; (3,5)/236; (4,5)/325
Читаем файл G:/AM/НС/poems/accents/words_1_7.txt
Число слогов: 7
Всего слов: 74749
Номер слога / Число уникальных слогов:
1/470, 2/1114, 3/841, 4/667, 5/517, 6/401, 7/350
Пара множеств слогов / Число слогов в пересечении этой пары:
(0,1)/342; (0,2)/289; (0,3)/253; (0,4)/198; (0,5)/132; (0,6)/82; (1,2)/613; (1,3)/462; (1,4)/327; (1,5)/208; (1,6)/107; (2,3)/505; (2,4)/344; (2,5)/216; (2,6)/112; (3,4)/383; (3,5)/218; (3,6)/111; (4,5)/236; (4,6)/103; (5,6)/126
Читаем файл G:/AM/НС/poems/accents/words_1_8.txt
Число слогов: 8
Всего слов: 26094
Номер слога / Число уникальных слогов:
1/218, 2/675, 3/453, 4/383, 5/278, 6/201, 7/160, 8/176
Пара множеств слогов / Число слогов в пересечении этой пары:
(0,1)/165; (0,2)/143; (0,3)/125; (0,4)/108; (0,5)/78; (0,6)/55; (0,7)/36; (1,2)/321; (1,3)/253; (1,4)/186; (1,5)/134; (1,6)/84; (1,7)/50; (2,3)/272; (2,4)/184; (2,5)/133; (2,6)/71; (2,7)/47; (3,4)/207; (3,5)/135; (3,6)/82; (3,7)/51; (4,5)/138; (4,6)/76; (4,7)/46; (5,6)/84; (5,7)/50; (6,7)/65

13. Создание класса С3

Класс С3 построен на основе служебных частей речи.
Разделим служебные части речи на две категории:
1) простые, например:
- вдали, из, от (предлоги);
- а, будто, впрочем (союзы);
- виш, вроде, вряд (частицы);
2) составные, например,
- вдали от, из числа, от имени (предлоги);
- а ведь, будто бы, в результате того что (союзы);
- во всяком случае, вроде бы, вряд ли (частицы);
Класс С3 включает простые служебные части речи и компоненты составных служебных частей речи, если они употребляются без ударений:
- служебная часть речи из категории "простые" включается в С3, если ее нет в С0-С2;
- компонент служебной части речи из категории "составные" включается в С3, если его нет в С0-С2.
Формирование С3 обеспечивает следующий код:

# Формируется файл с3.txt (класс С3)
ap = "'"
acc_dif = read_txt_f(path + 'acc_dif' + t)
# Словарь слов с одинаковым написанием, но с разными вариантами ударений
dict_dif = treat_acc(ap, acc_dif)
lst_fs = ['_предлоги', '_союзы', '_частицы']
lst_ClsType = ['0', '1', '2']
dict_s = {}
n_v_max = 0
for fs in lst_fs:
    lst_s = read_txt_f(path + fs + t)
    for w in lst_s:
        w = w.split()
        for c in w:
            n_v = find_n_vowels(c)
            if n_v > 1 and dict_dif.get(c) is None:
                dict_s.update({c.replace(',', '') : n_v})
                n_v_max = max(n_v_max, n_v)
dict_w = {}
for ns in range(2, n_v_max + 1):
    for ClsType in lst_ClsType:
        lst_wrds = read_txt_f(make_faws(ClsType, ns, 'words', path, t))
        for w in lst_wrds:
            w = w.split()
            dict_w.update({w[0] : 1}) # 1'255'439 слов
c3 = []
for c in dict_s.keys():
    if dict_w.get(c) is None: c3.append(c)
add_to_txt_f(c3, path + 'c3' + t) # 286 строки

Двадцать строк файла c3.txt:

через
черезо
чрезо
благодаря
сзади
назади
безо
кроме
наверху
вперед
впереди
ото
окрест
середь
вдогон
вдогонку
спереди
вдобавок
посреди
вдалеке

14. Наборы данных при кодировании букв слогов и слов номерами букв русского алфавита

Слово, подаваемое на вход НС, должно иметь числовое представление. В задаче определения ударений слово представляется как список слогов. Как вариант, каждая буква слога может быть представлена его номером в русском алфавите.
При таком кодировании задается n_max – максимальное число букв в слоге, и слог представляется списком из n_max чисел. Если кодируемый слог имеет меньше букв, то недостающие числа восполняются нулями, например:
Слово: вдохом;
n_max = 7;
Код слова по слогам: [[3, 5, 16, 0, 0, 0, 0], [23, 16, 14, 0, 0, 0, 0]].
Таким же образом можно кодировать слово по буквам без выделения в нем слогов. В таком случае задается максимальная длина слова. Если число букв в слове меньше этого значения, то в конце кодированного слова появляются нули, например:
Слово: вдохом;
n_max = 15;
Код слова по буквам: [3, 5, 16, 23, 16, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0].
В наборе данных, помимо кодов слов, присутствуют метки слов. В задаче определения ударений метка слова класса С1 – это номер ударного слога за вычетом числа 1. Уменьшение на 1 обусловлено тем, что индексация итерируемых объектов в Python (списков, массивов, строк и пр.) начинается с нуля. Таким образом, меткой слова вдохом будет число 0, поскольку ударение приходится в слове на первый слог.
Все закодированные слова класса С1 и их метки образуют набор данных. Он делится на обучающее и проверочное множества. Такие множества создаются для каждой группы слов класса С1 и записываются в бинарные файлы под следующими именами:
Обучающее множество (коды слов и метки слов): [m]_[ns]_trn_x.bin и [m]_[ns]_trn_y.bin.
Проверочное множество (коды слов и метки слов): [m]_[ns]_val_x.bin и [m]_[ns]_val_y.bin.
[m] и [ns] заменяются соответственно номером класса и числом слогов в словах группы.
Описанные варианты кодирования реализуются следующей программой:

# Создание набора данных, а затем обучающих и оценочных множеств
from sklearn.model_selection import train_test_split
dd = 'Формируем данные по'
if by_syls:
    n_max_max = 7
    # n_syl/n_max: 2/9; 3/8
    # n_syl/n_max_max: 2/6; 3/7
    print(dd, 'слогам')
else:
    print(dd, 'словам')
ClsType = '1' # Только '1'
val_size = 0.2
rng = range(3, 4) # (2, 9)
for n_syl in rng:
    s_n_syl = str(n_syl)
    fp = make_fp(ClsType, n_syl, path, t)
    fw = make_faws(ClsType, n_syl, 'words', path, t)
    tx = make_tvxy(ClsType, n_syl, 'trn_x', path, bn)
    ty = make_tvxy(ClsType, n_syl, 'trn_y', path, bn)
    vx = make_tvxy(ClsType, n_syl, 'val_x', path, bn)
    vy = make_tvxy(ClsType, n_syl, 'val_y', path, bn)
    all_words = prep_words_syls(fw, app = False)
    if by_syls:
        fs = make_faws(ClsType, n_syl, 'syl', path, t)
        all_syls = prep_words_syls(fs, app = False)
        n_max = 0
        syl_max = ''
        for syl in all_syls:
            syl = syl.split()
            syl = syl[0]
            l_s = len(syl)
            if l_s > n_max:
                n_max = l_s
                syl_max = syl
        n_max = min(n_max_max, n_max)
    else:
        nw_max = 0
        for word in all_words:
            word = word.split()
            acc = int(word[1]) - 1 # Номер ударного слога - 1
            word = word[0]
            nw_max = max(len(word), nw_max)
        nw_max += 3
        print('Размер вектора, представляющего слово:', nw_max)
    r = np.random.randint(len(all_words)) # Для вывода примера
    x_data = []
    y_data = []
    i = -1
    for word in all_words:
        word = word.split()
        acc = int(word[1]) - 1
        word = word[0]
        lst_syls = by_syllables(word)
        if by_syls:
            skip = False
            for syl in lst_syls:
                if len(syl) > n_max:
                    skip = True
                    break
            if skip: continue
        lst_lets = []
        n = 0 # Номер слога
        for syl in lst_syls:
            lst_let_syl = [] # Список номеров букв, входящих в слог
            for let in syl:
                ind = lst_abc.index(let) + 1
                lst_let_syl.append(ind)
            if by_syls:
                len_lst = len(lst_let_syl)
                if len_lst < n_max:
                    lst_let_syl.extend([0 for k in range(n_max - len_lst)])
                lst_lets.append(lst_let_syl)
            else:
                n += 1
                lst_let_syl.append(33 + n) # Цифра после очередного слога
                lst_let_syl.append(0) # Ставим нуль между слогами
                lst_lets.extend(lst_let_syl)
        if not by_syls:
            len_lst = len(lst_lets)
            if len_lst < nw_max:
                lst_lets.extend([0 for k in range(nw_max - len_lst)])
            elif len_lst > nw_max:
                lst_lets.pop(len_lst - 1)
        i += 1
        if i == r: word_r = word
        x_data.append(lst_lets)
        y_data.append(acc)
    print('Пример экземляра данных под номером', r, '(word, x_data, y_data)')
    print('', word_r, '\n', x_data[r], '\n', y_data[r])
    print('Разделяем исходные данные на обучающие и оценочные (' + str(val_size) + ')')
    x_trn, x_val, y_trn, y_val = train_test_split(x_data, y_data, test_size = val_size)
    add_to_bin_f(x_trn, tx, dtype = dtp)
    add_to_bin_f(y_trn, ty, dtype = dtp)
    add_to_bin_f(x_val, vx, dtype = dtp)
    add_to_bin_f(y_val, vy, dtype = dtp)
    lst_params = [str(by_syls), str(n_max if by_syls else nw_max)]
    add_to_txt_f(lst_params, fp)

15. Наборы данных при кодировании слога его номером в словаре слогов

Наборы данных создаются для каждой группы слов множества С0+С1+С2 и классов С1 и С2.
Представление слов в виде кодов его слогов предполагает выполнение следующих действий:
- загрузка словаря слогов;
- деление слова на слоги;
- поиск слога в словаре слогов и замена слога на его номер в этом словаре, если поиск удачен, или на 0 – в противном случае;
- формирование метки слова.
Пример.
Слово: быстроходная.
Число слогов: 5.
Класс: С1.
Группа: G5.
Список слогов слова: ['бы', 'стро', 'хо', 'дна', 'я'].
Кодированное слово: [254, 162, 98, 336, 8].
Номер ударного слога: 3.
Метка слова: 2 (номер ударного слога, уменьшенный на 1).
Совокупность кодированных слов и их меток обрабатываемой группы образуют набор данных, используемый для обучения и проверки НС (либо классификатора слов, либо определителя ударения).
Метка слова word класса С2 (два непереходящих ударения) определяется по словарю dict_classes возможных пар ударных слогов в группе слов, в которую входит word. по следующему алгоритму:
Входные данные: ns – номер группы (число слогов в словах группы).
1. Начало.
2. Сформировать по файлу words_2_ns.txt список all_words с элементами вида 'word acc_pos',
где word – это слово; acc_pos – разделенные запятой номера ударных слогов
(пример элемента списка: 'авиабаза 1,4').
3. Перебрать элементы списка all_words и сформировать словарь dict_classes, используя в качестве ключа acc_pos, а в качестве значения – номер acc_pos в словаре, начиная нумерацию меток с нуля.
4. Останов.
Пример. Словарь dict_classes группы G5:
{'1,4': 0, '1,5': 1, '2,5': 2, '1,3': 3, '3,5': 4, '2,4': 5, '2,3': 6, '4,5': 7, '1,2': 8, '3,4': 9}
Метка слова с парой ударных слогов acc_pos – это значение, найденное в dict_classes по ключу acc_pos.
Пример.
Число слогов: 5.
dict_classes: {'1,4': 0, '1,5': 1, '2,5': 2, '1,3': 3, '3,5': 4, '2,4': 5, '2,3': 6, '4,5': 7, '1,2': 8, '3,4': 9}.
Слово: авиабаза.
acc_pos: '1,4'
Метка слова: 0.
Слово: австралопитек.
acc_pos: '2,5'
Метка слова: 2.
Сформированный для заданных класса и группы слов набор данных делится на обучающее и проверочное множества, которые записываются в бинарные файлы с именами, определяемыми по следующим шаблонам:
[Cm]_[ns]_trn_x.bin, [Cm]_[ns]_trn_y.bin – обучающее множество;
[Cm]_[ns]_val_x.bin, [Cm]_[ns]_val_y.bin – проверочное множество.
На место [Cm] и [ns] подставляются соответственно имя класса (v, 1, 2) и номер группы (2, 3, ..., 12).
Программа создания наборов данных для всех классификаторов слов и определителей ударений:

# Создание обучающих и оценочных множеств для заданного классификатора
# Слог кодируется одной цифрой.
# ClsType = 'v' - классификатор слов (классы С0-С2)
# ClsType = '1' - определитель одного непереходящего ударения
# ClsType = '2' - определитель двух непереходящх ударений
ClsType = '2' # '1', '2', 'v'
from sklearn.model_selection import train_test_split
print('Тип классификатора:', ClsType)
print('Формируем данные, кодируя слог номером в syl_ClsType-файле')
dtp = np.int16
val_sz = 0.1 if ClsType == '2' else 0.05
rng = range(2, 13) # (2, 13)
for n_syl in rng:
    s_n_syl = str(n_syl)
    s_t = '_' + s_n_syl + t
    if ClsType == '2':
        # Словарь классов для определителей ударений в словах С2
        dict_classes = C2_classes(n_syl, path, t)
    else:
        dict_classes = None
    # Выход (приемники данных)
    fp = make_fp(ClsType, n_syl, path, t)
    tx = make_tvxy(ClsType, n_syl, 'trn_x', path, bn)
    ty = make_tvxy(ClsType, n_syl, 'trn_y', path, bn)
    vx = make_tvxy(ClsType, n_syl, 'val_x', path, bn)
    vy = make_tvxy(ClsType, n_syl, 'val_y', path, bn)
    dict_syls, N = make_dict_syls(ClsType, path, s_n_syl)
    lst_params = [str(N)] # Число уникальных слогов
    print('Формируем исходные данные')
    if ClsType == 'v':
        x0_t, x0_v, y0_t, y0_v = words_to_data(n_syl, dict_syls, path, '0', s_t, val_sz, ClsType)
    if ClsType in ['v', '1']:
        x1_t, x1_v, y1_t, y1_v = words_to_data(n_syl, dict_syls, path, '1', s_t, val_sz, ClsType)
    if ClsType in ['v', '2']:
        x2_t, x2_v, y2_t, y2_v = words_to_data(n_syl, dict_syls, path, '2', s_t, val_sz, ClsType, dict_classes)
    if ClsType == 'v':
        if len(x0_t) > 0 and len(x2_t) > 0:
            x_t = np.concatenate((x1_t, x0_t, x2_t))
            y_t = np.concatenate((y1_t, y0_t, y2_t))
            x_v = np.concatenate((x1_v, x0_v, x2_v))
            y_v = np.concatenate((y1_v, y0_v, y2_v))
        elif len(x0_t) > 0:
            x_t = np.concatenate((x1_t, x0_t))
            y_t = np.concatenate((y1_t, y0_t))
            x_v = np.concatenate((x1_v, x0_v))
            y_v = np.concatenate((y1_v, y0_v))
        else:
            x_t = np.concatenate((x1_t, x2_t))
            y_t = np.concatenate((y1_t, y2_t))
            x_v = np.concatenate((x1_v, x2_v))
            y_v = np.concatenate((y1_v, y2_v))
    elif ClsType == '1':
        x_t = x1_t; y_t = y1_t; x_v = x1_v; y_v = y1_v
    elif ClsType == '2':
        x_t = x2_t; y_t = y2_t; x_v = x2_v; y_v = y2_v
    add_to_bin_f(x_t, tx, dtype = dtp)
    add_to_bin_f(y_t, ty, dtype = dtp)
    add_to_bin_f(x_v, vx, dtype = dtp)
    add_to_bin_f(y_v, vy, dtype = dtp)
    add_to_txt_f(lst_params, fp)

16. Анализ частотного словаря О. Н. Ляшевской, С. А. Шарова

Частотный словарь русского языка [5] содержит частоты употребления лемм (исходных форм слов) в текстах Национального корпуса русского языка [6].
Одна строка из файла со словарем [5].

абажур    s    4.9    79    90    269

В каждой строке:
Lemma – лемма (исходная форма слова);
PoS – часть речи;
Freq (ipm) – частота леммы на 1 миллион употреблений;
R (range) – число сегментов корпуса, в которых встретилась лемма (из 100 возможных);
D – коэффициент Жуйана;
Doc – число текстов, в которых встретилась лемма.
Нам понадобятся только лемма и ее частота.
Оценим, используя [5], вероятность принадлежности случайно выбранного слова словарю [1]. А также определим по [5] вероятности принадлежности случайной леммы группам лемм. Леммы, как и слова [1], группируются по числу слогов.
Перенесем леммы [5] (с частотами) и основы [1] с числом слогов более 1 соответственно в словарь dict_freq и в множество set_w:

ff = path + 'freq/freqrnc2011.txt' # Имя файла с частотным словарем
ff = open(ff, encoding = 'utf-8')
line = ff.readline()
dict_freq = {}
while line:
    line = ff.readline()
    if not line: break
    line = line.split()
    word = line[0]
    if find_n_vowels(word) < 2: continue
    freq = float(line[2])
    freq_d = dict_freq.get(word)
    if freq_d is None or freq_d < freq:
        dict_freq.update({word : freq})
ff.close()
set_w = set() # Множество основ, имеющихся в словаре Зализняка
for let in list_abc: # 84451 основ
    all_words = read_txt_f(path + let + t)
    all_words = [w.replace('\n', '') for w in all_words if find_n_vowels(w) > 1]
    set_w = set_w.union(set(all_words))

Оценка вероятности принадлежности случайного слова словарю Зализняка равна 0.7012 и вычислена следующим кодом:

lst_freq = list(dict_freq) # Сначала только леммы (без частот)
set_f = set(lst_freq)
set_wf = set_w.intersection(set_f)
print(len(set_wf))
print('Вероятность принадлежности случайного слова словарю Зализняка:', round(len(set_wf) / len(set_f), 4))

Размеры групп лемм (группировка по числу слогов):
[9981, 16937, 12024, 6405, 2670, 990, 324, 106, 52, 16, 4]
вычисляет следующий код:

def one_lst(knd, rng, lst_freq, n_syl_min, n_syl_max):
    lst = [0 for k in rng]
    for itm in lst_freq:
        freq_syl = itm[1]
        n_syl = itm[2]
        if n_syl <= n_syl_max and n_syl >= n_syl_min:
            lst[n_syl - n_syl_min] += 1 if knd == 'amt' else freq_syl
        elif n_syl > n_syl_max:
            print(n_syl, itm[0])
    return lst
n_syl_min = 2; n_syl_max = 12
rng = range(n_syl_min, n_syl_max + 1)
lst_n_syl = one_lst('amt', rng, lst_freq, n_syl_min, n_syl_max)
print('Размеры групп лемм:')
print(lst_n_syl)

Вероятности принадлежности леммы группам:
[0.45167, 0.3185, 0.13819, 0.06581, 0.02053, 0.00396, 0.00096, 0.00023, 0.00012, 2e-05, 1e-05]
получены после исполнения следующего кода:

freq_sum = 0
for itm in lst_freq: freq_sum += itm[1]
lst_freq_syl = [round(freq_syl / freq_sum, 5) for freq_syl in lst_freq_syl]
freq_sum = sum(lst_freq_syl)
lst_freq_syl = [round(freq_syl / freq_sum, 5) for freq_syl in lst_freq_syl]
print(lst_freq_syl)

Заключение

Приведенные программы позволяют подготовить данные для обучения и проверки НС, выполняющих классификацию слов и определение ударения. И классификация слова, и определения ударений выполняются в подходящей группе слов (в группу собираются слова с одинаковыми числами слогов и ударных слогов).
Обученные НС, как показал эксперимент, правильно определяют непереходящие ударения с вероятностью 0,9474 в случае слов класса С1 и – 0,9759 в случае С2.
Программы создания и обучения НС так же, как и программы подготовки данных, включены в приложение.
Определение переходящих ударений требует привлечение контекста.
Так же рассмотренные программы могут быть употреблены для подготовки и уточнения статистических правил нахождения ударных слогов.

Литература

1. Грамматический словарь Зализняка А. А. [Электронный ресурс] URL: https://gufo.me/dict/zaliznyak (дата обращения: 01.06.2020).
2. Цирульник Л. И., Покладок Д. А. Грамматический словарь и правила определения словесного ударения для синтеза речи по тексту на мобильных устройствах. Информатика. Апрель-июнь 2012. С. 61-68.
3. Бабайцева В. В. Русский язык. Теория. 5-11 классы: Учебник для учеб. заведений с углубл. изуч. рус. яз. – М.: Дрофа, 1998. – 432 с.
4. Розенталь Д. Э., Голуб И. Б., Теленкова М. А. Современный русский язык. Учебное пособие для вузов. – М.: Рольф; Айрим-пресс, 1997. – 448 с.
5. Ляшевская О. Н., Шаров С. А. Частотный словарь современного русского языка (на материалах Национального корпуса русского языка). М.: Азбуковник, 2009.
6. Национальный корпус русского языка. [Электронный ресурс] URL: http://www.ruscorpora.ru/new/ (дата обращения: 01.06.2020).

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

# https://gufo.me/dict/zaliznyak Словарь ударений
# https://sysblok.ru/nlp/akcentuatory-pamjati-a-a-zaliznjaka-chast-iii/ История простановки ударений
# https://progaonline.com/accent Сервис простановки ударений
step = 20
colab = False # True False
# Если by_syls = True, то при кодировании букв их номерами в алфавите формируем
# данные для обучения и проверки по слогам, в противном случае - по словам
by_syls = True # True False (step == 11 не используется)
#
# Файлы:
# step == 1
# ?.txt - содержат базовые слова без ударений, например, абажур.
# step == 2
# ?_2_0.txt формируются по данным сайта,
# содержащего словарь ударений Зализняка (step = 1 и 2).
# step == 3
# ?_2.txt формируется по ?_2_0.txt в результате удаления повторов.
# Ставит знак ударения перед ё, если его там нет.
# acc_no.txt содержит полный перечень слов, в которых нет ударений
# step == 4
# acc_dif.txt формируется по файлам ?_2.txt, содержит в каждой строке
# число слов с одинаковым написанием (2 и более),
# слова с одинаковым написанием, но с разными вариантами ударений,
# например, 2 'автозав'одска автозаводск'а
# step == 5
# acc_two.txt содержит перечень слов с несколькими постоянными ударениями
# step == 6
# ?_3.txt формируется по ?_2.txt и содержит в каждой строке
# слово и список номеров слогов под ударением в слове, например:
# 'август 1
# 'авиаб'аза 1,4
# Содержит в разных строках слова в одинаковым написание,
# но с разными слогами под ударением, например:
# 'авиасъёмки 1
# авиасъёмки 4
# step == 7
# syl_v.txt, syl_v_1.txt, syl_v_2.txt, syl_v_3.txt, ..., syl_v_12.txt -
# формируются по файлам ?_2.txt;
# файл syl_v.txt содержит в каждой строке слог и число употреблений слога
# во всем словаре, например, "ся 159837";
# файл syl_v_i.txt содержит в каждой строке слог и число употреблений слога
# в словах с числом слогов i, например, ся 21497 ва файле syl0_4.txt;
# сортировка выполнена по числу слогов;
# syl_w_max.txt. Одна строка файла:
# а 11 агрометеорологическая 56 31325
# В строке:
# а - буква алфавита;
# 11 - наибольшее число слогов в словах на букву а;
# агрометеорологическая - пример слова с наибольшим числом слогов;
# 56 - число слов с наибольшим числом слогов;
# 31325 - число слов на букву 'а' в С0 + С1 + С2.
# step == 8
# acc_syl.txt -
# формируется по файлам ?_3.txt, содержат в каждой строке
# число слогов в слове,
# всего слов с указанным числом слогов,
# номер слога - число ударений на слог.
# например, 3 267395: 1-70935 2-143095 3-53365
# (267395 = 70935 + 143095 + 53365)
# step == 9
# ns - число слогов (гласных). 1 <= ns <= 12.
# acc.txt - содержит в каждой строке слог и число ударений на слог во всем
# словаре, например, ва 42309; сортировка по числу ударений.
# acc_ns.txt - содержит в каждой строке слог и число ударений на слог
# в словах с ns слогами; сортировка по числу ударений.
# acc-файлы формируются по файлам ?_3.txt,
# words_0_ns.txt - список слов с одинаковым написанием, но разными
# ударными слогами.
# word_s1_ns.txt - список слов с одним непереходящим ударением с ns слогами;
# Формируются по файлам ?_3.txt, которые содержат в каждой строке
# слово и через пробел номер слога под ударением; сортировка по алфавиту
# words_2_ns.txt - список слов с несколькими непереходящими ударениями
# step == 10
# syl_ClsType_ns.txt - словарь слогов words_ClsType_ns.txt (2 <= ns <= 12).
# step == 11. Не используется.
# params_i.txt содержит в первой строке True, если обучающие и оценочные данные
# сформированы по слогам, и False - в противном случае.
# Во второй строке файл содержит значение n_max - максимальное (допустимое)
# число букв в слоге.
# Примеры одного экземпляра данных при формировании по слогам в словах
# с числом слогов 2 и n_max = 6, n_max - максимальное число букв в слоге
# На месте каждой буквы ее номер в алфавите; если букв меньше n_max,
# то код слога дополняется нулями
# Кодирование по слогам: by_syls = True
# Слово: вдохом;
# Код слова по слогам: [[3, 5, 16, 0, 0, 0, 0], [23, 16, 14, 0, 0, 0, 0]]
# Метка слова (номер ударного слога - 1): 0
# Кодирование по словам: by_syls = False; nw_max = 15
# Буквы слога заменяем на их номера; после слога i ставим 33 + i;
# между слогами ставим i. Длина вектора, представляющего слово, равна nw_max
# Слово: взбрести
# Код слова: [3, 9, 2, 18, 6, 34, 0, 19, 20, 10, 35, 0, 0, 0, 0]
# Метка слова (номер ударного слога - 1): 1
# trn_x_2.bin, trn_y_2.bin, ..., trn_x_8.bin, trn_y_8.bin;
# val_x_2.bin, val_y_2.bin, ..., val_x_8.bin, val_y_8.bin -
# обучающие и оценочные множества - данные и их метки;
# окончание _? указывает на число слогов в словах множества;
# формируются по файлам words_1_2.txt, ..., words_1_8.txt
# step == 14.
# Создание данных для всех классификаторов
# ClsType = 'v' - классификатор слов (классы С0-С2)
# ClsType = '0' - определитель переходящих ударений
# ClsType = '1' - определитель одного непереходящего ударения
# ClsType = '2' - определитель двух непереходящих ударений
# ClsType = '3' - одновременно классификатор слов
#                 и определитель одного непереходящего ударения
# Случай ClsType = '3':
# Число классов ns + 2, где ns - число слогов в слове.
# Кодируем слог его номером в списке слогов группы, в которую входит слово.
# Формируются наборы данных для слов:
# - с одним непереходящим ударением (по words_1);
# - с переходящими ударениями (по words_0);
# - с двумя непереходящими ударениями (по words_2).
# По одному примеру из каждого набора:
# [слово, номер ударного слога] [код слова] метка
# ['сживи', '2'] [507, 57] 2 (метка - это номер ударного слога)
# ['пушке', '2'] [49, 381] 0 (может быть и ['пушке', '1'] [49, 381] 0)
# ['трёхстах', '1,2'] [734, 4963] 3 (два ударных слога,
#                                     метка - это число слогов + 1)
# Случай ClsType = '2'
# Так же как и в случае слов с одним непереходящим ударением,
# данные формируются для каждой группы слов.
# Группа - это слова с заданным числом слогов.
# Алгоритм формирования набора данных:
##Входные данные: ns - число слогов в слове.
##1. Сформировать по файлу words_2_ns словарь dict_classes
## с ключами 'i_acc1, i_acc2' - номера ударных слогов
## и значениями class_no - номер класса.
##2. По файлу words_2_ns сформировать набор данных, так же как
## и на шаге step == 14, определяя метки по словарю dict_classes.
##3. Разбить набор на обучающее и проверочное множества. Результат сохранить.
# Максимальное число классов – число сочетаний 2 из ns.
#
import sys
import requests
import re
import numpy as np
#
# Глобальные переменные
path = 'G:/AM/НС/poems/accents/' # Путь к данным
# Расширения файлов
t = '.txt'
bn = '.bin'
dtp = np.byte
# Строчные буквы русского алфавита без букв ъ, ы и ь
list_abc = ['а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п',
            'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'э', 'ю', 'я']
##list_abc = ['у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'э', 'ю', 'я']
##list_abc = ['а'] # а, б, в, ё, к, п
# Все строчные буквы русского алфавита
lst_abc = ['а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п',
         'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я']
vowels = 'а,е,ё,и,о,у,ы,э,ю,я' # Гласные
sonor = 'й,л,м,н,р' # Сонорные согласные
noise = 'б,в,г,д,ж,з,п,ф,к,т,ш,с,х,ц,ч,щ' # Шумные согласные (звонкие и глухие)
no_sound = 'ъ,ь' # Буквы, не образующие звуки
#
# Этапы получения словаря и работы с ним
# step == 1.
# Чтение и сохранение списка базовых слов.
# Каждая буква в своем файле, например, a.txt.
# Адреса (примеры):
# https://gufo.me/dict/zaliznyak?letter=а
# https://gufo.me/dict/zaliznyak?page=2&letter=а
# Могут быть слова с дефисом, например: агар-агар, ампер-час, ...
# step == 2.
# Чтение и сохранение в файл ?_2_0.txt производных (склонений)
# от базовых слов по файлу ?.txt.
# Поиск в тексте страницы ведется по <p>
# Адрес (примеры):
# https://gufo.me/dict/zaliznyak/облако
# https://gufo.me/dict/zaliznyak/абажур
# Производные слова (как на сайте):
# Абажур, абажуры, абажура, абажуров, абажуру, абажурам, абажур, абажуры, абажуром, абажурами, абажуре, абажурах
# https://gufo.me/dict/zaliznyak/облако
# <p><span>Абаж<u>у</u>р,</span></p>
# <p><span>абаж<u>у</u>ры,</span></p>
# ...
# <p><span>абаж<u>у</u>рах</span></p>
# Могут быть следующие подстроки:
# '<em>', '</em>', '<strong>1</strong>. ', '<strong>2</strong> ', '<strong>3</strong> '.
# step == 3.
# Удаление повторов в файле ?_2_0.txt и запись результата в файл ?_2.txt.
# Ставит знак ударения перед ё, если его там нет.
# Кроме того, для всех букв, кроме буквы 'п', слова, начинающиеся с буквы 'п',
# заносятся в отдельный список. После завершения обработки всех букв
# этот список добавляется к словам, начинающимся с буквы 'п'.
# После каждого слова в файле ?_2 следует число употреблений слова в ?_2_0
# Кроме того, создается acc_no.txt, с перечнем слов без ударений.
# step == 4.
# Поиск слов, одинаковых по написанию, но с разными ударными слогами.
# Выполняется по файлам ?_2.txt. Найденные слова заносятся в список, а затем
# сохраняются в файл acc_dif.txt.
# step == 5.
# acc_two.txt с перечнем слов с несколькми непереходящими ударениями
# Так же слова с несколькими, но переходящими ударениями могут быть в acc_dif.txt
# step == 6.
# Создание по файлу ?_2.txt файла ?_3.txt со строками вида <слово> <n1[,n2]>,
# n1, n2 - соответственно номера ударных гласных (слогов)
# <, n2> присутствует, если в слове две ударных гласных
# n1 = 0, если в слове нет ударных гласных
# step == 7.
# Деление слов по слогам (процедура by_syllables) и формирование файла syl_w.txt
# со списком слогов в результате чтения файлов ?_2.txt (например, a_2.txt).
# Одновременно для каждой буквы находим максимальное число слогов в слове,
# начинающемся с этой буквы, и записываем результат в файл syl_w_max.txt
# Розенталь Д.Э., Голуб И.Б., Теленкова М.А.
# Согласные сонорные sonor = 'й,л,м,н,р' - образуются с помощью голоса и незначительного шума.
# Согласные шумные звонкие noise_voice = 'б,в,г,д,ж,з' - образуются с помощью шума в сопровождении голоса.
# Согласные шумные глухие noise_no_voice = 'п,ф,к,т,ш,с,х,ц,ч,щ' - образуются с помощью шума в без участия голоса.
# Слогоделение:
# 1) сочетание шумных согласных между гласными отходит к последующему слогу: про-стой, зве-зда, ло-дка;
# 2) сочетание шумного согласного с сонорным между гласными отходит к последующему слогу: до-бро, ве-сна, ве-сло, до-гма;
# 3) сочетание сонорных согласных между гласными отходит к последующему слогу: ко-рма, то-мный.
# 4) сочетание сонорного согласного с шумным между гласными имеет слогораздел внутри этого сочетания: пар-та, кол-ба, лом-кий, брон-за;
# 5) сочетание 'й' с шумным или сонорным согласным имеет слогораздел внутри этого сочетания: лей-ка, вой-дем, тай-на, кай-ма, сай-ра;
# 6) буквы 'й', 'ъ' и 'ь' от предшествующих букв не отделяются (правила переноса, стр. 60, В.В.Бабайцева).
##Алгоритм слогоделения.
##Входные данные:
##word - слово с числом гласных 2 или более;
##vowels = 'а,е,ё,и,о,у,ы,э,ю,я' - гласные;
##sonor = 'й,л,м,н,р' - сонорные согласные;
##noise = 'б,в,г,д,ж,з,п,ф,к,т,ш,с,х,ц,ч,щ' - шумные согласные (звонкие и глухие);
##no_sound = 'ъь' - буквы, не образующие звуки.
##Выходные данные:
##word_syls - список с найденными слогами.
##Пример:
##word = 'звезда'
##word_syls = ['зве', 'зда']
##1. Начало.
##2. Вычислить n_vowels. // Число гласных в word
##3. n_parts = 0 // Число найденных слогов
##4. n_let = len(word) // Число непросмотренных букв слова
##5. word_syls = []
##6. n_let = n_let - 1
##7. part = word[n_let] // Берем в последний слог последнюю букву
##8. has_vowel = part in vowels // Флаг наличия гласной в формируемом слоге
##9. Пока n_parts < n_vowels - 1:
##    n_let = n_let - 1
##    let_cur = word[n_let] // Текущая буква слова
##    Если let_cur В vowels:
##        Если has_vowel: // В слоге уже имеется гласная, значит нужно начать новый слог
##            Добавить part В начало word_syls.
##            n_parts = n_parts + 1
##            Если n_parts == n_vowels - 1: Выход Из Цикла
##            part = let_cur // Начинаем формировать новый слог
##        Иначе:
##            part = let_cur + part // Включаем гласную в слог
##            has_vowel = True
##    Иначе:
##        let_0 = word[n_let + 1] // Предшествующая буква слова
##        Если has_vowel:
##            Если let_cur В no_sound: # Буква 'ъ' или 'ь'; нужно начать новый слог
##                Добавить part В начало word_syls.
##                n_parts = n_parts + 1
##                Если n_parts == n_vowels - 1: Выход Из Цикла
##                has_vowel = False
##            ИначеЕсли let_cur В sonor И let_0 В noise: // Сочетание сонорного и шумного согласных, например, кол-ба
##                Добавить part В начало word_syls.
##                n_parts = n_parts + 1
##                Если n_parts == n_vowels - 1: Выход Из Цикла
##                part = let_cur // Начинаем формировать новый слог
##                has_vowel = False
##            ИначеЕсли let_cur == 'й': // Сочетание 'й' с шумным или сонорным согласным, например, тай-на
##                Добавить part В начало word_syls.
##                n_parts = n_parts + 1
##                Если n_parts == n_vowels - 1: Выход Из Цикла
##                part = let_cur // Начинаем формировать новый слог
##                has_vowel = False
##            ИначеЕсли let_0 В vowels: // Согласная перед гласной, например, зве-зда
##                part = let_cur + part
##            ИначеЕсли let_cur В noise И let_0 В noise: // Сочетание шумных согласных, например, зве-зда
##                part = let_cur + part
##            ИначеЕсли let_cur В noise И let_0 В sonor: // Сочетание шумного и сонорного согласных, например, до-бро
##                part = let_cur + part
##            ИначеЕсли let_cur В sonor И let_0 В sonor: // Сочетание сонорных согласных, например, то-мный
##                part = let_cur + part
##        Иначе: // Две согласных в конце слога, например, кон-церт
##            part = let_cur + part
##10. part = word[:n_let + 1] // Первый слог слова
##11. Добавить part В начало word_syls.
##12. Останов.
# step == 8.
# Подсчет статистики для слов с одинаковым числом слогов.
# Формируем список n_syl n_let_all: 1-acc_1 2-acc_2 + ..., где
# n_syl - число слогов в слове (ns - другое обозначение);
# n_let_all - число слов с числом слогов n_syl;
# i-acc_i - номер слога и число ударений на слог с этим номером.
# Результат записываем в файл acc_syl.txt
# Вычисления выполняются по данным файлов вида ?_3.txt.
## Алгоритм (для слов с одним слогом под ударением).
##1. Инициализировать словарь dict_syl записями вида
##    {ns : [0 for m in range(ns + 1)]}
##    словарь, содержащий в качестве ключа ns - число слогов в слове,
##    а в качестве значения список размера ns + 1.
##    0-й элемент списка - число слов с числом слогов ns.
##    i-й элемент списка - это число ударений на слог i (i > 0).
##2. Получить слово и число слогов под ударениями.
##3. Если число слогов под ударениями равно 1:
##    Вычислить n_syl - число слогов (гласных) в слове.
##    Получить i_acc - номер слога под ударением.
##    Получить lst_i - соответствующее значение dict_syl.
##    Если это значение не None:
##        Обновить значение, увеличив на 1 lst_i[0] и lst_i[i_acc]
##    Иначе:
##        Добавить в dict_syl запись с ключом n_syl и значением,
##        в котором lst_i[0] = 1, lst_i[i_acc] = 1,
##        а все прочие элементы списка равны нулю.
##4. После обработки всех слов преобразовать словарь dict_syl
## в список требуемого вида и сохранить в файл ac_syl.txt.
# step == 9.
# Подсчет статистики для слогов под ударением; формирование файла acc.txt,
# а также аналогичных файлов acc_ns.txt для слов с ns слогами.
# Также создаются файлы words_0_ns, words_1_ns, words_2_ns.
##Алгоритм для acc.txt.
##1. Читаем файл ?_3.txt.
## В файле после слова следует номер слога под ударением
## или два номера, разделенные запятой, если в слове два слога под ударением
##2. Для каждого слова из файла выполняем слогоделение (формируем список слогов).
## Находим слог под ударением и заносим его в словарь слогов со значением 1
## или увеличиваем значение на 1, если слог уже имеется в словаре.
## В итоге это значение покажет,
## сколько раз слог находился под ударением в используемом словаре ударений.
##3. Сформированный словарь преобразовываем в список,
## сортируем по убыванию значения и записываем в файл acc.txt.
# step == 11. Не используем.
# Модель слога - список размера n_max из номеров букв, составляющих слог,
# а слова – в виде массива таких списков. Например, яб-ло-ко – ([33, 2], [13, 16], [12, 16]).
# Формирование обучающих и оценочных множеств (данные и их метки);
# для каждой группы слов (группы формируются по числу слогов в словах:
# группа из слов с двумя слогами, группа из слов с тремя слогами и так далее.
# Во всех словах группы имеется только один слог под ударением.
# (Слова с двумя слогами под ударением и слова без ударений изымаются.)
##Алгоритм.
##Входные данные:
## n_syl - число слогов в словах группы (номер группы).
## Имя файла со списком слов группы, например, words_1_2.txt (n_syl = 2).
## Имя файла со списком слогов слов группы, например, syl_1_2.txt (n_syl = 2).
## val_size - доля данных, передаваемых в оценочное множество,
## от общего объема данных, например, 0.2.
##Промежуточные данные:
## lst_words - список слов группы.
## lst_accs - список номеров слогов, находящихся под ударением:
## в слове lst_words[i] под ударением находится слог с номером lst_accs[i].
## Списки формируются по файлу words_<n_syl>.txt.
## lst_syls - список слогов в словах группы.
## n_max - число букв в самом длинном слоге рассматриваемой группы слов;
## определяется по файлу syl_<n_syl>.txt.
## n_data - число слов в группе.
## x_data - список размера n_data * n_syl *n_max
## с кодами слов рассматриваемой группы.
## y_data - список с метками слов группы.
##Выходные данные:
## n_trn - размер обучающего множества (число слов в этом множестве).
## n_val - размер оценочного множества.
## x_trn - массив размера n_trn * n_syl * n_max
## с кодами слов, входящих в обучающее множество.
## При обучении НС преобразовывается в массив формы (n_trn, n_syl, n_max).
## y_trn - массив меток слов из массива x_trn.
## Метка - номер слога под ударением, уменьшенный на 1.
## Таким образом, задача простановки ударений сводится к задаче классификации.
## Число классов равно n_syl.
## Для каждой группы слов создается и обучается своя НС.
## x_val - массив размера n_val * n_syl * n_max)
## с кодами слов, входящих в оценочное множество;
## При обучении НС преобразовывается в массив формы (n_val, n_syl, n_max).
## y_val - массив меток слов из массива x_val.
## Код слова - это массив размера n_syl * n_max, представляющий коды слогов слова.
## При обучении НС представляется массивом формы (n_trn, n_syl, n_max).
## Код слога - это последовательность кодов его букв.
## Код буквы - это число из диапазона [1, 33] -
## а - 1; б - 2; ..., я - 33.
## Все слоги кодируются вектором n_max,
## Если число букв в слоге менее n_max, то каждая недостающая буква
## представляется нулем.
## Метка этого слова - это номер ударного слога - 1.
##1. Прочитать файлы words_<n_syl>.txt и syl_<n_syl>.txt,
## сформировать списки lst_words, lst_accs и lst_syls и найти n_max, n_data.
##2. x_data = []; y_data = []
##3. k = -1
##4. Для word из lst_words:
##    k = k + 1
##    Сформировать список слогов слова.
##    Сформировать список word_code размера n_syl * n_max c кодами слогов слова.
##    Добавить word_code в x_data.
##    Добавить в y_data lst_accs[k] - 1
##4. Получить из x_data и y_data массивы x_trn, y_trn, x_val и y_val
## и записать их в бинарные файлы
## trn_x_<n_syl>, trn_y_<n_syl>, val_x_<n_syl> и val_y_<n_syl>.
# step == 12. Не используется.
# Формирование и обучение модели НС по данным, полученным на шаге step == 11.
# step == 13.
# Определение пересечений каждой пары множеств слогов.
##Алгоритм.
##1. Сформировать множества i-х и j-х (i != j) слогов;
##2. Найти n_cr - число общих слогов в этих множествах;
##3. c_cr = 2 * n_cr / (n_i + n_j),
## где n_i, n_j - соответственно число слогов в рассматриваемых множествах.
# step == 14.
# Создание данных для всех классификаторов
# ClsType = 'v' - классификатор слов (классы С0-С2)
# ClsType = '1' - определитель одного непереходящего ударения
# ClsType = '2' - определитель двух непереходящих ударений
# ClsType = '3' - одновременно классификатор слов
#                 и определитель одного непереходящего ударения
# Случай ClsType = '3':
# Число классов ns + 2, где ns - число слогов в слове.
# Класс 0 (С0) - это слова с переходящим ударением; формируется по файлу words_0_i.
# Последующие ns классов (С1) - это слова с постоянным ударением:
# в классе i ударение падает на слог i; формируется по файлу words_i.
# Последний класс ns + 1 (С2) - это слова с несколькими ударениями;
# Формируем обучающее и проверочное множества; слог кодируется одной цифрой.
# Цифра определяется номером слога в списке слогов syl_ClsType_i.txt (см. step == 7).
# В группе с ns слогами слова разделены на ns + 2 класса.
# формируется по файлу words2_i.
# Классы 0, 1, 2, ..., ns и ns + 1 разбиваются на обучающие
# и проверочные множества независимо.
# step == 15.
# Обучение НС по данным, сформированным на шаге step == 14.
# Результаты обучения сохраняются в файле вида model_ClsType_n_syl_model_num,
# где model_num - номер модели НС;
# ClsType - тип классификатора;
# n_syl - число слогов в слове
# Обучение может вестись как на всем наборе данных (full_set = True),
# так и на разделенном на обучающее и проверочное множества
# step == 16.
# Вывод примеров ошибочной простановки ударения.
# Загружаются обучающие и проверочные данные и модель НС.
# step == 17.
# Формирование файла acc_syl_two для слов с двумя непереходящими ударениями.
# Для группы слов с ns слогами файл содержит строку, в которой по аналогии
# с acc_syl находится следующая информация:
# число слогов в слове,
# всего слов с указанным числом слогов,
# всего классов - пар ударных слогов,
# номера пар ударных слогов (через запятую),
# частота пары ударных слогов (после дефиса):
# 4 4519: 6 - 1,2-1047 1,3-2760 1,4-439 2,3-190 2,4-72 3,4-11
# Создается по файлам words_2_i.txt.
##Алгоритм.
##1. Задать ns - номер группы, начиная со второй.
##2. Прочитать файл words_2_ns.
##3. Сформировать словарь с ключами 'i_acc1, i_acc2' - номера ударных слогов
## и значениями n_acc_2 - частота появления ключа.
##4. Используя этот словарь, сформировать строку файла acc_syl_two
# step == 18.
# Обработка предлогов, союзов и частиц.
# Формируется файл с3.txt (класс С3) из предлогов, союзов и частиц или их частей
# с числом не менее 2: составная служебная часть речи разделяется на компоненты.
# Если число слогов в компоненте более 2,
# то она ищется в соответствующих words_0, words_1 и words_2-файлах.
# Если поиск неудачен, то компонента включается в с3.txt.
# Исходные файлы: _предлоги.txt, _союзы.txt, _частицы.txt.
# step == 19.
# Вычисление размеров групп слов классов V, С0-С2 словарей слогов этих групп
# step == 20.
# Анализ частотного словаря О. Н. Ляшевской, С. А. Шарова http://dict.ruslang.ru/freq.php?
# Вычисляются вероятности поринадлежности случайной леммы группам лемм
# Леммы группируются по числу слогов
# Также вычисляется вероятность принадлежности случайно выбранного слова
# словарю Зализняка.
# step == 21.
# Предсказание ударений списка слов
# step == 22.
# Визуализация файла acc_syl.txt
#
# Общие процедуры и функции
# Готовит словарь классов для определения метки слова класса С2
def C2_classes(n_syl, path, t):
    b = ' '
    fw = make_faws('2', n_syl, 'words', path, t)
    all_words = prep_words_syls(fw, app = False)
    dict_classes = {}
    n_classes = -1
    for word in all_words:
        p = word.find(b)
        pos_a = word[p + 1:]
        if dict_classes.get(pos_a) is None:
            n_classes += 1
            dict_classes.update({pos_a : n_classes})
    return dict_classes
# Вставляет знак ударения (апостроф) перед гласной слога syl
def make_s(app, syl, vowels):
    s = ''
    for let in syl:
        s += ((app if let in vowels else '') + let)
    return s
# Формирует слово по кодам слогов и ставит знак ударения
# перед слогом с номером c в word_pred и c_0 в word_true
# Если c = c_0 = 0, то ударение не ставится и выводится одно слово
def make_print_wrds(ClsType, lst_syls, i_syls, app, vowels, n, c, c_0, dict_classes = {}):
    word_pred, word_true, c_str, c_0_str = '', '', '', ''
    if ClsType == '2':
        for itm in dict_classes.items():
            if itm[1] == c:
                c_str = itm[0]
                if c == c_0:
                    c_0_str = c_str
                    break
            elif itm[1] == c_0:
                c_0_str = itm[0]
    else:
        c_str = str(c + 1)
        c_0_str = str(c_0 + 1)
    k = 0
    for i in i_syls:
        syl = lst_syls[i - 1]
        k += 1
        k_str = str(k)
        if k_str in c_str:
            word_pred += make_s(app, syl, vowels)
        else:
            word_pred += syl
        if k_str in c_0_str:
            word_true += make_s(app, syl, vowels)
        else:
            word_true += syl
    n += 1
    print('Коды слогов:', i_syls)
    if c >= 0 and c_0 >= 0:
        print('{}.'.format(n), word_pred, c_str, word_true, c_0_str)
    else:
        print('Слово:', word_true)
    return n
# Формирует данные и их метки для различных классификаторов
# ClsType = 'v' - классификатор слов (классы С0-С2)
# ClsType = '1' - определитель одного непереходящего ударения
# ClsType = '2' - определитель двух непереходящих ударений
# ClsType = '3' - одновременно классификатор слов
#                 и определитель одного непереходящего ударения
def words_to_data(n_syl, dict_syls, path, suf, st, val_size, ClsType, dict_classes = None):
    words_n_syl = prep_words_syls(path + 'words_' + suf + s_t, app = False)
    n_words_all = len(words_n_syl)
    if n_words_all > 0:
        r = np.random.randint(n_words_all) # Для вывода примера
    i = -1
    x_dt = []
    y_dt = []
    for word in words_n_syl:
        word = word.split()
        if ClsType == 'v': # lb - метка класса
            lb = int(suf)
        elif ClsType == '1':
                lb = int(word[1]) - 1 # word[1] - номер ударного слога
        elif ClsType == '2':
            lb = word[1]
            lb = dict_classes.get(lb)
        elif ClsType == '3':
            if suf == '0':
                lb = 0
            elif suf == '2':
                lb = n_syl + 1
            else:
                lb = int(word[1])
        lst_wrd = []
        lst_syls = by_syllables(word[0])
        for syl in lst_syls:
            n = dict_syls.get(syl)
            if n is None:
                print('Не найден слог:', syl)
                n = 0
            lst_wrd.append(n)
        i += 1
        if i == r: word_r = [word[0], lb]
        x_dt.append(lst_wrd)
        y_dt.append(lb)
    print('Всего слов в words{}: {}'.format(suf, n_words_all))
    if n_words_all > 0:
        print('Пример экземпляра данных под номером', r, '([word, class], x_dt, y_dt)')
        print(word_r, x_dt[r], y_dt[r])
        # ['трёхдневную', '1,2'] [330, 3097, 553, 4] 4 - dict_classes
    print('Делим данные на обучающие и оценочные ({})'.format(val_size))
    x_t, x_v, y_t, y_v = train_test_split(x_dt, y_dt, test_size = val_size)
    return x_t, x_v, y_t, y_v
# Читает файл fn_2 со списком слов на букву let
# и формирует список слов со знаком ударения или без него, если app = True
def prep_all_words2(let, dd_2, app = True):
    print('БУКВА:', let)
    fn_2 = path + let + dd_2
    all_words2 = read_txt_f(fn_2)
    all_words2 = [word.split() for word in all_words2]
    all_words2 = [word[0] for word in all_words2]
    if app:
        all_words2 = [word.replace("'", '') for word in all_words2]
    return all_words2
# Читает файл fn со списком слов с заданным числом слогов
# и формирует список слов со знаком ударения или без него, если app = True
def prep_words_syls(fn, app = True, split = False):
    all_words = read_txt_f(fn)
    all_words = [word.replace('\n', '') for word in all_words]
    if app:
        all_words = [word.replace("'", '') for word in all_words]
    if split:
        all_words = [word.split() for word in all_words]
        all_words = [word[0] for word in all_words]
    return all_words
# Поиск номера ударного слога. Возвращает номер ударного слога и слово,
# в котором удален апостроф (знак ударения) перед найденным ударным слогом
def find_n1(ap, vowels, word):
    n1 = 0
    p = word.find(ap)
    word2 = word.replace(ap, '', 1)
    if p > 0:
        for m in range(p):
            let_m = word2[m : m + 1]
            if let_m in vowels: n1 += 1
    return str(n1 + 1), word2
# Форрмирует по списку acc словарь dict_a, ключом в котором является слово w
def treat_acc(ap, acc):
    dict_a = {}
    acc = [w.split() for w in acc]
    acc = [w[0].replace(ap, '') for w in acc]
    for w in acc: dict_a.update({w : 1})
    return dict_a
# Читает из двоичных файлов обучающие и проверочные данные
def read_trn_val(tx, ty, vx, vy, dtp):
    x_trn = read_bin_f(tx, False, dtype = dtp)
    y_trn = read_bin_f(ty, False, dtype = dtp)
    x_val = read_bin_f(vx, False, dtype = dtp)
    y_val = read_bin_f(vy, False, dtype = dtp)
    return x_trn, y_trn, x_val, y_val
# Добавляет массив arr в двоичный файл fn
def add_to_bin_f(arr, fn, dtype = np.uint8):
    with open(fn, 'wb') as f:
        np.array(arr, dtype = dtype).tofile(f)
    print('Создан бинарный файл', fn)
    print('Размер источника данных', len(arr))
# Читает в массив arr данные из двоичного файла fn
def read_bin_f(fn, reshape, sq = 0, dtype = np.uint8):
    print('Прочитан бинарный файл', fn)
    with open(fn, 'rb') as f:
        arr = np.fromfile(f, dtype = dtype)
    if reshape:
        sq = max(1, sq)
        arr_shape0 = int(arr.shape[0] / sq)
        arr = arr.reshape(arr_shape0, sq)
        print('Размер приемника данных', arr_shape0)
        return arr, arr_shape0
    else:
        print('Размер приемника данных', len(arr))
        return arr
# Формирует имя acc-, words- или syl-файла
def make_faws(ClsType, n_syl, aws, path, t):
    return path + aws + '_' + ClsType + '_' + str(n_syl) + t
# Формирует имя params-файла
def make_fp(ClsType, n_syl, path, t):
    return path + ClsType + '_' + str(n_syl) + '_params' + t
# Формирует имя файла
def make_tvxy(ClsType, n_syl, tvxy, path, bn):
    return path + ClsType + '_' + str(n_syl) + '_' + tvxy + bn
# Загружает данные из двоичных файлов и формирует обучающее и проверочное множества
def load_prep_data(ClsType, n_syl, t, bn, path, model_num, loss, dtp, vowels):
    print('Читаем параметры и данные')
    fp = make_fp(ClsType, n_syl, path, t)
    lst_params = read_txt_f(fp)
    # Число уникальных слогов + 1 - для one-hot представления
    n_max = int(lst_params[0]) + 1
    tx = make_tvxy(ClsType, n_syl, 'trn_x', path, bn)
    ty = make_tvxy(ClsType, n_syl, 'trn_y', path, bn)
    vx = make_tvxy(ClsType, n_syl, 'val_x', path, bn)
    vy = make_tvxy(ClsType, n_syl, 'val_y', path, bn)
    x_trn, y_trn, x_val, y_val = read_trn_val(tx, ty, vx, vy, dtp)
    y_trn_0 = y_trn
    y_val_0 = y_val
    len_trn = len(y_trn) # Число слов в обучающем и проверочном множествах
    len_val = len(y_val)
    #
    n_classes = int(max(max(y_trn), max(y_val))) + 1
    print('Число классов', n_classes)
    x_trn = x_trn.reshape(len_trn, n_syl)
    x_val = x_val.reshape(len_val, n_syl)
    sh = (n_syl, )
    if loss != 'sparse_categorical_crossentropy':
        y_trn = k_ut.to_categorical(y_trn, n_classes)
        y_val = k_ut.to_categorical(y_val, n_classes)
    # Вывод примеров слов
    r = np.random.randint(len_val)
    dict_syls, _ = make_dict_syls(ClsType, path, str(n_syl))
    lst_syls = list(dict_syls)
    make_print_wrds(ClsType, lst_syls, x_trn[r], "'", vowels, 0, -1, int(y_trn_0[r]) + 1)
    print('Метка:', y_trn[r])
    make_print_wrds(ClsType, lst_syls, x_val[r], "'", vowels, 0, -1, int(y_val_0[r]) + 1)
    print('Метка:', y_val[r])
    return x_trn, y_trn, y_trn_0, x_val, y_val, y_val_0, n_max, sh, n_classes
# Формирует модель нейронной сети
def make_model(n_classes, model_num, sh, activation,
             use_emb, num_syls, dim, size):
    print('Размер словаря слогов:', num_syls)
    inp = Input(shape = sh, dtype = np.float32)
    x = inp
    if use_emb:
        w_init = initializers.RandomNormal()
        x = Embedding(num_syls, input_length = dim, output_dim = size,
             embeddings_initializer = w_init, trainable = True)(x)
    if model_num == 2:
        x = Dropout(0.5)(x)
        x = Conv1D(filters = 16, kernel_size = 3, padding = 'same', activation = 'relu')(x)
        x = Flatten()(x)
    elif model_num == 3:
##        x = LSTM(32, dropout = 0.3, recurrent_dropout = 0.3)(x)
        r_s = True
        x = GRU(32, return_sequences = r_s,
                stateful = False, recurrent_initializer = 'glorot_uniform',
                recurrent_dropout = 0.3)(x)
        if r_s: x = Flatten()(x)
    elif model_num == 1:
        if use_emb: x = Flatten()(x)
    x = Dropout(0.3)(x)
    x = Dense(16, activation = 'relu')(x)
    output = Dense(n_classes, activation = activation)(x)
    model = Model(inp, output)
    model.summary()
##    from keras.utils import plot_model
##    plot_model(model, to_file = path + str(model_num) + '.png')
##    sys.exit()
    return model
# Вычисляет прогноз нейронной сети
def make_pred(x, y_0, txt):
    print('\n' + txt)
    y_pred = model.predict(x)
    len_y = len(y_0)
##    print('Два случайно выбранных прогноза:')
##    r = np.random.randint(len_y); print(r, x[r], y_pred[r], y_0[r])
##    r = np.random.randint(len_y); print(r, x[r], y_pred[r], y_0[r])
    classes = [np.argmax(m) for m in y_pred]
    nClassified = np.sum(classes == y_0)
    # Число ошиибочно классифицированных изображений
    n_mis = len_y - nClassified
    acc = 100.0 * nClassified / len_y
    print('Ошибочно классифицированно слов: {} из {}'.format(n_mis, len_y))
    print('Точность прогнозирования:', str(round(acc, 2)) + '%')
    return acc, classes, n_mis
# Формирует ключ словаря dict_cur и обновляет его
def treat_dict_syl(syl, n_syl, dict_cur):
    k_n = dict_cur.get(syl)
    sep = ','
    if k_n is None:
        k_n = '1 ' + str(n_syl) + sep
    else:
        lst_k_n = k_n.split()
        k_n = str(int(lst_k_n[0]) + 1) + ' '
        lst_k_n = lst_k_n[1].split(sep)
        n_syl = str(n_syl)
        if not n_syl in lst_k_n: lst_k_n.append(n_syl)
        lst_k_n.sort()
        for n_syl in lst_k_n:
            k_n += (n_syl + sep)
    dict_cur.update({syl : k_n[:len(k_n) - 1]})
# Создает словарь слогов по syl-файлу
# Ключ - слог; значение - номер слога в файле
def make_dict_syls(ClsType, path, s_n_syl):
    dict_syls = {}
    fs = path + 'syl_' + ClsType + '_' + s_n_syl + t
    all_syls = prep_words_syls(fs, app = False)
    N = 0
    for syl in all_syls:
        lst_syl = syl.split()
        N += 1
        dict_syls.update({lst_syl[0] : N})
    return dict_syls, N
# Записывает словарь dict_cur в файл fn_cur
def save_dict(n_cur, dict_cur, fn_cur):
    lst_0 = []
    for itm in dict_cur.items():
        itm = list(itm)
        syl = itm[0]
        itm = str(itm[1]).split()
        lst_syl = [syl, int(itm[0])]
        if len(itm) == 2: lst_syl.append(itm[1])
        lst_0.append(lst_syl)
    lst_0.sort(key = lambda i: i[1], reverse = True)
    lst = [itm[0] + ' ' + str(itm[1]) for itm in lst_0]
    if len(lst_0) > 0 and len(lst_0[0]) == 3: # syl_i.txt
        k = -1
        for itm in lst_0:
            k += 1
            lst[k] += (' ' + itm[2])
    n_s = len(lst)
    print('Число слов:', n_cur)
    print('Размер словаря слогов:', n_s)
    add_to_txt_f(lst, fn_cur)
# Вычисляет число слогов (гласных) в слове
def find_n_vowels(word):
    n_vowels = 0
    for s in word:
        if s in vowels: n_vowels += 1
    return n_vowels
# Формирует и возвращает список слогов слова word
def by_syllables(word):
    def next_syl(let_cur, part, word_syls, n_parts):
        word_syls.insert(0, part)
        n_parts = n_parts + 1
        part = let_cur # Начинаем формировать новый слог
        return part, n_parts
    n_vowels = find_n_vowels(word)
    n_parts = 0 # Число найденных слогов
    n_let = len(word) # Число непросмотренных букв слова
    word_syls = []
    n_let = n_let - 1
    part = word[n_let] # Берем в последний слог последнюю букву
    has_vowel = part in vowels
    while n_parts < n_vowels - 1:
        n_let = n_let - 1 # ==
        let_cur = word[n_let] # Текущая буква слова
        if let_cur in vowels:
            if has_vowel: # В слоге уже имеется гласная, значит нужно начать новый слог
                part, n_parts = next_syl(let_cur, part, word_syls, n_parts)
                if n_parts == n_vowels - 1: break
            else:
                part = let_cur + part # Включаем гласную в слог
                has_vowel = True
        else:
            let_0 = word[n_let + 1] # Предшествующая буква слова
            if has_vowel:
                if let_cur in no_sound: # Буква 'ъ' или 'ь': нужно начать новый слог
                    part, n_parts = next_syl(let_cur, part, word_syls, n_parts)
                    has_vowel = False
                elif let_cur in sonor and let_0 in noise:
                    # Сочетание сонорного и шумного согласных, например, кол-ба
                    part, n_parts = next_syl(let_cur, part, word_syls, n_parts)
                    if n_parts == n_vowels - 1: break
                    has_vowel = False
                elif let_cur == 'й': # Сочетание 'й' с шумным или сонорным согласным, например, тай-на
                    part, n_parts = next_syl(let_cur, part, word_syls, n_parts)
                    if n_parts == n_vowels - 1: break
                    has_vowel = False
                elif let_0 in vowels: # Согласная перед гласной, например, зве-зда
                    part = let_cur + part
                elif let_cur in noise and let_0 in noise: # Сочетание шумных согласных, например, зве-зда
                    part = let_cur + part
                elif let_cur in noise and let_0 in sonor: # Сочетание шумного и сонорного согласных, например, до-бро
                    part = let_cur + part
                elif let_cur in sonor and let_0 in sonor: # Сочетание сонорных согласных, например, то-мный
                    part = let_cur + part
            else: # Две согласных в конце слога, например, кон-церт
                part = let_cur + part
    part = word[:n_let + 1] # Первый слог слова
    word_syls.insert(0, part)
    return word_syls
# Оставляет в тексте только строчные русские буквы
def preprocess_s(s):
##    s = s.replace('ё', 'е')
    s = re.sub('[^а-яё-]', ' ', s)
    s = re.sub(' +', ' ', s)
    return s.strip()
# Добавляет список lst в текстовый файл fn
def add_to_txt_f(lst, fn):
    print('Создан файл', fn, 'с числом записей', len(lst))
    with open(fn, 'w', encoding = 'utf-8') as f:
        for val in lst: f.write((val + '\n') if val.find('\n') == -1 else val)
# Переносит в список lst данные текстового файла fn
def read_txt_f(fn, encoding = 'utf-8'):
    print('Читаем файл', fn)
    with open(fn, 'r', encoding = encoding) as f:
        lst = f.readlines() # <class 'list'>
    return lst
#
print('step =', step)
if step == 1:
    # Адреса страниц алфавитного указателя словаря [1]
    u0 = 'https://gufo.me/dict/zaliznyak?letter='
    un = 'https://gufo.me/dict/zaliznyak?page='
    dd = '&letter='
    pn = 500
    for let in list_abc:
        print('БУКВА:', let)
        fn = path + let + t
        all_words = []
        for m in range(pn):
            if m == 0:
                url = u0 + let
            else:
                url = un + str(m + 1) + dd + let
            print(url)
            response = requests.get(url) # Получаем Response
            if response.status_code == 404:
                print('АДРЕС НЕ НАЙДЕН')
                response.close()
                break
            #response.encoding = 'utf8'
            txt = response.text
            response.close()
            p = txt.find('<li>')
            txt = txt[p:]
            p = txt.find('</article>')
            txt = txt[:p]
            txt = preprocess_s(txt)
            txt = txt.split()
            print('Добавлено слов:', len(txt))
            for word in txt:
                if word != '-':
                    all_words.append(word)
        add_to_txt_f(all_words, fn)
        print('Число слов в файле:', len(all_words))
elif step == 2:
    u0 = 'https://gufo.me/dict/zaliznyak/'
    #https://gufo.me/dict/zaliznyak/нареветься
    ap = "'"
    ye = 'ё'
    let_p = 'п'
    dd = ''
    dd2 = '_2'
    for let in list_abc:
        print('БУКВА:', let)
        fn = path + let + dd + t
        fn2_0 = path + let + dd + dd2 + '_0' + t
        fn2 = path + let + dd + dd2 + t
        all_words = read_txt_f(fn)
        all_words2 = []
        lst_p = []
        for word in all_words:
##            word = 'нареветься'
            word = word.replace('\n', '')
            if len(word) == 1: continue
            w0 = word[0]
            url = u0 + word
            response = requests.get(url)
            if response.status_code == 404:
                print('НЕ НАЙДЕНО СЛОВО:', word)
            else:
                txt = response.text
                response.close()
                p = txt.find('<p>')
                txt = txt[p:]
                p = txt.find('<div')
                txt = txt[:p].rstrip()
                txt = txt.replace('<p><span>', '')
                txt = txt.replace(',', '')
                txt = txt.replace('</span></p>', '')
                txt = txt.replace('<u>', "'")
                txt = txt.replace('</u>', '')
                txt = txt.replace('<em>', '')
                txt = txt.replace('</em>', '')
                txt = txt.replace('<strong>1</strong>. ', '')
                txt = txt.replace('<strong>2</strong>. ', '')
                txt = txt.lower()
                txt = txt.split('\n')
                print(word, '-', len(txt))
                for word2 in txt:
                    if word2[0] == w0:
                        all_words2.append(word2)
##                        print(word2)
##                sys.exit()
        add_to_txt_f(all_words2, fn2_0)
elif step == 3:
    # Ставит знак ударения перед ё, если его там нет
    def treat_ye(ye, word):
        if ye in word:
            pye = word.find(ye)
            if word[pye - 1] != ap:
                word = word[:pye] + ap + word[pye:]
        return word
    def treat_let(dd_0, dd_2, ap, let, let_p, lst_p, acc_no):
        print('БУКВА:', let)
        fn_0 = path + let + dd_0
        fn_2 = path + let + dd_2
        all_words0 = read_txt_f(fn_0)
        n_words0 = len(all_words0)
        print('Число строк в источнике:', n_words0)
        all_words0 = [word.replace('\n', '') for word in all_words0]
        all_words0 = [treat_ye(ye, word) for word in all_words0]
        if let == let_p:
            print('Добавляем слова к словам на букву', let_p, len(lst_p))
            all_words0.extend(lst_p)
        all_words0 = [word for word in all_words0 if len(word) > 0 and word[0] != '-']
        print('Число слов в обработанном источнике:', len(all_words0))
        dict_wrds2 = {}
        no_acc = n_skiped = 0
        for word in all_words0:
            let_1 = word[0]
            if let_1 == let_p and let != let_p:
                lst_p.append(word)
            else:
                if let_1 == ap: let_1 = word[1]
                if let_1 != let:
                    n_skiped += 1
                    continue
                k = dict_wrds2.get(word)
                k = 1 if k is None else (k + 1)
                dict_wrds2.update({word : k})
        all_words2 = [itm[0] + ' ' + str(itm[1]) for itm in dict_wrds2.items()]
        for word in all_words2:
            n_a = word.count(ap)
            if n_a == 0:
                no_acc += 1
                acc_no.append(word)
        add_to_txt_f(all_words2, fn_2)
        print('Число слов после удаления повторов:', len(all_words2))
        print('Число слов без ударений в ?_2.txt-файлах:', no_acc)
        print('Пропущено слов:', n_skiped)
        return n_words0, no_acc, n_skiped
    # Подсчет числа слов (основ)
    n_word_base_all = 0
    for let in list_abc:
        fn_b = path + let + t
        all_words_base = read_txt_f(fn_b)
        n_word_base_all += len(all_words_base)
    print('Всего основ:', n_word_base_all) # 84'451
    ap = "'"
    ye = 'ё'
    fn_acc_no = path + 'acc_no' + t
    let_p = 'п'
    dd_0 = '_2_0' + t
    dd_2 = '_2' + t
    n_words0_all = no_acc_all = n_skiped_all = 0
    lst_p = []
    acc_no = []
    for let in list_abc:
        if let == let_p: continue
        n_words0, no_acc, n_skiped = treat_let(dd_0, dd_2, ap, let, let_p, lst_p, acc_no)
        n_words0_all += n_words0
        no_acc_all += no_acc
        n_skiped_all += n_skiped
    # Обработка буквы 'п', помимо удаления повторов добавляем слова на 'п'
    # из ?_2_0-файлов других букв
    n_words0, no_acc, n_skiped = treat_let(dd_0, dd_2, ap, let_p, let_p, lst_p, acc_no)
    n_words0_all += n_words0
    no_acc_all += no_acc
    n_skiped_all += n_skiped
    print('Всего слов в источниках:', n_words0_all) # 2'381'715
    print('Всего слов без ударений:', no_acc_all) # 2'872
    # Слово пропускается, если оно не начинается с текущей буквы
    print('Всего пропущено слов:', n_skiped_all) # 387
    add_to_txt_f(acc_no, fn_acc_no)
    # Сохраняем слова без ударений в строках по 30 слов в каждой
    fn_acc_no_30 = path + 'acc_no_30' + t
    acc_no_30 = []
    n = 0
    s = ''
    for w in acc_no:
        w = w.split()
        w = w[0]
        if n == 0:
            s = w
            n = 1
        elif n == 31:
            acc_no_30.append(s)
            s = w
            n = 0
        else:
            n += 1
            s += (' ' + w)
    add_to_txt_f(acc_no_30, fn_acc_no_30)
# Создание файла acc_dif.txt со списком слов с переходящим ударением
elif step == 4:
    # Поиск омографов с разными ударными слогами пыполняется по файлам ?_2.txt.
    # Найденные слова заносятся в список, а затем сохраняются в файл acc_dif.txt.
    # Пример строки файла: 'автозав'одская 'автозаводск'ая 2 автозаводская
    ap = "'"
    dict_dif = {}
    fn_acc_dif = path + 'acc_dif' + t
    dd_2 = '_2' + t
    n_words2_all = 0
    for let in list_abc:
        all_words2 = prep_all_words2(let, dd_2, app = False)
        n_words2_all += len(all_words2)
        for word in all_words2:
            wrd = word.replace(ap, '')
            hist = dict_dif.get(wrd)
            hist = word if hist is None else (hist + ' ' + word)
            dict_dif.update({wrd:hist})
    lst_dif = []
    n_dif = 0
    n_k2 = 0
    n_k3 = 0
    for itm in dict_dif.items():
        hist = itm[1].split()
        k = len(hist)
        if k > 1:
            n_dif += 1
            if k == 2:
                n_k2 += 1
            else:
                n_k3 += 1
            lst_dif.append(itm[1] + ' ' + str(k) + ' ' + hist[0].replace(ap, ''))
    add_to_txt_f(lst_dif, fn_acc_dif)
    print('Всего слов в ?_2-файлах:', n_words2_all) # 1'401'759
    print('Всего омографов с переходящим ударением:', n_dif) # 15'036
    print('Всего омографов c двумя повторам:', n_k2) # 14'895
    print('Всего омографов c тремя повторам:', n_k3) # 14
# Создание acc_two.txt с перечнем слов с несколькми непереходящими ударениями
# Так же слова с нескольки, но переходящими ударениями могут быть в acc_dif.txt
elif step == 5:
    ap = "'"
    fn_acc_two = path + 'acc_two' + t
    fn_dif = path + 'acc_dif' + t
    acc_dif = read_txt_f(fn_dif)
    # Словарь слов с одинаковым написанием, но с разными вариантами ударений.
    # Они не будут включены в acc_two.txt
    dict_dif = treat_acc(ap, acc_dif)
    dd_2 = '_2' + t
    acc_two = []
    for let in list_abc:
        all_words2 = prep_all_words2(let, dd_2, app = False)
        for word in all_words2:
            n_a = word.count(ap)
            if n_a > 1:
                if dict_dif.get(word) is None:
                    wrd = word
                    acc_pos = ''
                    for k in range(n_a):
                        n1, wrd = find_n1(ap, vowels, wrd)
                        acc_pos += n1 + ','
                    acc_two.append(word + ' ' + acc_pos[:-1])
    print('Всего слов с числом ударений более 1:', len(acc_two)) # 46'155
    add_to_txt_f(acc_two, fn_acc_two)
# Формирование файлов ?_3.txt
elif step == 6:
    ap = "'"
    b = ' '
    n_words_all_2 = 0
    dd_2 = '_2' + t
    dd_3 = '_3' + t
    for let in list_abc:
        all_words2 = prep_all_words2(let, dd_2, app = False)
        all_words3 = []
        for word3 in all_words2:
            n_words_all_2 += 1
            n1 = n2 = 0
            n_a = word3.count(ap)
            if n_a == 0:
                word3 += ' 0'
            elif n_a == 1:
                n1, _ = find_n1(ap, vowels, word3)
                word3 += (b + n1)
            elif n_a == 2:
                n1, word_3 = find_n1(ap, vowels, word3)
                n2, _ = find_n1(ap, vowels, word_3)
                word3 += (b + n1 + ',' + n2)
            elif n_a > 2:
                print('СЛОВО С ЧИСЛОМ УДАРЕНИЙ > 2:', word3)
            all_words3.append(word3)
        print('Число слов в файле-источнике:', len(all_words2))
        fn_3 = path + let + dd_3
        add_to_txt_f(all_words3, fn_3)
    print('Всего слов в источниках:', n_words_all_2) # 1'401'759
elif step == 7: # Создание файлов syl_v_i.txt
##    word = 'тау' # та-у
##    word = 'звезда' # зве-зда
##    word = 'хорошая' # ['хо', 'ро', 'ша', 'я']
##    word = 'корова' # ['ко', 'ро', 'ва']
##    word = 'конер' # ['ко', 'нер']
##    word = 'конверт' # кон-верт
##    word = 'концерт' # кон-церт
##    word = 'добро' # до-бро
##    word = 'томный' # то-мный
##    word = 'колба' # кол-ба
##    word = 'тайна' # тай-на
##    word = 'аудиенция' # а-у-ди-ен-ци-я
##    word = 'крестец' # кре-стец
##    word = 'поаймачнее' # ['по', 'ай', 'ма', 'чне', 'е']
##    word = 'денационализировавшаяся' # ['де', 'на', 'ци', 'о', 'на', 'ли', 'зи', 'ро', 'ва', 'вша', 'я', 'ся']
##    word = 'авиасъёмками' # ['а', 'ви', 'асъ', 'ём', 'ка', 'ми']
##    word = 'темно-оранжев' # ['те', 'мно', '-о', 'ран', 'жев']
##    word = 'быстротекуще' # ['бы', 'стро', 'те', 'ку', 'ще']
##    word = 'подкова' # ['по', 'дко', 'ва']
##    word = 'биохимия' # ['би', 'о', 'хи', 'ми', 'я']
##    word = 'быстроходная' # ['бы', 'стро', 'хо', 'дна', 'я']
##    word = 'бездна' # ['бе', 'здна']
##    word_syls = by_syllables(word)
##    print(word_syls)
##    sys.exit()
    print('Формирование словарей слогов для НС типа v - классификатор слов')
    # Список с максимальным числом слогов (по буквам)
    lst_max = [11, 10, 11, 11, 12, 10, 6, 9, 11, 11, 7, 11, 10, 10, 11, 11, 12, 12, 11, 10, 10, 10, 10, 9, 11, 9, 8, 11, 8, 9]
    n_words_all_2 = 0
    dict_syl = {}
    fn_syl = path + 'syl_w' + t # Имена файлов-приемников
    fn_syl_max = path + 'syl_w_max' + t
    rng = range(12)
    lst_dict_syl = [{} for k in rng]
    lst_n_words = [0 for k in rng]
    lst_fn = [path + 'syl_v_' + str(k + 1) + t for k in rng]
    dd_2 = '_2' + t
    lst_syl_max = []
    k_max = -1
    for let in list_abc:
        k_max += 1
        syl_max_0 = lst_max[k_max]
        print('Задано маскимальное число слогов:', syl_max_0)
        all_words2 = prep_all_words2(let, dd_2, app = True)
        n_words = len(all_words2)
        n_words_all_2 += n_words
        n_syl_max = syl_max = 0
        word_max = ''
        for word in all_words2:
            n_syl_d = find_n_vowels(word) - 1 # Число слогов - 1
            # В слове может быть дефис: агар-агар. Дефис оставляем
            word_syls = by_syllables(word)
            syl_cur = len(word_syls)
            if syl_cur > syl_max:
                word_max = word
                syl_max = syl_cur
            if syl_cur == syl_max_0:
                n_syl_max += 1
            n_syl = 0
            for syl in word_syls:
                n_syl += 1
                treat_dict_syl(syl, n_syl, dict_syl)
                treat_dict_syl(syl, n_syl, lst_dict_syl[n_syl_d])
            lst_n_words[n_syl_d] += 1
        if syl_max != syl_max_0:
            print('Ошибка: syl_max != syl_max_0')
            sys.exit()
        lst_syl_max.append(let + ' ' + str(syl_max) + ' ' + word_max
                         + ' ' + str(n_syl_max) + ' ' + str(n_words))
    add_to_txt_f(lst_syl_max, fn_syl_max)
    save_dict(n_words_all_2, dict_syl, fn_syl)
    for n_2 in rng:
        n_wrds_cur = lst_n_words[n_2]
        n_21 = n_2 + 1
        save_dict(n_wrds_cur, lst_dict_syl[n_2], lst_fn[n_2])
    # ClsType = 'v'
    # Число слов: 1401759
    # Размер словаря слогов: 15231
    # Гласных Слов     Слогов
    # 1 3'746      3'672
    # 2 67'975     10'274
    # 3 268'854      9'603
    # 4 409'288      7'393
    # 5 338'501      5'127
    # 6 187'694      3'399
    # 7 84'609      2'211
    # 8 30'990      1'345
    # 9 7'968      688
    # 10 1'661      306
    # 11     376      152
    # 12      97         54
elif step == 8: # Создание файла acc_syl.txt с числом записей 12
    dict_syl = {}
    for ns in range(1, 13):
        dict_syl.update({ns : [0 for m in range(ns + 1)]})
    fn_acc_syl = path + 'acc_syl' + t
    dd_3 = '_3' + t; b = ' '; c = ','
    n_words_all = 0
    for let in list_abc:
        print('БУКВА:', let)
        fn_3 = path + let + dd_3
        all_words3 = prep_words_syls(fn_3)
        for word in all_words3:
            n_syl = find_n_vowels(word) # Число слогов
            p = word.find(b)
            pos_a = word[p + 1:]
            lst_pos_a = pos_a.split(sep = c) # Список с номерами ударных слогов
            lst_pos_a = [int(pos_a) for pos_a in lst_pos_a]
            i_acc = lst_pos_a[0]
            if len(lst_pos_a) == 1 and i_acc > 0:
                n_words_all += 1
                lst_i = dict_syl.get(n_syl)
                lst_i[0] += 1
                lst_i[i_acc] += 1
    lst = []
    for itm in dict_syl.items():
        ns = itm[0]
        lst_dict = itm[1]
        acc_syl = str(ns) + b + str(lst_dict[0]) + ': '
        for k in range(1, ns + 1):
            acc_syl += ('' if lst_dict[k] == 0 else (str(k) + '-' + str(lst_dict[k]) + b))
        lst.append(acc_syl.rstrip())
    add_to_txt_f(lst, fn_acc_syl)
    print('Число слов с одним слогом под ударением:', n_words_all) # 1'352'732
# Создание acc-файлов и файлов words_0_i, word_1_i, words_2_i
elif step == 9:
    def treat_dict_cur(acc, dict_cur, pos_a = None):
        k = dict_cur.get(acc)
        k = 1 if k is None else (k + 1)
        dict_cur.update({acc : k})
    def save_dict_wrds(dict_cur, fn_cur):
        lst_wrd = list(dict_cur.items())
        lst_wrd = [itm[0] + ' ' + str(itm[1]) for itm in lst_wrd]
        add_to_txt_f(lst_wrd, fn_cur)
        return len(lst_wrd)
    ap = "'"
    # Словарь слов с одинаковым написанием, но с разными вариантами ударений.
    # Они не будут включены в words_1_i.txt и words_2_i.txt
    fn_dif = path + 'acc_dif' + t
    acc_dif = read_txt_f(fn_dif)
    dict_dif = treat_acc(ap, acc_dif)
    # Словарь слов с несколькими ударениями. Они не будут включены в words_1_i
    fn_two = path + 'acc_two' + t
    acc_two = read_txt_f(fn_two)
    dict_two = treat_acc(ap, acc_two)
    dict_acc = {}
    fn_acc = path + 'acc' + t
    n_words_all = 0
    rng = range(12)
    lst_dict_acc = [{} for k in rng]
    lst_dict_words1 = [{} for k in rng]
    lst_dict_words0 = [{} for k in rng]
    lst_dict_words2 = [{} for k in rng]
    lst_n_words = [0 for k in rng]
    lst_fn = [path + 'acc_' + str(k + 1) + t for k in rng]
    lst_fw0 = [make_faws('0', k + 1, 'words', path, t) for k in rng]
    lst_fw1 = [make_faws('1', k + 1, 'words', path, t) for k in rng]
    lst_fw2 = [make_faws('2', k + 1, 'words', path, t) for k in rng]
    dd_3 = '_3' + t
    b = ' '
    c = ','
    for let in list_abc:
        print('БУКВА:', let)
        fn_3 = path + let + dd_3
        all_words3 = prep_words_syls(fn_3)
        for word_0 in all_words3:
            p = word_0.find(b)
            pos_a = word_0[p + 1:]
            word = word_0[:p]
            n_syl_d = find_n_vowels(word) - 1 # Число слогов - 1
            lst_pos_a = pos_a.split(sep = c) # Список с номерами слогов под ударением
            lst_pos_a = [int(pos_a) - 1 for pos_a in lst_pos_a]
            if lst_pos_a[0] > -1:
                n_words_all += 1
                word_syls = by_syllables(word)
                one_acc = True # Флаг слова с одним непереходящим ударением
                if dict_dif.get(word) is not None:
                    treat_dict_cur(word, lst_dict_words0[n_syl_d])
                    one_acc = False
                # Слово с двумя непереходящими ударениями
                elif dict_two.get(word) is not None:
                    lst_dict_words2[n_syl_d].update({word : pos_a})
                    one_acc = False
                for pos_a in lst_pos_a:
                    acc_syl = word_syls[pos_a] # Слог под ударением
                    treat_dict_cur(acc_syl, dict_acc)
                    treat_dict_cur(acc_syl, lst_dict_acc[n_syl_d])
                    if one_acc: # Слова с одним неподвижным ударением
                        dict_cur = lst_dict_words1[n_syl_d]
                        acc_pos = dict_cur.get(word)
                        if acc_pos is None:
                            dict_cur.update({word : pos_a + 1})
                        lst_n_words[n_syl_d] += 1
    save_dict(n_words_all, dict_acc, fn_acc)
    for n_2 in rng:
        n_wrds_cur = lst_n_words[n_2]
        n_21 = n_2 + 1
        save_dict(n_wrds_cur, lst_dict_acc[n_2], lst_fn[n_2])
    n_wrds0 = n_wrds1 = n_wrds2 = 0
    for n_2 in rng:
        n_wrds0 += save_dict_wrds(lst_dict_words0[n_2], lst_fw0[n_2])
        n_wrds1 += save_dict_wrds(lst_dict_words1[n_2], lst_fw1[n_2])
        n_wrds2 += save_dict_wrds(lst_dict_words2[n_2], lst_fw2[n_2])
    dd = 'Всего слов:'
    print(dd, n_words_all, '(с ударениями)')
    dd = dd[:-1] + ' с'
    print(dd, 'одинаковым написанием, но с разными ударными слогами:', n_wrds0)
    print(dd, 'одним непереходящим ударением:', n_wrds1)
    print(dd, 'несколькими непереходящими ударениями:', n_wrds2)
    # Всего слов с ударениями: 1'398'887
    # Всего слов с одинаковым написанием, но с разными ударными слогами (words_0): 15'036
    # Всего слов с одним непереходящим ударением (words_1): 1'323'899
    # Всего слов с несколькими непереходящими ударениями (words_22): 45'651
    # Число слогов под ударением (всего): 12'485 (число записей в acc.txt)
    # Гласных Различных слогов под ударением
    # 1            3'589
    # 2            8'098
    # 3            7'414
    # 4            5'313
    # 5            3'394
    # 6            1'962
    # 7            1'181
    # 8             683
    # 9             258
    # 10             96
    # 11             37
    # 12                6
    # Гласных         words_1     words_0     words_2
    #    1             3515         74         0            
    #    2             61611         3004         88
    #    3            256333         4992        1306
    #    4            394888         4778        4519
    #    5            324692         1540     10571
    #    6            173701         517     12899
    #    7             74749         105        9638
    #    8             26094         26        4844
    #    9             6535            0        1433
    # 10             1385            0         276
    # 11             307            0         69
    # 12                89            0         8
# Создаем syl_ClsType_ns.txt - словари слогов words_ClsType_ns.txt
elif step == 10:
    # Создает словари слогов групп слов
    for ClsType in ['0', '1', '2']:
        print('Вид классификаторов:', ClsType)
        n_words_all_ClsType = 0
        for ns in range(1, 13):
            dict_syl_ClsType = {}
            fn_syl_ClsType = make_faws(ClsType, ns, 'syl', path, t) # Файл-приемник
            fn_ClsType = make_faws(ClsType, ns, 'words', path, t) # Файл-источник
            all_words_ClsType = prep_words_syls(fn_ClsType, app = False, split = True)
            n_words_ClsType = len(all_words_ClsType)
            n_words_all_ClsType += n_words_ClsType
            for word in all_words_ClsType:
                word_syls = by_syllables(word)
                for syl in word_syls:
                    n = dict_syl_ClsType.get(syl)
                    if n is None: n = 0
                    dict_syl_ClsType.update({syl : n + 1})
            save_dict(n_words_ClsType, dict_syl_ClsType, fn_syl_ClsType)
        print('Всего слов в группах для НС типа {}: {}'.format(ClsType, n_words_all_ClsType))
    # Всего слов с одним непереходящим ударением: 1'320'384
    # Гласных Слов     Размер словаря слогов
    # 2 61611     10110
    # 3 256333      9433
    # 4 394888      7117
    # 5 324692      4826
    # 6 173701      3108
    # 7 74749      1948
    # 8 26094      1138
    # 9 6535      546
    # 10 1385      232
    # 11     307      120
    # 12      89      46
    # Всего слов с двумя непереходящими ударениями: 45'651
    # Гласных Слов     Размер словаря слогов
    # 2      88      145
    # 3 1306      989
    # 4 4519      1666
    # 5 10571      1834
    # 6 12899      1550
    # 7 9638      1174
    # 8 4844      775
    # 9 1433      419
    # 10     276      201
    # 11      69      83
    # 12      8      19
# Создание набора данных, а затем обучающих и оценочных множеств
elif step == 11: # Не используем
    from sklearn.model_selection import train_test_split
    dd = 'Формируем данные по'
    if by_syls:
        n_max_max = 7
        # n_syl/n_max: 2/9; 3/8
        # n_syl/n_max_max: 2/6; 3/7
        print(dd, 'слогам')
    else:
        print(dd, 'словам')
    ClsType = '1' # Только '1'
    val_size = 0.2
    rng = range(3, 4) # (2, 9)
    for n_syl in rng:
        s_n_syl = str(n_syl)
        fp = make_fp(ClsType, n_syl, path, t)
        fw = make_faws(ClsType, n_syl, 'words', path, t)
        tx = make_tvxy(ClsType, n_syl, 'trn_x', path, bn)
        ty = make_tvxy(ClsType, n_syl, 'trn_y', path, bn)
        vx = make_tvxy(ClsType, n_syl, 'val_x', path, bn)
        vy = make_tvxy(ClsType, n_syl, 'val_y', path, bn)
        all_words = prep_words_syls(fw, app = False)
        if by_syls:
            fs = make_faws(ClsType, n_syl, 'syl', path, t)
            all_syls = prep_words_syls(fs, app = False)
            n_max = 0
            syl_max = ''
            for syl in all_syls:
                syl = syl.split()
                syl = syl[0]
                l_s = len(syl)
                if l_s > n_max:
                    n_max = l_s
                    syl_max = syl
            n_max = min(n_max_max, n_max)
        else:
            nw_max = 0
            for word in all_words:
                word = word.split()
                acc = int(word[1]) - 1 # Номер ударного слога - 1
                word = word[0]
                nw_max = max(len(word), nw_max)
            nw_max += 3
            print('Размер вектора, представляющего слово:', nw_max)
        r = np.random.randint(len(all_words)) # Для вывода примера
        x_data = []
        y_data = []
        i = -1
        for word in all_words:
            word = word.split()
            acc = int(word[1]) - 1
            word = word[0]
            lst_syls = by_syllables(word)
            if by_syls:
                skip = False
                for syl in lst_syls:
                    if len(syl) > n_max:
                        skip = True
                        break
                if skip: continue
            lst_lets = []
            n = 0 # Номер слога
            for syl in lst_syls:
                lst_let_syl = [] # Список номеров букв, входящих в слог
                for let in syl:
                    ind = lst_abc.index(let) + 1
                    lst_let_syl.append(ind)
                if by_syls:
                    len_lst = len(lst_let_syl)
                    if len_lst < n_max:
                        lst_let_syl.extend([0 for k in range(n_max - len_lst)])
                    lst_lets.append(lst_let_syl)
                else:
                    n += 1
                    lst_let_syl.append(33 + n) # Цифра после очередного слога
                    lst_let_syl.append(0) # Ставим нуль между слогами
                    lst_lets.extend(lst_let_syl)
            if not by_syls:
                len_lst = len(lst_lets)
                if len_lst < nw_max:
                    lst_lets.extend([0 for k in range(nw_max - len_lst)])
                elif len_lst > nw_max:
                    lst_lets.pop(len_lst - 1)
            i += 1
            if i == r: word_r = word
            x_data.append(lst_lets)
            y_data.append(acc)
        print('Пример экземляра данных под номером', r, '(word, x_data, y_data)')
        print('', word_r, '\n', x_data[r], '\n', y_data[r])
        print('Разделяем исходные данные на обучающие и оценочные (' + str(val_size) + ')')
        x_trn, x_val, y_trn, y_val = train_test_split(x_data, y_data, test_size = val_size)
        add_to_bin_f(x_trn, tx, dtype = dtp)
        add_to_bin_f(y_trn, ty, dtype = dtp)
        add_to_bin_f(x_val, vx, dtype = dtp)
        add_to_bin_f(y_val, vy, dtype = dtp)
        lst_params = [str(by_syls), str(n_max if by_syls else nw_max)]
        add_to_txt_f(lst_params, fp)
elif step == 12: # Обучение по данным с шага step == 11. Не используем.
    import time
    from keras.models import Model
    from keras.layers import Input, Dense, Dropout
    import keras.utils as k_ut
    def make_branch(n_syl, model_num, sh, activation, with_output):
        inp = Input(shape = sh, dtype = np.float32)
        if model_num == 2:
            x = Dropout(0.5)(inp)
            x = Conv1D(filters = 16, kernel_size = 3, padding = 'same', activation = 'relu')(x)
            x = Flatten()(x)
        elif model_num == 3:
            x = LSTM(32, dropout = 0.3, recurrent_dropout = 0.3)(inp)
        elif model_num == 1:
            x = Dropout(0.3)(inp)
        x = Dense(16, activation = 'relu')(x)
        if with_output:
            output = Dense(n_syl, activation = activation)(x)
        return inp, (output if with_output else x)
    #
    ClsType = '1' # Только '1'
    n_syl = 3 # Число классов равно числу слогов в слове
    model_num = 3 # 1 - Dense; 2 - Conv1D; 3 - LSTM
    epochs = 60
    s_n_syl = str(n_syl)
    fp = make_fp(ClsType, n_syl, path, t)
    lst_params = read_txt_f(fp)
    lst_params = [param.replace('\n', '') for param in lst_params]
    by_syls = lst_params[0] == 'True'
    n_max = int(lst_params[1])
    dd = 'Читаем данные, сформированные по'
    if by_syls:
        branches = False # True False
        with_output = True # True False
        if branches:
            from keras.layers import average # concatenate
            lst_x_trn = []
            lst_x_val = []
            lst_inp = []
            lst_out = []
        else:
            with_output = True # Всегда
        print(dd, 'слогам')
        n_parts = n_syl
    else:
        print(dd, 'словам')
        n_parts = 1
        branches = False # Всегда
        with_output = True # Всегда
    #
    tx = make_tvxy(ClsType, n_syl, 'trn_x', path, bn)
    ty = make_tvxy(ClsType, n_syl, 'trn_y', path, bn)
    vx = make_tvxy(ClsType, n_syl, 'val_x', path, bn)
    vy = make_tvxy(ClsType, n_syl, 'val_y', path, bn)
    x_trn, y_trn, x_val, y_val = read_trn_val(tx, ty, vx, vy, dtp)
    y_val_0 = y_val
    len_trn = len(y_trn)
    len_val = len(y_val)
    y_trn = k_ut.to_categorical(y_trn, n_syl)
    y_val = k_ut.to_categorical(y_val, n_syl)
    if model_num > 1:
        if model_num == 2:
            from keras.layers import Flatten, Conv1D
        else:
            from keras.layers import LSTM
        x_trn = x_trn.reshape(len_trn, n_parts, n_max)
        x_val = x_val.reshape(len_val, n_parts, n_max)
        if branches:
            for k in range(n_syl):
                x = x_trn[:, k, :]
                x = x.reshape(len_trn, 1, n_max)
                lst_x_trn.append(x)
                x = x_val[:, k, :]
                x = x.reshape(len_val, 1, n_max)
                lst_x_val.append(x)
            sh = (1, n_max, )
        else:
            sh = (n_parts, n_max, )
    else:
        wrd_len = n_parts * n_max
        x_trn = x_trn.reshape(len_trn, wrd_len)
        x_val = x_val.reshape(len_val, wrd_len)
        if branches:
            ns = 0
            for k in range(n_syl):
                x = x_trn[:, ns:(ns + n_max)]
                lst_x_trn.append(x)
                x = x_val[:, ns:(ns + n_max)]
                lst_x_val.append(x)
                ns += n_max
            sh = (n_max, )
        else:
            sh = (wrd_len, )
    print(x_trn[215])
    print(y_trn[215])
    print(x_val[215])
    print(y_val[215])
    #
    batch = 128
    optimizer = 'RMSprop' # RMSprop adam
    loss = 'mse' # mae mse binary_crossentropy categorical_crossentropy poisson
    activation = 'softmax' # sigmoid softmax
    print(' Оптимизатор:', optimizer, '\n', 'Функция потерь:', loss)
    print(' Функция активации классифицирующего слоя:', activation)
    metrics = ['accuracy']
    # if loss == 'binary_crossentropy': metrics.append('categorical_accuracy')
    #
    inp, output = make_branch(n_syl, model_num, sh, activation, with_output)
    if branches:
        for k in range(n_syl):
            print(lst_x_trn[k][215])
            print(lst_x_val[k][215])
        lst_inp.append(inp)
        lst_out.append(output)
        for k in range(n_syl - 1):
            inp, output = make_branch(n_syl, model_num, sh, activation, with_output)
            lst_inp.append(inp)
            lst_out.append(output)
            output = average(lst_out) # concatenate([lst_out) / dot(lst_out, axes = -1) / add(lst_out)
        if not with_output:
            output = Dense(n_syl, activation = activation)(output)
    model = Model(lst_inp if branches else inp, output)
    model.summary()
    model.compile(optimizer = optimizer, loss = loss, metrics = metrics)
    start_time = time.time() # Время начала обучения
    if branches:
        x_trn = lst_x_trn
        x_val = lst_x_val
    model.fit(x_trn, y_trn, batch_size = batch, epochs = epochs,
             verbose = 2, validation_data = (x_val, y_val))
    print('Время обучения:', time.time() - start_time)
    y_pred = model.predict(x_val)
    if not branches:
        print('Два случайно выбранных прогноза:')
        r = np.random.randint(len_val); print(r, x_val[r], y_pred[r], y_val_0[r])
        r = np.random.randint(len_val); print(r, x_val[r], y_pred[r], y_val_0[r])
    classes = [np.argmax(m) for m in y_pred]
    nClassified = np.sum(classes == y_val_0)
    # Число ошиибочно классифицированных изображений
    n_mis = len_val - nClassified
    acc = 100.0 * nClassified / len_val
    print('Ошибочно классифицированно', n_mis, 'слов из', len_val)
    print('Точность прогнозирования:', str(round(acc, 2)) + '%')
elif step == 13:
    ClsType = '1'
    print('Определение пересечения каждой пары множеств слогов слов класса', ClsType)
    rng = range(2, 9) # (2, 9)
    for n_syl in rng:
        rng_syl = range(n_syl)
        s_n_syl = str(n_syl)
        fw = make_faws(ClsType, n_syl, 'words', path, t)
        all_words = prep_words_syls(fw, app = False)
        # Список множеств слогов: множество первых слогов, вторых и так далее
        lst_sets = [set() for k in rng_syl]
        for word in all_words:
            word = word.split()
            acc = int(word[1]) - 1
            word = word[0]
            lst_syls = by_syllables(word) # Список слогов слова
            for k in rng_syl:
                syl_k = lst_syls[k]
                lst_sets[k].add(syl_k)
        print('Число слогов:', n_syl)
        print('Всего слов:', len(all_words))
        print('Номер слога / Число уникальных слогов:')
        s = ''
        for k in rng_syl:
            s += (', ' + str(k + 1) + '/' + str(len(lst_sets[k])))
        print(s[2:])
        print('Пара множеств слогов / Число слогов в пересечении этой пары:')
        s = ''
        for k in range(n_syl - 1):
            set_k = lst_sets[k]
            for m in range(k + 1, n_syl):
                set_int = set_k.intersection(lst_sets[m])
                n_cr = len(set_int)
                if n_cr > 0:
                    s += ('; ' + '(' + str(k) + ',' + str(m) + ')' + '/' + str(n_cr))
        if s == '':
            print('Число слогов в пересечении всех пар словарей слогов: 0')
        else:
            print(s[2:])
        # Число слогов: 2
        # Всего слов: 61611
        # Номер слога / Число уникальных слогов:
        # 1/2590, 2/8864
        # Пара множеств слогов / Число слогов в пересечении этой пары:
        # (0,1)/1344
        # Читаем файл G:/AM/НС/poems/accents/words_1_3.txt
        # Число слогов: 3
        # Всего слов: 256333
        # Номер слога / Число уникальных слогов:
        # 1/2006, 2/4911, 3/6033
        # Пара множеств слогов / Число слогов в пересечении этой пары:
        # (0,1)/1277; (0,2)/882; (1,2)/2131
        # Читаем файл G:/AM/НС/poems/accents/words_1_4.txt
        # Число слогов: 4
        # Всего слов: 394888
        # Номер слога / Число уникальных слогов:
        # 1/1633, 2/3546, 3/3283, 4/3308
        # Пара множеств слогов / Число слогов в пересечении этой пары:
        # (0,1)/1181; (0,2)/924; (0,3)/563; (1,2)/1900; (1,3)/934; (2,3)/1388
        # Читаем файл G:/AM/НС/poems/accents/words_1_5.txt
        # Число слогов: 5
        # Всего слов: 324692
        # Номер слога / Число уникальных слогов:
        # 1/1143, 2/2566, 3/2127, 4/1744, 5/1687
        # Пара множеств слогов / Число слогов в пересечении этой пары:
        # (0,1)/870; (0,2)/705; (0,3)/516; (0,4)/300; (1,2)/1579; (1,3)/935; (1,4)/474; (2,3)/1083; (2,4)/486; (3,4)/666
        # Читаем файл G:/AM/НС/poems/accents/words_1_6.txt
        # Число слогов: 6
        # Всего слов: 173701
        # Номер слога / Число уникальных слогов:
        # 1/782, 2/1673, 3/1450, 4/1072, 5/892, 6/801
        # Пара множеств слогов / Число слогов в пересечении этой пары:
        # (0,1)/578; (0,2)/502; (0,3)/416; (0,4)/270; (0,5)/156; (1,2)/1049; (1,3)/706; (1,4)/450; (1,5)/241; (2,3)/833; (2,4)/492; (2,5)/249; (3,4)/532; (3,5)/236; (4,5)/325
        # Читаем файл G:/AM/НС/poems/accents/words_1_7.txt
        # Число слогов: 7
        # Всего слов: 74749
        # Номер слога / Число уникальных слогов:
        # 1/470, 2/1114, 3/841, 4/667, 5/517, 6/401, 7/350
        # Пара множеств слогов / Число слогов в пересечении этой пары:
        # (0,1)/342; (0,2)/289; (0,3)/253; (0,4)/198; (0,5)/132; (0,6)/82; (1,2)/613; (1,3)/462; (1,4)/327; (1,5)/208; (1,6)/107; (2,3)/505; (2,4)/344; (2,5)/216; (2,6)/112; (3,4)/383; (3,5)/218; (3,6)/111; (4,5)/236; (4,6)/103; (5,6)/126
        # Читаем файл G:/AM/НС/poems/accents/words_1_8.txt
        # Число слогов: 8
        # Всего слов: 26094
        # Номер слога / Число уникальных слогов:
        # 1/218, 2/675, 3/453, 4/383, 5/278, 6/201, 7/160, 8/176
        # Пара множеств слогов / Число слогов в пересечении этой пары:
        # (0,1)/165; (0,2)/143; (0,3)/125; (0,4)/108; (0,5)/78; (0,6)/55; (0,7)/36; (1,2)/321; (1,3)/253; (1,4)/186; (1,5)/134; (1,6)/84; (1,7)/50; (2,3)/272; (2,4)/184; (2,5)/133; (2,6)/71; (2,7)/47; (3,4)/207; (3,5)/135; (3,6)/82; (3,7)/51; (4,5)/138; (4,6)/76; (4,7)/46; (5,6)/84; (5,7)/50; (6,7)/65
# Создание обучающих и оценочных множеств для заданного классификатора
# Слог кодируется одной цифрой.
# ClsType = 'v' - классификатор слов (классы С0-С2)
# ClsType = '1' - определитель одного непереходящего ударения
# ClsType = '2' - определитель двух непереходящх ударений
elif step == 14:
    ClsType = '2' # '1', '2', 'v'
    from sklearn.model_selection import train_test_split
    print('Тип классификатора:', ClsType)
    print('Формируем данные, кодируя слог номером в syl_ClsType-файле')
    dtp = np.int16
    val_sz = 0.1 if ClsType == '2' else 0.05
    rng = range(2, 13) # (2, 13)
    for n_syl in rng:
        s_n_syl = str(n_syl)
        s_t = '_' + s_n_syl + t
        if ClsType == '2':
            # Словарь классов для определителей ударений в словах С2
            dict_classes = C2_classes(n_syl, path, t)
        else:
            dict_classes = None
        # Выход (приемники данных)
        fp = make_fp(ClsType, n_syl, path, t)
        tx = make_tvxy(ClsType, n_syl, 'trn_x', path, bn)
        ty = make_tvxy(ClsType, n_syl, 'trn_y', path, bn)
        vx = make_tvxy(ClsType, n_syl, 'val_x', path, bn)
        vy = make_tvxy(ClsType, n_syl, 'val_y', path, bn)
        dict_syls, N = make_dict_syls(ClsType, path, s_n_syl)
        lst_params = [str(N)] # Число уникальных слогов
        print('Формируем исходные данные')
        if ClsType == 'v':
            x0_t, x0_v, y0_t, y0_v = words_to_data(n_syl, dict_syls, path, '0', s_t, val_sz, ClsType)
        if ClsType in ['v', '1']:
            x1_t, x1_v, y1_t, y1_v = words_to_data(n_syl, dict_syls, path, '1', s_t, val_sz, ClsType)
        if ClsType in ['v', '2']:
            x2_t, x2_v, y2_t, y2_v = words_to_data(n_syl, dict_syls, path, '2', s_t, val_sz, ClsType, dict_classes)
        if ClsType == 'v':
            if len(x0_t) > 0 and len(x2_t) > 0:
                x_t = np.concatenate((x1_t, x0_t, x2_t))
                y_t = np.concatenate((y1_t, y0_t, y2_t))
                x_v = np.concatenate((x1_v, x0_v, x2_v))
                y_v = np.concatenate((y1_v, y0_v, y2_v))
            elif len(x0_t) > 0:
                x_t = np.concatenate((x1_t, x0_t))
                y_t = np.concatenate((y1_t, y0_t))
                x_v = np.concatenate((x1_v, x0_v))
                y_v = np.concatenate((y1_v, y0_v))
            else:
                x_t = np.concatenate((x1_t, x2_t))
                y_t = np.concatenate((y1_t, y2_t))
                x_v = np.concatenate((x1_v, x2_v))
                y_v = np.concatenate((y1_v, y2_v))
        elif ClsType == '1':
            x_t = x1_t; y_t = y1_t; x_v = x1_v; y_v = y1_v
        elif ClsType == '2':
            x_t = x2_t; y_t = y2_t; x_v = x2_v; y_v = y2_v
        add_to_bin_f(x_t, tx, dtype = dtp)
        add_to_bin_f(y_t, ty, dtype = dtp)
        add_to_bin_f(x_v, vx, dtype = dtp)
        add_to_bin_f(y_v, vy, dtype = dtp)
        add_to_txt_f(lst_params, fp)
# Обучение НС. Данные, созданы на шаге step == 14. Слог кодируется одной цифрой
elif step == 15:
    import time
    if colab:
        from tensorflow.keras.models import Model
        from tensorflow.keras.layers import Input, Dense, Dropout
        from tensorflow.keras.layers import Flatten, Conv1D
        from tensorflow.keras.layers import LSTM, GRU
        from tensorflow.keras.layers import Embedding
        from tensorflow.keras import initializers
        import tensorflow.keras.utils as k_ut
        import tensorflow.keras.callbacks as cb
        from google.colab import drive
        drv = '/content/drive/'
        drive.mount(drv)
        path = drv + 'My Drive/accents/'
    else:
        from keras.models import Model
        from keras.layers import Input, Dense, Dropout
        from keras.layers import Flatten, Conv1D
        from keras.layers import LSTM, GRU
        from keras.layers import Embedding
        from keras import initializers
        import keras.utils as k_ut
        import keras.callbacks as cb
    class EarlyStoppingByValAcc(cb.Callback):
        def on_train_begin(self, logs = {}):
            self.acc = []
        def on_epoch_end(self, epoch, logs = {}):
            name = 'accuracy' if colab else 'acc'
            acc = logs.get(name)
            if full_set:
                if acc is None:
                    print('EarlyStoppingByValAcc: плохое значение acc')
                elif acc >= acc_stop:
                    print('Эпоха {}: достигнуто заданное значение acc'.format(epoch))
                    self.model.stop_training = True
            else:
                val_acc = logs.get('val_' + name)
                if acc is None or val_acc is None:
                    print('EarlyStoppingByValAcc: плохое значение acc или val_acc')
                elif acc >= acc_stop and val_acc >= val_acc_stop:
                    print('Эпоха %05d: достигнуты заданные значения acc и val_acc' % epoch)
                    self.model.stop_training = True
    def convert_to_bin(x_t):
        lst_x_t = []
        for syl in x_t:
            n_b = np.binary_repr(syl)
            n_b = n_b.zfill(n_bin)
            n_b = n_b.replace('0', '0 ')
            n_b = n_b.replace('1', '1 ')
            n_b = n_b.rstrip().split()
            lst_x_t.extend(n_b)
        lst_x_t = [int(b) for b in lst_x_t]
        return np.array(lst_x_t)
    ClsType = '2' # Тип классификатора - v, 1 или 2
    print('Тип классификатора:', ClsType)
    n_syl = 4
    print('Число слогов в слове:', n_syl)
    use_emb = True # True False
    # Если False, то число переводится в one-hot представление
    use_bin = False # True False. use_bin = True - плохо
    full_set = False # True False - если True, то обучение без валидации на всех данных
    dtp = np.int16
    optimizer = 'adam' # RMSprop adam
    activation = 'softmax' # sigmoid softmax - на последнем слое
    epochs = 90
    # mae mse binary_crossentropy categorical_crossentropy poisson
    # sparse_categorical_crossentropy cosine_proximity
    loss = 'mse'
    size = 128
    batch = 128
    acc_stop = 1.0
    val_acc_stop = 1.0
    model_num = 3 # 1 - Dense; 2 - Conv1D; 3 - GRU (LSTM)
    if ClsType == 'v':
        if n_syl > 6:
            model_num = 1
            loss = 'poisson'
            if n_syl == 7:
                size = 64 # Размер выхода слоя Embedding (128)
            else:
                size = 32
            batch = 32
    elif ClsType == '1':
        if not full_set:
            print('Остановка по условию val_acс >= {}'.format(val_acc_stop))
        loss = 'mse'
        if n_syl > 7:
            model_num = 1
            loss = 'poisson'
            size = 32
            batch = 32
    elif ClsType == '2':
        model_num = 1
        loss = 'poisson'
        size = 32
        batch = 64
        if full_set:
            if n_syl > 6: size = 64
            if n_syl == 11:
                loss = 'mse'
                batch = 32
        else:
            batch = 32
            size = 64
            if n_syl not in [10, 11]:
                size = 32
                batch = 64
            if n_syl == 11: loss = 'mse'
    print('Остановка по условию acc >= {}'.format(acc_stop))
    callbacks = [EarlyStoppingByValAcc()]
    print('Оптимизатор: {} \nФункция потерь: {}'.format(optimizer, loss))
    print('Функция активации классифицирующего слоя:', activation)
    metrics = ['accuracy']
    if loss == 'binary_crossentropy': metrics.append('categorical_accuracy')
    #
    x_t, y_t, y_t_0, x_v, y_v, y_v_0, n_max, sh, n_classes = \
         load_prep_data(ClsType, n_syl, t, bn, path, model_num, loss, dtp, vowels)
    print('Всего слов:', len(y_t_0) + len(y_v_0))
    if full_set:
        print('Обучение на всех данных')
        x_t = np.concatenate((x_t, x_v))
        y_t = np.concatenate((y_t, y_v))
        y_t_0 = np.concatenate((y_t_0, y_v_0))
        val_data = ()
    else:
        print('Обучение с оценкой')
        val_data = (x_v, y_v)
    #
    model = make_model(n_classes, model_num, sh, activation, use_emb, n_max, n_syl, size)
    model.compile(optimizer = optimizer, loss = loss, metrics = metrics)
    start_time = time.time() # Время начала обучения
    model.fit(x_t, y_t, batch_size = batch, epochs = epochs,
             verbose = 2, validation_data = val_data, callbacks = callbacks)
    print('Время обучения:', time.time() - start_time)
##    score = model.evaluate(x_v, y_v, verbose = 0)
##    print(score)
    acc, _, _ = make_pred(x_t, y_t_0, 'Обучающее множество')
    a = int(acc * 100)
    if full_set:
        v_a = 0
    else:
        acc, _, _ = make_pred(x_v, y_v_0, 'Проверочное множество')
        v_a = int(acc * 100)
    fn_m = path + 'model_{}_{}_{}_{}_{}_{}.{}'.format(ClsType, n_syl, model_num, size, a, v_a, 'h5')
    model.save(fn_m)
##    model.save_weights(fn_m)
##    print('Веса модели сохранены в файл', fn_m)
    print('Модель записана в файл', fn_m)
elif step == 16: # Вывод примеров ошибочной простановки ударения
    def show_words(ClsType, lst_syls, x, y_0, txt, vowels, n_to_prn, dict_classes):
        _, classes, n_missed = make_pred(x, y_0, txt)
        app = "'"
        if n_missed > 0:
            print('\nСлова с ошибочно проставленным ударением (не более {})'.format(n_to_prn))
            print('Прогноз / Истина')
            n = 0
            for (c, c_0, i_syls) in zip(classes, y_0, x):
                if c != c_0:
                    n = make_print_wrds(ClsType, lst_syls, i_syls, app, vowels, n, c, c_0, dict_classes)
                if n == n_to_prn: break
        print('\nСлова с правильно проставленным ударением (не более {})'.format(n_to_prn))
        n = 0
        for (c, c_0, i_syls) in zip(classes, y_0, x):
            if c == c_0:
                n = make_print_wrds(ClsType, lst_syls, i_syls, app, vowels, n, c_0, c_0, dict_classes)
            if n == n_to_prn: break
        return n_missed
    if colab:
        import tensorflow.keras as keras
        import tensorflow.keras.utils as k_ut
        from google.colab import drive
        drv = '/content/drive/'
        drive.mount(drv)
        path = drv + 'My Drive/accents/'
    else:
        import keras
        import keras.utils as k_ut
    ClsType = '2'
    n_syl = 4
    if ClsType == '2':
        # Словарь классов для определителей ударений в словах С2
        dict_classes = C2_classes(n_syl, path, t)
    else:
        dict_classes = {}
    model_num = 1
    size = 32
    a = 10000
    v_a = 9513
    dtp = np.int16
    use_emb = True
    loss = 'mse'
    fn_m = path + 'model_{}_{}_{}_{}_{}_{}.{}'.format(ClsType, n_syl, model_num, size, a, v_a, 'h5')
    x_t, y_t, y_t_0, x_v, y_v, y_v_0, n_max, sh, n_classes = \
         load_prep_data(ClsType, n_syl, t, bn, path, model_num, loss, dtp, vowels)
    if v_a == 0:
        x_t = np.concatenate((x_t, x_v))
        y_t = np.concatenate((y_t, y_v))
        y_t_0 = np.concatenate((y_t_0, y_v_0))
        n_words = len(y_t_0)
    else:
        n_words = len(y_t_0) + len(y_v_0)
    print('Загрузка модели из файла {}. Число слогов в слове: {}.'.format(fn_m, n_syl))
    model = keras.models.load_model(fn_m)
##    model.summary(); sys.exit()
    dict_syls, _ = make_dict_syls(ClsType, path, str(n_syl))
    lst_syls = list(dict_syls)
    n_to_prn = 3 # Максимальное число выводимых слов
    if v_a == 0:
        n_mis = 0
    else:
        ttl = 'Проверочное множество'
        n_mis = show_words(ClsType, lst_syls, x_v, y_v_0, ttl, vowels, n_to_prn, dict_classes)
    ttl = 'Обучающее множество'
    n_mis += show_words(ClsType, lst_syls, x_t, y_t_0, ttl, vowels, n_to_prn, dict_classes)
    print('Всего слов: {}. Всего ошибок: {}.'.format(n_words, n_mis))
# Создание файла acc_syl_two.txt с числом записей 11.
# Содержит частоту употребления пар ударных слогов в каждой группе слов
# с двумя непереходящими ударениями (класс С2)
elif step == 17:
    fn_acc_syl_two = path + 'acc_syl_two' + t
    b = ' '
    n_words_all = 0
    lst = []
    rng = range(2, 13) # (2, 13)
    for n_syl in rng:
        fw = make_faws('2', n_syl, 'words', path, t)
        all_words = prep_words_syls(fw, app = False)
        dict_classes = {}
        for word in all_words:
            n_words_all += 1
            p = word.find(b)
            pos_a = word[p + 1:]
            k = dict_classes.get(pos_a)
            k = 1 if k is None else (k + 1)
            dict_classes.update({pos_a : k})
        lst_acc = list(dict_classes.items()) # Список частот пар ударных слогов
        lst_acc.sort(key = lambda i: i[0])
        acc_syl_2 = '{} {}: {} - '.format(n_syl, len(all_words), len(lst_acc))
        for itm in lst_acc:
            acc_syl_2 += (itm[0] + '-' + str(itm[1]) + b)
        lst.append(acc_syl_2.rstrip())
    add_to_txt_f(lst, fn_acc_syl_two)
    print('Число слов с двумя непереходящими ударениями:', n_words_all) # 45'651
    # Пример строки файла (число слогов, слов, классов частоты классов):
    # 4 4519: 6 - 1,2-1047 1,3-2760 1,4-439 2,3-190 2,4-72 3,4-11
# Формируется файл с3.txt (класс С3)
elif step == 18:
    ap = "'"
    acc_dif = read_txt_f(path + 'acc_dif' + t)
    # Словарь слов с одинаковым написанием, но с разными вариантами ударений
    dict_dif = treat_acc(ap, acc_dif)
    lst_fs = ['_предлоги', '_союзы', '_частицы']
    lst_ClsType = ['0', '1', '2']
    dict_s = {}
    n_v_max = 0
    for fs in lst_fs:
        lst_s = read_txt_f(path + fs + t)
        for w in lst_s:
            w = w.split()
            for c in w:
                n_v = find_n_vowels(c)
                if n_v > 1 and dict_dif.get(c) is None:
                    dict_s.update({c.replace(',', '') : n_v})
                    n_v_max = max(n_v_max, n_v)
    dict_w = {}
    for ns in range(2, n_v_max + 1):
        for ClsType in lst_ClsType:
            lst_wrds = read_txt_f(make_faws(ClsType, ns, 'words', path, t))
            for w in lst_wrds:
                w = w.split()
                dict_w.update({w[0] : 1}) # 1'255'439 слов
    c3 = []
    for c in dict_s.keys():
        if dict_w.get(c) is None: c3.append(c)
    add_to_txt_f(c3, path + 'c3' + t) # 286 строки
# Вычисление размеров групп слов классов V, С0-С2 словарей слогов этих групп
# Вычисление числа слов в исходных файлах
elif step == 19:
    def calc_size(lst_gr_nm, nm, t, ttl, ttl2 = ''):
        lst_02 = [[], [], []]
        for n_sb, sb in zip(range(3), lst_gr_nm):
            lst_sb = lst_02[n_sb]
            for ns in range(1, 13):
                fw = path + nm + '_' + sb + '_' + str(ns) + t
                all_words = read_txt_f(fw)
                lst_sb.append(len(all_words))
        for n_sb, sb in zip(range(3), lst_gr_nm):
            lst_sb = lst_02[n_sb]
            print(ttl.format(sb))
            for k in lst_sb:
                print(k)
        if nm == 'words':
            print(ttl2)
            for ns in range(12):
                k = 0
                for n_sb in range(3):
                    k += lst_02[n_sb][ns]
                print(k)
##    calc_size(['0', '1', '2'], 'words', t, 'Число слов в группах класса С{}:',
##             'Число слов в группах V = С0 + С1 + С2:')
##    calc_size(['v', '1', '2'], 'syl', t, 'Размеры словарей слогов групп класса С{}:')
    # Формирует список с числом строк в let-файлах (?_2_0.txt, ?_2.txt, ?_3.txt)
    def make_lst_let(dd):
        n_all = 0
        lst_n = []
        for let in list_abc:
            let_dd = let + dd + t
            fn = path + let_dd
            all_words = read_txt_f(fn)
            n = len(all_words)
            n_all += n
            lst_n.append(let_dd + ' - ' + str(n))
        for n in lst_n:
            print(n)
        print('Всего:', n_all)
##    make_lst_let('')
##    make_lst_let('_2_0')
##    make_lst_let('_2')
##    make_lst_let('_3')
    # Формирует список с числом строк в файлах заданного типа, например, syl-файлах
    def make_lst_fn(ClsType, nm):
        n_all = 0
        lst_n = []
        for ns in range(1, 13):
            fn_dd = nm + '_' + ClsType + '_' + str(ns) + t
            fn = path + fn_dd
            all_lines = read_txt_f(fn)
            n = len(all_lines)
            n_all += n
            lst_n.append(fn_dd + ' - ' + str(n))
        for n in lst_n:
            print(n)
        print('Всего:', n_all)
##    make_lst_fn('0', 'syl')
##    make_lst_fn('1', 'syl')
##    make_lst_fn('2', 'syl')
##    make_lst_fn('v', 'syl')
    # Наиболее частотные слоги множества С0+С1+С2 и групп
    def most_freq():
        lst_n = []
        for ns in range(13):
            if ns == 0:
                fn_dd = 'C0+C1+C2: '
                fn = path + 'acc' + t
            else:
                fn_dd = 'G' + str(ns) + ': '
                fn = path + 'acc_' + str(ns) + t
            fn = open(fn, encoding = 'utf-8')
            line = fn.readline()
            fn.close()
            lst_n.append(fn_dd + line.replace('\n', ''))
        for n in lst_n: print(n)
##    most_freq()
##    make_lst_fn('0', 'words')
##    make_lst_fn('1', 'words')
##    make_lst_fn('2', 'words')
    # Печатает коды слогов слова
    def find_syls_codes(ClsType, word):
        lst_syls = by_syllables(word)
        dict_syls, _ = make_dict_syls(ClsType, path, str(len(lst_syls)))
        lst_wrd = []
        for syl in lst_syls:
            n = dict_syls.get(syl)
            if n is None:
                print('Не найден слог:', syl)
                n = 0
            lst_wrd.append(n)
        print(word)
        print(lst_wrd)
    word = 'быстроходная' # ['бы', 'стро', 'хо', 'дна', 'я']
##    find_syls_codes('1', word)
    def find_word_2_label(word, acc_pos):
        n_syl = find_n_vowels(word)
        dict_classes = C2_classes(n_syl, path, t)
        print(dict_classes)
        lb = dict_classes.get(acc_pos)
        print(lb)
    find_word_2_label('авиабаза', '1,4') # авиабаза 1,4
# Анализ частотного словаря
elif step == 20:
    import matplotlib.pyplot as plt
    def new_itm(itm):
        n_syl = find_n_vowels(itm[0])
        itm = list(itm)
        itm.append(n_syl)
        return itm
    def one_chart(lst, rng, dd):
        yMin = 0
        yMax = 1.05 * max(lst)
        plt.figure(figsize = (6, 2.4))
        ind = np.arange(len(rng))
        width = 0.5 # Ширина столбца диаграммы
        plt.ylim(yMin, yMax)
        plt.bar(ind, lst, width)
        plt.xlabel('Число слогов')
        plt.ylabel(dd + ' лемм')
        plt.title(dd + ' лемм с разным числом слогов')
        plt.xticks(ind, rng)
        plt.show()
    def one_lst(knd, rng, lst_freq, n_syl_min, n_syl_max):
        lst = [0 for k in rng]
        for itm in lst_freq:
            freq_syl = itm[1]
            n_syl = itm[2]
            if n_syl <= n_syl_max and n_syl >= n_syl_min:
                lst[n_syl - n_syl_min] += 1 if knd == 'amt' else freq_syl
            elif n_syl > n_syl_max:
                print(n_syl, itm[0])
        return lst
    # Точности классификаторов слов и определителей ударений
    acc_Fv = [99.04, 99.44, 99.50, 99.55, 99.61, 99.99, 100, 100, 100, 100, 100]
    acc_F1 = [98.29, 97.52, 97.86, 98.70, 99.51, 99.68, 100, 100, 100, 100, 100]
    acc_F1 = [f1 * fv / 10000 for (f1, fv) in zip(acc_F1, acc_Fv)]
    acc_F2 = [fv / 100 for fv in acc_Fv]
    acc_v = [99.04, 99.44, 99.50, 99.55, 99.61, 99.99, 100, 100, 100, 100, 100]
    acc_v = [94.41, 98.72, 99.14, 99.26, 99.22, 99.04, 99.00, 98.49, 100, 100, 100]
    acc_1 = [86.40, 94.30, 96.44, 97.79, 98.95, 99.06, 99.31, 98.78, 100, 100, 100]
    acc_1 = [f1 * fv / 10000 for (f1, fv) in zip(acc_1, acc_v)]
    acc_2 = [100, 92.37, 95.35, 97.35, 97.75, 97.92, 97.31, 93.05, 89.28, 100, 100]
    acc_2 = [f2 * fv / 10000 for (f2, fv) in zip(acc_2, acc_v)]
    set_w = set() # Множество основ, имеющихся в словаре Зализняка
    for let in list_abc: # 84451 основ
        all_words = read_txt_f(path + let + t)
        all_words = [w.replace('\n', '') for w in all_words if find_n_vowels(w) > 1]
        set_w = set_w.union(set(all_words))
    ff = path + 'freq/freqrnc2011.txt' # Имя файла с частотным словарем
    ff = open(ff, encoding = 'utf-8')
    line = ff.readline()
    dict_freq = {}
    n_prop = 0
    while line:
        line = ff.readline()
        if not line: break
##        if 's.PROP' in line:
##            n_prop += 1
##            continue
        line = line.split()
        word = line[0]
        if find_n_vowels(word) < 2: continue
        freq = float(line[2])
        freq_d = dict_freq.get(word)
        if freq_d is None or freq_d < freq:
            dict_freq.update({word : freq})
    ff.close()
##    print('Число имен собственных:', n_prop) # 2418
    print('Размер прочитаного частотного словаря (число слогов более 1):', len(dict_freq)) # 51733 (49317)
    lst_freq = list(dict_freq) # Сначала берем только леммы
    set_f = set(lst_freq)
    set_wf = set_w.intersection(set_f)
    print(len(set_wf))
    print('Вероятность принадлежности случайного слова словарю Зализняка:',
         round(len(set_wf) / len(set_f), 4))
    lst_freq = list(dict_freq.items()) # Теперь берем леммы и их частоты
    lst_freq.sort(key = lambda itm: itm[1], reverse = True)
    freq_max = lst_freq[0][1]
    print('Наибольшая частота в словах с числом слогов более 1:', freq_max) # 35801.8
    lst_freq = [new_itm(itm) for itm in lst_freq]
    # Размеры групп лемм
    n_syl_min = 2; n_syl_max = 12
    rng = range(n_syl_min, n_syl_max + 1)
    lst_n_syl = one_lst('amt', rng, lst_freq, n_syl_min, n_syl_max)
    print('Размеры групп лемм:')
    print(lst_n_syl)
    # [9981, 16937, 12024, 6405, 2670, 990, 324, 106, 52, 16, 4]
    lst_freq_syl = one_lst('freq', rng, lst_freq, n_syl_min, n_syl_max)
##    # Приведенная частота лемм в группах
##    n_syl_sum = sum(lst_n_syl)
##    lst_freq_syl = [round(freq_syl / n_syl_sum, 4) for freq_syl in lst_freq_syl]
##    print(lst_freq_syl)
##    one_chart(lst_n_syl, rng, 'Число')
##    one_chart(lst_freq_syl, rng, 'Частота')
    # Вероятности принадлежности случайной леммы группам
    freq_sum = 0
    for itm in lst_freq: freq_sum += itm[1]
    lst_freq_syl = [round(freq_syl / freq_sum, 5) for freq_syl in lst_freq_syl]
    freq_sum = sum(lst_freq_syl)
    lst_freq_syl = [round(freq_syl / freq_sum, 5) for freq_syl in lst_freq_syl]
    print(lst_freq_syl)
    sys.exit()
    # Диаграмма вероятностей принадлежности леммы группам
    one_chart(lst_freq_syl, rng, 'Вероятность')
    # Вероятности правильного определения ударений в группах классов С1 и С2
    # на полном и разделенном наборах данных
    acc_F1 = [freq_syl * f1 for (freq_syl, f1) in zip(lst_freq_syl, acc_F1)]
    acc_F2 = [freq_syl * f2 for (freq_syl, f2) in zip(lst_freq_syl, acc_F2)]
    acc_1 = [freq_syl * f1 for (freq_syl, f1) in zip(lst_freq_syl, acc_1)]
    acc_2 = [freq_syl * f2 for (freq_syl, f2) in zip(lst_freq_syl, acc_2)]
    print('acc_F1 = ', round(sum(acc_F1), 4))
    print('acc_F2 = ', round(sum(acc_F2), 4))
    print('acc_1 = ', round(sum(acc_1), 4))
    print('acc_2 = ', round(sum(acc_2), 4))
# Прогноз ударений в словах списка lst_words
elif step == 21:
    if colab:
        import tensorflow.keras as keras
        import tensorflow.keras.utils as k_ut
        from google.colab import drive
        drv = '/content/drive/'
        drive.mount(drv)
        path = drv + 'My Drive/accents/'
    else:
        import keras
        import keras.utils as k_ut
    lst_pred = True
    ClsType = '2'
    # Все слова списка должны иметь одинаковое число слогов
    lst_words = ['татаканье', 'веялочных', 'барабанов']
    lst_words = ['абзац'] # абз'ац
    lst_words = ['автосборок', 'австро-венгерск'] # 'автосб'орок
    n_syl = find_n_vowels(lst_words[0])
    model_num = 1
    size = 32
    a = 10000
    v_a = 9513
    fn_m = path + 'model_{}_{}_{}_{}_{}_{}.{}'.format(ClsType, n_syl, model_num, size, a, v_a, 'h5')
    print('Загрузка модели из файла {}. Число слогов в слове: {}.'.format(fn_m, n_syl))
    model = keras.models.load_model(fn_m)
    dict_syls, _ = make_dict_syls(ClsType, path, str(n_syl))
    lst_syls = list(dict_syls)
    for word in lst_words:
        i_syls = []
        lst_syls = by_syllables(word)
        for syl in lst_syls:
            n = dict_syls.get(syl)
            if n is None:
                print('Не найден слог:', syl)
                n = 0
            i_syls.append(n)
        arr_i_syls = np.array(i_syls)
        y_pred = model.predict(arr_i_syls.reshape(1, n_syl))
        acc_pos = [np.argmax(m) for m in y_pred]
        c_0 = acc_pos[0]
        if ClsType == '2':
            # Словарь классов для определителей ударений в словах С2
            dict_classes = C2_classes(n_syl, path, t)
        else:
            dict_classes = {}
        print('Номер ударного слога:', c_0)
        make_print_wrds(ClsType, list(dict_syls), i_syls, "'", vowels, 1, c_0, c_0, dict_classes)
##Номер ударного слога: 2
##Коды слогов: [29, 29, 1039, 2]
##Слово: тат'аканье
##Номер ударного слога: 1
##Коды слогов: [52, 10, 13, 366]
##Слово: в'еялочных
##Номер ударного слога: 3
##Коды слогов: [65, 9, 65, 407]
##Слово: бараб'анов

Список работ

Рейтинг@Mail.ru