alpの日記: 6/11 通勤音楽
- 6/11 通勤音楽
- R.Simpson, String Quartett #9, Delmé String Quartett
- Beethoven, Sonate #4 Es-Dur Op.7 & #6 F-Dur Op.10-2, Stephen Kovacevich-pf
- 同, Große Fuge B-Dur Op133, Budapest Quartet
昨日の日記を読みながら、LINDAT/CLARINのUDPipe API向けpython3ラッパー「UDPipe2UD.py」を書いてみた。Universal Dependenciesのid、form、lemma、upos、xpos、feats、head、deprel、deps、miscを全部格納するようにしたため、ちょっと長くなってしまった。
#! /usr/bin/python3 -i
class UDPipeEntry:
def __init__(self,result):
self.result=result
if "\n" in result:
t=[]
for r in result.split("\n"):
w=UDPipeEntry(r)
if w.id>0:
t.append(w)
for i,w in enumerate(t):
w.head=w if w.head==0 else t[i+w.head-w.id]
self.tokens=t
else:
w=result.split("\t")
try:
w[0],w[6]=int(w[0]),int(w[6])
except:
w=[0]*10
self.id,self.form,self.lemma,self.upos,self.xpos,self.feats,self.head,self.deprel,self.deps,self.misc=w if len(w)==10 else [0]*10
def __repr__(self):
return self.result
def __getitem__(self,item):
return self.tokens[item]
def __len__(self):
return len(self.tokens)
def browse(self):
import webbrowser,urllib.parse
h="http://kanji.zinbun.kyoto-u.ac.jp/~yasuoka/kyodokenkyu/ud-kanbun/conllusvg/viewer.svg"
webbrowser.open(h+"#"+urllib.parse.quote(str(self)))
class UDPipe2UD:
def __init__(self,lang="ja",option="tokenizer&tagger&parser"):
self.parseURL="http://lindat.mff.cuni.cz/services/udpipe/api/process?model="+lang+"&"+option
def __call__(self,sentence):
import urllib.request,urllib.parse,json
with urllib.request.urlopen(self.parseURL+"&data="+urllib.parse.quote(sentence)) as r:
q=r.read()
return UDPipeEntry(json.loads(q)["result"])
上のプログラムを「UDPipe2UD.py」に保存したら、とりあえず「望遠鏡で泳ぐ彼女を見た」をUDPipe2UDで係り受け解析してみよう。
% python3 -i UDPipe2UD.py
>>> ja=UDPipe2UD(lang="ja")
>>> s=ja("望遠鏡で泳ぐ彼女を見た")
>>> s.browse()
>>> print(s)
# newdoc
# newpar
# sent_id = 1
# text = 望遠鏡で泳ぐ彼女を見た
1 望遠鏡 望遠鏡 NOUN NN _ 3 obl _ SpaceAfter=No
2 で で ADP PS _ 1 case _ SpaceAfter=No
3 泳ぐ 泳ぐ VERB VV _ 4 acl _ SpaceAfter=No
4 彼女 彼女 PRON NP _ 6 obj _ SpaceAfter=No
5 を を ADP PS _ 4 case _ SpaceAfter=No
6 見 見る VERB VV _ 0 root _ SpaceAfter=No
7 た た AUX AV _ 6 aux _ SpaceAfter=No
うまく行けば、こんな感じのブラウザが立ち上がってきて、↑のUniversal Dependenciesが出力される。本来「望遠鏡←obl─見」となるべきところ、この出力結果では「望遠鏡←obl─泳ぐ」となってしまっているが、現時点ではUDPipeの解析性能が足りない、ということだろう。
>>> print(s[0])
1 望遠鏡 望遠鏡 NOUN NN _ 3 obl _ SpaceAfter=No
>>> print(s[0].lemma)
望遠鏡
>>> print(s[2])
3 泳ぐ 泳ぐ VERB VV _ 4 acl _ SpaceAfter=No
>>> print(s[2].upos)
VERB
>>> print(s[2].head)
4 彼女 彼女 PRON NP _ 6 obj _ SpaceAfter=No
>>> print(s[2].head.head)
6 見 見る VERB VV _ 0 root _ SpaceAfter=No
なお、この「UDPipe2UD.py」は、他の言語にも対応している。英語なら
>>> en=UDPipe2UD(lang="en")
>>> s=en("He sat down with smiling face")
>>> s.browse()
>>> print(s)
で、古典中国語(漢文)なら
>>> lzh=UDPipe2UD(lang="lzh",option="tokenizer=presegmented&tagger&parser")
>>> s=lzh("不入虎穴不得虎子")
>>> s.browse()
>>> print(s)
で、それぞれ動作するはずである。ぜひ試してみてほしい。
一昨昨日の日記で、私(安岡孝一)は書いた。
せめてCOTOHA APIも、ちゃんとUniversal Dependencies v2対応してくれないかなぁ。
『自然言語処理のためにMeCabを入れるのに疲れたのでCOTOHA APIを使った』を横目に見つつ、私なりにあれこれ考えてみて、COTOHA API構文解析v1のUniversal Dependencies v2向けpython3ラッパー「Cotoha2UD.py」を書いてみた。UPOSへの変換を、ある程度ちゃんとしようとしたら、思ったより長くなってしまった。
#! /usr/bin/python3 -i
class CotohaEntry:
def __init__(self,response):
import json
self.response=response
self.result=json.loads(response)["result"]
self.tokens=[]
for b in self.result:
for w in b["tokens"]:
w["xpos"]=w["pos"] if w["features"]==[] else w["pos"]+"["+",".join(w["features"])+"]"
w["feats"]="_"
w["head"]=w
w["deprel"]="root"
w["deps"]="_"
w["misc"]="SpaceAfter=No"
self.tokens.insert(w["id"],w)
for w in self.tokens:
if "dependency_labels" in w:
for r in w["dependency_labels"]:
t=self.tokens[r["token_id"]]
t["head"]=w
if r["label"]=="neg":
t["deprel"]="aux"
t["feats"]="Polarity=Neg"
else:
t["deprel"]=r["label"].replace("dobj","obj").replace("name","flat").replace("pass",":pass")
p={ "名詞接尾辞":"NOUN", "冠名詞":"NOUN", "補助名詞":"NOUN",
"動詞語幹": "VERB",
"冠動詞":"ADV", "冠形容詞":"ADV", "連用詞":"ADV",
"形容詞語幹":"ADJ",
"連体詞":"DET",
"接続詞":"CCONJ",
"独立詞":"INTJ",
"括弧":"PUNCT", "句点":"PUNCT", "読点":"PUNCT", "空白":"PUNCT",
"Symbol":"SYM",
"Number":"NUM" }
for w in self.tokens:
if w["pos"]=="名詞":
w["upos"]="NOUN"
if "代名詞" in w["xpos"] or "指示" in w["xpos"]:
w["upos"]="PRON"
if "固有" in w["xpos"]:
w["upos"]="PROPN"
else:
w["upos"]=p[w["pos"]] if w["pos"] in p else "PART"
if w["deprel"]=="case":
w["upos"]="ADP"
elif w["deprel"]=="cop" or w["deprel"].startswith("aux"):
w["upos"]="AUX"
def __repr__(self):
return "".join("\t".join([str(t["id"]+1),t["form"],t["lemma"],t["upos"],t["xpos"],t["feats"],str(0 if t["head"] is t else t["head"]["id"]+1),t["deprel"],t["deps"],t["misc"]])+"\n" for t in self.tokens)
def browse(self):
import webbrowser,urllib.parse
h="http://kanji.zinbun.kyoto-u.ac.jp/~yasuoka/kyodokenkyu/ud-kanbun/conllusvg/viewer.svg"
webbrowser.open(h+"#"+urllib.parse.quote(str(self)))
class Cotoha2UD:
def __init__(self,accessToken):
self.parseURL="https://api.ce-cotoha.com/api/dev/nlp/v1/parse"
self.accessToken=accessToken
def __call__(self,sentence):
import urllib.request,json
h={ "Content-Type":"application/json;charset=UTF-8",
"Authorization":"Bearer "+self.accessToken }
d={ "sentence":sentence }
u=urllib.request.Request(self.parseURL,json.dumps(d).encode(),h)
with urllib.request.urlopen(u) as r:
q=r.read()
return CotohaEntry(q)
上のプログラムを「Cotoha2UD.py」に保存したら、ちょっと面倒くさい手順で「アクセストークン」を取得する。そこまでが出来たら、とりあえず「望遠鏡で泳ぐ彼女をみた」を、Cotoha2UDで係り受け解析してみよう。
% python3 -i Cotoha2UD.py
>>> ja=Cotoha2UD(accessToken="アクセストークン")
>>> s=ja("望遠鏡で泳ぐ彼女を見た")
>>> s.browse()
>>> print(s)
1 望遠鏡 望遠鏡 NOUN 名詞 _ 3 nmod _ SpaceAfter=No
2 で で ADP 格助詞[連用] _ 1 case _ SpaceAfter=No
3 泳 泳ぐ VERB 動詞語幹[G] _ 5 amod _ SpaceAfter=No
4 ぐ ぐ AUX 動詞接尾辞[連体] _ 3 aux _ SpaceAfter=No
5 彼女 彼女 PRON 名詞[代名詞] _ 7 obj _ SpaceAfter=No
6 を を ADP 格助詞[連用] _ 5 case _ SpaceAfter=No
7 見 見る VERB 動詞語幹[A] _ 0 root _ SpaceAfter=No
8 た た AUX 動詞接尾辞[終止] _ 7 aux _ SpaceAfter=No
うまく行けば、こんな感じのブラウザが立ち上がってきて、↑のUniversal Dependenciesが出力される。本来「望遠鏡←obl─見」となるべきところ、この出力結果では「望遠鏡←nmod─泳」となってしまっているが、これはCOTOHA APIが解析をミスっているためなので、私としてはどうにもならない。うーん、COTOHA APIは「アクセストークン」が手間だし、1日1000回の制限があるし、やっぱりUDPipe APIの方が楽かなぁ…。
これだけで選択肢がほとんどない。ケースだけだと正直現行の HP のデスクトップケースが好ましい (本当はこれに +αで 3.5 or 5 inch取付けが欲しい) のですが、中身が話にならない。
UDPipe 1.2が古典中国語(漢文)をサポートした、との御連絡をいただいた。Universal Dependencies 2.4の「四書」を学習させたモデルなのだが、「文切り」の性能が38.9%までしか上がらなかったので、あくまで「単語切り」「品詞付与」「係り受け」に限定した方がいいだろう、とのことである。LINDAT/CLARINのサーバAPIにも実装されたので、ちょっと使ってみよう。
% python3
>>> import urllib.parse,urllib.request,json
>>> url="http://lindat.mff.cuni.cz/services/udpipe/api/process?model=lzh&tokenizer=presegmented&tagger&parser"
>>> s="不入虎穴不得虎子"
>>> with urllib.request.urlopen(url+"&data="+urllib.parse.quote(s)) as r:
... q=r.read()
...
>>> print(json.loads(q)["result"])
# newdoc
# newpar
# sent_id = 1
# text = 不入虎穴不得虎子
1 不 不 ADV v,副詞,否定,無界 Polarity=Neg 2 advmod _ SpaceAfter=No
2 入 入 VERB v,動詞,行為,移動 _ 6 advcl _ SpaceAfter=No
3 虎 虎 NOUN n,名詞,主体,動物 _ 2 obj _ SpaceAfter=No
4 穴 穴 NOUN n,名詞,固定物,地形 Case=Loc 3 flat _ SpaceAfter=No
5 不 不 ADV v,副詞,否定,無界 Polarity=Neg 6 advmod _ SpaceAfter=No
6 得 得 VERB v,動詞,行為,得失 _ 0 root _ SpaceAfter=No
7 虎 虎 NOUN n,名詞,主体,動物 _ 6 obj _ SpaceAfter=No
8 子 子 NOUN n,名詞,人,関係 _ 7 conj _ SpaceAfter=No
SVGで可視化すると、こんな感じ。「入らずんば」のadvclは正解しているが、残念ながら「虎←nmod─穴」と「虎←nmod─子」が読めておらず、漢文における名詞の連続を、まだ、うまく理解できていないようだ。さて、どう鍛えていくのがいいかな…。
AdobeのNLP-Cube 0.1.0.7がリリースされた、との御連絡をいただいた。とりあえず、pip3とpython3で、日本語モデル1.1も含め、インストール。
% pip3 install nlpcube==0.1.0.7
% python3
>>> from cube.api import Cube
>>> Cube().load("ja",1.1)
>>> quit()
今回のリリースの目玉は、Universal Dependenciesの出力回りの改善で、解析性能とかは改善されてない。試しに、言語処理100本ノック2015の『吾輩は猫である』から「ヴァイオリンを始める」文をNLP-Cubeで探してみよう。
% python3
>>> from cube.api import Cube
>>> ja=Cube()
>>> ja.load("ja")
>>> import urllib.request
>>> with urllib.request.urlopen("http://www.cl.ecei.tohoku.ac.jp/nlp100/data/neko.txt") as r:
... q=r.read()
...
>>> u=ja(q.decode("utf-8"))
>>> for s in u:
... f=False
... for w in s:
... if w.lemma=="ヴァイオリン" and w.label=="obj":
... if s[w.head-1].lemma=="始める":
... f=True
... if f:
... print("".join(str(w)+"\n" for w in s))
...
1 「 「 PUNCT _ _ 2 punct _ SpaceAfter=No
2 君 君 PRON _ _ 12 nsubj _ SpaceAfter=No
3 は は ADP _ _ 2 case _ SpaceAfter=No
4 ヴァイオリン ヴァイオリン NOUN _ _ 9 obj _ SpaceAfter=No
5 を を ADP _ _ 4 case _ SpaceAfter=No
6 いつ いつ NOUN _ _ 7 compound _ SpaceAfter=No
7 頃 頃 NOUN _ _ 9 obl _ SpaceAfter=No
8 から から ADP _ _ 7 case _ SpaceAfter=No
9 始め 始める VERB _ _ 12 acl _ SpaceAfter=No
10 た た AUX _ _ 9 aux _ SpaceAfter=No
11 の の PART _ _ 9 case _ SpaceAfter=No
12 かい かい AUX _ _ 0 root _ SpaceAfter=No
13 。 。 PUNCT _ _ 12 punct _ SpaceAfter=No
SVGで可視化するとこんな感じ。「君←nsubj」の係り受けリンクが「始め」じゃなくて「かい」から出ているのはイラっとくるものの、「ヴァイオリンを始める」がうまく検索できている。ただ、私(安岡孝一)の見る限り、日本語の解析性能においては、現状ではNLP-CubeよりGiNZAの方が上である。このあたり、「望遠鏡で泳ぐ彼女を見た」などを解析して、実際に確かめてみてほしい。
COTOHA APIの係り受け解析がUniversal Dependencies準拠だと聞いたので、試しに使ってみることにした。「アクセストークン」の取得がかなり面倒くさいのだが、そこまで行けば、あとはcurl一発で「望遠鏡で泳ぐ彼女を見た」を文法解析できる。
% curl -X POST -H 'Content-Type:application/json;charset=UTF-8' -H 'Authorization:Bearer アクセストークン' -d '{"sentence":"望遠鏡で泳ぐ彼女を見た"}' https://api.ce-cotoha.com/api/dev/nlp/v1/parse
{
"result" : [ {
"chunk_info" : {
"id" : 0,
"head" : 1,
"dep" : "D",
"chunk_head" : 0,
"chunk_func" : 1,
"links" : [ ]
},
"tokens" : [ {
"id" : 0,
"form" : "望遠鏡",
"kana" : "ボウエンキョウ",
"lemma" : "望遠鏡",
"pos" : "名詞",
"features" : [ ],
"dependency_labels" : [ {
"token_id" : 1,
"label" : "case"
} ],
"attributes" : { }
}, {
"id" : 1,
"form" : "で",
"kana" : "デ",
"lemma" : "で",
"pos" : "格助詞",
"features" : [ "連用" ],
"attributes" : { }
} ]
}, {
"chunk_info" : {
"id" : 1,
"head" : 2,
"dep" : "D",
"chunk_head" : 0,
"chunk_func" : 1,
"links" : [ {
"link" : 0,
"label" : "condition"
} ]
},
"tokens" : [ {
"id" : 2,
"form" : "泳",
"kana" : "オヨ",
"lemma" : "泳ぐ",
"pos" : "動詞語幹",
"features" : [ "G" ],
"dependency_labels" : [ {
"token_id" : 0,
"label" : "nmod"
}, {
"token_id" : 3,
"label" : "aux"
} ],
"attributes" : { }
}, {
"id" : 3,
"form" : "ぐ",
"kana" : "グ",
"lemma" : "ぐ",
"pos" : "動詞接尾辞",
"features" : [ "連体" ],
"attributes" : { }
} ]
}, {
"chunk_info" : {
"id" : 2,
"head" : 3,
"dep" : "D",
"chunk_head" : 0,
"chunk_func" : 1,
"links" : [ {
"link" : 1,
"label" : "adjectivals"
} ]
},
"tokens" : [ {
"id" : 4,
"form" : "彼女",
"kana" : "カノジョ",
"lemma" : "彼女",
"pos" : "名詞",
"features" : [ "代名詞" ],
"dependency_labels" : [ {
"token_id" : 2,
"label" : "amod"
}, {
"token_id" : 5,
"label" : "case"
} ],
"attributes" : { }
}, {
"id" : 5,
"form" : "を",
"kana" : "ヲ",
"lemma" : "を",
"pos" : "格助詞",
"features" : [ "連用" ],
"attributes" : { }
} ]
}, {
"chunk_info" : {
"id" : 3,
"head" : -1,
"dep" : "O",
"chunk_head" : 0,
"chunk_func" : 1,
"links" : [ {
"link" : 2,
"label" : "object"
} ],
"predicate" : [ "past" ]
},
"tokens" : [ {
"id" : 6,
"form" : "見",
"kana" : "ミ",
"lemma" : "見る",
"pos" : "動詞語幹",
"features" : [ "A" ],
"dependency_labels" : [ {
"token_id" : 4,
"label" : "dobj"
}, {
"token_id" : 7,
"label" : "aux"
} ],
"attributes" : { }
}, {
"id" : 7,
"form" : "た",
"kana" : "タ",
"lemma" : "た",
"pos" : "動詞接尾辞",
"features" : [ "終止" ],
"attributes" : { }
} ]
} ],
"status" : 0,
"message" : ""
}
ざっと見たところ、rootがどこに刺さっているのか明示されていないし、品詞がかなり独特である。正直なところ、これではUniversal Dependencies準拠とは言い難い。とりあえずGraphviz Onlineで、単語間の係り受けを可視化してみたところ、こんな感じ。本来「望遠鏡←obl─見」であるべきところが、残念ながら「望遠鏡←nmod─泳」と解析されてしまっていて、かなり悲しい。
これだと、UDPipe APIの方が「アクセストークン」も不要で使いやすいし、解析結果も上だったりする。せめてCOTOHA APIも、ちゃんとUniversal Dependencies v2対応してくれないかなぁ。
物事のやり方は一つではない -- Perlな人