パスワードを忘れた? アカウント作成
15306962 journal
人工知能

yasuokaの日記: 単語内のリンクにgoeswithを用いた日本語係り受け解析器の製作

日記 by yasuoka

6月4日の日記でリリースしたbert-large-japanese-char-extendedをもとに、日本語係り受け解析器を作ってみることにした。ただ、bert-large-japanese-char-extendedの単語長は1文字なので、UD_Japanese-GSDの単語長である国語研短単位とは、長さが合わない。多少、悩んだあげく、単語内のリンクにgoeswithを使っておいて、解析後にgoeswithを削り取ることで、単語組み上げをおこなうことにした。Google Colaboratory(GPU版)で、TransformersのAutoModelForTokenClassificationを使って、日本語係り受け解析器を作ってみよう。

!test -d UD_Japanese-GSD || git clone --depth=1 https://github.com/universaldependencies/UD_Japanese-GSD
!pip install transformers datasets
from transformers import AutoTokenizer,AutoConfig,AutoModelForTokenClassification,DataCollatorForTokenClassification,TrainingArguments,Trainer
from unicodedata import category
from datasets.arrow_dataset import Dataset
brt="KoichiYasuoka/bert-large-japanese-char-extended"
with open("UD_Japanese-GSD/ja_gsd-ud-train.conllu","r",encoding="utf-8") as f:
  r=f.read()
tok,tag=[],[]
for u in r.strip().split("\n\n"):
  w=[t for t in u.split("\n") if t.startswith("# text = ")][0]
  s=[t.split("\t") for t in u.split("\n") if not t.startswith("#")]
  for i,v in enumerate([v for v in w[9:] if category(v)!="Zs"]):
    t=s[i]
    x,t[0],t[1],t[2],t[8],t[9]=t[1],str(i+1),v,"_","_","_" if t[9].find("SpaceAfter=No")<0 else "SpaceAfter=No"
    if v!=x:
      s.insert(i+1,[str(i+2),x[1:],"_","X","_","_",t[6] if t[7]=="goeswith" else str(i+1),"goeswith","_",t[9]])
      t[9]="SpaceAfter=No"
      for t in [t for t in s if int(t[6])>i+1]:
        t[6]=str(int(t[6])+1)
  tok.append([t[1] for t in s])
  tag.append(["\t".join(t[3:6]+["{:+}".format(int(t[6])-int(t[0])) if int(t[6]) else "0",t[7]]) for t in s])
lid={l:i for i,l in enumerate(set(sum(tag,[])))}
tkz=AutoTokenizer.from_pretrained(brt)
dts=Dataset.from_dict({"tokens":tok,"tags":tag,"input_ids":[tkz.convert_tokens_to_ids(s) for s in tok],"labels":[[lid[t] for t in s] for s in tag]})
cfg=AutoConfig.from_pretrained(brt,num_labels=len(lid),label2id=lid,id2label={i:l for l,i in lid.items()})
mdl=AutoModelForTokenClassification.from_pretrained(brt,config=cfg)
dcl=DataCollatorForTokenClassification(tokenizer=tkz)
arg=TrainingArguments(output_dir="/tmp",overwrite_output_dir=True,per_device_train_batch_size=4,save_total_limit=2)
trn=Trainer(model=mdl,args=arg,data_collator=dcl,train_dataset=dts)
trn.train()
trn.save_model("bert-large-japanese-char-ud-goeswith")
tkz.save_pretrained("bert-large-japanese-char-ud-goeswith")

GPU版なら45分程度で、bert-large-japanese-char-ud-goeswithモデルが出来上がるはずだ。「昨日は誰も来なかった」を係り受け解析してみよう。

!pip install transformers deplacy
import torch
from transformers import AutoTokenizer,AutoModelForTokenClassification
from unicodedata import category
tokenizer=AutoTokenizer.from_pretrained("bert-large-japanese-char-ud-goeswith")
model=AutoModelForTokenClassification.from_pretrained("bert-large-japanese-char-ud-goeswith")
def nlp(sentence):
  s=[t for t in sentence if category(t)!="Zs"]
  m=[i-j-1 for j,i in enumerate([i for i,t in enumerate(sentence) if category(t)=="Zs"])]
  e=tokenizer.encode(s,return_tensors="pt",add_special_tokens=False)
  for i,q in enumerate(torch.argmax(model(e)[0],dim=2)[0].tolist()):
    t=model.config.id2label[q].split("\t")
    t[3]=str(int(t[3])+i+1) if int(t[3]) else "0"
    s[i]=[s[i],"_"]+t+["_","_" if i in m else "SpaceAfter=No"]
  for i in [i for i in range(len(s)-1,0,-1) if s[i][6]=="goeswith"]:
    t=s.pop(i)
    s[i-1][0]+=t[0]
    s[i-1][8]=t[8]
    for t in [t for t in s if int(t[5])>i]:
      t[5]=str(int(t[5])-1)
  return "\n".join("\t".join([str(i+1)]+t) for i,t in enumerate(s))+"\n\n"
doc=nlp("昨日は誰も来なかった")
import deplacy
deplacy.render(doc,Japanese=True)
deplacy.serve(doc,port=None)

私(安岡孝一)の手元では、以下の結果になった。

昨日   NOUN ═╗<════╗ obl(斜格補語)
は     ADP  <╝     ║ case(格表示)
誰     PRON ═╗<══╗ ║ nsubj(主語)
も     ADP  <╝   ║ ║ case(格表示)
来     VERB ═╗═╗═╝═╝ root(親)
なかっ AUX  <╝ ║     aux(動詞補助成分)
た     AUX  <══╝     aux(動詞補助成分)

1    昨日    _    NOUN    名詞-普通名詞-副詞可能    _    5    obl    _    SpaceAfter=No
2    は    _    ADP    助詞-係助詞    _    1    case    _    SpaceAfter=No
3    誰    _    PRON    代名詞    _    5    nsubj    _    SpaceAfter=No
4    も    _    ADP    助詞-係助詞    _    3    case    _    SpaceAfter=No
5    来    _    VERB    動詞-非自立可能    _    0    root    _    SpaceAfter=No
6    なかっ    _    AUX    助動詞    Polarity=Neg    5    aux    _    SpaceAfter=No
7    た    _    AUX    助動詞    _    5    aux    _    SpaceAfter=No

SVGで可視化すると、こんな感じ。goeswithを削り取る前の状態も見てみよう。

!pip install transformers deplacy
import torch
from transformers import AutoTokenizer,AutoModelForTokenClassification
from unicodedata import category
tokenizer=AutoTokenizer.from_pretrained("bert-large-japanese-char-ud-goeswith")
model=AutoModelForTokenClassification.from_pretrained("bert-large-japanese-char-ud-goeswith")
def nlp(sentence):
  s=[t for t in sentence if category(t)!="Zs"]
  m=[i-j-1 for j,i in enumerate([i for i,t in enumerate(sentence) if category(t)=="Zs"])]
  e=tokenizer.encode(s,return_tensors="pt",add_special_tokens=False)
  for i,q in enumerate(torch.argmax(model(e)[0],dim=2)[0].tolist()):
    t=model.config.id2label[q].split("\t")
    t[3]=str(int(t[3])+i+1) if int(t[3]) else "0"
    s[i]=[s[i],"_"]+t+["_","_" if i in m else "SpaceAfter=No"]
  return "\n".join("\t".join([str(i+1)]+t) for i,t in enumerate(s))+"\n\n"
doc=nlp("昨日は誰も来なかった")
import deplacy
deplacy.render(doc,Japanese=True)
deplacy.serve(doc,port=None)

私の手元では、goeswithを削り取る前は以下の状態だった。

昨 NOUN ═╗═╗<══════╗ obl(斜格補語)
日 X    <╝ ║       ║ goeswith(泣き別れ)
は ADP  <══╝       ║ case(格表示)
誰 PRON ═╗<══════╗ ║ nsubj(主語)
も ADP  <╝       ║ ║ case(格表示)
来 VERB ═════╗═╗═╝═╝ root(親)
な AUX  ═╗═╗<╝ ║     aux(動詞補助成分)
か X    <╝ ║   ║     goeswith(泣き別れ)
っ X    <══╝   ║     goeswith(泣き別れ)
た AUX  <══════╝     aux(動詞補助成分)

1    昨    _    NOUN    名詞-普通名詞-副詞可能    _    6    obl    _    SpaceAfter=No
2    日    _    X    _    _    1    goeswith    _    SpaceAfter=No
3    は    _    ADP    助詞-係助詞    _    1    case    _    SpaceAfter=No
4    誰    _    PRON    代名詞    _    6    nsubj    _    SpaceAfter=No
5    も    _    ADP    助詞-係助詞    _    4    case    _    SpaceAfter=No
6    来    _    VERB    動詞-非自立可能    _    0    root    _    SpaceAfter=No
7    な    _    AUX    助動詞    Polarity=Neg    6    aux    _    SpaceAfter=No
8    か    _    X    _    _    7    goeswith    _    SpaceAfter=No
9    っ    _    X    _    _    7    goeswith    _    SpaceAfter=No
10    た    _    AUX    助動詞    _    6    aux    _    SpaceAfter=No

形態素解析器(MeCabやfugashi)に頼らず、系列ラベリングだけで組み上げたにしては、まずまずの結果だ。さて、6月22日発表予定の『世界のUniversal Dependenciesと係り受け解析ツール群』に、このアイデアも使えるかな。

この議論は、yasuoka (21275)によって ログインユーザだけとして作成されたが、今となっては 新たにコメントを付けることはできません。
typodupeerror

日本発のオープンソースソフトウェアは42件 -- ある官僚

読み込み中...