Чтобы подготовить русскоязычные тексты для дальнейшего анализа, мне однажды понадобилось разбить их на предложения. Разумеется, автоматически. Что приходит на ум в первую очередь, если нужно разделить текст на предложения? Разбить по точкам — угадал?
Если вы начнете это делать, то довольно быстро столкнетесь с тем, что точка — это не всегда разделитель предложений (“т.к.”, “т.д.”, “т.п.”, “пр.”, “S.T.A.L.K.E.R.”). Причем эти токены не всегда будут исключениями при разбивке текста на предложения. Например, “т.п.” может быть в середине предложения, а может и в конце.
Вопросительный и восклицательный знак тоже не всегда разделяют текст на предложения. Например, “Yachoo!”. Предложения могут разделять и другие знаки, например, двоеточие (когда следует список из отдельных утверждений).
Поэтому я долго не думая поискал готовый инструмент и остановился на Томита-парсере от Яндекса. О нем и расскажу.
Вообще, Томита-парсер — это мощный инструмент для извлечения фактов из текста. Сегментатор (разбивка текста на предложения) в нем — лишь часть проекта. Томита-парсер можно скачать сразу в виде бинарника и запускать из командной строки. Мне эта система понравилась тем, что она работает на основе правил, не прихотлива к ресурсам и дает возможность настраивать процесс сегментации. А также по моим наблюдениям в большинстве случаев отлично справляется с задачей.
Еще мне понравилось, что при возникновении вопросов можно задать их на github и иногда даже получить ответ.
Запуск
Запускается Томита-парсер таким образом
$ echo "Парсеp, Разбей эти... буквы, знаки и т.п. на предложения. И покажи пож. как со словом S.T.A.L.K.E.R. получится." | ./tomita-linux64 config.proto
То есть чтение происходит из stdin, вывод — в stdout.
Результат получаем примерно такой:
[10:01:17 17:06:37] - Start. (Processing files.)
Парсер , Разбей эти . . . буквы , знаки и т.п . на предложения .
И покажи пож . как со словом S. T. A. L. K. E. R. получится .
[10:01:17 17:06:37] - End. (Processing files.)
Одна строка — одно предложение. На этом примере видно, что разбивка прошла корректно.
Особенности
На что обращаем внимание.
- В результат добавляются пробелы перед знаками пунктуации.
- Лишние пробелы удаляются.
- Происходит автоматическая коррекция некоторых опечаток (например, в исходном тексте последняя буква в слове “Парсеp” — это английская “пи”, а в обработанном тексте — это уже русская “эр”).
Эти особенности могут быть как плюсами так и минусами в зависимости от того, что вы дальше будете делать с полученным текстом. Я, например, дальше по полученному тексту строю синтаксические деревья с помощью SyntaxNet, а там как раз знаки препинания должны быть отделены пробелами, так что для меня это плюс.
Настройки
Я столкнулся с тем, что при анализе предложений, содержащих адреса, система разбивает их некорректно. Пример:
$ echo "Я живу на ул. Ленина и меня зарубает время от времени." | ./tomita-linux64 config.proto
[10:01:17 18:00:38] - Start. (Processing files.)
Я живу на ул .
Ленина и меня зарубает время от времени .
[10:01:17 18:00:38] - End. (Processing files.)
Как видим, разбивка прошла некорректно. К счастью, такие вещи можно настраивать. Для этого в gzt файле прописываем
TAbbreviation "ул." {
key = { "abbreviation_г." type = CUSTOM }
text = "ул."
type = NewerEOS
}
То есть просим считать, что после “ул.” предложение всегда продолжается. Пробуем:
$ echo "Я живу на ул. Ленина и меня зарубает время от времени." | ./tomita-linux64 config.proto
[10:01:17 18:20:59] - Start. (Processing files.)
Я живу на ул. Ленина и меня зарубает время от времени .
[10:01:17 18:20:59] - End. (Processing files.)
Теперь все хорошо. Пример настроек я выложил на github.
Какие минусы
О некоторых особенностях я упомянул выше. Пару слов о минусах инструмента на данный момент.
Первое — это документация. Она есть, но в ней описано не все. Попробовал сейчас поискать настройку, которую описал выше — не нашел.
Второе — это отсутствие легкой возможности работы с парсером в режиме демона. Обработка одного текста за 0.3-0.4 секунды с учетом загрузки всей системы в память для меня не критична, так как вся обработка идет в фоновых процессах и среди них есть гораздо более жирные задачи. Для кого-то это может стать узким местом.
Пример вызова из PHP
Как и говорил выше, подаем входные данные в stdin, читаем из stdout. Пример ниже сделан на основе github.com/makhov/php-tomita:
<?php
class TomitaParser
{
/**
* @var string Path to Yandex`s Tomita-parser binary
*/
protected $execPath;
/**
* @var string Path to Yandex`s Tomita-parser configuration file
*/
protected $configPath;
/**
* @param string $execPath Path to Yandex`s Tomita-parser binary
* @param string $configPath Path to Yandex`s Tomita-parser configuration file
*/
public function __construct($execPath, $configPath)
{
$this->execPath = $execPath;
$this->configPath = $configPath;
}
public function run($text)
{
$descriptors = array(
0 => array('pipe', 'r'), // stdin
1 => array('pipe', 'w'), // stdout
2 => array('pipe', 'w') // stderr
);
$cmd = sprintf('%s %s', $this->execPath, $this->configPath);
$process = proc_open($cmd, $descriptors, $pipes, dirname($this->configPath));
if (is_resource($process))
{
fwrite($pipes[0], $text);
fclose($pipes[0]);
$output = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
return $this->processTextResult($output);
}
throw new Exception('proc_open fails');
}
/**
* Обработка текстового результата
* @param string $text
* @return string[]
*/
public function processTextResult($text)
{
return array_filter(explode("n", $text));
}
}
$parser = new TomitaParser('/home/mnv/tmp/tomita/tomita-linux64', '/home/mnv/tmp/tomita/config.proto');
var_dump($parser->run('Предложение раз. Предложение два.'));
Проверяем:
$ php example.php
/home/mnv/tmp/tomita/example.php:66:
array(2) {
[0] =>
string(32) "Предложение раз . "
[1] =>
string(32) "Предложение два . "
}
В завершение
Мне в процессе работы над текстом регулярно попадаются проекты, в которых авторы делают сегментатор самостоятельно. Возможно потому, что с первого взгляда задача кажется чуть проще, чем на самом деле. Надеюсь статья будет полезна тем, кто собирается сделать очередной сегментатор в рамках своего проекта и сэкономит время, выбрав готовый вариант.
Буду рад узнать из комментариев, каким инструментом для разбивки текста на предложения пользуетесь вы?
Автор: mnv