Определение границ предложения php
Я хотел бы разделить текст на предложения в PHP. В настоящее время я использую регулярное выражение, которое обеспечивает точность ~ 95% и улучшает работу с использованием лучшего подхода. Я видел инструменты NLP, которые делают это в Perl, Java и C, но не вижу ничего, что бы соответствовало PHP. Знаете ли вы о таком инструменте?
Ответы
Ответ 1
Расширенное регулярное выражение
Предполагая, что вы позаботились об обращении: аббревиатуры Mr.
и Mrs.
и т.д., то следующее однорежимное решение работает очень хорошо:
<?php // test.php Rev:20160820_1800
$split_sentences = '%(?#!php/i split_sentences Rev:20160820_1800)
# Split sentences on whitespace between them.
# See: http://stackoverflow.com/a/5844564/433790
(?<= # Sentence split location preceded by
[.!?] # either an end of sentence punct,
| [.!?][\'"] # or end of sentence punct and quote.
) # End positive lookbehind.
(?<! # But don\'t split after these:
Mr\. # Either "Mr."
| Mrs\. # Or "Mrs."
| Ms\. # Or "Ms."
| Jr\. # Or "Jr."
| Dr\. # Or "Dr."
| Prof\. # Or "Prof."
| Sr\. # Or "Sr."
| T\.V\.A\. # Or "T.V.A."
# Or... (you get the idea).
) # End negative lookbehind.
\s+ # Split on whitespace between sentences,
(?=\S) # (but not at end of string).
%xi'; // End $split_sentences.
$text = 'This is sentence one. Sentence two! Sentence thr'.
'ee? Sentence "four". Sentence "five"! Sentence "'.
'six"? Sentence "seven." Sentence \'eight!\' Dr. '.
'Jones said: "Mrs. Smith you have a lovely daught'.
'er!" The T.V.A. is a big project! '; // Note ws at end.
$sentences = preg_split($split_sentences, $text, -1, PREG_SPLIT_NO_EMPTY);
for ($i = 0; $i < count($sentences); ++$i) {
printf("Sentence[%d] = [%s]\n", $i + 1, $sentences[$i]);
}
?>
Обратите внимание, что вы можете легко добавлять или отбирать аббревиатуры из выражения. Учитывая следующий тестовый параграф:
Это предложение одно. Предложение два! Предложение три? Предложение "четыре". Приговор "пять"! Предложение "шесть"? Приговор "семь". Приговор "восемь!" Доктор Джонс сказал: "Миссис Смит, у тебя прекрасная дочь!" T.V.A. это большой проект!
Вот результат из script:
Sentence[1] = [This is sentence one.]
Sentence[2] = [Sentence two!]
Sentence[3] = [Sentence three?]
Sentence[4] = [Sentence "four".]
Sentence[5] = [Sentence "five"!]
Sentence[6] = [Sentence "six"?]
Sentence[7] = [Sentence "seven."]
Sentence[8] = [Sentence 'eight!']
Sentence[9] = [Dr. Jones said: "Mrs. Smith you have a lovely daughter!"]
Sentence[10] = [The T.V.A. is a big project!]
Существенное регулярное решение
Автор вопроса отметил, что приведенное выше решение "игнорирует многие варианты" и не является достаточно общим. Я не уверен, что это значит, но суть вышеупомянутого выражения примерно такая же чистая и простая, как вы можете получить. Вот он:
$re = '/(?<=[.!?]|[.!?][\'"])\s+(?=\S)/';
$sentences = preg_split($re, $text, -1, PREG_SPLIT_NO_EMPTY);
Обратите внимание, что оба решения правильно идентифицируют предложения, заканчивающиеся кавычкой после окончания пунктуации. Если вам не нужны соответствующие предложения, заканчивающиеся в кавычки, регулярное выражение может быть упрощено до: /(?<=[.!?])\s+(?=\S)/
.
Изменить: 20130820_1000 Добавлено T.V.A.
(другое прерывистое слово, которое нужно игнорировать) для регулярного выражения и тестовой строки. (чтобы ответить на вопрос о комментариях PapyRef)
Редактировать: 20130820_1800. Настроить и переименовать регулярное выражение и добавить shebang. Также исправлены регулярные выражения, чтобы предотвратить разделение текста на конечные пробелы.
Ответ 2
Небольшое улучшение для кого-то другого:
$re = '/# Split sentences on whitespace between them.
(?<= # Begin positive lookbehind.
[.!?] # Either an end of sentence punct,
| [.!?][\'"] # or end of sentence punct and quote.
) # End positive lookbehind.
(?<! # Begin negative lookbehind.
Mr\. # Skip either "Mr."
| Mrs\. # or "Mrs.",
| Ms\. # or "Ms.",
| Jr\. # or "Jr.",
| Dr\. # or "Dr.",
| Prof\. # or "Prof.",
| Sr\. # or "Sr.",
| \s[A-Z]\. # or initials ex: "George W. Bush",
# or... (you get the idea).
) # End negative lookbehind.
\s+ # Split on whitespace between sentences.
/ix';
$sentences = preg_split($re, $story, -1, PREG_SPLIT_NO_EMPTY);
Ответ 3
Как низкотехнологичный подход, вы можете захотеть использовать серию вызовов explode
в цикле, используя.,!, и? как игла. Это будет очень интенсивно для памяти и процессора (поскольку большинство текстовых обработок). У вас будет куча временных массивов и один главный массив со всеми найденными предложениями, численно проиндексированными в правильном порядке.
Кроме того, вам нужно будет проверить общие исключения (например, в титрах, таких как Mr. и Dr.), но со всем, что находится в массиве, эти типы проверок не должны быть такими уж плохими.
Я не уверен, что это лучше, чем регулярное выражение с точки зрения скорости и масштабирования, но это было бы целесообразно. Насколько велики эти блоки текста, которые вы хотите разбить на предложения?
Ответ 4
Я использовал это регулярное выражение:
preg_split('/(?<=[.?!])\s(?=[A-Z"\'])/', $text);
Не будет работать над предложением, начинающимся с числа, но должно иметь очень мало ложных срабатываний. Конечно, то, что вы делаете, тоже. Моя программа теперь использует
explode('.',$text);
потому что я решил, что скорость важнее точности.
Ответ 5
Создайте список сокращений, подобных этому
$skip_array = array (
'Jr', 'Mr', 'Mrs', 'Ms', 'Dr', 'Prof', 'Sr' , etc.
Скомпилируйте их в выражение
$skip = '';
foreach($skip_array as $abbr) {
$skip = $skip . (empty($skip) ? '' : '|') . '\s{1}' . $abbr . '[.!?]';
}
Запустите этот preg_split, чтобы разбить предложения.
$lines = preg_split ("/(?<!$skip)(?<=[.?!])\s+(?=[^a-z])/",
$txt, -1, PREG_SPLIT_NO_EMPTY);
И если вы обрабатываете HTML, следите за тем, чтобы теги были удалены, что устраняет пробел между предложениями. <p></p>
Если у вас situations.Like
этот where.They
склеивается, становится намного сложнее разобрать.
Ответ 6
@ridgerunner Я написал ваш PHP-код в С#
В результате получается 2 предложения:
- Mr. J. Dujardin régle sa T.V.
- A. en esp. uniquement
Правильным результатом должно быть предложение: г-н J. Dujardin régle sa T.V.A. en esp. uniquement
и с нашим тестовым абзацем
string sText = "This is sentence one. Sentence two! Sentence three? Sentence \"four\". Sentence \"five\"! Sentence \"six\"? Sentence \"seven.\" Sentence 'eight!' Dr. Jones said: \"Mrs. Smith you have a lovely daughter!\" The T.V.A. is a big project!";
Результат
index: 0 sentence: This is sentence one.
index: 22 sentence: Sentence two!
index: 36 sentence: Sentence three?
index: 52 sentence: Sentence "four".
index: 69 sentence: Sentence "five"!
index: 86 sentence: Sentence "six"?
index: 102 sentence: Sentence "seven.
index: 118 sentence: " Sentence 'eight!'
index: 136 sentence: ' Dr. Jones said: "Mrs. Smith you have a lovely daughter!
index: 193 sentence: " The T.V.
index: 203 sentence: A. is a big project!
Код С#:
string sText = "Mr. J. Dujardin régle sa T.V.A. en esp. uniquement";
Regex rx = new Regex(@"(\S.+?
[.!?] # Either an end of sentence punct,
| [.!?]['""] # or end of sentence punct and quote.
)
(?<! # Begin negative lookbehind.
Mr. # Skip either Mr.
| Mrs. # or Mrs.,
| Ms. # or Ms.,
| Jr. # or Jr.,
| Dr. # or Dr.,
| Prof. # or Prof.,
| Sr. # or Sr.,
| \s[A-Z]. # or initials ex: George W. Bush,
| T\.V\.A\. # or "T.V.A."
) # End negative lookbehind.
(?=|\s+|$)",
RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
foreach (Match match in rx.Matches(sText))
{
Console.WriteLine("index: {0} sentence: {1}", match.Index, match.Value);
}
Ответ 7
Попробуйте это -
https://stackoverflow.com/info/366284/natural-language-identification-in-php
http://pear.php.net/package/Text_LanguageDetect