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

На грядках растёт много лука. / Сильно натянута тетива лука.

Замок закрыли ключом. / Замок окружён рвом.

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

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

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

Рассмотрим этот механизм: мне известно что-то про замок (1) и про замок (2), я знаю, что замок (1) открывается и закрывается ключом, висит на стене и т. д., а замок (2) можно построить, можно осадить и т. д. То есть если у символа несколько значений и у каждого значения свой контекст, то по контексту можно однозначно восстановить значение, если заранее знать их набор. Кстати, замок (1) и замок (2) справедливо могут быть старыми или, например, чёрными. Но, если в окружении слова находятся другие слова, относящиеся к конкретному значению (например "башни за стеной" или "с трудом отпёрся"), то значение восстановимо. Так мы понимаем, что перед нами: за́мок или замо́к.

Перед нами открывается необычная возможность: объединять два значения в один символ — компрессия словаря через объединение слов в омонимы. В этой работе мы сознательно отказываемся от однозначности слов. Рассмотрим, насколько естественные языки поддаются такому сжатию. Посмотрим, как выглядят языки с максимальным числом омонимов в словаре.

Механизм сжатия

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

Более формально. Есть символ A и у него k значений  M=\{a_1, ..., a_k\}каждому значению a_i, сопоставлено множество контекстов C_{a_i}, причем

\forall a_i, a_j \in M, a_i \neq a_j : C_{a_i} \cap C_{a_j} = \varnothing \quad(a).

(a) это гарантия того, что значение идентифицируемо. Такова структура нашего слова-омонима.

Теперь, если мы решили соединить два слова A и B, значения и контексты нового слова V будут M_V = M_A \cup M_B и C_V = C_A \cup C_B соответственно. Ключевым моментом будет соблюдение условия (а), что любые два значения не будут иметь общих контекстов и будут различимы.

Рассмотрим предел сжатия, которое может позволить такое объединение. Построим граф G, где вершины будут словами, а рёбра будут означать, что контексты слов пересекаются. То есть если слова соединены ребром, то их употребление похоже и однозначно отличить одно от другого не получится, при имеющихся контекстах. Если объединить два похожих слова, то мы не сможем однозначно установить значение омонима. Объединить в один символ возможно слова без общих контекстов, то есть слова, не соединенные ребром. Таким образом, мы стремимся объединить максимальное число несмежных слов одним символом, минимизируя общее число символов. А это есть хроматическое число графа G.

Справка. Хроматического числа графа

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

\chi(G) = \min{k \mid \exists c: V \to {1, \dots, k}, \forall (u,v) \in E \implies c(u) \neq c(v)}

Два любых неразличимых слова (связи E) не обозначаются одним символом (отображение c слова в символ). Ищем минимальное число символов при заданных различимостях.

Реализация

Разберем детали реализации. По ходу буду демонстрировать выводы на примере базы из корпуса русской Википедии.

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

Код
from datasets import load_dataset
from tqdm import tqdm

filename = "./base/russian_wikipedia.txt"

ds = load_dataset(
    "wikimedia/wikipedia",
    "20231101.ru",
    split="train",
    streaming=True
)

for item in ds:
    print(item["text"][:500])
    break

with open(filename, "w", encoding="utf-8") as f:
    for i, item in enumerate(tqdm(ds)):
        if i >= 10000:
            break
        f.write(item["text"] + "\n")

from os import walk
import re
from tqdm import tqdm

base_path = './base/'

data_text = ''

for dirpath, dirnames, filenames in walk(base_path):
    for file_name in filenames:
        if file_name[-4:] != '.txt': continue

        print('Read:', file_name)
        try:
            current_text = open(dirpath + '/' + file_name, 'r', encoding='utf-8').read()
            data_text += current_text
        except Exception as e:
            print(f'Error reading {file_name}: {e}')

def prepare_text(text):
    n_text = text.lower()
    n_text = re.sub(r'[^а-яё\s]+', ' ', n_text)
    n_text = re.sub(r'ё', 'е', n_text)
    n_text = re.sub(r'\n+', ' ', n_text)
    n_text = re.sub(r'\s+', ' ', n_text)

    return n_text

data_text = prepare_text(data_text)

alphabet = list(set(data_text))

print('---')
print('Final data length:', len(data_text), 'symbols')
print('Alphabet length:', len(alphabet))
print('Alphabet:', alphabet)
print('Text sample:', data_text[:100])


from collections import defaultdict, Counter
from typing import List, Dict, Set

words = data_text.split()
freq = Counter(words)

min_frequency = 5

vocab = {w for w, c in freq.items() if c >= min_frequency}

print(f"Before word count: {len(freq)}")
print(f"Vocabulary size: {len(vocab)}")
Литва ( ), официальное название — Литовская Республика () — государство, расположенное в Северной Европе. Площадь —  км². Протяжённость с севера на юг — 280 км, а с запада на восток — 370 км. Население составляет  человек (август, 2023). Занимает 137-е место в мире по численности населения и 121-е по территории. Имеет выход к Балтийскому морю, расположена на его восточном побережье. Береговая линия составляет всего 99 км (наименьший показатель среди государств Балтии). На севере граничит с Ла
10000it [00:20, 495.20it/s]

Read: russian_wikipedia.txt
---
Final data length: 110594518 symbols
Alphabet length: 33
Alphabet: ['б', 'о', 'т', 'ы', 'с', 'р', 'ш', 'е', 'ц', 'в', 'й', 'у', 'ф', 'ю', 'п', 'х', 'ч', 'и', 'э', 'м', 'д', 'а', 'ь', 'г', 'ъ', 'я', 'з', 'л', ' ', 'ж', 'н', 'к', 'щ']
Text sample: литва официальное название литовская республика государство расположенное в северной европе площад

Before word count: 592173
Vocabulary size: 163793
Было: 
Литва ( ), официальное название — Литовская Республика () — государство, расположенное в Северной Европе. Площад

Стало:
литва официальное название литовская республика государство расположенное в северной европе площад

было 592173 слов, после отбрасывания редких 163793

Контекстом было принято считать неупорядоченное множество радиуса r слов слева и справа от выбранного слова. Получаем 13 373 031 различных контекстов для r=2 (окно контекста 4 слова).

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

Подробнее про вычисление классов и код

Разбиение на контексты

from tqdm import tqdm

word_to_id = {w: i for i, w in enumerate(sorted(vocab))}
id_to_word = {i: w for w, i in word_to_id.items()}

context_to_words: Dict[int, Set[int]] = defaultdict(set)

radius = 2

ctx_counter = 0

for i, w in enumerate(tqdm(words[radius:len(words) - radius])):
    if w not in vocab:
        continue

    w_id = word_to_id[w]
    left = max(0, i - radius)
    right = min(len(words), i + radius + 1)

    context_word_ids = set()
    for j in range(left, right):
        if j == i:
            continue
        cand = words[j]
        if cand in vocab:
            context_word_ids.add(word_to_id[cand])

    if not context_word_ids:
        continue

    ctx_key = frozenset(context_word_ids)
    ctx_id = hash(ctx_key)

    context_to_words[ctx_id].add(w_id)

print(f"Total contexts: {len(context_to_words)}")

Разбиение на классы (будущие символы). Собирается словарь {контекст: набор слов}. Получается, для каждого контекста имеется набор слов, которые ни при каких условиях не могут быть объединены, они друг для друга добавляются вblock . Таким образом, пройдясь по всем контекстам, получаем таблицу невозможных объединений. Затем жадный алгоритм проходится по всем словам (начиная с малочастотных, заканчивая частотными). Ищется первый подходящий класс, в который слово может быть включено (с участниками которого у него не будет конфликтов из block). Если такого класса не находится, то создается новый.

blocked: Dict[int, Set[int]] = defaultdict(set)

for ctx_id, ctx_words in tqdm(context_to_words.items()):
    ctx_words_list = list(ctx_words)
    for a in ctx_words_list:
        for b in ctx_words_list:
            if a != b:
                blocked[a].add(b)

class_members: List[Set[int]] = []
word_to_class: Dict[str, int] = {}

sorted_words = sorted(vocab, key=lambda x: (freq[x], x))

for w in tqdm(sorted_words):
    w_id = word_to_id[w]

    placed = False
    for class_id, members in enumerate(class_members):
        if not (blocked[w_id] & members):
            members.add(w_id)
            word_to_class[w] = class_id
            placed = True
            break

    if not placed:
        class_members.append({w_id})
        word_to_class[w] = len(class_members) - 1

classes: Dict[int, List[str]] = {}
for w, cls in word_to_class.items():
    classes.setdefault(cls, []).append(w)

for cls in classes:
    classes[cls].sort()
Структурные свойства графа G

Среднее число контекстов, приходящееся на одно слово 82.21, стандартное отклонение 2148.47. Среднее число слов на контекст 1.01 при стандартном отклонении 0.09.

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

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

На корпусе Википедии алгоритм порождает 1420 классов. Распределение крайне неравномерное: класс 0 содержит 121 703 слова. Это подавляющее большинство лексики, чьи контексты уникальны и не пересекаются ни с кем. Классы 1-10 содержат от 13 305 до 554 слов. Далее размер классов быстро убывает до 1–2 слов.

Это означает, что словарь из 163 793 слов сжимается до 1420 символов (в ~115 раз, что впечатляет).

Примеры объединений:

Class 405: 4 words
['индикаторов', 'плане', 'рассказ', 'шведском']
---
Class 406: 4 words
['медицинском', 'мистерий', 'списке', 'ход']
---
Class 407: 4 words
['будущем', 'герасимов', 'немного', 'орехово']
---
Class 408: 4 words
['награжден', 'регрессия', 'содержится', 'труп']
---
Class 409: 4 words
['новом', 'образовании', 'светлее', 'углем']
---
...
---
Class 884: 2 words
['земле', 'твердые']
---
Class 885: 2 words
['обряды', 'реакции']
---
Class 886: 2 words
['ооо', 'роща']
---
Class 887: 2 words
['записал', 'музыка']
---
Class 888: 1 words
['леви']
---
...
---
Class 1413: 1 words
['к']
---
Class 1414: 1 words
['из']
---
Class 1415: 1 words
['по']
---
Class 1416: 1 words
['с']
---
Class 1417: 1 words
['на']
---
Class 1418: 1 words
['и']
---
Class 1419: 1 words
['в']
---

Эксперименты

База

длина всей базы (в словах)

количество уникальных слов

Количество выбранных слов с учётом частоты

Александр Пушкин "Сказка о царе Солтане"

3998

1309

172

Русскоязычная Википедия (10к статей)

14 918 719

592 173

100 484

Русскоязычная Википедия (лемматизированная, первые 10к статей)

14 918 719

269 226

50 219

Англоязычная Википедия (первые 350к статей)

16 133 765

198 888

46 776

Александр Пушкин "Сказка о царе Солтане"

Радиус окрестности контекста r

Количество уникальных контекстов

Средний диапазон кол-ва контекстов на слово

Средний диапазон кол-ва слов на контекст

Количество классов

Размеры первых нескольких классов

1

487

4.97 ± 5.72

1.76 ± 2.19

27

71, 25, 17, 7, 6, 4, ...

2

1009

7.42 ± 10.09

1.27 ± 1.16

24

101, 22, 11, 6, 4, 2, ...

3

1330

8.55 ± 12.21

1.11 ± 0.71

17

120, 18, 7, 5, 4, 3, ...

5

1630

9.70 ± 14.22

1.02 ± 0.20

5

153, 12, 4, 2, 1

Русскоязычная Википедия

Радиус окрестности контекста r

Количество уникальных контекстов

Средний диапазон кол-ва контекстов на слово

Средний диапазон кол-ва слов на контекст

Количество классов (новых слов)

Размеры первых нескольких классов

Номер класса после которого размер <=3

2

12 749 346

131.32 ± 2803.68

1.03 ± 1.35

2845

56797, 10247, 4587, 2755, ...

680

3

13 488 033

134.82 ± 2919.05

1.00 ± 0.19

413

88561, 5775, 1691, 838, ...

108

10

13 694 232

136.86 ± 2965.85

1.00 ± 0.08

28

95180, 3344, 976, 417, ...

15

Пример последних нескольких классов с контекстным окном 20 слов (r = 10)
Class 8: 31 words
['августа', 'апреля', 'б', 'был', 'верховного', 'ветлужский', 'гг', 'германии', 'дом', 'завод', 'империи', 'иркутск', 'как', 'кино', 'лв', 'место', 'мир', 'мира', 'наук', 'область']
---
Class 9: 17 words
['академии', 'белая', 'второй', 'го', 'город', 'если', 'их', 'млн', 'ноября', 'память', 'район', 'российский', 'россия', 'сентября', 'сергачский', 'х', 'э']
---
Class 10: 17 words
['богородский', 'века', 'во', 'декабря', 'его', 'им', 'имени', 'первый', 'пр', 'района', 'рсфср', 'советский', 'союза', 'то', 'тыс', 'у', 'федерации']
---
Class 11: 14 words
['большая', 'володарский', 'воскресенский', 'города', 'для', 'июля', 'й', 'населения', 'около', 'он', 'п', 'при', 'российской', 'совета']
---
Class 12: 9 words
['большой', 'годы', 'городецкий', 'д', 'или', 'лет', 'мая', 'я', 'января']
---
Class 13: 9 words
['день', 'из', 'н', 'области', 'павловский', 'сша', 'т', 'улица', 'ум']
---
Class 14: 5 words
['е', 'км', 'марта', 'не', 'ссср']
---
Class 15: 3 words
['год', 'м', 'р']
---
Class 16: 3 words
['г', 'за', 'россии']
---
Class 17: 1 words
['до']
---
Class 18: 1 words
['от']
---
Class 19: 1 words
['году']
---
Class 20: 1 words
['а']
---
Class 21: 1 words
['к']
---
Class 22: 1 words
['года']
---
Class 23: 1 words
['по']
---
Class 24: 1 words
['с']
---
Class 25: 1 words
['на']
---
Class 26: 1 words
['и']
---
Class 27: 1 words
['в']
---

Англоязычная Википедия

Радиус окрестности контекста r

Количество уникальных контекстов

Средний диапазон кол-ва контекстов на слово

Средний диапазон кол-ва слов на контекст

Количество классов

Размеры первых нескольких классов

Номер класса после которого размер <= 3

2

13 748 502

315.28 ± 7122.35

1.07 ± 1.71

2082

20830, 4639, 2324, 1493, ...

574

3

15 363 701

329.73 ± 7544.83

1.00 ± 0.10

111

40440, 3171, 998, 536, ...

54

10

15 592 931

334.69 ± 7682.55

1.00 ± 0.07

27

44101, 1785, 440, 185, ...

Пример нескольких классов (r = 3)
Class 31: 6 words
['climbs', 'henry', 'newcomers', 'oxides', 'rivers', 'village']
---
Class 32: 6 words
['aegean', 'bangla', 'dominance', 'language', 'opening', 'prize']
---
Class 33: 6 words
['chin', 'conquered', 'estate', 'largest', 'museum', 'nemesis']
---
Class 34: 6 words
['department', 'geoffrey', 'mike', 'sulfide', 'tri', 'university']
---
...
---
Class 58: 4 words
['battles', 'copper', 'film', 'starring']
---
Class 59: 4 words
['count', 'knight', 'pro', 'war']
---
Class 60: 4 words
['dynasty', 'most', 'store', 'tamil']
---
Class 61: 4 words
['containing', 'from', 'matt', 'medieval']
---
Class 62: 3 words
['faith', 'genus', 'soul']
---
Class 63: 3 words
['der', 'onto', 'winner']
---
Class 64: 2 words
['fly', 'method']
---
...
---
Class 95: 2 words
['north', 'over']
---
Class 96: 1 words
['some']
---
Class 97: 3 words
['all', 'between', 'by']
---
Class 98: 1 words
['or']
---
Class 99: 1 words
['also']
---
Class 100: 2 words
['after', 'one']
---
Class 101: 1 words
['with']
---
Class 102: 1 words
['for']
---
Class 103: 1 words
['as']
---
...

Русскоязычная Википедия (лемматизированная)

Радиус окрестности контекста r

Количество уникальных контекстов

Средний диапазон кол-ва контекстов на слово

Средний диапазон кол-ва слов на контекст

Количество классов

Размеры первых нескольких классов

Номер класса после которого размер <= 3

2

13 279 174

274.12 ± 4241.74

1.04 ± 0.65

823

29336, 5538, 2463, 1511, ...

338

3

14 094 390

281.84 ± 4421.57

1.00 ± 0.11

121

43194, 3356, 1088, 546, ...

57

10

14 282 574

286.10 ± 4492.47

1.01 ± 0.10

33

45854, 2419, 790, 387, ...

22

Первое, что бросается в глаза: первый класс забирает подавляющее большинство слов, далее количество слов на класс стремительно убывает. Это означает, что почти все слова попарно несмежны (контексты не пересекаются), и лишь небольшая доля образует плотные подграфы. Граф очень разрежён. Хроматическое число определяется несколькими плотными кликами слов, которые трудно различить по контексту.

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

Было решено это проверить, лемматизировав датасет. Ожидалось, он будет хуже сжиматься, так как эти "слои морфологической согласованности" схлопнутся, и останется более плотная структура. На практике вышло наоборот: лемматизированный корпус сжался лучше (х61 против х35). При этом лемматизированный корпус показывает даже большее число уникальных контекстов. Дело в том, что вместе с формами слов объединились и их связи (большого дома, большому дому, ... -> большой дом). Большинство таких связей было "параллельно", а приведение всего к леммам схлопнуло их в одну. Исчезли связи, которые были отправлены из одного слова в различные словоформы, которые делали структуру графа гуще. Это сделало его более разрежённым, что дало больше свободы для сжатия. Это видно по количеству слов на контекст: 1.03 ± 1.35 у оригинальной против 1.04 ± 0.65 у лемматизированной. Эта характеристика полезна для оценки возможности сжатия, так как слова, встречающиеся в одном контексте, не могут быть объединены.

Большее число контекстов лемматизированного корпуса

Лемматизатор схлопывает разные формы слова в одно, и общая частотность слов возрастает (исчезает шум из редких форм редких слов), большая доля слов проходит порог частоты (который был одинаковым для всех экспериментов), и поэтому больше слов попадает в контексты на этапе сбора. От этого число уникальных контекстов возрастает.

При r=2 английский сжимается слабее (х22 против х35). Это выглядит как подтверждение гипотезы: английская морфология беднее: меньше форм, больше слов на контекст (1.07 ± 1.71 в ан. против 1.03 ± 1.35 в ру.), меньше вершин, которые можно было бы склеить. При больших r разница нивелируется: и там, и там граф становится разреженным, и хроматическое число определяется уже не морфологией, а функциональной лексикой: предлогами, артиклями, союзами, которые сочетаются со всем и образуют клики, задающие нижнюю границу.

Справка. Оценка хроматического числа графа.

Нижней границей хроматического числа графа выступает размер максимальной клики. Говоря простым языком, клика — это самая "жесткая" структура, для которой потребуется максимальное число цветов. Число цветов всего графа будет не меньше количества вершин максимальной клики.

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

Перспективы

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

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

  1. Использовать полученные классы как алфавит для шифровки подстановкой. Нужно рассмотреть возможность дешифровки. Чтобы восстановить задуманное значение-слово, нужно распознать контекст вокруг него. Но так как изначальные контексты были на естественном языке, восстанавливать их нужно из значений классов (которых может быть ощутимо много). Следовало бы начинать с малочисленных классов, а дальше в худшем случае перебор всех значений символов. Зацепкой для снижения перебора тут может быть совместная употребимость слов, так как в классах зачастую лежат несочетаемые друг с другом слова. Это может существенно снизить перебор. Слова по бокам будет сложнее перевести, так как их контексты будут менее корректными.

    Я не являюсь специалистом в криптографии, но мне недавно попадалась очень интересная статья, в которой рассматривался механизм перевода языка без параллельных источников как механизм разгадки шифра (deciphering). Это осуществлялось через поиск подстановок слов (замены слов одного языка на слова другого), максимизирующий правдоподобие текста переведенного с этой подстановкой. Возможно, подобные методики были бы полезны, для работы с таким шифром.

  2. Было бы любопытно прикинуть, насколько новый словарь поддается повторному сжатию. Теоретически контекст таким образом будет расширен через контексты новых символов. Все будет так же завязано на изначальных контекстах, только взгляд на них будет происходить через призму новых символов. Окно будет эквивалентно сумме радиусов на всех итерациях. Это похоже на композицию сверточных слоев нейросетей.

  3. Как хорошо будут обучаться и работать с таким текстом нейронки? Как поменяется энтропия и предсказуемость по сравнению с естественным языком? Замена слов на классы-омонимы снижает энтропию наблюдаемой последовательности, смещая фокус с лексики на семантику. Для нейросети это выглядит как уменьшение словаря и исчезновение длинного хвоста редких слов. Поэтому такой текст может быть удобен для обучения компактных языковых моделей, но становится более требовательным к контекстному механизму и может терять лексическую и фактологическую точность. Информация не исчезает: она переносится в условное распределение исходного слова относительно класса и контекста.

  4. Можно ли, имея корпус текста на естественном языке, распознать среди слов многозначные? В нашем алгоритме за значения мы полагали уже сами слова. Для алгоритма не важно, сколько значений у слова: при любом их числе условия различимости соблюдаются и алгоритм работает корректно. Но если мы зададимся другим вопросом: можно ли распознать значения, присущие символу? По идее в символе может быть любое количество значений, главное, чтобы их контексты попарно не пересекались. То есть для каждого слова мы можем рассмотреть 2^{|C|}потенциальных значений (количество подмножеств его контекстов). Но как среди них отбирать валидные "значения"? В первую очередь требуется более формальное и адекватное определение значения. А этот вопрос требует отдельного анализа.