2018年06月27日
言語処理100本ノックでPython入門 #55 - Stanford Core NLPの出力から人名を抜き出す
今日は、言語処理100本ノック 2015の第6章・問題55を解きます。
■ 問題
55. 固有表現抽出
入力文中の人名をすべて抜き出せ.
■ どうやって解くか
まず、Stanford Core NLPが出力した解析結果(xmlフィル)ファイルを見てみます。
人名の場合には、以下のようにNERタグが PERSON となっています。
これを抜き出せばOKですね。
<token id="4"> <word>Alan</word> <lemma>Alan</lemma> <CharacterOffsetBegin>646</CharacterOffsetBegin> <CharacterOffsetEnd>650</CharacterOffsetEnd> <POS>NNP</POS> <NER>PERSON</NER> <Speaker>PER0</Speaker> </token
■ Pythonのコード
from xml.etree import ElementTree def getWords(): xdoc = ElementTree.parse('nlp.txt.xml') root = xdoc.getroot() sentences = root.find('document/sentences') for e in sentences.findall('sentence/tokens/token'): if e.find('NER').text == 'PERSON': yield e.find('word') def main(): with open('result55.txt', 'w', encoding='utf8') as w: for word in getWords(): w.write(f'{word.text}\n') if __name__ == '__main__': main()
ソースコードは、GitHubで公開しています。
■ 結果
以下、結果です。
Alan Turing Joseph Weizenbaum MARGIE Schank Wilensky Meehan Lehnert Carbonell Lehnert Racter Jabberwacky Moore
Posted by gushwell at
22:30
│Comments(0)
2018年06月24日
言語処理100本ノックでPython入門 #54 - Stanford Core NLPの出力を品詞タグ付け
今日は、言語処理100本ノック 2015の第6章・問題54を解きます。
■ 問題
■ Pythonのコード
ソースコードは、GitHubで公開しています。
■ すこし説明
問題53とほとんど変わらないです。
getWords関数で以下のように3つのタプルを返すようにして、
ただし、ピリオドやカンマなどの記号を除外するために、正規表現を使っています。 pos.textがどのような値を取りうるのかがわからないのですが、ここでは、アルファベット大文字が1文字でも含まれて入れば、記号ではない別の品詞だと判断しています。
■ 結果
先頭の30行ほどを掲載します。
■ 問題
54. 品詞タグ付け
Stanford Core NLPの解析結果XMLを読み込み,単語,レンマ,品詞をタブ区切り形式で出力せよ.
■ Pythonのコード
import re from xml.etree import ElementTree def getWords(): xdoc = ElementTree.parse('nlp.txt.xml') root = xdoc.getroot() sentences = root.find('document/sentences') for e in sentences.findall('sentence/tokens/token'): yield e.find('word'), e.find('lemma'), e.find('POS') def main(): with open('result54.txt', 'w', encoding='utf8') as w: for word, lemma, pos in getWords(): m = re.search(r'[A-Z]', pos.text) if m: w.write(f'{word.text}\t{lemma.text}\t{pos.text}\n') if __name__ == '__main__': main()
ソースコードは、GitHubで公開しています。
■ すこし説明
問題53とほとんど変わらないです。
getWords関数で以下のように3つのタプルを返すようにして、
for e in sentences.findall('sentence/tokens/token'): yield e.find('word'), e.find('lemma'), e.find('POS')main関数では、このタプルの word.text, lemma.text, pos.txtををタブ区切りで出力しています。
ただし、ピリオドやカンマなどの記号を除外するために、正規表現を使っています。 pos.textがどのような値を取りうるのかがわからないのですが、ここでは、アルファベット大文字が1文字でも含まれて入れば、記号ではない別の品詞だと判断しています。
m = re.search(r'[A-Z]', pos.text) if m: w.write(f'{word.text}\t{lemma.text}\t{pos.text}\n')
■ 結果
先頭の30行ほどを掲載します。
Natural natural JJ Natural natural JJ language language NN processing processing NN From from IN Wikipedia Wikipedia NNP the the DT free free JJ encyclopedia encyclopedia NN Natural natural JJ language language NN processing processing NN -LRB- -lrb- -LRB- NLP nlp NN -RRB- -rrb- -RRB- is be VBZ a a DT field field NN of of IN computer computer NN science science NN artificial artificial JJ intelligence intelligence NN and and CC linguistics linguistics NNS concerned concern VBN with with IN the the DT interactions interaction NNS between between IN computers computer NNS
Posted by gushwell at
23:00
│Comments(0)
2018年06月21日
言語処理100本ノックでPython入門 #53 - Stanford Core NLPの出力から単語抽出
今日は、言語処理100本ノック 2015の第6章・問題53です。
■ 問題
■ Stanford Core NLPをダウンロード
以下のURLからStanford Core NLPをダウンロードします。
https://stanfordnlp.github.io/CoreNLP/
これを、corenlpというフォルダに配置します。
■ Stanford Core NLPを実行
以下のようなコマンドを投入し、Stanford Core NLPを起動します。
これで、nlp.txt.xmlファイルが作成されます。
ちなみに、Javaのバージョンは、java version "1.8.0_101" です。
でも、作成したnlp.txt.xml見ると文の判断が正しくないので、入力ファイルを手で修正。
入力ファイルは、基本1行1文なのだけれど、Stanford Core NLPは、ピリオドが行の単位と認識するようなので、タイトル行の最後にもピリオドを付加しています。4か所くらいあったかな。
得られたXMLファイルの先頭部分を載せておきます。
■ XMLファイルを操作する
XMLファイルを読み込むには、xml.etreeライブラリを使います。
続いて、ルートを取得します。
さらに、
■ Pythonのコード
作成したPythonのコードです。
ソースはGitHubでも公開しています。
■ 結果
結果の先頭部分を載せます。
ピリオドやカンマも抽出しています。本来は、これらは除外すべきかもしれませんが良しとします。
■ 問題
53. Tokenization
Stanford Core NLPを用い,入力テキストの解析結果をXML形式で得よ.また,このXMLファイルを読み込み,入力テキストを1行1単語の形式で出力せよ.
■ Stanford Core NLPをダウンロード
以下のURLからStanford Core NLPをダウンロードします。
https://stanfordnlp.github.io/CoreNLP/
これを、corenlpというフォルダに配置します。
■ Stanford Core NLPを実行
以下のようなコマンドを投入し、Stanford Core NLPを起動します。
java -cp "/Users/xxxxxxx/corenlp/*" -Xmx3g edu.stanford.nlp.pipeline.StanfordCoreNLP -annotators tokenize,ssplit,pos,lemma,ner -file nlp.txt
これで、nlp.txt.xmlファイルが作成されます。
ちなみに、Javaのバージョンは、java version "1.8.0_101" です。
でも、作成したnlp.txt.xml見ると文の判断が正しくないので、入力ファイルを手で修正。
入力ファイルは、基本1行1文なのだけれど、Stanford Core NLPは、ピリオドが行の単位と認識するようなので、タイトル行の最後にもピリオドを付加しています。4か所くらいあったかな。
得られたXMLファイルの先頭部分を載せておきます。
<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet href="CoreNLP-to-HTML.xsl" type="text/xsl"?> <root> <document> <docId>nlp.txt</docId> <sentences> <sentence id="1"> <tokens> <token id="1"> <word>Natural</word> <lemma>natural</lemma> <CharacterOffsetBegin>0</CharacterOffsetBegin> <CharacterOffsetEnd>7</CharacterOffsetEnd> <POS>JJ</POS> <NER>O</NER> <Speaker>PER0</Speaker> </token> <token id="2"> <word>language</word> <lemma>language</lemma> <CharacterOffsetBegin>8</CharacterOffsetBegin> <CharacterOffsetEnd>16</CharacterOffsetEnd> <POS>NN</POS> <NER>O</NER> <Speaker>PER0</Speaker> </token> <token id="3"> <word>processing</word> <lemma>processing</lemma> <CharacterOffsetBegin>17</CharacterOffsetBegin> <CharacterOffsetEnd>27</CharacterOffsetEnd> <POS>NN</POS> <NER>O</NER> <Speaker>PER0</Speaker> </token>
■ XMLファイルを操作する
XMLファイルを読み込むには、xml.etreeライブラリを使います。
from xml.etree import ElementTree次に、入力ファイルを指定し、nlp.txt.xmlをパースします。
xdoc = ElementTree.parse('nlp.txt.xml')これで、読み込んだ結果がtree構造として返されます。
続いて、ルートを取得します。
root = xdoc.getroot()このrootを使い、必要な要素を取り出していきます。
sentences = root.find('document/sentences')で、rootの直下から、パスを指定してXML elementを取得。
さらに、
for e in sentences.findall('sentence/tokens/token/word'): yield eで、word要素をすべて取り出します。 実際のテキストは、
e.textで取得できます。
■ Pythonのコード
作成したPythonのコードです。
from xml.etree import ElementTree def getWords(): xdoc = ElementTree.parse('nlp.txt.xml') root = xdoc.getroot() sentences = root.find('document/sentences') for e in sentences.findall('sentence/tokens/token/word'): yield e def main(): with open('result53.txt', 'w', encoding='utf8') as w: for word in getWords(): w.write(f'{word.text}\n') if __name__ == '__main__': main()
ソースはGitHubでも公開しています。
■ 結果
結果の先頭部分を載せます。
ピリオドやカンマも抽出しています。本来は、これらは除外すべきかもしれませんが良しとします。
Natural language processing . From Wikipedia , the free encyclopedia . Natural language processing -LRB- NLP -RRB- is a field of computer science , artificial intelligence , and linguistics concerned
Posted by gushwell at
07:40
│Comments(0)
2018年06月17日
言語処理100本ノックでPython入門 #52 - nltkでステミング
今日は、言語処理100本ノック 2015の第6章・問題52を解きます。
■ 問題
■ nltkライブラリのstemモジュールを使う
ステミングとは、単語の変化形の変化した部分を取り除く処理のことらしいです。
問題に、「stemmingモジュールを利用するとよい」とあるので、以下のようなコマンドを投入。
python3.xで利用できるステミングモジュールはないかな、って調べたら、nltk というパッケージでstemmingができるらしいです。
僕が利用しているpythonのanacondaは、デフォルトでいろんなライブラリが入っているので、まずは、anacondaにあるかどうか調べます。
でも、pylintが以下のようなエラーを吐きます。
無視してもいいのですが、せっかくなので、pylintを新しくします。
では、stemを使って、Porterのステミングアルゴリズムで語幹を取り出します。
ちなみに、
■ Pythonのコード
以下、出来上がったPythonのコードを載せます。
ソースはGitHubでも公開しています。
そうだ、書き忘れていたけど、Pythonでも、C#の逐語的リテラル文字列のような書き方ができるようになったんですね。
便利になりました。
■ 結果
■ 問題
52. ステミング
51の出力を入力として受け取り,Porterのステミングアルゴリズムを適用し,単語と語幹をタブ区切り形式で出力せよ. Pythonでは,Porterのステミングアルゴリズムの実装としてstemmingモジュールを利用するとよい.
■ nltkライブラリのstemモジュールを使う
ステミングとは、単語の変化形の変化した部分を取り除く処理のことらしいです。
問題に、「stemmingモジュールを利用するとよい」とあるので、以下のようなコマンドを投入。
conda install stemmingでも、以下のエラーが出て見つかりません。
Solving environment: failed PackagesNotFoundError: The following packages are not available from current channels: - stemmingそもそもこのstemmingライブラリは、python3.xに非対応らしいです。
python3.xで利用できるステミングモジュールはないかな、って調べたら、nltk というパッケージでstemmingができるらしいです。
僕が利用しているpythonのanacondaは、デフォルトでいろんなライブラリが入っているので、まずは、anacondaにあるかどうか調べます。
conda listとやったら、
nltk 3.2.2 py36_0と出てきました。これを使おうとおもいます。
from nltk import stemで stem モジュールをimportします。
でも、pylintが以下のようなエラーを吐きます。
F0002:<class 'AttributeError'>: 'TreeRebuilder3k' object has no attribute 'visit_joinedstr'どうも、pylintが古いみたいです。
無視してもいいのですが、せっかくなので、pylintを新しくします。
conda install -c anaconda pylint無事、エラーが消えました。
では、stemを使って、Porterのステミングアルゴリズムで語幹を取り出します。
stemmer = stem.PorterStemmer() stm = stemmer.stem(word)とすれば、語幹が得られます。stemとは語幹という意味です。
ちなみに、
stemmer = stem.LancasterStemmer()とすれば、LancasterStemmerという別のアルゴリズムも使えるみたいです。
■ Pythonのコード
以下、出来上がったPythonのコードを載せます。
from nltk import stem def enumStem(): stemmer = stem.PorterStemmer() #stemmer = stem.LancasterStemmer() with open('result51.txt', 'r', encoding='utf8') as fin: for line in fin: word = line.rstrip('\n') if word != '': yield word, stemmer.stem(word) def main(): with open('result52.txt', 'w', encoding='utf8') as w: for word, stm in enumStem(): w.write(f'{word}\t{stm}\n') if __name__ == '__main__': main()
ソースはGitHubでも公開しています。
そうだ、書き忘れていたけど、Pythonでも、C#の逐語的リテラル文字列のような書き方ができるようになったんですね。
f'{word}\t{stm}\n'
便利になりました。
■ 結果
Natural natur language languag processing process From from Wikipedia wikipedia the the free free encyclopedia encyclopedia Natural natur language languag processing process NLP nlp is is a a field field of of computer comput science scienc artificial artifici intelligence intellig and and linguistics linguist concerned concern with with the the interactions interact between between computers comput and and human human ... 以下省略
Posted by gushwell at
22:30
│Comments(0)
2018年06月12日
言語処理100本ノックでPython入門 #51 - 英文テキスト 単語の切り出し
今日は、言語処理100本ノック 2015の第6章・問題51を解きます。
■ 問題
51. 単語の切り出し
空白を単語の区切りとみなし,50の出力を入力として受け取り,1行1単語の形式で出力せよ.ただし,文の終端では空行を出力せよ.
■ Pythonのコード
import re def enumWords(): with open('result50.txt', 'r', encoding='utf8') as fin: for line in fin: words = re.split(r'[\s\.",:;()]+', line) for w in words: if re.match(r'^[a-zA-Z]', w): yield w def main(): with open('result51.txt', 'w', encoding='utf8') as w: w.writelines([x + '\n' for x in enumWords()]) if __name__ == '__main__': main()
ソースコードは、GitHubで公開しています。
■ ちょっと説明
単純に空白を区切り文字としてプログラム書いて実行して結果をみると、出力ファイルの中に以下のような内容がありました。
In 1950, Alan Turing published an article titled "Computing Machinery and Intelligence" which ...
なんか、常識的に考えておかしいです。
「空白を単語の区切り」とあるけど、, " ( ) ? などの記号をどうしたらよいかは、問題読んでもよくわからないんですよね。
ということで、単語の区切りとなりそうな空白以外の記号も区切り記号として書き直したのが上のコードです。
それと、今回初めて、writelinesメソッド使ってみました。これで一気に複数行をファイルに出力できます。 でも、引数の配列の各行の最後に改行入れないといけないのが面倒です。
with open('result51.txt', 'w', encoding='utf8') as w: w.writelines([x + '\n' for x in enumWords()])
■ 結果
先頭の30語だけ掲載します。
Natural language processing From Wikipedia the free encyclopedia Natural language processing NLP is a field of computer science artificial intelligence and linguistics concerned with the interactions between computers and human
Posted by gushwell at
08:00
│Comments(0)
2018年06月10日
言語処理100本ノックでPython入門 #50 - 英文テキスト 分区切り
本日から、言語処理100本ノック 2015の第6章に入ります。
まずは、問題50を解きます。
■ 問題
第6章: 英語テキストの処理
英語のテキスト(nlp.txt)に対して,以下の処理を実行せよ.
50. 文区切り
(. or ; or : or ? or !) → 空白文字 → 英大文字というパターンを文の区切りと見なし,入力された文書を1行1文の形式で出力せよ.
■ Pythonのコード
import re def enumSentence(): with open('nlp.txt', 'r', encoding='utf8') as fin: for line in fin: nl = re.sub(r'(\.|;|:|\?|!)(\s+)([A-Z])', r'\1\n\3', line) ss = re.split(r'\n', nl) for s in filter(lambda w: len(w) > 0, ss): yield s def main(): with open('result501.txt', 'w', encoding='utf8') as w: for sentence in enumSentence(): w.write(sentence + '\n') if __name__ == '__main__': main()
ソースコードは、GitHubで公開しています。
■ ちょっと説明
matchで1文を抜き出そうとしたのですが、文の途中に.がある場合や、引用符の中に、(. or ; or : or ? or !) → 空白文字 → 英大文字 という パターンがある場合に正規表現がうまく書けませんでした。
それなので、考えるのやめて安易な方法でやることにしました。
sub関数で文の区切りの空白を\nに置き換えて、\nでsplitして、1行を取り出すという戦術。
でも、この時、subじゃなくて、reaplceと書いてうまくいかずに、なぜ?って思ってしまいました。 substitute の略だと思うけど、どうも忘れてしまいます。 グループ化した文字列を参照するときは、\1, \2, \3 など使います。
それと、正規表現書く時は、rで始める文字列にしたほうが使いやすいですね。
それと久しぶりにfilter使ってみました。たまに使わないと忘れてしまいそうです。
for s in filter(lambda w: len(w) > 0, ss): yield sちなみに、入力ファイルの先頭行の
Natural language processingは、(. or ; or : or ? or !)で終わってないけど、これも文として扱うことにします。
■結果
先頭部分だけ載せます。画面だと途中で改行されてますが、ご容赦を。
Natural language processing From Wikipedia, the free encyclopedia Natural language processing (NLP) is a field of computer science, artificial intelligence, and linguistics concerned with the interactions between computers and human (natural) languages. As such, NLP is related to the area of humani-computer interaction. Many challenges in NLP involve natural language understanding, that is, enabling computers to derive meaning from human or natural language input, and others involve natural language generation. History The history of NLP generally starts in the 1950s, although work can be found from earlier periods. In 1950, Alan Turing published an article titled "Computing Machinery and Intelligence" which proposed what is now called the Turing test as a criterion of intelligence. The Georgetown experiment in 1954 involved fully automatic translation of more than sixty Russian sentences into English. The authors claimed that within three or five years, machine translation would be a solved problem. ...
Posted by gushwell at
22:30
│Comments(0)
2018年06月07日
言語処理100本ノックでPython入門 #49 - 名詞間の係り受けパスの抽出
今日は、言語処理100本ノック 2015の問題49です。
「第5章:係り受け解析」最後の問題になりました。
まだ、Pythonの文法で知らないことも結構あると思いますが、それなりのコードは書けるようになったかなと思います。やりたいアルゴリズムを実装する上で、困ることはほとんどなくなったように思います。あとはいろんなライブラリの使い方を覚えて行ければ...
■ 問題
49. 名詞間の係り受けパスの抽出
文中のすべての名詞句のペアを結ぶ最短係り受けパスを抽出せよ.ただし,名詞句ペアの文節番号が iとj(i<j)のとき,係り受けパスは以下の仕様を満たすものとする.また,係り受けパスの形状は,以下の2通りが考えられる.
- 問題48と同様に,パスは開始文節から終了文節に至るまでの各文節の表現(表層形の形態素列)を"->"で連結して表現する 文節
- iとjに含まれる名詞句はそれぞれ,XとYに置換する
例えば,「吾輩はここで始めて人間というものを見た。」という文(neko.txt.cabochaの8文目)から,次のような出力が得られるはずである.
- 文節iから構文木の根に至る経路上に文節jが存在する場合: 文節iiから文節jjのパスを表示
- 上記以外で,文節iiと文節jjから構文木の根に至る経路上で共通の文節kkで交わる場合: 文節iiから文節kkに至る直前のパスと文節jjから文節kに至る直前までのパス,文kkの内容を"|"で連結して表示
Xは | Yで -> 始めて -> 人間という -> ものを | 見た Xは | Yという -> ものを | 見た Xは | Yを | 見た Xで -> 始めて -> Y Xで -> 始めて -> 人間という -> Y Xという -> Y
■ 新たに得たPythonの知識
Python はメソッドのオーバーロードが使えない。
intの最大値
import sys
sys.maxsize
深いコピーができる
import copy copy.deepcopy(x)
■ 簡単な説明
Chunkクラスには、hasNounメソッドを追加。
analyze関数は今回は変更。 EOSを明確に示すために、sentenseの最後には、Chunk(sys.maxsize, -1) を追加しました。これがEOSを意味します。
sentence.append(Chunk(sys.maxsize, -1))これに伴い、これまでは、dst:-1の時は、次のChunkはないことを示していましたが、最後のChunkのdstは、EOSを表すChunkへの番号をセットするようにしました。
if destNo == -1: destNo = num + 1各関数の動作については、コードにコメントを付けたのでそれを読んでください。
なお、主要な変数は以下の通りです。
- 変数ciは、文節i(Chunkクラス) を表す
- 変数cjは、文節j(Chunkクラス) を表す
- 変数ckは、文節k(Chunkクラス) を表す
- 変数kは、文節kのインデックスを表す (sentenceのインデックス)
ただ、あまり効率の良いコードとは言えないです。改良の余地hじゃありますが、まあ良しとします。
■ Pythonのコード
import re import sys import copy import functools # 形態素を表すクラス class Morph: def __init__(self, surface, base, pos, pos1): self.surface = surface self.base = base self.pos = pos self.pos1 = pos1 def toList(self): return [self.surface, self.base, self.pos, self.pos1] # 文節を表すクラス (複数の形態素からなる) class Chunk: def __init__(self, number, dst): self.number = number self.morphs = [] self.dst = dst self.srcs = [] def hasNoun(self): for m in self.morphs: if m.pos == '名詞': return True return False def concatMorphs(self): seq = filter(lambda x: x.pos != '記号', self.morphs) return functools.reduce(lambda x, y: x + y.surface, seq, '') # 文章をsentenceに分割する。ひとつのsentenceは、Chunk(文節)の配列からなる def analyze(): article = [] sentence = [] chunk = None with open('neko.txt.cabocha', 'r', encoding='utf8') as fin: for line in fin: words = re.split(r'\t|,|\n| ', line) if line[0] == '*': num = int(words[1]) destNo = int(words[2].rstrip('D')) if destNo == -1: destNo = num + 1 chunk = Chunk(num, destNo) sentence.append(chunk) elif words[0] == 'EOS': if sentence: sentence.append(Chunk(sys.maxsize, -1)) for index, c in enumerate(sentence, 0): sentence[c.dst].srcs.append(index) article.append(sentence) sentence = [] else: chunk.morphs.append(Morph( words[0], words[7], words[1], words[2], )) return article # 名詞句(Chunk)のペアを列挙する def enumPairs(sentence): count = len(sentence) for i in range(count): for j in range(i+1, count): if sentence[i].hasNoun() and sentence[j].hasNoun(): yield sentence[i], sentence[j] # ciからのパスの中にcjが含まれているかを調べる def containsOther(ci, cj, sentence): curr = ci while curr.dst >= 0: if curr == cj: return True curr = sentence[curr.dst] return False # ciからのパスとcjからのパスが出会う番号を返す def connectedPoint(ci, cj, sentence): k = ci.dst while k >= 0: curr = cj while curr.dst >= 0: if curr.number == k: return k curr = sentence[curr.dst] k = sentence[k].dst return -1 # chunkの名詞の部分を to で指定した値に変更(オリジナルは変更しない) def replaceTo(to, chunk): dup = copy.deepcopy(chunk) for m in dup.morphs: if m.pos == '名詞': m.surface = to break return dup # 'Xで -> 始めて -> 人間という -> Y' というパス文字列を生成 def makePath1(ci, cj, sentence): path = [] curr = replaceTo('X', ci) while curr.number < cj.number: path.append(curr.concatMorphs()) curr = sentence[curr.dst] path.append('Y') return '{}'.format(' -> '.join(path)) # 'Xは | Yという -> ものを | 見た' という形式のパス文字列を生成 def makePath2(ci, cj, ck, sentence): ci = replaceTo('X', ci) cj = replaceTo('Y', cj) p1 = ci.concatMorphs() list1 = [] curr = cj while curr.number < ck.number: list1.append(curr.concatMorphs()) curr = sentence[curr.dst] p2 = '{}'.format(' -> '.join(list1)) list2 = [] curr = ck while curr.dst > 0: list2.append(curr.concatMorphs()) curr = sentence[curr.dst] p3 = '{}'.format(' -> '.join(list2)) return '{} | {} | {}'.format(p1, p2, p3) # 1センテンスから、パスを列挙する def extractPaths(sentence): for ci, cj in enumPairs(sentence): if containsOther(ci, cj, sentence): yield makePath1(ci, cj, sentence) else: k = connectedPoint(ci, cj, sentence) if k > 0: yield makePath2(ci, cj, sentence[k], sentence) # ファイルを読み込み、1センテンス毎に、extractPathsを呼び出し、Path(複数)を取り出し、ファイルに出力 def main(): article = analyze() with open('result49.txt', 'w', encoding='utf8') as w: for sentence in article: for path in extractPaths(sentence): w.write(path + '\n') if __name__ == '__main__': main()
ソースはGitHubでも公開しています。
■ 結果
先頭の10センテンスの結果を掲載します。
Xは -> Y Xで | Yが | つかぬ Xでも -> 薄暗い -> Y Xでも | Y | 泣いて -> 記憶している Xでも | Yだけは | 記憶している Xでも -> 薄暗い -> 所で -> 泣いて -> Y Xで | Y | 泣いて -> 記憶している Xで | Yだけは | 記憶している Xで -> 泣いて -> Y X | Yだけは | 記憶している X -> 泣いて -> Y Xだけは -> Y Xは | Yで -> 始めて -> 人間という -> ものを | 見た Xは | Yという -> ものを | 見た Xは | Yを | 見た Xで -> 始めて -> Y Xで -> 始めて -> 人間という -> Y Xという -> Y Xで | Yは | 種族であったそうだ Xで | Yという -> 人間中で | 種族であったそうだ Xで | Y中で | 種族であったそうだ Xで | Y -> 獰悪な | 種族であったそうだ Xで | Yな | 種族であったそうだ Xで -> 聞くと -> Y Xは | Yという -> 人間中で | 種族であったそうだ Xは | Y中で | 種族であったそうだ Xは | Y -> 獰悪な | 種族であったそうだ Xは | Yな | 種族であったそうだ Xは -> Y Xという -> Y Xという | Y -> 獰悪な | 種族であったそうだ Xという | Yな | 種族であったそうだ Xという -> 人間中で -> Y X中で | Y -> 獰悪な | 種族であったそうだ X中で | Yな | 種族であったそうだ X中で -> Y X -> Y X -> 獰悪な -> Y Xな -> Y Xというのは | Yを -> 捕えて -> 煮て -> 食うという | 話である Xというのは -> Y Xを -> 捕えて -> 煮て -> 食うという -> Y Xは | Yという -> 考も | なかったから -> 思わなかった Xは | Yも | なかったから -> 思わなかった Xという -> Y Xの -> Y Xの | Yと | 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである Xの -> 掌に -> 載せられて -> 持ち上げられた -> Y Xの -> 掌に -> 載せられて -> 持ち上げられた -> 時 -> フワフワした -> Y Xに | Yと | 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである Xに -> 載せられて -> 持ち上げられた -> Y Xに -> 載せられて -> 持ち上げられた -> 時 -> フワフワした -> Y Xと -> 持ち上げられた -> Y Xと -> 持ち上げられた -> 時 -> フワフワした -> Y X -> フワフワした -> Y
Posted by gushwell at
08:00
│Comments(0)
2018年06月04日
言語処理100本ノックでPython入門 #48 - 名詞から根へのパスの抽出
今日は、言語処理100本ノック 2015の問題48です。
■ 問題
48. 名詞から根へのパスの抽出
文中のすべての名詞を含む文節に対し,その文節から構文木の根に至るパスを抽出せよ. ただし,構文木上のパスは以下の仕様を満たすものとする.「吾輩はここで始めて人間というものを見た」という文(neko.txt.cabochaの8文目)から,次のような出力が得られるはずである.
- 各文節は(表層形の)形態素列で表現する
- パスの開始文節から終了文節に至るまで,各文節の表現を"->"で連結する
吾輩は -> 見た ここで -> 始めて -> 人間という -> ものを -> 見た 人間という -> ものを -> 見た ものを -> 見た
■ Pythonのコード
import re import functools class Morph: def __init__(self, surface, base, pos, pos1): self.surface = surface self.base = base self.pos = pos self.pos1 = pos1 def toList(self): return [self.surface, self.base, self.pos, self.pos1] class Chunk: def __init__(self, number, dst): self.number = number self.morphs = [] self.dst = dst self.srcs = [] def print(self): print(self.number) print([x.toList() for x in self.morphs]) print(self.dst, self.srcs) print() def concatMorphs(self): seq = filter(lambda x: x.pos != '記号', self.morphs) return functools.reduce(lambda x, y: x + y.surface, seq, '') def analyze(): article = [] sentence = [] chunk = None with open('neko.txt.cabocha', 'r', encoding='utf8') as fin: for line in fin: words = re.split(r'\t|,|\n| ', line) if line[0] == '*': num = int(words[1]) destNo = int(words[2].rstrip('D')) chunk = Chunk(num, destNo) sentence.append(chunk) elif words[0] == 'EOS': if sentence: for index, c in enumerate(sentence, 0): sentence[c.dst].srcs.append(index) article.append(sentence) sentence = [] else: chunk.morphs.append(Morph( words[0], words[7], words[1], words[2], )) return article def findNouns(sentence): for chunk in sentence: for m in chunk.morphs: if m.pos == '名詞': yield chunk break def makePath(sentence, chunk): curr = sentence[chunk.number] path = [] while curr.dst >= 0: path.append(curr.concatMorphs()) curr = sentence[curr.dst] path.append(curr.concatMorphs()) return path def enumPath(article): for sentence in article: for chunk in findNouns(sentence): path = makePath(sentence, chunk) if len(path) >= 2: yield path def main(): article = analyze() with open('result48.txt', 'w', encoding='utf8') as w: for path in enumPath(article): w.write('{}\n'.format(' -> '.join(path))) if __name__ == '__main__': main()
ソースコードは、GitHubで公開しています。
■ コードの簡単な説明
Morphクラス、Chunkクラス、analyze関数は変更ありません。
問題47は、結構複雑なコードで書いているうちに自分が何をやりたかったのか判らなくなることがありましたが、 この問題48は、問題47に比べて複雑さもそれほどではなかったです。
enumPath関数
analyze関数で得たarticleから、名詞を含む文節に対し,その文節から構文木の根に至るパスを抽出する関数
findNouns関数
enumPathの下請け関数
1つの文から、名詞を含む文節(Chunk)を列挙する。 makePath関数
enumPathの下請け関数
findNounsで得た文節から終了文節に至るまでのパスを作成するする関数
main関数
enumPathで得たパスを-> で連結しファイルに出力する
■ ひとり言
そういえば、初めて while 文を使ったかも。
makePathの中で使っています。while文は他の言語にもあるので、何も難しいことはないですね。
各文節を"->"で連結するのに、joinを使っていますが、いまだに、以下の書き方に慣れないです。
' -> '.join(path)
■ 結果
吾輩は -> 猫である 名前は -> 無い どこで -> 生れたか -> つかぬ 見当が -> つかぬ 何でも -> 薄暗い -> 所で -> 泣いて -> 記憶している 所で -> 泣いて -> 記憶している ニャーニャー -> 泣いて -> 記憶している いた事だけは -> 記憶している 吾輩は -> 見た ここで -> 始めて -> 人間という -> ものを -> 見た 人間という -> ものを -> 見た ものを -> 見た あとで -> 聞くと -> 種族であったそうだ それは -> 種族であったそうだ 書生という -> 人間中で -> 種族であったそうだ 人間中で -> 種族であったそうだ 一番 -> 獰悪な -> 種族であったそうだ 獰悪な -> 種族であったそうだ 書生というのは -> 話である 我々を -> 捕えて -> 煮て -> 食うという -> 話である 当時は -> なかったから -> 思わなかった 何という -> 考も -> なかったから -> 思わなかった 考も -> なかったから -> 思わなかった 彼の -> 掌に -> 載せられて -> 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである 掌に -> 載せられて -> 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである スーと -> 持ち上げられた -> 時 -> フワフワした -> 感じが -> あったばかりである 時 -> フワフワした -> 感じが -> あったばかりである 感じが -> あったばかりである 掌の -> 上で -> 落ちついて -> 見たのが -> 人間という -> ものの -> 見始であろう
Posted by gushwell at
07:30
│Comments(0)