パスワードを忘れた? アカウント作成

yasuokaさんのトモダチの日記みんなの日記も見てね。 最新から新しい日記やタレこみを確認できますよ。

15456883 journal
日本

yasuokaの日記: 国語研「長単位」とSudachiPyのモードC

日記 by yasuoka

昨日の日記に対して、SudachiPyのモードCを使えば「長単位」の形態素解析ができるのではないか、との御意見をいただいた。最新版のSudachiPyをインストールしつつ、ちょっとやってみよう。

$ pip3 install -U SudachiPy SudachiDict-core --user
$ echo 全学年にわたって小学校の国語の教科書に大量の挿し絵が用いられている | sudachipy -m C
全    接頭辞,*,*,*,*,*    全
学年    名詞,普通名詞,一般,*,*,*    学年
に    助詞,格助詞,*,*,*,*    に
わたっ    動詞,非自立可能,*,*,五段-ラ行,連用形-促音便    渡る
て    助詞,接続助詞,*,*,*,*    て
小学校    名詞,普通名詞,一般,*,*,*    小学校
の    助詞,格助詞,*,*,*,*    の
国語    名詞,普通名詞,一般,*,*,*    国語
の    助詞,格助詞,*,*,*,*    の
教科書    名詞,普通名詞,一般,*,*,*    教科書
に    助詞,格助詞,*,*,*,*    に
大量    名詞,普通名詞,形状詞可能,*,*,*    大量
の    助詞,格助詞,*,*,*,*    の
挿し絵    名詞,普通名詞,一般,*,*,*    挿し絵
が    助詞,格助詞,*,*,*,*    が
用い    動詞,一般,*,*,上一段-ア行,未然形-一般    用いる
られ    助動詞,*,*,*,助動詞-レル,連用形-一般    られる
て    助詞,接続助詞,*,*,*,*    て
いる    動詞,非自立可能,*,*,上一段-ア行,終止形-一般    居る
EOS

「全学年」「にわたって」「ている」が複数行に分かれてしまっており、SudachiPyのモードCは、残念ながら「長単位」ではない。ただ、国語研「長単位」よりも、SudachiPyのモードCの方が、私(安岡孝一)個人としては扱いやすいような気もする。さてさて、どうしたらいいかな。

15456195 journal
人工知能

yasuokaの日記: Transformersとbert-base-japanese-char-extendedとUD_Japanese-GSDで作る日本語「長単位」形態素解析器

日記 by yasuoka

今日のNINJALサロンで話題になったので、日本語「長単位」形態素解析器を試作してみることにした。方法としてはUD_Japanese-GSDのLUWPOSを、Transformersのrun_ner.pybert-base-japanese-char-extendedで、何とか「長単位」の系列ラベリングに落とし込む。Google Colaboratory (GPU版)だと、こんな感じ。

!pip install transformers datasets seqeval
!test -d UD_Japanese-GSD || git clone https://github.com/universaldependencies/UD_Japanese-GSD
!test -f run_ner.py || curl -LO https://raw.githubusercontent.com/huggingface/transformers/v`pip list | sed -n 's/^transformers *\([^ ]*\) *$/\1/p'`/examples/pytorch/token-classification/run_ner.py

for d in ["train","dev","test"]:
  with open("UD_Japanese-GSD/ja_gsd-ud-"+d+".conllu","r",encoding="utf-8") as f:
    r=f.read()
  with open(d+".json","w",encoding="utf-8") as f:
    tokens=[]
    tags=[]
    i=0
    for s in r.split("\n"):
      t=s.split("\t")
      if len(t)==10 and not s.startswith("#"):
        for c in t[1]:
          tokens.append(c)
        b=t[9][t[9].index("LUWBILabel=")+11]
        p=[u[7:] for u in t[9].split("|") if u.startswith("LUWPOS=")][0]
        if p=="名詞-普通名詞-副詞可能":
          p="名詞-普通名詞-一般"
        p=p.replace("-","/")
        tags.extend([b+"-"+p]+["I-"+p]*(len(t[1])-1))
      else:
        if len(tokens)>0:
          print("{\"tokens\":[\""+"\",\"".join(tokens)+"\"],\"tags\":[\""+"\",\"".join(tags)+"\"]}",file=f)
          tokens=[]
          tags=[]
    if len(tokens)>0:
      print("{\"tokens\":[\""+"\",\"".join(tokens)+"\"],\"tags\":[\""+"\",\"".join(tags)+"\"]}",file=f)

!python run_ner.py --model_name_or_path KoichiYasuoka/bert-base-japanese-char-extended --train_file train.json --validation_file dev.json --test_file test.json --output_dir ja_luw.pos --do_train --do_eval --do_predict

ただ「名詞-普通名詞-副詞可能」というLUWPOSが、一ヶ所だけ現れて気持ち悪かったので、そこは「名詞-普通名詞-一般」に直している。私(安岡孝一)の手元では、15分程度でja_luw.posが出来上がり、以下のmetricsとなった。

***** train metrics *****
  epoch                    =        3.0
  train_loss               =     0.2678
  train_runtime            = 0:13:01.48
  train_samples            =       7050
  train_samples_per_second =     27.064
  train_steps_per_second   =      3.386

***** eval metrics *****
  epoch                   =        3.0
  eval_accuracy           =     0.9637
  eval_f1                 =     0.9705
  eval_loss               =     0.1588
  eval_precision          =     0.9678
  eval_recall             =     0.9734
  eval_runtime            = 0:00:06.57
  eval_samples            =        507
  eval_samples_per_second =     77.053
  eval_steps_per_second   =      9.727

***** predict metrics *****
  predict_accuracy           =     0.9669
  predict_f1                 =     0.9672
  predict_loss               =     0.1594
  predict_precision          =     0.9624
  predict_recall             =     0.9721
  predict_runtime            = 0:00:06.92
  predict_samples_per_second =     78.466
  predict_steps_per_second   =      9.826

96%程度なので、まあ悪くない感じだ。出来上がったja_luw.posで、「全学年にわたって小学校の国語の教科書に大量の挿し絵が用いられている」を形態素解析してみよう。

from transformers import AutoModelForTokenClassification,AutoTokenizer,TokenClassificationPipeline
mdl=AutoModelForTokenClassification.from_pretrained("ja_luw.pos")
tkz=AutoTokenizer.from_pretrained("ja_luw.pos")
nlp=TokenClassificationPipeline(model=mdl,tokenizer=tkz,aggregation_strategy="simple")
d=nlp(inputs="全学年にわたって小学校の国語の教科書に大量の挿し絵が用いられている")
print(d)

私の手元では、以下の結果になった。

[{'entity_group': '名詞/普通名詞/一般', 'score': 0.99953014, 'word': '全学年', 'start': 0, 'end': 3}, {'entity_group': '助詞/格助詞', 'score': 0.99852294, 'word': 'にわたって', 'start': 3, 'end': 8}, {'entity_group': '名詞/普通名詞/一般', 'score': 0.9992971, 'word': '小学校', 'start': 8, 'end': 11}, {'entity_group': '助詞/格助詞', 'score': 0.99976945, 'word': 'の', 'start': 11, 'end': 12}, {'entity_group': '名詞/普通名詞/一般', 'score': 0.99958575, 'word': '国語', 'start': 12, 'end': 14}, {'entity_group': '助詞/格助詞', 'score': 0.99973565, 'word': 'の', 'start': 14, 'end': 15}, {'entity_group': '名詞/普通名詞/一般', 'score': 0.99962956, 'word': '教科書', 'start': 15, 'end': 18}, {'entity_group': '助詞/格助詞', 'score': 0.99977046, 'word': 'に', 'start': 18, 'end': 19}, {'entity_group': '形状詞/一般', 'score': 0.98628527, 'word': '大量', 'start': 19, 'end': 21}, {'entity_group': '助詞/格助詞', 'score': 0.99945784, 'word': 'の', 'start': 21, 'end': 22}, {'entity_group': '名詞/普通名詞/一般', 'score': 0.9996125, 'word': '挿し絵', 'start': 22, 'end': 25}, {'entity_group': '助詞/格助詞', 'score': 0.99980253, 'word': 'が', 'start': 25, 'end': 26}, {'entity_group': '動詞/一般/上一段/ア行', 'score': 0.9828689, 'word': '用い', 'start': 26, 'end': 28}, {'entity_group': '助動詞/助動詞/レル', 'score': 0.99877167, 'word': 'られ', 'start': 28, 'end': 30}, {'entity_group': '助動詞/上一段/ア行', 'score': 0.99854565, 'word': 'ている', 'start': 30, 'end': 33}]

「全学年」「にわたって」「小学校」「の」「国語」「の」「教科書」「に」「大量」「の」「挿し絵」「が」「用い」「られ」「ている」となっており、私の見る限り、正しく解析できているようだ。さて、UD_Japanese-GSDLUWがリリースされたら、このあたり、もっとうまくいくかな。

15449480 journal
教育

yasuokaの日記: CaboChaのpythonバインドにおける「バグ」と「構文解説」 1

日記 by yasuoka

ネットサーフィンしていたところ、Tech Teacher Blogで「Pythonで自然言語処理を行うには?具体的な手順を解説!」(2021年10月2日)という記事を見つけた。pythonで自然言語処理をおこないたいらしいのだが、どういうわけかTransformersGiNZAも出てこない。CaboChaに至っては、こんな感じ。

Cabochaは構文解説を行うためのライブラリです。構文解析は形態素解説の次の段階で、単語同士の結び付きを解説し、構文木を作成することを指します。日本語の構文解析が行えるライブラリは、他に中々ありません。日本語の自然言語処理を行いたいなら、MecabとCabochaはほぼ確実に使うことになるでしょう。

「構文解説」とか「形態素解説」とか「単語同士の結び付きを解説」とか、正直ワケがわからない。『日本語の構文解析における3つの「係り受け」』にも書いた通り、CaboChaは二文節間の「係り受け」解析器であり、「単語同士の結び付きを解説」したりはしない。また、CaboCha 0.69のpythonバインドには、かなり致命的なバグがある。ちょっと使ってみよう。

% python3
>>> import CaboCha
>>> c=CaboCha.Parser()
>>> s1=c.parse("吾輩は猫である")
>>> s2=c.parse("名前はまだ無い")
>>> print(s1.toString(CaboCha.FORMAT_TREE))
名前は---D
    まだ-D
      無い
EOS

>>> print(s2.toString(CaboCha.FORMAT_TREE))
名前は---D
    まだ-D
      無い
EOS

>>> print(s1.sentence())
名前はまだ無い
>>> print(s2.sentence())
名前はまだ無い
>>> print(s1 is s2)
False
>>> print(s1==s2)
False

こういうバグを知った上で、それでもCaboChaのpythonバインドを勧めているのなら、それはそれでイバラの道だとは思うのだけど、どうしてTech Teacherは、TransformersやGiNZAを使わないんだろ。

15427359 journal
日本

yasuokaの日記: 子の名づけにおける平仮名又は片仮名と三代戸籍禁止の原則 1

日記 by yasuoka

ネットサーフィンしていたところ、ダ・ヴィンチニュースで『自分と同じ漢字でも読み方が違えば子どもの名前にできる?』(2021年9月7日)という問題を見つけた。

【問い】日本では、親と同じ名前を子どもにつけることは原則認められていないが、母が「幸子(さちこ)」、子どもが「幸子(ゆきこ)」のように、読み方が違えば認められる。

もちろん答は「×」(cf.名古屋高等裁判所[昭和38年(ラ)第128号]昭和38年11月9日決定、『高等裁判所民事判例集』第16巻第8号664~669頁)だが、解説に妙なことが書かれていた。

【解説】人名に使える漢字は、戸籍法の施行規則で定められている。現在は常用漢字2136字に加え、人名用漢字は863字。2015(平成27)年に巫女の「巫」、2017(平成29)年に渾身(こんしん)の「渾」が加わった。この範囲内で子どもの名前をつけるわけだが、常用漢字の中にはよくない意味を持つものもある。難解なもの、卑猥(ひわい)な意味の場合は「命名権の乱用」と判断され、認められない場合もあるのだ。ところで、海外では子どもに自分と同じ名前をつけ、親には「シニア」、子には「ジュニア」などと付記して区別したりもするが、日本では子に親と同じ名前をつけられるのか。同じ戸籍内で、親と同じ名前を子どもにつけることは原則、認められていない。同じ戸籍内だと、兄弟、祖父母と孫の関係でもつけられないわけだ。また、母が「幸子(さちこ)」、子どもが「幸子(ゆきこ)」のように、読み方が違っても、漢字が同じならばこれも認められない。

「この範囲内で子どもの名前をつける」とあるが、これらの漢字に加えて、片仮名又は平仮名も認められている(戸籍法施行規則第六十条)。また、「同じ戸籍内で」「祖父母と孫の関係」とあるが、三代戸籍禁止の原則によって「祖父母と孫」は同じ戸籍には入らない。現在の戸籍法第六条を見てみよう。

第六条 戸籍は、市町村の区域内に本籍を定める一の夫婦及びこれと氏を同じくする子ごとに、これを編製する。ただし、日本人でない者(以下「外国人」という。)と婚姻をした者又は配偶者がない者について新たに戸籍を編製するときは、その者及びこれと氏を同じくする子ごとに、これを編製する。

つまり、婚姻の時点で新たな戸籍を編製するので、祖父母と孫は同じ戸籍に入らない。ヤヤコシイのは私生児の場合で、この場合は、母子の戸籍を新たに編製するか、ケースによっては、母を祖父母の戸籍に残す形で子のみの単独戸籍を編製する。さらにヤヤコシイのは、祖父母が孫を特別養子縁組する場合だが、この場合は子の単独戸籍の編製が先行して、命名が先におこなわれるので、極端な話、祖父母のどちらかと同じ名であっても構わない。「祖父母と孫」が問題になるケースは、現在の戸籍法では有り得ないはずなのだが、それとも、私(安岡孝一)が何か見落としてるのかしら?

15426680 journal
人工知能

yasuokaの日記: 「実験を行っています」をUniDic2UDはどう解析するのか

日記 by yasuoka

「実験を行っています」という文を、UniDic2UDがどう解析するのか、ちょっと確かめてみた。まずは、最新版にアップデート。

$ pip3 install -U unidic2ud

アップデートできたら、コマンドラインでダイレクトに解析してみよう。

$ echo 実験を行っています | unidic2ud
# sent_id = 1
# text = 実験を行っています
1    実験    実験    NOUN    名詞-普通名詞-サ変可能    _    3    obj    _    SpaceAfter=No
2    を    を    ADP    助詞-格助詞    _    1    case    _    SpaceAfter=No
3    行っ    行う    VERB    動詞-一般    _    0    root    _    SpaceAfter=No
4    て    て    SCONJ    助詞-接続助詞    _    3    mark    _    SpaceAfter=No
5    い    居る    AUX    動詞-非自立可能    _    3    aux    _    SpaceAfter=No
6    ます    ます    AUX    助動詞    _    3    aux    _    SpacesAfter=\n

私(安岡孝一)の見る限り、ちゃんと「行う」に解析しているように見える。まあ、係り受けのフィードバックがあるから、このぐらいは読めてほしいところだけど。

15425383 journal
人工知能

yasuokaの日記: rinna/japanese-roberta-baseのトークナイザをRemBertTokenizerFastで置き換えるには

日記 by yasuoka

思うところあって、rinna/japanese-roberta-baseのトークナイザを、TransformersのRemBertTokenizerFastで置き換えてみた。T5Tokenizerはdo_lower_case=Trueを嫌っているし、まして[CLS]を付与したりしてくれないからだ。とりあえずGoogle Colaboratoryで、やってみよう。

!pip install 'transformers>=4.10.0' sentencepiece
from transformers import RemBertTokenizerFast,RobertaForMaskedLM
from transformers.file_utils import cached_path,hf_bucket_url
rinna="rinna/japanese-roberta-base"
tokenizer=RemBertTokenizerFast.from_pretrained(rinna,vocab_file=cached_path(hf_bucket_url(rinna,"spiece.model")))
model=RobertaForMaskedLM.from_pretrained(rinna)
tokenizer.do_lower_case=True
tokenizer.backend_tokenizer.pre_tokenizer.add_prefix_space=False
model.config.tokenizer_class="RemBertTokenizerFast"
tokenizer.save_pretrained("my.rinna.model")
model.save_pretrained("my.rinna.model")

うまくいけばmy.rinna.modelに新たなモデルが作られる。add_prefix_space=Falseすべきか迷ったのだが、これがTrueだと、[MASK]の直後に"_"を入れてくるのでウザイのだ。ちょっと使ってみよう。

import torch
from transformers import AutoTokenizer,AutoModelForMaskedLM
tokenizer=AutoTokenizer.from_pretrained("my.rinna.model")
model=AutoModelForMaskedLM.from_pretrained("my.rinna.model")
s=tokenizer("4年に1度[MASK]は開かれる。",return_offsets_mapping=True)
print(s)
ids=s["input_ids"]
mask=ids.index(tokenizer.mask_token_id)
tokens=tokenizer.convert_ids_to_tokens(ids)
print(tokens,mask)
inputs=torch.tensor([ids])
with torch.no_grad():
  outputs=model(inputs)
  pred=outputs[0][0,mask].topk(5)
for i,t in enumerate(tokenizer.convert_ids_to_tokens(pred.indices)):
  tokens[mask]=t
  print(i+1,tokens)

「4年に1度[MASK]は開かれる。」を穴埋めさせてみたところ、私(安岡孝一)の手元では以下の結果になった。

{'input_ids': [4, 37, 44, 24, 368, 6, 11, 21583, 8, 5], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'offset_mapping': [(0, 0), (0, 1), (1, 3), (3, 4), (4, 5), (5, 11), (11, 12), (12, 16), (16, 17), (0, 0)]}
['[CLS]', '4', '年に', '1', '度', '[MASK]', 'は', '開かれる', '。', '[SEP]'] 5
1 ['[CLS]', '4', '年に', '1', '度', '大会', 'は', '開かれる', '。', '[SEP]']
2 ['[CLS]', '4', '年に', '1', '度', '株主総会', 'は', '開かれる', '。', '[SEP]']
3 ['[CLS]', '4', '年に', '1', '度', 'オールスターゲーム', 'は', '開かれる', '。', '[SEP]']
4 ['[CLS]', '4', '年に', '1', '度', 'オリンピック', 'は', '開かれる', '。', '[SEP]']
5 ['[CLS]', '4', '年に', '1', '度', 'アジア競技大会', 'は', '開かれる', '。', '[SEP]']

まあまあの結果だが、文末の[SEP]は無い方がいいのかもしれない。ただ、単語長がUniDicより長い部分があるので、トークナイザを置き換えてもSuPar-UniDicには適さないようだ。うーん、残念。

15423437 journal
人工知能

yasuokaの日記: 日本語・中国語・タイ語の係り受け解析モジュールesuparリリース(仮)

日記 by yasuoka

9月14日一昨日昨日の日記の手法を一まとめにして、esuparというpython3モジュールとしてリリース(仮)した。日本語と中国語(簡化字・繁体字および文言文/漢文)とタイ語の係り受け解析がおこなえる。Linux系OSなら

$ pip3 install esupar --user

一発でインストール可能なはずだ。インストールがうまくいったら、まずは「太郎は花子が読んでいる本を次郎に渡した」を係り受け解析してみよう。

$ python3
>>> import esupar
>>> nlp=esupar.load("ja")
>>> doc=nlp("太郎は花子が読んでいる本を次郎に渡した")
>>> print(doc)
1    太郎    _    PROPN    _    _    12    nsubj    _    SpaceAfter=No
2    は    _    ADP    _    _    1    case    _    SpaceAfter=No
3    花子    _    PROPN    _    _    5    nsubj    _    SpaceAfter=No
4    が    _    ADP    _    _    3    case    _    SpaceAfter=No
5    読ん    _    VERB    _    _    8    acl    _    SpaceAfter=No
6    で    _    SCONJ    _    _    5    mark    _    SpaceAfter=No
7    いる    _    AUX    _    _    5    aux    _    SpaceAfter=No
8    本    _    NOUN    _    _    12    obj    _    SpaceAfter=No
9    を    _    ADP    _    _    8    case    _    SpaceAfter=No
10    次郎    _    PROPN    _    _    12    obl    _    SpaceAfter=No
11    に    _    ADP    _    _    10    case    _    SpaceAfter=No
12    渡し    _    VERB    _    _    0    root    _    SpaceAfter=No
13    た    _    AUX    _    _    12    aux    _    _

>>> import deplacy
>>> deplacy.render(doc,Japanese=True)
太郎 PROPN ═╗<════════╗ nsubj(主語)
は   ADP   <╝         ║ case(格表示)
花子 PROPN ═╗<══╗     ║ nsubj(主語)
が   ADP   <╝   ║     ║ case(格表示)
読ん VERB  ═╗═╗═╝<╗   ║ acl(連体修飾節)
で   SCONJ <╝ ║   ║   ║ mark(標識)
いる AUX   <══╝   ║   ║ aux(動詞補助成分)
本   NOUN  ═╗═════╝<╗ ║ obj(目的語)
を   ADP   <╝       ║ ║ case(格表示)
次郎 PROPN ═╗<╗     ║ ║ obl(斜格補語)
に   ADP   <╝ ║     ║ ║ case(格表示)
渡し VERB  ═╗═╝═════╝═╝ root(親)
た   AUX   <╝           aux(動詞補助成分)

esupar.loadのパラメータは、"ja"が日本語、"zh"が現代中国語、"lzh"が古典中国語、"th"がタイ語だったりする。まだまだチューニングが不十分なので、解析精度はイマイチだが、ぜひ試しに使ってみてほしい。

15422438 journal
中国

yasuokaの日記: chinese-bert-wwm-ext-uposによる現代中国語の係り受け解析

日記 by yasuoka

昨日の日記の手法をchinese-bert-wwm-ext-uposに適用して、現代中国語の係り受け解析モデルを試作してみた。Google Colaboratoryで動かしてみよう。

!pip install 'transformers>=4.7.0' 'supar>=1.1.1' 'deplacy>=2.0.1'
from transformers import AutoModelForTokenClassification,AutoTokenizer,TokenClassificationPipeline
from transformers.file_utils import cached_path,hf_bucket_url
from supar import Parser
brt="KoichiYasuoka/chinese-bert-wwm-ext-upos"
mdl=AutoModelForTokenClassification.from_pretrained(brt)
tkz=AutoTokenizer.from_pretrained(brt)
pos=TokenClassificationPipeline(model=mdl,tokenizer=tkz,aggregation_strategy="simple")
prs=Parser.load(cached_path(hf_bucket_url(brt,"supar.model")))
def nlp(s):
  d=pos(s)
  e=prs.predict([[t["word"].replace(" ","") for t in d]])
  e.sentences[0].values[3]=tuple([t["entity_group"] for t in d])
  e.sentences[0].values[9]=tuple(["SpaceAfter=No" if t["end"]==u["start"] else "_" for t,u in zip(d,d[1:])]+["_"])
  return e
doc=nlp("下雨天和星期一总是让我沮丧")
import deplacy
deplacy.render(doc)
deplacy.serve(doc,port=None)

「下雨天和星期一总是让我沮丧」を係り受け解析してみたところ、私(安岡孝一)の手元では以下の結果になった。

下雨 NOUN  <╗       compound
天   PART  ═╝═╗═╗<╗ nsubj
和   CCONJ <╗ ║ ║ ║ cc
星期 NOUN  ═╝<╝ ║ ║ conj
一   NUM   <════╝ ║ nummod
总   ADV   <════╗ ║ advmod
是让 VERB  ═══╗═╝═╝ root
我   PRON  <╗ ║     nsubj
沮丧 ADJ   ═╝<╝     ccomp

1    下雨    _    NOUN    _    _    2    compound    _    SpaceAfter=No
2    天    _    PART    _    _    7    nsubj    _    SpaceAfter=No
3    和    _    CCONJ    _    _    4    cc    _    SpaceAfter=No
4    星期    _    NOUN    _    _    2    conj    _    SpaceAfter=No
5    一    _    NUM    _    _    2    nummod    _    SpaceAfter=No
6    总    _    ADV    _    _    7    advmod    _    SpaceAfter=No
7    是让    _    VERB    _    _    0    root    _    SpaceAfter=No
8    我    _    PRON    _    _    9    nsubj    _    SpaceAfter=No
9    沮丧    _    ADJ    _    _    7    ccomp    _    _

SVGで可視化すると、こんな感じ。「天」=nummod⇒「一」はどう考えても変で、「星期」=nummod⇒「一」とすべきだろう。また、「我」⇐nsubj=「沮丧」は「是让」=obj⇒「我」とした上で、「是让」=ccomp⇒「沮丧」をxcompにしたいところだ。うーん、このあたり、どうすれば精度を上げられるかな。

15421517 journal
人工知能

yasuokaの日記: roberta-base-thai-syllable-uposによるタイ語の係り受け解析

日記 by yasuoka

9月11日一昨日の日記の手法を合わせた上に、SuParの助けを借りて、タイ語の係り受け解析モデルを試作してみた。Google Colaboratoryで動かしてみよう。

!pip install transformers'>='4.7.0 supar'>='1.1.1 deplacy'>='2.0.1
from transformers import AutoModelForTokenClassification,AutoTokenizer,TokenClassificationPipeline
from transformers.file_utils import cached_path,hf_bucket_url
from supar import Parser
brt="KoichiYasuoka/roberta-base-thai-syllable-upos"
mdl=AutoModelForTokenClassification.from_pretrained(brt)
tkz=AutoTokenizer.from_pretrained(brt)
pos=TokenClassificationPipeline(model=mdl,tokenizer=tkz,aggregation_strategy="simple")
prs=Parser.load(cached_path(hf_bucket_url(brt,"supar.model")))
def nlp(s):
  d=pos(s)
  e=prs.predict([[t["word"] for t in d]])
  e.sentences[0].values[3]=tuple([t["entity_group"] for t in d])
  e.sentences[0].values[9]=tuple(["SpaceAfter=No" if t["end"]==u["start"] else "_" for t,u in zip(d,d[1:])]+["_"])
  return e
doc=nlp("หลายหัวดีกว่าหัวเดียว")
import deplacy
deplacy.render(doc,WordRight=True)
deplacy.serve(doc,port=None)

「หลายหัวดีกว่าหัวเดียว」を係り受け解析してみたところ、私(安岡孝一)の手元では以下の結果になった。

   det       ╔> DET  หลาย
advmod ╔════>╚═ NOUN หัว
  root ╚═╔═════ ADJ  ดี
  case   ║ ╔══> ADP  กว่า
   obl   ╚>╚═╔═ NOUN หัว
  amod       ╚> ADJ  เดียว

1    หลาย    _    DET    _    _    2    det    _    SpaceAfter=No
2    หัว    _    NOUN    _    _    3    advmod    _    SpaceAfter=No
3    ดี    _    ADJ    _    _    0    root    _    SpaceAfter=No
4    กว่า    _    ADP    _    _    5    case    _    SpaceAfter=No
5    หัว    _    NOUN    _    _    3    obl    _    SpaceAfter=No
6    เดียว    _    ADJ    _    _    5    amod    _    _

SVGで可視化すると、こんな感じ。「หัว」⇐advmod=「ดี」を除いて、まずまずの解析結果だ。ただ、このままだと、ちょっと使いにくいので、さて、どういう形でパッケージ化したらいいかな。

15418219 journal
人工知能

yasuokaの日記: bert-base-japanese-uposとTokenClassificationPipelineでおこなう日本語形態素解析

日記 by yasuoka

昨日の日記の手法を使って、bert-base-japanese-uposのトークナイザもBertTokenizerFastに入れ換えてみたところ、TokenClassificationPipelineの結果が改善された。Transformersを最新版に更新しつつ、ちょっとやってみよう。

$ pip3 install -U transformers
$ python3
>>> from transformers import AutoModelForTokenClassification,AutoTokenizer,TokenClassificationPipeline
>>> brt="KoichiYasuoka/bert-base-japanese-upos"
>>> mdl=AutoModelForTokenClassification.from_pretrained(brt)
>>> tkz=AutoTokenizer.from_pretrained(brt)
>>> nlp=TokenClassificationPipeline(model=mdl,tokenizer=tkz,aggregation_strategy="simple")
>>> d=nlp(inputs="國境の長いトンネルを拔けると雪國であつた。難儀な難儀は難儀する。")
>>> print(d)
[{'entity_group': 'NOUN', 'score': 0.99924845, 'word': '國境', 'start': 0, 'end': 2}, {'entity_group': 'ADP', 'score': 0.9997904, 'word': 'の', 'start': 2, 'end': 3}, {'entity_group': 'ADJ', 'score': 0.995852, 'word': '長い', 'start': 3, 'end': 5}, {'entity_group': 'NOUN', 'score': 0.9995555, 'word': 'トンネル', 'start': 5, 'end': 9}, {'entity_group': 'ADP', 'score': 0.9998084, 'word': 'を', 'start': 9, 'end': 10}, {'entity_group': 'VERB', 'score': 0.99934673, 'word': '拔ける', 'start': 10, 'end': 13}, {'entity_group': 'CCONJ', 'score': 0.9946812, 'word': 'と', 'start': 13, 'end': 14}, {'entity_group': 'NOUN', 'score': 0.9560932, 'word': '雪國', 'start': 14, 'end': 16}, {'entity_group': 'AUX', 'score': 0.9979013, 'word': 'で', 'start': 16, 'end': 17}, {'entity_group': 'AUX', 'score': 0.9907406, 'word': 'あつた', 'start': 17, 'end': 20}, {'entity_group': 'PUNCT', 'score': 0.9998325, 'word': '。', 'start': 20, 'end': 21}, {'entity_group': 'ADJ', 'score': 0.9873648, 'word': '難儀', 'start': 21, 'end': 23}, {'entity_group': 'AUX', 'score': 0.99952304, 'word': 'な', 'start': 23, 'end': 24}, {'entity_group': 'NOUN', 'score': 0.99909914, 'word': '難儀', 'start': 24, 'end': 26}, {'entity_group': 'ADP', 'score': 0.99976534, 'word': 'は', 'start': 26, 'end': 27}, {'entity_group': 'VERB', 'score': 0.99604756, 'word': '難儀', 'start': 27, 'end': 29}, {'entity_group': 'AUX', 'score': 0.99927384, 'word': 'する', 'start': 29, 'end': 31}, {'entity_group': 'PUNCT', 'score': 0.99983144, 'word': '。', 'start': 31, 'end': 32}]

さすがに読みにくいので、単語(word)と品詞(entity_group)を抜き出してみよう。

>>> print([(t["word"],t["entity_group"]) for t in d])
[('國境', 'NOUN'), ('の', 'ADP'), ('長い', 'ADJ'), ('トンネル', 'NOUN'), ('を', 'ADP'), ('拔ける', 'VERB'), ('と', 'CCONJ'), ('雪國', 'NOUN'), ('で', 'AUX'), ('あつた', 'AUX'), ('。', 'PUNCT'), ('難儀', 'ADJ'), ('な', 'AUX'), ('難儀', 'NOUN'), ('は', 'ADP'), ('難儀', 'VERB'), ('する', 'AUX'), ('。', 'PUNCT')]

なかなかいい感じだ。ただ、私(安岡孝一)個人としては、ちゃんとstartとendが出るようになったのが、かなりうれしかったりする。入力文字列との対応関係を取れると、便利なことが多いのだ。まあ、このあたりは目的にもよるのだが、ぜひ使ってみてほしい。

typodupeerror

目玉の数さえ十分あれば、どんなバグも深刻ではない -- Eric Raymond

読み込み中...