web-dev-qa-db-ja.com

NLTKのStanford NER Taggerを使用して個人と組織のリストを抽出する

私はPython NLTK。

from nltk.tag.stanford import NERTagger
st = NERTagger('/usr/share/stanford-ner/classifiers/all.3class.distsim.crf.ser.gz',
               '/usr/share/stanford-ner/stanford-ner.jar') 
r=st.tag('Rami Eid is studying at Stony Brook University in NY'.split())
print(r) 

出力は次のとおりです。

[('Rami', 'PERSON'), ('Eid', 'PERSON'), ('is', 'O'), ('studying', 'O'),
('at', 'O'), ('Stony', 'ORGANIZATION'), ('Brook', 'ORGANIZATION'),
('University', 'ORGANIZATION'), ('in', 'O'), ('NY', 'LOCATION')]

私が欲しいのは、このリストからこの形式のすべての人と組織を抽出することです:

Rami Eid
Sony Brook University

タプルのリストをループしようとしました:

for x,y in i:
        if y == 'ORGANIZATION':
            print(x)

しかし、このコードはすべてのエンティティを1行に1つだけ出力します。

Sony 
Brook 
University

実際のデータでは、複数の組織、1つの文の人が存在する可能性がありますが、異なるエンティティ間に制限を設定するにはどうすればよいですか?

24
user1680859

@ -Vaulsteinによって発見された link のおかげで、配布された(少なくとも2012年に)訓練されたスタンフォードタガーが名前付きエンティティをチャンクしないことは明らかです受け入れられた答え から:

多くのNERシステムは、IOBラベルなどのより複雑なラベルを使用します。B-PERSなどのコードは、個人エンティティの開始位置を示します。 CRFClassifierクラスと機能ファクトリーはそのようなラベルをサポートしていますが、現在配布しているモデルでは使用されていません(2012年現在)

次のオプションがあります。

  1. 同じようにタグ付けされた単語の実行を収集します。たとえば、PERSONでタグ付けされたすべての隣接する単語は、1つの名前付きエンティティとしてまとめられる必要があります。それは非常に簡単ですが、もちろん異なる名前のエンティティを組み合わせることがあります。 (たとえば、_New York, Boston [and] Baltimore_は1つではなく約3つの都市です。)Edit:これは、Alvasのコードが受け入れられたアンカーで行うことです。より簡単な実装については、以下を参照してください。

  2. nltk.ne_recognize()を使用します。スタンフォードレコグナイザーは使用しませんが、エンティティをチャンクします。 (これは、エンティティタガーという名前のIOBのラッパーです)。

  3. Stanford taggerが返す結果に加えて、独自のチャンク化を行う方法を見つけてください。

  4. 関心のあるドメインの独自のIOB名前付きエンティティチャンカーを(スタンフォードツールまたはNLTKのフレームワークを使用して)トレーニングします。これを正しく行う時間とリソースがあれば、おそらく最良の結果が得られます。

Edit:連続した名前付きエンティティ(上記のオプション1)の実行を引き出したい場合は、_itertools.groupby_を使用する必要があります:

_from itertools import groupby
for tag, chunk in groupby(netagged_words, lambda x:x[1]):
    if tag != "O":
        print("%-12s"%tag, " ".join(w for w, t in chunk))
_

_netagged_words_が質問の_(Word, type)_タプルのリストである場合、これは以下を生成します。

_PERSON       Rami Eid
ORGANIZATION Stony Brook University
LOCATION     NY
_

繰り返しますが、同じタイプの2つの名前付きエンティティが隣接して存在する場合、このアプローチはそれらを結合します。例えば。 _New York, Boston [and] Baltimore_は約1つの都市ではなく3つの都市です。

27
alexis

IOB/BIOは、[〜#〜] i [〜#〜]nside、[〜#〜] o [〜#〜]utside、[〜#〜] b [〜#〜]egginning(IOB)、または時々別名[〜#〜] b [〜#〜]eginning、[〜#〜] i [ 〜#〜]nside、[〜#〜] o [〜#〜]utside(BIO)

スタンフォードNEタガーは、IOB/BIOスタイルのタグを返します。

[('Rami', 'PERSON'), ('Eid', 'PERSON'), ('is', 'O'), ('studying', 'O'),
('at', 'O'), ('Stony', 'ORGANIZATION'), ('Brook', 'ORGANIZATION'),
('University', 'ORGANIZATION'), ('in', 'O'), ('NY', 'LOCATION')]

('Rami', 'PERSON'), ('Eid', 'PERSON')はPERSONとしてタグ付けされ、「Rami」はBeginningまたはNEチャンク、「Eid」は内部です。そして、NE以外はすべて「O」でタグ付けされることがわかります。

連続したNEチャンクを抽出するアイデアは、 正規表現を使用した名前付きエンティティ認識:NLTK に非常に似ていますが、スタンフォードNEチャンカーAPIは解析するニースツリーを返さないため、これを行う必要があります。

def get_continuous_chunks(tagged_sent):
    continuous_chunk = []
    current_chunk = []

    for token, tag in tagged_sent:
        if tag != "O":
            current_chunk.append((token, tag))
        else:
            if current_chunk: # if the current chunk is not empty
                continuous_chunk.append(current_chunk)
                current_chunk = []
    # Flush the final current_chunk into the continuous_chunk, if any.
    if current_chunk:
        continuous_chunk.append(current_chunk)
    return continuous_chunk

ne_tagged_sent = [('Rami', 'PERSON'), ('Eid', 'PERSON'), ('is', 'O'), ('studying', 'O'), ('at', 'O'), ('Stony', 'ORGANIZATION'), ('Brook', 'ORGANIZATION'), ('University', 'ORGANIZATION'), ('in', 'O'), ('NY', 'LOCATION')]

named_entities = get_continuous_chunks(ne_tagged_sent)
named_entities = get_continuous_chunks(ne_tagged_sent)
named_entities_str = [" ".join([token for token, tag in ne]) for ne in named_entities]
named_entities_str_tag = [(" ".join([token for token, tag in ne]), ne[0][1]) for ne in named_entities]

print named_entities
print
print named_entities_str
print
print named_entities_str_tag
print

[でる]:

[[('Rami', 'PERSON'), ('Eid', 'PERSON')], [('Stony', 'ORGANIZATION'), ('Brook', 'ORGANIZATION'), ('University', 'ORGANIZATION')], [('NY', 'LOCATION')]]

['Rami Eid', 'Stony Brook University', 'NY']

[('Rami Eid', 'PERSON'), ('Stony Brook University', 'ORGANIZATION'), ('NY', 'LOCATION')]

ただし、2つのNEが連続している場合、間違っている可能性があるという制限に注意してください。それでも、2つのNEが「O」なしで連続している例は考えられません。


@alexisが示唆したように、スタンフォードNEの出力をNLTKツリーに変換することをお勧めします。

from nltk import pos_tag
from nltk.chunk import conlltags2tree
from nltk.tree import Tree

def stanfordNE2BIO(tagged_sent):
    bio_tagged_sent = []
    prev_tag = "O"
    for token, tag in tagged_sent:
        if tag == "O": #O
            bio_tagged_sent.append((token, tag))
            prev_tag = tag
            continue
        if tag != "O" and prev_tag == "O": # Begin NE
            bio_tagged_sent.append((token, "B-"+tag))
            prev_tag = tag
        Elif prev_tag != "O" and prev_tag == tag: # Inside NE
            bio_tagged_sent.append((token, "I-"+tag))
            prev_tag = tag
        Elif prev_tag != "O" and prev_tag != tag: # Adjacent NE
            bio_tagged_sent.append((token, "B-"+tag))
            prev_tag = tag

    return bio_tagged_sent


def stanfordNE2tree(ne_tagged_sent):
    bio_tagged_sent = stanfordNE2BIO(ne_tagged_sent)
    sent_tokens, sent_ne_tags = Zip(*bio_tagged_sent)
    sent_pos_tags = [pos for token, pos in pos_tag(sent_tokens)]

    sent_conlltags = [(token, pos, ne) for token, pos, ne in Zip(sent_tokens, sent_pos_tags, sent_ne_tags)]
    ne_tree = conlltags2tree(sent_conlltags)
    return ne_tree

ne_tagged_sent = [('Rami', 'PERSON'), ('Eid', 'PERSON'), ('is', 'O'), 
('studying', 'O'), ('at', 'O'), ('Stony', 'ORGANIZATION'), 
('Brook', 'ORGANIZATION'), ('University', 'ORGANIZATION'), 
('in', 'O'), ('NY', 'LOCATION')]

ne_tree = stanfordNE2tree(ne_tagged_sent)

print ne_tree

[でる]:

  (S
  (PERSON Rami/NNP Eid/NNP)
  is/VBZ
  studying/VBG
  at/IN
  (ORGANIZATION Stony/NNP Brook/NNP University/NNP)
  in/IN
  (LOCATION NY/NNP))

次に:

ne_in_sent = []
for subtree in ne_tree:
    if type(subtree) == Tree: # If subtree is a noun chunk, i.e. NE != "O"
        ne_label = subtree.label()
        ne_string = " ".join([token for token, pos in subtree.leaves()])
        ne_in_sent.append((ne_string, ne_label))
print ne_in_sent

[でる]:

[('Rami Eid', 'PERSON'), ('Stony Brook University', 'ORGANIZATION'), ('NY', 'LOCATION')]
24
alvas

警告:このモデル「all.3class.distsim.crf.ser.gz」を入手しても、使用しないでください。

    第一の理由:

このモデルでは、スタンフォードNLPの人々は精度が悪いことを公然と認めています

    第二の理由:

大文字と小文字が区別されるため、精度が低くなります。

    解決

「english.all.3class.caseless.distsim.crf.ser.gz」というモデルを使用します

1
yunus

トピック作成者が望んでいるものを印刷するという要件に正確に従っているわけではありません。

listx = [('Rami', 'PERSON'), ('Eid', 'PERSON'), ('is', 'O'), ('studying', 'O'),
('at', 'O'), ('Stony', 'ORGANIZATION'), ('Brook', 'ORGANIZATION'),
('University', 'ORGANIZATION'), ('in', 'O'), ('NY', 'LOCATION')]


def parser(n, string):
    for i in listx[n]:
        if i == string:
            pass
        else:
            return i

name = parser(0,'PERSON')
lname = parser(1,'PERSON')
org1 = parser(5,'ORGANIZATION')
org2 = parser(6,'ORGANIZATION')
org3 = parser(7,'ORGANIZATION')


print name, lname
print org1, org2, org3

出力は次のようになります

Rami Eid
Stony Brook University
1

enumerate」メソッドを使用してみてください。

単語のリストにNERを適用する場合、(Word、type)のタプルが作成されたら、enumerate(list)を使用してこのリストを列挙します。これにより、リスト内のすべてのタプルにインデックスが割り当てられます。

そのため、後でPERSON/ORGANISATION/LOCATIONをリストから抽出すると、インデックスが添付されます。

1   Hussein
2   Obama
3   II
6   James
7   Naismith
21   Naismith
19   Tony
20   Hinkle
0   Frank
1   Mahan
14   Naismith
0   Naismith
0   Mahan
0   Mahan
0   Naismith

これで、連続したインデックスに基づいて、単一の名前を除外できます。

フセインオバマII、ジェームズネイスミス、トニーハンク、フランクマハン

0
Akash Tyagi

pythonのpycorenlpラッパーを使用してから、 'entitymentions'をキーとして使用して、単一の文字列で人または組織の連続したチャンクを取得します。

0
Abhishek Bisht