views:

3039

answers:

12

How do you take paragraph or large amount of text and break it into sentences (perferably using Ruby) taking into account cases such as Mr. and Dr. and U.S.A? (Assuming you just put the sentences into an array of arrays)

UPDATE: One possible solution I thought of involves using a parts-of-speech tagger (POST) and a classifier to determine the end of a sentence:

Getting data from Mr. Jones felt the warm sun on his face as he stepped out onto the balcony of his summer home in Italy. He was happy to be alive.

CLASSIFIER Mr./PERSON Jones/PERSON felt/O the/O warm/O sun/O on/O his/O face/O as/O he/O stepped/O out/O onto/O the/O balcony/O of/O his/O summer/O home/O in/O Italy/LOCATION ./O He/O was/O happy/O to/O be/O alive/O ./O

POST Mr./NNP Jones/NNP felt/VBD the/DT warm/JJ sun/NN on/IN his/PRP$ face/NN as/IN he/PRP stepped/VBD out/RP onto/IN the/DT balcony/NN of/IN his/PRP$ summer/NN home/NN in/IN Italy./NNP He/PRP was/VBD happy/JJ to/TO be/VB alive./IN

Can we assume, since Italy is a location, the period is the valid end of the sentence? Since ending on "Mr." would have no other parts-of-speech, can we assume this is not a valid end-of-sentence period? Is this the best answer to the my question?

Thoughts?

A: 

I'm not a Ruby guy, but a RegEx that split on

 ^(Mr|Mrs|Ms|Mme|Sta|Sr|Sra|Dr|U\.S\.A)[\.\!\?\"] [A-Z]

would be my best bet, once you've got the paragraph (split on \r\n). This assumes that your sentences are proper cased.

Obviously this is a fairly ugly RegEx. What about forcing two spaces between sentences

Eric
+1  A: 

Maybe try splitting it up by a period followed by a space followed by an uppercase letter? I'm not sure how to find uppercase letters, but that would be the pattern I'd start looking at.

Edit: Finding uppercase letters with Ruby.

Another Edit:

Check for sentence ending punctuation that follow words that don't start with uppercase letters.

Jarrod
Hello, Mr. Jarrod. I don't think it would work.
dangph
What if you split it up at the periods that followed words that don't start with uppercase letters?
Jarrod
This was exactly what I came up with, but I wanted to know if there were even better solutions. Granted it wouldn't work if the sentence ended with a proper noun such as "I went to Italy."
henry74
one very common case that would fail on is names like "Mr. Dibbler"
Martin DeMello
A: 

Breaking on a period followed by a space and a capitalized letter wouldn't fly for titles like "Mr. Brown."

The periods make things difficult, but an easy case to handle is exclamation points and question marks. However, there are cases that would make this not work. i.e. the corporate name of Yahoo!

Evan Meagher
A: 

Well obviously paragraph.split('.') won't cut it

#split will take a regex as an answer so you might try using a zero-width lookbehind to check for a word starting with a capital letter. Of course this will split on proper nouns so you may have to resort to a regex like this /(Mr\.|Mrs\.|U\.S\.A ...) which would horrendously ugly unless you built the regex programmatically.

Ryan Neufeld
A: 

I think this is not always resoluble, but you could split based on ". " (a period followed by and empty space) and verifying that the word before the period isn't in a list of words like Mr, Dr, etc.

But, of course, your list may omit some words, and in that case you will get bad results.

eKek0
+5  A: 

Just to make it clear, there is no simple solution to that. This is topic of NLP research as a quick Google search shows.

However, it seems that there are some open source projects dealing with NLP supporting sentence detection, I found the following Java-based toolset:

openNLP

Additional comment: The problem of deciding where sentences begin and end is also called sentence boundary disambiguation (SBD) in natural language processing.

0xA3
I wasn't able to find an easy ruby wrapper for openNLP - have you come across any? They did have an sentence splitter though...
henry74
@phillc: Well, so called sentence boundary disambiguation "is the problem in natural language processing of deciding where sentences begin and end". (http://en.wikipedia.org/wiki/Sentence_boundary_disambiguation)
0xA3
+2  A: 

This is a hard problem if you really care about getting it right. You'll find that NLP parser packages probably provide this functionality. If you want something faster, you'll need to end up duplicating some of that functionality with a trained probabilistic function of a window of tokens (you'd probably want to count a line feed as a token, since i may drop a period if it's the end of a paragraph).

Edit: I recommend the Stanford parser if you can use Java. I have no recommendation for other languages, but I'm very interested in hearing what else is out there that is open source.

Kevin Peterson
Yes, I've played with the Stanford NLP parser but didn't find a sentence splitter. If you're interested in using it, there is a rjb (ruby to java bridge) wrapper someone created on github which I was able to get working with relative ease. Here is the link for those of you interested http://github.com/tiendung/ruby-nlp/tree/masterNOTE: on windows, you must change colons to semi-colons when loading java libraries. Cheers.
henry74
You are right, there's no sentence splitter in the parser package, but there is a tokenizer, which gets you part of the way there. It handles things like those mentioned, "Mr." as a token versus "." as an end of sentence.
Kevin Peterson
There is a sentence splitter: edu.stanford.nlp.process.DocumentPreprocessor . Try the command: java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument.txt > oneTokenizedSentencePerLine.txt . (This is done via a (good but heuristic) FSM, so it's fast; you're not running the probabilistic parser.)
Christopher Manning
+1  A: 

Unfortunately I'm not a ruby guy but maybe an example in perl will get you headed in the right direction. Using a non matching look behind for the ending punctuation then some special cases in a not behind followed by any amount of space followed by look ahead for a capital letter. I'm sure this isn't perfect but I hope it points you in the right direction. Not sure how you would know if U.S.A. is actually at the end of the sentence...

#!/usr/bin/perl

$string = "Mr. Thompson is from the U.S.A. and is 75 years old. Dr. Bob is a dentist. This is a string that contains several sentances. For example this is one. Followed by another. Can it deal with a question?  It sure can!";

my @sentances = split(/(?:(?<=\.|\!|\?)(?<!Mr\.|Dr\.)(?<!U\.S\.A\.)\s+(?=[A-Z]))/, $string);

for (@sentances) {
    print $_."\n";
}
Copas
+7  A: 

Try looking at the Ruby wrapper around the Stanford Parser. It has a getSentencesFromString() function.

StompChicken
I'll continue to play with the Stanford parser - it's in there somewhere! Thanks!
henry74
edu.stanford.nlp.process.DocumentPreprocessor, by the way
StompChicken
Yes, either via the Ruby wrapper or directly by calling edu.stanford.nlp.process.DocumentPreprocessor (from code or from the command-line:java edu.stanford.nlp.process.DocumentPreprocessor /u/nlp/data/lexparser/textDocument.txt > oneTokenizedSentencePerLine.txt ,you can divide text into sentences. (This is done via a (good but heuristic) FSM, so it's fast; you're not running the probabilistic parser.)
Christopher Manning
+1  A: 

Take a look at the Python sentence splitter in NLTK (Natural Language Tool Kit):

Punkt sentence tokenizer

It's based on the following paper:

Kiss, Tibor and Strunk, Jan (2006): Unsupervised Multilingual Sentence Boundary Detection. Computational Linguistics 32: 485-525.

The approach in the paper is quite interesting. They reduce the problem of sentence splitting to the problem of determining how strongly a word is associated with following punctuation. The overloading of periods after abbreviations is responsible for most of the ambiguous periods, so if you can identify the abbreviations you can identify the sentence boundaries with a high probability.

I've tested this tool informally a bit and it seems to give good results for a variety of (human) languages.

Porting it to Ruby would be non-trivial, but it might give you some ideas.

pat
A: 

THANKS!

I really enjoyed this discussion, so I got interested in the parser. I tried it and I wrote down some notes on how to get everything working with Ruby and or Rails!

Trying to go with the regular expression was a nightmare..

my 2 cents

+1  A: 

Looks like this ruby gem might do the trick.

http://github.com/SlyShy/Tactful_Tokenizer

I'll give it a shot. Thanks!
henry74