Разбор предложений по шаблонам русского языка

    Существует несколько парсеров, подходящих для русского языка. Некоторые из них могут даже выполнять синтаксический анализ, как SyntaxNet, MaltParser и AOT:
    Мама мыла раму пластиковых окон

    … или выявлять факты, как Tomita.

    Глядя на эти парсеры, я вижу какую-то огромную сложность вычислений, требования к памяти, лицензионные ограничения и… ограниченность каждого решения, увы.

    Чтобы понять, что же там такого сложного, мне захотелось сделать собственный парсер. Благо выходные оказались длинными.

    Основная идея


    Я подумал, как мы сами разбираем текст? Как выделяем из фразы ключевые элементы, строим в голове отношения между словами?

    Говорят, что Tomita построен на основе GLR-парсера, который, в свою очередь расширяет LR-парсер, который читает слова по порядку, пытается строить дерево отношений между ними.

    У меня же была мысль, что текст надо рассматривать как набор штампов, на которые у нас наметан глаз. "Белый мотылек на красной розе", "темное небо над синим морем", "глупый пингвин робко прячет" — везде видим существительное и относящееся к нему прилагательное. Причем мы понимаем, что «белый» относится к мотыльку, а не к розе. Как мы это делаем? Как минимум, мы видим, что «белый» мужского рода, как и «мотылек», а «красная» женского, как и роза. В случае с «небом» и «морем» нам помогает падеж, в который поставлено существительное.

    Далее, находя штампы, получившееся кусочки фразы соединяем в другие штампы, и так, пока не поймем всю фразу целиком — «мотылек на розе» (мотылек — белый, роза — красная), «небо над морем» (небо — темное, море — синее).

    Выбор правильного инструмента


    То есть, для поиска шаблона (прилагательное, существительное) мне нужно искать пару слов в том же падеже, числе и роде. Как? Естественным решением определения характеристик (граммем) в Python использовать pymorphy2 by kmike

    import pymorphy2 as py
    def tags(word):
        morph = py.MorphAnalyzer()
        return morph.parse(word)
    
    >>> print(tags('красной')[0])
    Parse(word='красной', tag=OpencorporaTag('ADJF,Qual femn,sing,gent'), normal_form='красный', score=0.125, methods_stack=((<DictionaryAnalyzer>, 'красной', 86, 8),))
    >>> print(tags('красной')[0].tag.grammemes)
    frozenset({'femn', 'ADJF', 'sing', 'gent', 'Qual'})
    

    Слова 'femn', 'ADJF', 'sing', 'gent', 'Qual' — это обозначения для граммем, принятых в pymorphy2. Обозначения уникальны, их можно использовать для однозначного определения нужных характеристик слова.

    Первые штрихи на холсте


    Теперь, имея инструмент, составим простой шаблон:

    source = '''
    Вася ест кашу
    # сущ  глагол  сущ
    # что/кто  делает с_чем-то
    NOUN,nomn VERB NOUN,accs
    
    '''
    

    Здесь ищем существительное (NOUN) в именительном падеже (nomn), за ним глагол (VERB), далее существительное в винительном падеже (accs). Не описанные в шаблоне характеристики нам не важны.

    Сделаем его читалку:

    class PPattern:
        def __init__(self):
            super().__init__()
    
    import io
    
    def parseSource(src):
        def parseLine(s):
            nonlocal arr, last
            s = s.strip()
            if s == '':
                last = None
                return
            if s[0] == '#':
                return
            if last is None:
                last = PPattern()
                arr.append(last)
                last.example = s
            else:
                last.tags = s.split()
            
        arr = []
        last = None
        buf = io.StringIO(src)
        s = buf.readline()
        while s:
            parseLine(s)
            s = buf.readline()
        return arr
    
    s = parseSource(source)
    

    Пусть вас здесь не пугает работа через StringIO — я хотел сделать потоковое чтение, просто на всякий случай, если понадобится читать большие тексты.

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

    source = '''
    Вася ест кашу
    # сущ  гл  сущ
    # что/кто  делает с_чем-то
    NOUN,nomn VERB NOUN,accs
    
    Красивый цветок
    ADJF NOUN
    
    Птица сидит на крыше
    # сущ  гл  предлог сущ
    NOUN,nomn VERB NOUN,loct
    '''
    
    text = '''
    Мама мыла раму
    Вася разбил окно
    Лара сама мыла раму
    Мама мыла пластиковые окна
    '''
    
    import pymorphy2 as py
    
    class PPattern:
        def __init__(self):
            super().__init__()
    
        def checkPhrase(self,text):
            def checkWordTags(tags, grams):
                for t in tags:
                    if t not in grams:
                        return False
                return True
    
            def checkWord(tags, word):
                variants = morph.parse(word)
                for v in variants:
                    if checkWordTags(self.tags[nextTag].split(','), v.tag.grammemes):
                        return (word, v)
                return None
            
            morph = py.MorphAnalyzer()
            words = text.split()
            nextTag = 0
            result = []
            for w in words:
                res = checkWord(self.tags[nextTag].split(','), w)
                if res is not None:
                    result.append(res)
                    nextTag = nextTag + 1
                    if nextTag >= len(self.tags):
                        return result
            return None
    
    def parseText(pats, text):
        def parseLine(line):
            was = False
            for p in pats:
                res = p.checkPhrase(line)
                if res:
                    print('+',line, p.tags, [r[0] for r in res])
                    was = True
            if not was:                
                print('-',line)
    
        buf = io.StringIO(text)
        s = buf.readline()
        while s:
            s = s.strip()
            if s != '':
                parseLine(s)
            s = buf.readline()
    
    patterns = parseSource(source)
    parseText(patterns, text)
    

    Pymorphy2 при анализе слова возвращает массив всех возможных вариантов, что это за слово может быть: «мыла» — это существительное или глагол. Поэтому наша задача проверить все эти варианты и выбрать из них такой, что характеристики слова подойдут под шаблон. Это делается в функции checkWord.

    Получаем результат разбора:

    + Мама мыла раму ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['Мама', 'мыла', 'раму']
    + Вася разбил окно ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['Вася', 'разбил', 'окно']
    + Лара сама мыла раму ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['Лара', 'мыла', 'раму']
    + Лара сама мыла раму ['ADJF', 'NOUN'] ['сама', 'мыла']
    + Мама мыла пластиковые окна ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['Мама', 'мыла', 'окна']
    + Мама мыла пластиковые окна ['ADJF', 'NOUN'] ['пластиковые', 'окна']

    Ну что же, неплохо для начала.

    И что, это всё?


    Нет, конечно, теперь надо описать соответствие падежей, родов и т.д. между словами. Модифицируем описание шаблона:

    source = '''
    
    # Красивый цветок
    ADJF NOUN
    -a-  -b-
    # Правила выведения, разделяющие пробелы обязательны
    = a.case = b.case
    = a.number = b.number
    = a.gender = b.gender
    
    '''

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

    Добавляем разбор правил в парсинг шаблонов. Правило компилируется в две лямбды — для получения значения до символа "=", и для для получения второго значения.

        def parseFunc(v, names):
            dest = v.split('.')
            index = names.index(dest[0])
            dest = (eval('lambda a: a.' + '.'.join(dest[1:])), index)
            return dest
        def parseLine(s):
            ...
            elif s[0] == '-': # внутренние имена
                s = [x.strip('-') for x in s.split()]
                last.names = s
            elif s[0] == '=': # правила
                s = [x for x in s[1:].split() if x != '']
                dest = parseFunc(s[0],last.names)
                src = parseFunc(s[2],last.names)
                last.rules.append(((dest[1],src[1]), dest, src))
            else:
            ...
    

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

    ...
                res = checkWord(self.tags[nextTag].split(','), w)
                if res is not None:
                    result.append(res)
                    usedP.add(wi)
                    if not self.checkRules(usedP, result):
                        result.remove(res)
                        usedP.remove(wi)
                    else:
                        nextTag = nextTag + 1
                        if nextTag >= len(self.tags):
                            return (result, usedP)
    ...
        def checkRules(self, used, result):
            for r in self.rules:
                if max(r[0]) < len(result):
                    destRes = result[r[0][0]]
                    destV = destRes[1]
                    destFunc = r[1][0]
                    srcRes = result[r[0][1]]
                    srcFunc = r[2][0]
                    srcV = srcRes[1]
                    if not self.checkPropRule(destFunc,destV, srcFunc, srcV):
                        return False
            return True
    
        def checkPropRule(self, getFunc, getArgs, srcFunc, srcArgs, \
                          op = lambda x,y: x == y):
            v1 = getFunc(getArgs)
            v2 = srcFunc(srcArgs)
            return op(v1,v2)
    
    

    Прогоним на классике

    + Эти типы стали есть на нашем складе ['ADJF', 'NOUN'] ['Эти', 'типы']
    + Эти типы стали есть на нашем складе ['ADJF', 'NOUN'] ['нашем', 'складе']
    + Эти типы стали есть на нашем складе ['NOUN,nomn', 'VERB', 'PREP', 'NOUN,loct'] ['типы', 'стали', 'на', 'складе']
    + Эти типы стали есть на нашем складе ['NOUN,nomn', 'VERB', 'PREP', 'NOUN,loct'] ['стали', 'есть', 'на', 'складе']


    Еще введем правило для имен:

    # хомяк Коля
    NOUN Name
    -a-  -b-
    = a.tag.case = b.tag.case
    = a.tag.number = b.tag.number
    

    Это дает разбор:
    + Сестра Татьяна - учительница ['NOUN', 'Name'] ['Сестра', 'Татьяна']

    Больше правил, хороших и разных


    Все было так хорошо, что означало, что мы чего-то не заметили. Парсер сломался на фразе «Младшие братья Миша и Вова ходят в детский сад» — он не смог подтвердить правило = a.gender = b.gender, потому что «младшие» не имеет родовой принадлежности и может относиться как к слову мужского рода «братья», так и к женскому «сестры».

    Следовательно, правило должно быть более сложным. Ну, раз я все равно компилирую лямбды из текста, то можно создать вместо двух одну, возвращающую результат проверки. Тогда это правило можно будет записать в виде выражения на чистом Python-е:

    = a.tag.gender is None or a.tag.gender == b.tag.gender

    Мне показалось, что у Python должно быть встроенное средство получения имен «a» и «b», задействованных в выражении. Предчувствие не обмануло, небольшое чтение help и документации привело меня к парсеру AST, в котором было все необходимое, и следующему коду:

    import ast
    
    def parseSource(src):
        def parseFunc(expr, names):
            m = ast.parse(expr)
            # Получим список уникальных задействованных имен
            varList = list(set([ x.id for x in ast.walk(m) if type(x) == ast.Name]))
            # Найдем их позиции в грамматике
            indexes = [ names.index(v) for v in varList ]
            lam = 'lambda %s: %s' % (','.join(varList), expr)
            return (indexes, eval(lam), lam)
    

    Все правила переписал на выражения Python. Кстати, если правило записано неправильно, то оно не компилируется еще при чтении словаря шаблонов и программа вылетает по exception, так что если словарь прочитался, то правила выполнимы.

    И все получилось:
    + Младшие братья Миша и Вова ходят в детский сад ['ADJF', 'NOUN'] ['Младшие', 'братья']
    Весь текст и его разбор
    Фразы в основном взяты из Букваря. Ведь если начинать машину читать, то лучше пользоваться проверенным способом.
    # Текст, который будем парсить
    text = '''
    Мама мыла раму
    Вася разбил окно
    Лара сама мыла раму
    Рано ушла наша Шура
    Мама мыла пластиковые окна
    
    Наша семья
    У нас большая семья
    Папа и брат Илья работают на заводе
    Мама ведет хозяйство
    Сестра Татьяна - учительница
    Я учусь в школе
    Младшие братья Миша и Вова ходят в детский сад
    
    Эти типы стали есть на нашем складе
    '''
    

    Словарь шаблонов

    # Описания шаблонов
    source = '''
    # Вася ест кашу
    # сущ  гл  сущ
    # что/кто  делает с_чем-то
    NOUN,nomn VERB NOUN,accs
    # определения внутренних имен
    -a-       -b-    -c-
    = a.tag.number == b.tag.number
    
    # Именованная сущность
    :SNOUN
    # Красивый цветок
    ADJF NOUN
    -a-  -b-
    # Правила сооответствия шаблону
    = a.tag.case == b.tag.case
    = a.tag.number == b.tag.number
    = a.tag.gender is None or a.tag.gender == b.tag.gender
    
    # Птица сидит на крыше
    # сущ  гл  предлог сущ
    NOUN,nomn VERB PREP NOUN,loct
    -a-        -b- -c-  -d-
    = a.tag.number == b.tag.number
    
    # стали есть
    VERB INFN
    
    # хомяк Коля
    NOUN Name
    -a-  -b-
    = a.tag.case == b.tag.case
    = a.tag.number == b.tag.number 
    
    # серп и молот
    NOUN CONJ NOUN
    -a-  -c- -b-
    = a.tag.case == b.tag.case
    
    #
    NOUN PNCT NOUN
    -a-  -c- -b-
    = a.tag.case == b.tag.case
    = c.normal_form == '-'
    '''
    

    Разбор
    + Мама мыла раму ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['Мама', 'мыла', 'раму']
    + Вася разбил окно ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['Вася', 'разбил', 'окно']
    + Лара сама мыла раму ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['Лара', 'мыла', 'раму']
    + Рано ушла наша Шура ['ADJF', 'NOUN'] ['наша', 'Шура']
    + Мама мыла пластиковые окна ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['Мама', 'мыла', 'окна']
    + Мама мыла пластиковые окна ['ADJF', 'NOUN'] ['пластиковые', 'окна']
    + Наша семья ['ADJF', 'NOUN'] ['Наша', 'семья']
    + У нас большая семья ['ADJF', 'NOUN'] ['большая', 'семья']
    + Папа и брат Илья работают на заводе ['NOUN', 'Name'] ['Папа', 'Илья']
    + Папа и брат Илья работают на заводе ['NOUN', 'Name'] ['и', 'Илья']
    + Папа и брат Илья работают на заводе ['NOUN', 'Name'] ['брат', 'Илья']
    + Папа и брат Илья работают на заводе ['NOUN', 'CONJ', 'NOUN'] ['Папа', 'и', 'брат']
    + Мама ведет хозяйство ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['Мама', 'ведет', 'хозяйство']
    + Сестра Татьяна - учительница ['NOUN', 'Name'] ['Сестра', 'Татьяна']
    + Сестра Татьяна - учительница ['NOUN', 'PNCT', 'NOUN'] ['Сестра', '-', 'учительница']
    + Сестра Татьяна - учительница ['NOUN', 'PNCT', 'NOUN'] ['Татьяна', '-', 'учительница']
    + Я учусь в школе ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['Я', 'учусь', 'в']
    + Я учусь в школе ['NOUN,nomn', 'VERB', 'PREP', 'NOUN,loct'] ['Я', 'учусь', 'в', 'школе']
    + Младшие братья Миша и Вова ходят в детский сад ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['братья', 'ходят', 'в']
    + Младшие братья Миша и Вова ходят в детский сад ['ADJF', 'NOUN'] ['Младшие', 'братья']
    + Младшие братья Миша и Вова ходят в детский сад ['ADJF', 'NOUN'] ['детский', 'сад']
    + Младшие братья Миша и Вова ходят в детский сад ['NOUN', 'CONJ', 'NOUN'] ['братья', 'и', 'Вова']
    + Младшие братья Миша и Вова ходят в детский сад ['NOUN', 'CONJ', 'NOUN'] ['Миша', 'и', 'Вова']
    + Эти типы стали есть на нашем складе ['ADJF', 'NOUN'] ['Эти', 'типы']
    + Эти типы стали есть на нашем складе ['ADJF', 'NOUN'] ['нашем', 'складе']
    + Эти типы стали есть на нашем складе ['NOUN,nomn', 'VERB', 'PREP', 'NOUN,loct'] ['типы', 'стали', 'на', 'складе']
    + Эти типы стали есть на нашем складе ['NOUN,nomn', 'VERB', 'PREP', 'NOUN,loct'] ['стали', 'есть', 'на', 'складе']
    + Эти типы стали есть на нашем складе ['VERB', 'INFN'] ['стали', 'есть']



    Что дальше?


    1. Как видите, я остановился на поиске отдельных шаблонов, но не стал результат разбора объединять в дерево синтаксического разбора. Тому есть несколько причин, и одна из них — я не уверен, что стоит это делать. Каждый вариант разбора дает нам маленькую крупицу информации. Объединяя их в дерево, мы пытаемся втиснуть знания в искусственную структуру. Ребенок может читать и понимать предложения, не зная, какое слово в нем подлежащее, а какое — сказуемое. Он берет крупицы и создает в своей голове картину (описываемого) мира. Зачем же нам требовать от машины большего?

    2. Очевидно не хватает правила, насколько одно слово может быть удалено в тексте от другого. Так «Папа» стал «Ильей», хотя между ними стоят слова «и брат».

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

    4. В правилах, помимо остальных частей речи, не хватает знаков пунктуации. Можно ввести константные литералы «NOUN '-' NOUN», а можно, как выше в примере с учительницей, проверять знак в правиле.

    5. Pymorphy2 умеет предполагать принадлежность слов к частям речи, поэтому возможны даже такие варианты:
    >>> parseText(patterns, 'бятые пуськи')
    + бятые пуськи ['ADJF', 'NOUN'] ['бятые', 'пуськи']
    + бятые пуськи ['NOUN', 'Name'] ['бятые', 'пуськи']

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

    Код лежит на GitHub.
    Share post

    Comments 24

      +1
      как это реагирует на генитивную неоднозначность (вчера видел соседа Игоря) или неоднозначность части речи (самое дллинное что придумал — косой с косой косой у косы косил, косой косой не косил)
        0

        В первом предложении отсутствует субъект действия, поэтому "кто-то видел соседа" не разберётся. Для этого в модель нужно вводить возможность предположений отсутствующих слов. А "соседа Игоря" — без проблем.


        "Косой косой", если допускаем перестановку, то разберутся оба варианта. Но и человек так же разберёт.

          +1
          нужно вводить возможность предположений отсутствующих слов.

          обязательно, таких предложений полно. и ещё, часть речи может быть (выступать в качестве) практически любой другой частью речи:
          прилагательное -> существительное (видел этого рыжего)
          существительное -> прилагательное (стоял столбом), глагол
          глагол -> существительное, и т.п.
            0
            'рыжего' может воспринять в качестве существительного
            >>> parseText(patterns,'свидетель видел этого рыжего')
            + свидетель видел этого рыжего ['NOUN,nomn', 'VERB', 'NOUN,accs'] ['свидетель', 'видел', 'рыжего']
            + свидетель видел этого рыжего ['ADJF', 'NOUN'] ['этого', 'рыжего']

            'столбом' как наречие — увы, нет. Но вообще это определяется словарем, в данном случае OpenCorpora.
        0

        "Да нет, наверное" ©

          +1
          а если интерпретировать семантику, то чайник долго закипает и чайник долго НЕ закипает значит одно и то же
          0
          Смешались в кучу люди кони… особенно радуют попытки притянуть в этот странный процесс детей и понимание.
          Попробуйте представить что понимание что к чему относится происходит из за того что есть слово которое ассоциируется с вполне конкретным объектом виденным визуально. И попытки притянуть к этому процессу падежи и роды, ну это как жесткое с теплым.
            0

            Увиденное ранее не всегда поможет понять предложение. Например, я видел "белую розу", но разобрать предложение "белый мотылёк на красной розе" это не особо поможет. С другой стороны падеж и род тоже не самое главное в таком разборе. Например можно придумать предложение "Белый мотылёк видит красный пион." Падеж и род пишутся одинаково в обоих случаях. Мне кажется, тут важнее близость двух слов Прилагательное+Существительное для разбора, а остальные признаки менее важны. (хотя и могут играть вспомогательную роль)

              +1
              «Красный пион видит белый мотылёк». Это одно явление описывается с вашим или нет? Как по мне, то одно, просто акценты разные. Мы же знаем, что у мотылька есть способность видеть, а у пиона нет.
                +1

                Да, всё верно. Смотря какая задача у нас при разборе предложения. Мы пока остановились на самой простейшей задаче (которую автор решает в статье) — определить "какое прилагательное с каким существительным связано". Получаются пары "красный+пион" и "белый+мотылек"

                0
                Близость — да, важна, см.п.3.
                0

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

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

                      Очень интересно. А можно ссылки на научные статьи?

                      Вбейте в гугл «глагольная группа» со словами дети, психология итп. Гугл выдаст очень много интересного на тему понимания речи.
                      Вот одна из интересных статей elementy.ru/novosti_nauki/431181 (пробежался по тексту, выглядит приличной).

                      У меня где-то была подборка хороших книг и статей, попробую ее найти.
                        0
                        Можно. Начините прям с самого начала. От истоков нейросетей. Там масса информации. В частности о процессе обобщения информации нейронами. Как там ваш «супер ии» который уже прям все понимает и кучу выставок обьездил? :)
                          0
                          Наш «супер ии» чувствует себя хорошо.
                          0
                          Статья по ссылке действительно интересная, спасибо.

                          Кстати, в моей первой версии правил я их назвал «правилами выведения», похожие на правила из статьи:

                          # Красивый цветок
                          ADJF NOUN
                          -a-  -b-
                          # Правила выведения, разделяющие пробелы обязательны
                          = a.case = b.case
                          = a.number = b.number
                          = a.gender = b.gender
                          

                          потому я хотел их использовать двумя способами:
                          — проверять слова из входного потока на соответствие правилу — т.е. то, что делается в статье
                          — и выполнять обратную операцию «выведения», т.е. создавать словосочетания, подставляя слова и ставя их в форму, соответствующую основному слову — в правиле левая часть, до "=", считалась подчиненной, а правая основной.

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

                          = a.tag.gender is None or a.tag.gender == b.tag.gender

                          понять, что род прилагательного нужно поставить в соответствие роду существительного, а не наоборот.

                          Но это временное отступление, я сейчас примерно представляю, как можно одновременно использовать сложные правила и иметь «выведение» нужной формы слов.
                    0
                    Нет смысла изобретать велосипед. На нашем ресурсе есть серия статей, например
                    Сравнение и создание морфологических анализаторов в NLTK. habrahabr.ru/post/340404. При наличии национального корпуса русского лучшего анализа чем NLTK пока не существует.
                      0

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


                      За ссылку спасибо. Комментарии под ней видели?

                      +2
                      В свое время глубоко копал тему. Достиг серьезных результатов в семантическом анализе для русского языка но потом переключился на другое. Энтузиастам и интересующимся могу посоветовать ознакомится с этим описанием моих исследований.
                        0
                        А код не выкладывали в открытий доступ (на github'e и т.п.)?
                          0
                          нет )
                        0
                        `

                        Only users with full accounts can post comments. Log in, please.