web-dev-qa-db-ja.com

Python-テキストを文に分割するための正規表現(文章トークン化)

文字列から文章のリストを作成し、それらを印刷したい。これを行うためにNLTKを使用したくありません。そのため、小数、略語、名前のタイトルではなく、文に.comが含まれている場合は、文の最後でピリオドで分割する必要があります。これは、正規表現で機能しません。

import re

text = """\
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't.
"""
sentences = re.split(r' *[\.\?!][\'"\)\]]* *', text)

for stuff in sentences:
        print(stuff)    

どのように見えるかの出力例

Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. 
Did he mind?
Adam Jones Jr. thinks he didn't.
In any case, this isn't true...
Well, with a probability of .9 it isn't.
20
user3590149
(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s

これを試して。これを文字列に分割します。デモを確認することもできます。

http://regex101.com/r/nG1gU7/27

28
vks

わかりましたので、文トケナイザーは、正規表現、nltk、CoreNLPを使用して、私が少し詳しく調べたものです。あなたは自分で書くことになり、それはアプリケーションに依存します。このようなものはトリッキーで価値があり、人々は自分のトークナイザーコードをただ渡すだけではありません。 (最終的に、トークン化は決定論的な手順ではなく、確率的であり、コーパスまたはドメインにも非常に大きく依存します(例:ソーシャルメディアの投稿対Yelpのレビュー対...)

一般に、1つのグレートホワイトの絶対的な正規表現に依存することはできません。いくつかの正規表現(正と負の両方)を使用する関数を記述する必要があります。また、略語の辞書、およびそれを知っているいくつかの基本的な言語解析。 「I」、「USA」、「FCC」、「TARP」は大文字で表記されます。

これがどれほど簡単に非常に複雑になるかを説明するために、決定論的トークナイザーの機能仕様justを書いて、単一か複数かを決定してみましょうピリオド( '。'/'...')は文末、または他の何かを示します:

function isEndOfSentence(leftContext, rightContext)

  1. 数字または通貨内の小数に対してFalseを返します。 1.23、1.23ドル、「これは私の$ .02だけです」1.2.3のようなセクション参照、2014年9月7日のようなヨーロッパの日付形式、192.168.1.1のようなIPアドレス、MACアドレスも考慮してください...
  2. 既知の略語に対してFalseを返します(個々の文字にトークン化しないでください)。 「米国株式は下落している」;これには、既知の略語の辞書が必要です。 A.B.Cのような未知の略語を検出するためのコードを追加しない限り、その辞書以外のものは間違いを犯します。リストに追加します。
  3. 文の終わりの省略記号「...」は終端ですが、文の途中ではそうではありません。これは思ったほど簡単ではありません。左側のコンテキストと右側のコンテキストを確認する必要があります。具体的には、RHSが大文字であり、「I」や略語のような大文字で表記された単語を再度検討してください。以下に、あいまいさを証明する例を示します:彼女は私にとどまるように頼みました...私は1時間後に去りました。(それは1文か2文でしたか?決定することは不可能です)
  4. また、句読点のその他の文末ではない使用を検出および拒否するためのいくつかのパターンを作成することもできます。絵文字:-)、ASCII art、spaced ellipses。。。。 Twitter(適応性をさらに高める)。@midnightがTwitterユーザーであるか、 Comedy Centralで表示する 、テキストの省略形、または単に不要な/ジャンク/タイプミスの句読点であるかどうかを確認するにはどうすればよいですか? -つまらない。
  5. これらのすべての否定的なケースを処理した後、空白が続く孤立した期間は文末である可能性が高いとlikely意的に言うことができます。 (最終的に、あなたが本当に余分な正確さを買いたいなら、あなたは重みを使用するあなた自身の確率的な文トケナイザーを書いて、特定のコーパスでそれを訓練することになります(例えば、法的テキスト、放送メディア、StackOverflow、Twitter、フォーラムのコメントなど) )次に、模範とトレーニングエラーを手動で確認する必要があります。 Manning and Jurafskyの本またはCourseraコース[a]を参照してください。最終的には、支払う準備ができているのと同じくらい正確になります。
  6. 上記はすべて、明らかに英語/略語、米国の数値/時刻/日付形式に固有のものです。国や言語に依存しないようにしたい場合は、より大きな提案です。コーパス、ネイティブスピーカーがラベルを付けてQAを作成する必要があります。
  7. 上記はすべてASCIIのみです。入力をユニコードにすることを許可すると、物事はさらに難しくなります(そして、トレーニングセットは必然的にはるかに大きくなるか、よりまばらになります)

単純な(決定論的な)場合、function isEndOfSentence(leftContext, rightContext)はブール値を返しますが、より一般的な意味では確率的です:0.0-1.0のfloatを返します(特定の '。'が文末であるという信頼レベル) 。

参照:[a] Courseraのビデオ:「基本的なテキスト処理2-5-文のセグメンテーション-スタンフォードNLP-ダンジュラフスキー&クリスマニング教授」 [更新:YouTubeにあった非公式バージョンが削除されました]

27
smci

入力をドットまたは?ではなくスペースに従って分割してみてください。これを行うと、ドットまたは?は最終結果に出力されません。

>>> import re
>>> s = """Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't."""
>>> m = re.split(r'(?<=[^A-Z].[.?]) +(?=[A-Z])', s)
>>> for i in m:
...     print i
... 
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it.
Did he mind?
Adam Jones Jr. thinks he didn't.
In any case, this isn't true...
Well, with a probability of .9 it isn't.
4
Avinash Raj
sent = re.split('(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)(\s|[A-Z].*)',text)
for s in sent:
    print s

ここで使用される正規表現は次のとおりです:(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)(\s|[A-Z].*)

最初のブロック:(?<!\w\.\w.):このパターンは、負のフィードバックループで検索します(?<!)すべての単語(\w)に続いてフルストップ(\.)、他の単語が続く(\.)

2番目のブロック:(?<![A-Z][a-z]\.):このパターンは、大文字のアルファベットで始まるものをネガティブフィードバックループで検索します([A-Z])、小文字のアルファベット([a-z])ドットまで(\.) 見つかった。

3番目のブロック:(?<=\.|\?):このパターンは、ドット(\.) OR疑問符(\?)

4番目のブロック:(\s|[A-Z].*):このパターンは、3番目のブロックのドットOR疑問符の後に検索します。空白を検索します(\s) OR大文字のアルファベットで始まる文字列([A-Z].*)。このブロックは、入力が次の場合に分割することが重要です

Hello world.Hi今日はここにいます。

すなわち、ドットの後にスペースがあるか、スペースがない場合。

2
Mehul Gupta

これを試して:

(?<!\b(?:[A-Z][a-z]|\d|[i.e]))\.(?!\b(?:com|\d+)\b)
0
walid toumi

私の例は、ブラジルポルトガル語に適応したALiの例に基づいています。アリありがとう。

ABREVIACOES = ['sra?s?', 'exm[ao]s?', 'ns?', 'nos?', 'doc', 'ac', 'publ', 'ex', 'lv', 'vlr?', 'vls?',
               'exmo(a)', 'ilmo(a)', 'av', 'of', 'min', 'livr?', 'co?ls?', 'univ', 'resp', 'cli', 'lb',
               'dra?s?', '[a-z]+r\(as?\)', 'ed', 'pa?g', 'cod', 'prof', 'op', 'plan', 'edf?', 'func', 'ch',
               'arts?', 'artigs?', 'artg', 'pars?', 'rel', 'tel', 'res', '[a-z]', 'vls?', 'gab', 'bel',
               'ilm[oa]', 'parc', 'proc', 'adv', 'vols?', 'cels?', 'pp', 'ex[ao]', 'eg', 'pl', 'ref',
               '[0-9]+', 'reg', 'f[ilí]s?', 'inc', 'par', 'alin', 'fts', 'publ?', 'ex', 'v. em', 'v.rev']

ABREVIACOES_RGX = re.compile(r'(?:{})\.\s*$'.format('|\s'.join(ABREVIACOES)), re.IGNORECASE)

        def sentencas(texto, min_len=5):
            # baseado em https://stackoverflow.com/questions/25735644/python-regex-for-splitting-text-into-sentences-sentence-tokenizing
            texto = re.sub(r'\s\s+', ' ', texto)
            EndPunctuation = re.compile(r'([\.\?\!]\s+)')
            # print(NonEndings)
            parts = EndPunctuation.split(texto)
            sentencas = []
            sentence = []
            for part in parts:
                txt_sent = ''.join(sentence)
                q_len = len(txt_sent)
                if len(part) and len(sentence) and q_len >= min_len and \
                        EndPunctuation.match(sentence[-1]) and \
                        not ABREVIACOES_RGX.search(txt_sent):
                    sentencas.append(txt_sent)
                    sentence = []

                if len(part):
                    sentence.append(part)
            if sentence:
                sentencas.append(''.join(sentence))
            return sentencas

完全なコード: https://github.com/luizanisio/comparador_elastic

0
Luiz Anísio

適切な英語の文が非アルファで始まっておらず、引用された品詞が含まれていないための素朴なアプローチ:

import re
text = """\
Mr. Smith bought cheapsite.com for 1.5 million dollars, i.e. he paid a lot for it. Did he mind? Adam Jones Jr. thinks he didn't. In any case, this isn't true... Well, with a probability of .9 it isn't.
"""
EndPunctuation = re.compile(r'([\.\?\!]\s+)')
NonEndings = re.compile(r'(?:Mrs?|Jr|i\.e)\.\s*$')
parts = EndPunctuation.split(text)
sentence = []
for part in parts:
  if len(part) and len(sentence) and EndPunctuation.match(sentence[-1]) and not NonEndings.search(''.join(sentence)):
    print(''.join(sentence))
    sentence = []
  if len(part):
    sentence.append(part)
if len(sentence):
  print(''.join(sentence))

NonEndingsを少し拡張することにより、誤検出の分割を減らすことができます。その他の場合は、追加のコードが必要になります。このアプローチでは、タイプミスを賢明な方法で処理するのは困難です。

このアプローチで完璧に達することは決してありません。ただし、タスクによっては「十分」に機能する場合があります...

0
Ali

上記のsmciのコメントを考慮してこれを書きました。これは、外部ライブラリを必要とせず、正規表現を使用しない中道アプローチです。ピリオドや引用符など、ラッパー内のターミネータで終了する文の省略形およびアカウントのリストを提供できます:[。 "、? '、。)]。

abbreviations = {'dr.': 'doctor', 'mr.': 'mister', 'bro.': 'brother', 'bro': 'brother', 'mrs.': 'mistress', 'ms.': 'miss', 'jr.': 'junior', 'sr.': 'senior', 'i.e.': 'for example', 'e.g.': 'for example', 'vs.': 'versus'}
terminators = ['.', '!', '?']
wrappers = ['"', "'", ')', ']', '}']


def find_sentences(paragraph):
   end = True
   sentences = []
   while end > -1:
       end = find_sentence_end(paragraph)
       if end > -1:
           sentences.append(paragraph[end:].strip())
           paragraph = paragraph[:end]
   sentences.append(paragraph)
   sentences.reverse()
   return sentences


def find_sentence_end(paragraph):
    [possible_endings, contraction_locations] = [[], []]
    contractions = abbreviations.keys()
    sentence_terminators = terminators + [terminator + wrapper for wrapper in wrappers for terminator in terminators]
    for sentence_terminator in sentence_terminators:
        t_indices = list(find_all(paragraph, sentence_terminator))
        possible_endings.extend(([] if not len(t_indices) else [[i, len(sentence_terminator)] for i in t_indices]))
    for contraction in contractions:
        c_indices = list(find_all(paragraph, contraction))
        contraction_locations.extend(([] if not len(c_indices) else [i + len(contraction) for i in c_indices]))
    possible_endings = [pe for pe in possible_endings if pe[0] + pe[1] not in contraction_locations]
    if len(paragraph) in [pe[0] + pe[1] for pe in possible_endings]:
        max_end_start = max([pe[0] for pe in possible_endings])
        possible_endings = [pe for pe in possible_endings if pe[0] != max_end_start]
    possible_endings = [pe[0] + pe[1] for pe in possible_endings if sum(pe) > len(paragraph) or (sum(pe) < len(paragraph) and paragraph[sum(pe)] == ' ')]
    end = (-1 if not len(possible_endings) else max(possible_endings))
    return end


def find_all(a_str, sub):
    start = 0
    while True:
        start = a_str.find(sub, start)
        if start == -1:
            return
        yield start
        start += len(sub)

このエントリからKarlのfind_all関数を使用しました: Pythonで部分文字列のすべての出現を検索します

0
TennisVisuals

私は正規表現が得意ではありませんが、上記の「ブルートフォース」という単純なバージョンは

sentence = re.compile("([\'\"][A-Z]|([A-Z][a-z]*\. )|[A-Z])(([a-z]*\.[a-z]*\.)|([A-Za-z0-9]*\.[A-Za-z0-9])|([A-Z][a-z]*\. [A-Za-z]*)|[^\.?]|[A-Za-z])*[\.?]")

つまり、開始可能な単位は「[A-Z]」または「[A-Z]
注意してください、ほとんどの正規表現は貪欲であるため、(or)を実行するときの順序は非常に重要です。それが、なぜ私がi.e。正規表現を最初に書いたのか、そしてInc。

0
Priyank Pathak