Русская морфология

andreypaa

Новичок
GTHack
так же советую подумать об омонимии, на 2-ом шаге у вас может получиться не одно слово, а несколько, обычно два; надо будет решить какой из них оставить, а какое выкинуть

-~{}~ 05.08.09 19:25:

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

Жигaн

Новичок
GTHack
1) getAllFormsWithGramInfo() - устаревшая конструкция, используй findWord()

2)
PHP:
function transform_word(phpMorphy $morphy, $original, $replacement, $strict = false) {
    $result_forms = array();

    // ищем слово в словаре, получаем коллекцию парадигм, в которые входит слово
    if(false === ($collection = $morphy->findWord($original))) {
        throw new Exception("Can`t find $original word");
    }

    // ищем в словаре слово на которве бедкм менять
    if(false === ($collection_repl = $morphy->findWord($replacement))) {
        throw new Exception("Can`t find $original word");
    }


    // омонимия на уровне разных частей речи - ключ, лук, душа, стали и т.п.
    foreach($collection as $descriptor) {
        // омонимия внутри форм одного слова
        // стол имеет одинаковые формы для именительного и винительного падежей
        foreach($descriptor->getFoundWordForm() as $form) {
            // запоминаем граммемы и часть речи для $original
            $grammems = $form->getGrammems();
            $part_of_speech = $form->getPartOfSpeech();

            // ищем форму в $collection_repl c этими характеристиками

            // Ищем в коллекции по части речи
            foreach($collection_repl->getByPartOfSpeech($part_of_speech) as $descriptor_repl) {
                foreach($descriptor_repl->getWordFormsByGrammems($grammems) as $form_repl) {
                    // ВСЕ граммемы для каждой найденной формы должны совпадать с $grammems (если $strict === true)
                    if(!$strict || $form_repl->getGrammems() == $grammems) {
                        // ОК, нашли форму
                        $result_forms[$form_repl->getWord()] = 1;
                    }
                }
            }
        }
    }

    return array_keys($result_forms);
}

var_dump(transform_word($morphy, 'ДВИЖЕНЬЯХ', 'ПЕРЕМЕЩЕНИЕ'));
$original и $replacement могут быть в любых формах

PHP:
var_dump(transform_word($morphy, 'ДВИЖЕНЬЯХ', 'ПЕРЕМЕЩЕНИЯМИ'));
аналогично примеру выше
 

GTHack

Новичок
СПАСИБО !
не ожидал ТАКОГО ответа :)
прямо готовую функцию для меня сваял - супер !

-~{}~ 06.08.09 11:12:

а есть функция которая по моему конкретному указанию преобразовывала бы слово ?
т.е. допустим хочу глагол
"ХОЖУ" во множественном числе, прошедшем времени
как то так
function("ХОЖУ", "МН ПРОШ") = "ХОДИЛИ"

-~{}~ 06.08.09 11:17:

ещё добавлю
попобовал я по Вашему совету вместо
getAllFormsWithGramInfo заюзать findWord

var_dump($morphy->getAllFormsWithGramInfo('ХОЖУ'));
var_dump($morphy->findWord('ХОЖУ'));

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

-~{}~ 06.08.09 14:20:

ещё вопросик - расшифровку сокращений
"С ИМ,МН,МР,ОД" и прочих где можно глянуть ?

-~{}~ 06.08.09 17:30:

функция не всегда срабатывает

var_dump(transform_word($morphy, 'СТУЛОМ', 'КРЕСЛО'));

пусто

-~{}~ 06.08.09 21:34:

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

Жигaн

Новичок
ещё добавлю
попобовал я по Вашему совету вместо
getAllFormsWithGramInfo заюзать findWord

var_dump($morphy->getAllFormsWithGramInfo('ХОЖУ'));
var_dump($morphy->findWord('ХОЖУ'));

абсолютно разный результат, если в первом случае я действительно получаю все сволоформы с грамматикой, то второй вообще даёт похоже все внутренние правила морфологических преобразований, и никак с аргументом функции не связано (похоже я явно недопонимаю как её использовать)
На днях залью документацию на sf, посмотри как сделан getAllFormsWithGramInfo() в common.php

ещё вопросик - расшифровку сокращений
"С ИМ,МН,МР,ОД" и прочих где можно глянуть ?
тут почти все есть: http://aot.ru/docs/rusmorph.html

функция не всегда срабатывает

var_dump(transform_word($morphy, 'СТУЛОМ', 'КРЕСЛО'));
у них род разный.
так делать не надо: сел на стул => сел на кресло ;)

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

-~{}~ 07.08.09 04:56:

GTHack
посмотри этот вариант
http://phpclub.ru/paste/index.php?show=2318

сделал исключение для родов + это
function("ХОЖУ", "МН ПРОШ") = "ХОДИЛИ"
 

GTHack

Новичок
класс phpMorphy_TextTools - СУПЕР !
(я его в src вынес и в common.php подключил - думаю нужно и в дистрибутив и в документацию включить)
changeFormByGrammems - СУПЕР !
вот еще бы не просто словоформы возвращала, а из грамеммы (незнаю правильно ли я выражаюсь)

а то если ей скормишь недостаточное кол-во параметров - она ведь массив возвращает, со всеми возможными вариантами - это и правильно, но вот уточнить какие именно варианты возвращает - имхо не помешает !

P.S. ща на своем блоге зафигачу хвалебную статейку о великом и могучем классе PHPMorphy :)

(только надо сначала синонимайзер до ума довести)
 

pilot911

Новичок
у них род разный.
так делать не надо: сел на стул => сел на кресло


то есть вообще без вариантов, когда синоним другого рода ?
 

GTHack

Новичок
Pilot911 - уже ведь всё переделано - бери и пользуйся
последнее сообщение от Жигана почитай
 

pilot911

Новичок
сюда запощу код, потому как в пастебине он исчезнет со временем

PHP:
<?php
class phpMorphy_TextTools_Grammems {
    protected
        $all_grammems,
        $essential_grammems,
        $essential_grammems_with_gender;

    function __construct() {
        $this->all_grammems = $this->getAllGrammems();
        $this->essential_grammems = $this->getEssentialGrammems();
        $this->essential_grammems_with_gender =
array_merge($this->essential_grammems, $this->getGenderGrammems());
    }

    protected function flatizeArray($ary) {
        return call_user_func_array('array_merge', $ary);
    }

    protected function getAllGrammems() {
        return array(
            // род
            array('МР', 'ЖР', 'СР'),
            // одушевленность, неодушевленность
            array('ОД', 'НО'),
            // число
            array('ЕД', 'МН'),
            // падеж
            array('ИМ', 'РД', 'ДТ', 'ВН', 'ТВ', 'ПР', 'ЗВ',
'2'),
            // действительный, страдательный залог
            array('ДСТ', 'СТР'),
            // время
            array('НСТ', 'ПРШ', 'БУД'),
            // повелительная форма глагола
            array('ПВЛ'),
            // лицо
            array('1Л', '2Л', '3Л'),
            // краткость
            array('КР'),
            // сравнительная форма
            array('СРАВН'),
            // превосходная степень
            array('ПРЕВ')
        );
    }

    protected function getEssentialGrammems() {
        $r = $this->getAllGrammems();
        unset($r[0]); // род
        unset($r[1]); // одушевленность, неодушевленность
        // граммемы по которым происходит сопоставление

        return $this->flatizeArray($r);
    }

    protected function getGenderGrammems() {
        list($result) = $this->getAllGrammems();

        return $result;
    }

    function normalizeGrammems($grammems, $withGender) {
        return array_values(
            array_intersect(
                $grammems,
                $withGender ? $this->essential_grammems_with_gender :
$this->essential_grammems
            )
        );
    }

    function stripGrammems($grammems, $exclude) {
        return array_diff($grammems, $exclude);
    }

    function isEqualGrammems($a, $b) {
        return count($a) == count($b) && count(array_diff($a, $b)) == 0;
    }

    function findGrammemsGroup($grammem) {
        foreach($this->all_grammems as $group) {
            if(in_array($grammem, $group)) {
                return $group;
            }
        }

        return false;
    }
}

class phpMorphy_TextTools {
    protected
        $morphy,
        $grammems
        ;

    function __construct(phpMorphy $morphy) {
        $this->morphy = $morphy;
        $this->grammems = $this->createGrammems();
    }

    protected function createGrammems() {
        return new phpMorphy_TextTools_Grammems();
    }

    protected function isCheckGenderForPartOfSpeech($pos) {
        // для существительных род не проверяем
        return $pos != 'С';
    }

    function changeFormByPattern($original, $replacement, $strict = true) {
        $result_forms = array();

        // ищем слово в словаре, получаем коллекцию парадигм, в которые входит
слово
        if(false === ($collection = $this->morphy->findWord($original))) {
            throw new Exception("Can`t find $original word");
        }

        // ищем в словаре слово на которве бедкм менять
        if(false === ($collection_repl = $this->morphy->findWord($replacement)))
{
            throw new Exception("Can`t find $replacement word");
        }

        // омонимия на уровне разных частей речи - ключ, лук, душа, стали и
т.п.
        foreach($collection as $descriptor) {
            // омонимия внутри форм одного слова
            // стол имеет одинаковые формы для именительного и винительного
падежей
            foreach($descriptor->getFoundWordForm() as $form) {
                // запоминаем граммемы и часть речи для $original
                $part_of_speech = $form->getPartOfSpeech();
                $with_gender =
$this->isCheckGenderForPartOfSpeech($part_of_speech);
                $grammems =
$this->grammems->normalizeGrammems($form->getGrammems(), $with_gender);
                //var_dump($form->getWord(), $grammems);
                
                // ищем форму в $collection_repl c этими характеристиками

                // Ищем в коллекции по части речи
                foreach($collection_repl->getByPartOfSpeech($part_of_speech) as
$descriptor_repl) {
                    $forms_repl = count($grammems) ?
$descriptor_repl->getWordFormsByGrammems($grammems) : $descriptor_repl;

                    foreach($forms_repl as $form_repl) {
                        if($form_repl->getPartOfSpeech() == $part_of_speech) {
                            // ВСЕ граммемы для каждой найденной формы должны
совпадать с $grammems (если $strict === true)
                            $repl_grammems =
$this->grammems->normalizeGrammems($form_repl->getGrammems(), $with_gender);

                            if(
                                $form_repl->getPartOfSpeech() == $part_of_speech
&& 
                                (!$strict ||
$this->grammems->isEqualGrammems($repl_grammems, $grammems))
                            ) {
                                // ОК, нашли форму
                                $result_forms[$form_repl->getWord()] = 1;
                            }
                        }
                    }
                }
            }
        }

        return array_keys($result_forms);
    }

    protected function stripInGroupGrammems($all, $user) {
        foreach($user as $grammem) {
            if(false !== ($group =
$this->grammems->findGrammemsGroup($grammem))) {
                $all = $this->grammems->stripGrammems($all, $group);
            }
        }

        return $all;
    }

/*
    function changeFormByGrammems($original, $grammems) {
        if(false === ($collection = $this->morphy->findWord($original))) {
            throw new Exception("Can`t find $original word");
        }

        $result = array();
        $grammems = (array)$grammems;

        foreach($collection as $descriptor) {
            foreach($descriptor->getFoundWordForm() as $found_form) {
                $stripped_grammems =
$this->stripInGroupGrammems($found_form->getGrammems(), $grammems);
                $new_grammems = array_merge($stripped_grammems, $grammems);

                foreach($descriptor->getWordFormsByGrammems($new_grammems) as
$form) {
                    $result[$form->getWord()] = 1;
                }
            }
        }

        return array_keys($result);
    }
*/
    function changeFormByGrammems($original, $grammems) {
        if(false === ($collection = $this->morphy->findWord($original))) {
            throw new Exception("Can`t find $original word");
        }

        $result = array();
        $grammems = (array)$grammems;

        foreach($collection as $descriptor) {
            foreach($descriptor->getFoundWordForm() as $found_form) {
                foreach($descriptor->getWordFormsByGrammems($grammems) as $form)
{
                    if($form->getPartOfSpeech() ==
$found_form->getPartOfSpeech()) {
                        $result[$form->getWord()] = 1;
                    }
                }
            }
        }

        return array_keys($result);
    }
}

$tools = new phpMorphy_TextTools($morphy);

$words = array(
    'ДИВАНАМИ' => 'КРОВАТЬ',
    'СТУЛОМ' => 'КРЕСЛО',

    'ХОРОШИЙ' => 'ПЛОХОЙ',
    'ПОЛУЧШЕ' => 'ПЛОХОЙ',
    'НАИЛУЧШИЕ' => 'ПЛОХОЙ',
    'ХОРОША' => 'ПЛОХОЙ',

    'ДУША' => 'СТАЛЬ',

    'БИЛИ' => 'ГЛАДИТЬ',
    'БЬЮЩИЙ' => 'ПИТЬ',

    'ТОРЧКОМ' => 'СТОЙМЯ',

    'ДВАДЦАТЬЮ' => 'ТРИДЦАТЬ',

    'А' => 'НО',

);

foreach($words as $from_word => $to_word) {
    $result = $tools->changeFormByPattern($from_word, $to_word);

    echo $from_word, ": ", implode(', ', $result), "\n";
}

var_dump($tools->changeFormByGrammems('ХОЖУ', array('МН', 'ПРШ')));
?>
 

Reenz

Новичок
Всем добрый день. Наткнулся на эту тему, очень мне пригодилась, автору спасибо.
Так вот есть вопрос у меня - а можно ли как-то перебрать все слова, содержащиеся в дефолтной базе? А то у меня задача сейчас - создать таблицу всех словоформ глаголов. Т.е. БД, где записи вида: глагол-в-нач-форме / прш-время-ед-число / прш-время-мн-число и т.д.

Буду благодарен если подскажете.
 

Жигaн

Новичок
GTHack
а то если ей скормишь недостаточное кол-во параметров - она ведь массив возвращает, со всеми возможными вариантами - это и правильно, но вот уточнить какие именно варианты возвращает - имхо не помешает !
PHP:
    function changeFormByGrammems($original, $grammems, $withGrammems = false) {
        if(false === ($collection = $this->morphy->findWord($original))) {
            throw new Exception("Can`t find $original word");
        }

        $result = array();
        $grammems = (array)$grammems;

        foreach($collection as $descriptor) {
            foreach($descriptor->getFoundWordForm() as $found_form) {
                foreach($descriptor->getWordFormsByGrammems($grammems) as $form) {
                    if($form->getPartOfSpeech() == $found_form->getPartOfSpeech()) {
                        $result[$form->getWord()][] = implode(', ', $form->getGrammems());
                    }
                }
            }
        }

        return $withGrammems ? $result : array_keys($result);
    }

var_dump($tools->changeFormByGrammems('ХОЖУ', array('МН', 'ПРШ'), true));
так пойдёт?

Reenz
Т.е. БД, где записи вида: глагол-в-нач-форме / прш-время-ед-число / прш-время-мн-число и т.д.
Если тебе только это нужно, мне проще будет сделать дамп в нужном виде. Только уточни структуру.
 

Reenz

Новичок
Спасибо, я уже пропарсил xml соварик и из него все сгенерировал )
 

Reenz

Новичок
Автор оригинала: GTHack
можно поподробнее ?
Да это я поторопился =/ при подробном рассмотрении понял что лажа получилась =)

У меня вот вопрос, можно ли как-то с помощью функций, первое - узнать часть речи слова (С/Г/ПРИЛ и т.д.). И второе - узнать id флексии леммы этого слова.
 

Rin

*
Код:
часть_речи
        C=существительное
        П=прилагательное
        МС=местоимение-существительное
        Г=глагол в личной форме
        ПРИЧАСТИЕ=причастие
        ДЕЕПРИЧАСТИЕ=деепричастие
        ИНФИНИТИВ=инфинитив
        МС-ПРЕДК=местоимение-предикатив
        МС-П=местоименное прилагательное
        ЧИСЛ=числительное (количественное)
        ЧИСЛ-П=порядковое числительное
        Н=наречие
        ПРЕДК=предикатив
        ПРЕДЛ=предлог
        СОЮЗ=союз
        МЕЖД=междометие
        ЧАСТ=частица
        ВВОДН=вводное слово
род
        мр=мужской
        жр=женский
        ср=средний
одушевленность
        од=одушевленность
        но=неодушевленность
число
        ед=единственное
        мн=множественное
падеж
        им=именительный
        рд=родительный
        дт=дательный
        вн=винительный
        тв=творительный
        пр=предложеный
        зв=звательный
        2=второй родительный или второй предложный
вид
        св=совершенный
        нс=несовершенный
переходность
        пе=переходная форма
        нп=непереходная форма
повелительность
        пвл=повелительная форма глагола
        безл=безличный глагол
залог
        дст=действительный
        стр=страдательный
время
        нст=настоящее
        прш=прошедшее
        буд=будущее
лицо
        1л=первое
        2л=второе
        3л=третье
другие_параметры
        0=неизменяемое
        кр=краткость (для прилагательных и причастий).
        сравн=сравнительная форма (для прилагательных).
        имя=Имя
        фам=Фамилия
        отч=Отчество
        лок=локативность
        орг=организация
        кач=качественное прилагательное
        вопр=вопросительность (для наречий)
        относ=относительность (для наречий)
        дфст=слово обычно не имеет множественного числа
        опч=частая опечатка или ошибка
        жарг=жаргонизм
        арх=архаизм
        проф=профессионализм
        аббр=аббревиатура
 

Жигaн

Новичок
Reenz
Узнать часть речи можно при помощи
1)
PHP:
$morphy->getPartOfSpeech($word)
2)
PHP:
if(false !== ($paradigms = $morphy->findWord($word))) {

foreach($paradigms as $paradigm) {
    foreach($paradigm->getFoundWordForm() as $form) {
        $pos = $form->getPartOfSpeech()
    }
}

}
первый вариант намного быстрее второго (по скорости как getPseudoRoot)

И второе - узнать id флексии леммы этого слова.
Это можно, но зачем?
 

GTHack

Новичок
А что на счёт перебора словаря то ?
можно допустим достать все глаголы ?
и как можно дополнить словарь ?
 

Reenz

Новичок
PHP:
$morphy->getPartOfSpeech($word)
Спасибо!

А по поводу id флексии, просто хотел сделать идентефикацию по номеру леммы. Т.е. в идеале надо получить порядковый номер леммы. Леммы то встречаются одинаковые по тексту. Хочу просто сделать функцию, на входе - текст, на выходе - слова заменяются указателями на часть речи и лемму слова. Это для частотного анализа. Хочу упорядочить леммы по частям речи и частоте использования.

$morphy->findWord('ТАНЦУЮ');

[0] => Array
(
[count] => 1
[offset] => 570016
[cplen] => 0
[plen] => 0
[flen] => 2
[common_ancode] => 669
[forms_count] => 132
[packed_forms_count] => 76
[affixes_size] => 723
[form_no] => 1
[pos_id] => 2
[base_prefix] =>
[base_suffix] => ЕВАТЬ
)

[offset] - я думал вот это id леммы или флексии, но что-то я смотрю не подходит. Я с xml-словарем работал, а в дефолтном .bin леммы вообще так же упорядочены, по алфавиту?

GTHack: я как тест попробовал построить такую таблицу по существительным - id + 12 столбцов по падежам и числам, все нормально. Но для глаголов как оказалось столбцов надо 130 и больше, с учетом всех отглагольных причастий, деепричастий и т.д. Я отказался от этого.
 

Жигaн

Новичок
Reenz
Понял что нужно, сейчас покажу как сделать.

-~{}~ 16.08.09 03:04:

Reenz
Извини, не мог залить по гпрс каналу.

Есть три варианта для решения вопроса

1) Используя mysql.

Скачай дамп https://sourceforge.net/projects/phpmorphy/files/phpmorphy-dictionaries/0.3.x - source/phpmorphy-0.3.x-mysql-dump.zip/download
PHP:
select
concat(f2.prefix, l.base_str, f2.suffix) as form,
concat(f.prefix, l.base_str, f.suffix) as lemma,
p2.pos
from lemmas l
join flexias f on f.`form_no`=0 and f.`flexia_model_id`=l.flexia_id
join flexias f2 on f2.`flexia_model_id`=f.flexia_model_id
join ancodes a on a.`id`=f.`ancode_id`
join ancodes a2 on a2.`id`=f2.`ancode_id`
join poses p on p.`id`=a.`pos_id`
join poses p2 on p2.id=a2.pos_id
where
p.`pos`='ИНФИНИТИВ'
p.s. На индексы посмотри, может не хватать.

2) средствами библиотеки
3)
скачай версию 0.3.3. https://sourceforge.net/projects/phpmorphy/files/phpmorphy/0.3.3/phpmorphy-0.3.3.zip/download
В предыдущей был баг, работать не будет.

PHP:
class Dumper {
    protected
        $helper;

    function __construct($morphy) {
        $this->helper = $morphy->getCommonMorphier()->getHelper();
    }

    function handler($path, $binAnnot) {
        // в $path слово
        var_dump($path, $this->helper->getPartOfSpeech($path, $binAnnot));
    }
}

$dumper = new Dumper($morphy);
$fsa = $morphy->getCommonMorphier()->getFinder()->getFsa();
$fsa->collect($fsa->getRootTrans(), array($dumper, 'handler'), true);
Перебор всех слов в словаре, работает очень долго.

4) использую mealy_list
Тут возни много, если не подойдут предыдущие варианты, скажу как сделать дамп через mealy_list.

-~{}~ 16.08.09 03:08:

GTHack
и как можно дополнить словарь ?
При помощи MorphWizard, посмотри на http://aot.ru/download.php
Сделать морду к базе у меня пока руки не доходят.
 

after7days

Новичок
Потрясающе, спасибо огромное за библиотеку!

А структура словарей у вас нигде не описана?
Я бы попробовал написать конвертер из каких-нибудь популярных баз.
 
Сверху