The jonki

呼ばれて飛び出てじょじょじょじょーんき

2019年に買ってよかったもの(日用品編・技術書編・ガジェット編・漫画編)

今年もこの時期が来ました.総決算です.今年は日用品編・技術書編・ガジェット編・漫画編の4段階です. www.jonki.net

日用品編

カモミールティー.これはずっと飲んでます.コーヒーは1日に1,2杯にしているので,基本的にはずっとこれ飲んでます.はちみつが入っているっぽく喉にも良い.特に癖はなく飲みやすいです.

会社で水を飲むために買いました.自販機行くとついつい炭酸とか買ってしまうのでこれで我慢.

今までずっと背の高い枕を使っていましたが,大○家具さんで枕の選び方を教えてもらい買いました(Amazonでw).寝るときの体の態勢はフラットになる方がよいらしく,私は今のベッドだとこの低めの枕がちょうど良かったです.

今まで足の土台が暖かくなるやつを使ってたんですが,冬場寒いのって膝とか割と上のほうだったのでこちらを購入.あまり推奨されてないと思うけどブランケットかけるとコタツみたいな暖かさになるので,冷え性にバッチリの商品でした.

無駄にル・クルーゼのコップ.蓋付きで可愛い感じの探してこれになりました.可愛いです.

今年からキャンプ生活を始めたのでスノーピークも自然に足を運ぶように.これは手のひらサイズに小さくなるので,バイクとかに入れて活躍してもらってます.意外と安っぽくなく,ものも結構入ります.そこはさすがにスノーピークさん.

これは割とベストバイ.トーストが好きなんですが,これを水に浸して入れると平凡なトースターが某高級メーカーのトースターに生まれ変わります.

水を無限に吸い取るスポンジ.バイクの洗車用に,キャンプのときのテントとか拭くように.

バックパックに求めるのは軽量さということに気づき,これに至りました.THULEバックパックの方がしっかりしてるけど,これは圧倒的に軽いので暫くはこれ使います.

除電ブラシは1個デスク周りにあるとこの時期安心.小さいのも買ってましたが,このサイズがちょうどよく使い勝手が良いです.

z-lightは実は2つ目です.最近ライトは温色が良いと思うようになり買い替え.z-lightはモノがしっかりしてて,安いやつとは作りが違います.

Z-LIGHT LEDデスクライト ブラック 調色機能付き Z-80NB

Z-LIGHT LEDデスクライト ブラック 調色機能付き Z-80NB

  • 発売日: 2017/10/15
  • メディア: ホーム&キッチン

はんだシュッ太郎.大量のハンダ間違いに気づいたあなたに最適な商品.

逆作用ピンセット.普通のピンセットと違い,力をかけないときに閉じるものです.表面実装したいあなたに画期的な商品.

技術書編

その名の通り,アルゴリズムが視覚化されてコンパクトに丁寧にまとまっています.アルゴリズムって視覚的なイメージが大切なんで,長々と言葉で説明されるよりも,わかりやすい図で一発なんですよね,ってのがこの本でよくわかります.基本的なことしか取り上げてないけど,マージソートダイクストラ,ハッシュ,LinkedList,公開鍵暗号方式,とか怪しかったら買っといてもいいかも.私もアルゴリズム忘れるたびにこれ見直します.

形態素解析器書いてから,文字コード周りの知識がほぼないことに気づいて買いました.文字集合・符号化まわりに関して,歴史から始まるので使用の変遷や仕組みを知れて読んでて楽しいです.その分ボリューミーなので読み応えがあります.

[改訂新版]プログラマのための文字コード技術入門 (WEB+DB PRESS plusシリーズ)

[改訂新版]プログラマのための文字コード技術入門 (WEB+DB PRESS plusシリーズ)

マスタリングTCP/IP.実は学部生のときの輪読本.久しぶりに読んでもやっぱり神本でした.物理層からアプリ層まで丁寧かつ完結に書いています.インターネットがどのように成り立っているのか,よくわかっていない方は見ると良いかも.

コンピュータの構成と設計.通称パタヘネ本.Kindleで上下巻セット売ってるので買っておきましょう.ただ紙媒体の方が複数ページ見ながら課題とか取り組めるので良いかも.ただ持ち運ぶ重さではないので,私は紙と電子版,両方買いました.

情報理論 Math&Science.情報理論の理解が怪しかったので読みました.まだ最初しか読めてませんが,導出がめちゃくちゃ丁寧.

情報理論 Math&Science (ちくま学芸文庫)

情報理論 Math&Science (ちくま学芸文庫)

昨今,クラウドソーシング使うことも増えてきたので読みました.ワーカーをどう評価するか?などいろいろな事例があり参考になります.

Googleの方がツイートしてたので読みました.割と新し目の本なので,最近のDNNな手法も結構トピックとして取り上げられているのが特徴的.

Introduction to Natural Language Processing (Adaptive Computation and Machine Learning series)

Introduction to Natural Language Processing (Adaptive Computation and Machine Learning series)

  • 作者:Jacob Eisenstein
  • 出版社/メーカー: The MIT Press
  • 発売日: 2019/10/01
  • メディア: ハードカバー

こちらもGoogleの方が学校に寄付するという形で話題になっていた本です.ドイツ版の数学ガールという感じで,レナという中学生?の女の子とレナのパソコンに入っているAIビムとのグラフ理論を学ぶ本です.数学ガールと同じで我々(レナ側)の考え方に対してAIビムがコメント・解説していくスタイルです.中盤ぐらいまでは良いのですが,後半は普通に難しかったです.ただグラフ理論が実世界でどのような場面で役に立つか,中学生が興味を持てるように物語が展開していくので読み物として面白いです.残念ながら絶版ぽいです.私も古本で入手しました.

最短経路の本 レナのふしぎな数学の旅

最短経路の本 レナのふしぎな数学の旅

30講シリーズ.これは会社の後輩に教えてもらいました.いろいろなシリーズが30講となっていてどのトピックも評判が良いです.レナ本もそうなんですが,講がちゃんと分かれていると1日3講すすめる,みたいな読み方ができるので私の性格にあっていると気づきました.

線形代数30講 (数学30講シリーズ)

線形代数30講 (数学30講シリーズ)

  • 作者:志賀 浩二
  • 出版社/メーカー: 朝倉書店
  • 発売日: 1988/03/01
  • メディア: 単行本
固有値問題30講 (数学30講シリーズ)

固有値問題30講 (数学30講シリーズ)

  • 作者:志賀 浩二
  • 出版社/メーカー: 朝倉書店
  • 発売日: 1991/05/01
  • メディア: 単行本

CPUの作り方.パタヘネ本読むのもいいけど,DIYで作るのも良いよ.

CPUの創りかた

CPUの創りかた

形態素解析本.自然言語処理Advent Calendarでもこちらの本を使って記事を書きました形態素解析興味なくても辞書引きとか最小コスト法のとこだけでも読むと楽しいよ.

形態素解析の理論と実装 (実践・自然言語処理シリーズ)

形態素解析の理論と実装 (実践・自然言語処理シリーズ)

ガジェット編

Anker 30Wの2つ.黒いスリムタイプのほうが若干軽いですが,両方小さくてかなり良い.持ち運びはこのサイズが最善ではないだろうか.

Qi充電したいけど,手を引っ掛けるところもほしい!と思った私にぴったりだったこの商品.金属使ってないのでQi充電できます.

MOMO STICK スマートフォン マルチバンド [正規品] デニム (ネイビー)

MOMO STICK スマートフォン マルチバンド [正規品] デニム (ネイビー)

  • 出版社/メーカー: モモディズジャパン
  • メディア: エレクトロニクス

Kindle OasisOasisは初代持っていましたが,防水ということで乗り換え.画面が大きくなったので技術書なども結構読めるようになりました.

Kindle Oasis 色調調節ライト搭載 Wi-Fi 32GB 電子書籍リーダー

Kindle Oasis 色調調節ライト搭載 Wi-Fi 32GB 電子書籍リーダー

  • 発売日: 2019/07/24
  • メディア: エレクトロニクス

この夏,キャンプに持っていって扇風機にもなるし充電器にもなるということで大活躍でした.

こちらもキャンプで大活躍でしたが,被災時とかにもモバイルバッテリー兼ライトになるので,1台ぐらい持っていても良いと思う.

初の骨伝導イヤホン.Amazon骨伝導はたくさんありますが,まともなブランドがAftershokzぐらいしかなかったので,最新作であるAeropex買いました.めっちゃ軽いし,耳穴塞がないので良いです.イメージとしてはカフェで音楽が流れている状態をどこにでも持ち出せる感じですね.音質云々というよりはBGMという感じ.家で嫁が何か勉強とかしてるときに,自分はこれつけて本を読んでたりします.

旅行用に買いました.これ買ったと人に言うとSwitch持ってるのに必要?と言われるのですが,まじでLiteいいから.ベッドでゴロゴロするのに最適なサイズ&重量です.

Nintendo Switch Lite ターコイズ

Nintendo Switch Lite ターコイズ

  • 作者:
  • 出版社/メーカー: 任天堂
  • 発売日: 2019/09/20
  • メディア: Video Game

今流行のRing fitじゃない方です.Ring fit買えなくてこっち.早見沙織大塚明夫がトレーナーになって一緒にボクシングしてくれます.ボクシングでビートマニアする感じですね.普通に息が切れるぐらい激しい運動ができます.

Fit Boxing (フィットボクシング) -Switch

Fit Boxing (フィットボクシング) -Switch

  • 作者:
  • 出版社/メーカー: イマジニア
  • 発売日: 2018/12/20
  • メディア: Video Game

お風呂でもiPad使いたい!ということで買いました.ジップロックなどとことなり袋でタッチがシャワシャワにならないのが気に入ってます.ただその分,ケースガッチリしてるので,ケースのつけ外しは頻繁にしない想定になると思います.

ECLIPSE CREAT .Macbookを置くために買った台.無駄に高いですが,質感とデザインが良いので気に入ってます.

ディスプレイに引っ掛けて軽い物置にしています.私はティッシュペーパーとか小物など置いてます.非常に便利.

キングジム 棚 ディスプレイボード 黒 DB-500クロ

キングジム 棚 ディスプレイボード 黒 DB-500クロ

  • 発売日: 2016/03/24
  • メディア: オフィス用品

漫画編

Rebuildfmで取り上げられていて読んだ本.完全に同世代なので色々共感しながら読めました.若干,後半が雑な印象がなくもないが,普通に楽しい.

岡崎に捧ぐ(1) (コミックス単行本)

岡崎に捧ぐ(1) (コミックス単行本)

とうとうアニメ化が発表されました.アニメ化などは鬼滅などが先んじていますが,個人的には今ジャンプで一番おもしろいです.幽遊白書とかブリーチの初期とかと比べても遜色ないぐらいに面白いので買って.

呪術廻戦 1 (ジャンプコミックスDIGITAL)

呪術廻戦 1 (ジャンプコミックスDIGITAL)

一流の殺し屋が日常生活をしつつも色々事件に巻き込まれていく物語なんですが,シリアスとコミカルが絶妙にマッチしていて面白い.不良・ヤクザものが苦手な人でも好きになるかも.騙されたと思って1冊買ってみて.

ザ・ファブル(1) (ヤングマガジンコミックス)

ザ・ファブル(1) (ヤングマガジンコミックス)

以上です.それでは良いお年を〜.

arXivのComments情報をFeedlyに挿入するChrome拡張作った

タイトル下の緑色の文が挿入されたComments

arXivRSSを私はFeedlyで消費しています.しかし,arXivRSSには論文のCommentsフィールドが含まれていないので,"Accepted at ACL2020!"みたいな情報がRSSリーダー上で取得できません.そのため実際にそのページを開く必要があるのですが,あまり興味のない論文の場合,面倒です.

そこでarXivRSSの消化速度改善のために,このようなCommentsフィールドをFeedly上に無理やり挿入するChrome拡張を作りました.下記が動作イメージです.Feedlyでエントリを読んでいる際に,それがarXivの論文であれば,裏側でその論文のComments情報を取得し,貼り付けています(タイトル下の緑色の字の箇所がそれです)

via GIPHY

インストールはこちらから

chrome.google.com

また,以前に作ったarXiv用のChrome拡張はこちらの記事を参照のこと.

www.jonki.net

そうか,君はアメリカから帰って1年になるんだね

f:id:jonki:20191214231451j:plain
SLTでの発表で訪れたアテネを訪れたときのパルテノン神殿

この記事は,研究留学 Advent Calendar 2019 - Adventar 15日目の記事です. 昨日はKo Watanabeさんの研究留学アドベントカレンダー【14日目】|渡邉 洸(Ko Watanabe)|noteでした.

私は,カーネギーメロン大学CMU)のLanguage Technology Instituteというところに1年間留学(企業派遣)していました.CMUでは対話システムに関する研究を行っており,対話システムや機械学習は初学者でしたが,なんとかやり切りました. ですが実はもう帰国して1年以上経っています. CMUの留学準備,留学中に関しては,以前書いてしまったので,帰国後1年経って落ち着いたこの状況や当時を振り返ろうと思います. 会社派遣であるため,留学前後で同じ会社・職場なのですが,見える世界がだいぶ変わりました. 何か良いこと書こう!と思ったのですが,とくに思いつかず,後から振り返るための私的な日記となってしまいました.すみません.またタイトルに特に意味はありません.

www.jonki.net

当時の決断を振り返って

やらない後悔を恐れた自分の決断を褒めたい

私は研究室ではインターネット(特にP2P)で修士を取り,会社ではインタラクションのR&Dにいました.その事もあって,機械学習もまともに触っていない状況でした.海外で働く・勉強するということは,何となく1つの野望としては持っていたものの,そのための計画や努力をしてるとは言えない状態でした.企業留学の説明会を聞いたときも,自分が留学メンバーに選ばれるべきでない理由は一瞬で30個ぐらい思いつきました. ただ実はこの説明会を聞いたのは2年連続で,その最初の説明会を聞いたときは弱気になって応募もしませんでした.しかし企業留学は,おそらく日系企業で働く限り,千載一遇ともいえる機会なので,この機会を逃すべきではないのではないか?と考え,そこからは考えるのをやめ,ほぼ勢いで応募しました.応募後は腹を決めて,自分のこれまでとこれからのキャリアプランを考えて,面接を勝ち抜くロジックを作りました.今振り返ると,あのタイミングで決断をせずに,また1年,2年とズラしていたら,まず間違いなく留学メンバーには選ばれなかったと思います(社内事情など).いや,まじであのときの俺GJ.ここで1つの世界線が変わりました.ちなみにたとえこの時の面接が落ちていたとしてもプラスになっていたと思います.というのも面接を勝ち抜く上で面接官(投資家)にアピールするためには,自分のキャリア・ポテンシャルを整理し,説得する必要があります.キャリアを見つめる機会というのは,なおざりにしがちだったので,この時の思考はとても良かったものだと思います.

サンプルが1である以上,それ全部生存バイアスだろ,と言われるとは思うのですが,実際そうだと思います.ただ,自分が今そう思う状況は,それ以上でもそれ以下でもないので良しとしましょう.

当時の留学を振り返って

リアル精神と時の部屋

今思うともう少し異分野の人やモノとの交流をしといたほうが,留学に多様性が出たなぁと思います.でも研究に没頭したおかげで,毎日が精神と時の部屋というか,昨日の自分より強くなった今日の自分を毎日感じるのが楽しかったです.特に私はゼロベースだったので.

日記のおすすめ

もともと会社に送る月報用に書いていたのですが,途中で用途を変えて,その日思ったこと(起きたこと,良かったこと,愚痴など),メンタルに関連する出来事を書くようにしました. 公開する予定がないので,適当に書いた箇条書きですが,書くことで意外と落ち着きますし,その日その時に思っていたことって簡単に忘れるんですよね.ラボメンに初めて会った時,英語うまくいかないとき,reject食らって先生に励まされた時,投稿締切が迫ってるけど良い結果がでない時,寒すぎる時,嫁と旅行に行った時,など当時どういうことを思っていたのか色々書いてあって気持ちが蘇ります.日記はその時の気持ちの保存作業であるので,将来のために気持ちはたくさんバックアップしておきましょう.過去の自分に励まされます.

今を振り返って

周りからの期待値が上がる

会社から帰って来て感じるのは,上司などからの期待値が上がることです.大げさに言うと,英語をマスターし,DNNを完全理解し,対話理解の真髄を知った男として扱われます.自分の中でのレベルは間違いなく向上したと感じますが,極端にそれを受け止められるのは戸惑います.それでも,周りからの期待値が低いことと違い,ポジティブなことなのでそれを励みに,プレッシャーにして,頑張れる環境なのかな,とは感じます.理由は何にせよ,一目置かれるというのは通常発生しにくい状況なので,留学という単語が強い意味を持つ日本では,これは様々な場面において,非常に有利に使える武器になると思います.

帰国後の1年間成長したのか?

しませんでした(笑)とは言いませんが,やはり留学中の精神と時の部屋での修行と比べると,今の成長スピードは練度も落ちて,モチベーションも失い気味かなぁと思います.研究テーマの選び方など,私自身の芯となる部分を形成できていないので,色々と中途半端になってしまったのは自分の責任でもあるので反省かなぁと思います.会社というのは,特にモチベーションがなくても,何かしらの仕事はあったりするもので,下手するとそういった仕事をこなしてるだけの日々になりがちなので気をつけたいです.いや,本当に.忙しく働いていると成長している気がする,んですが,そんなことはなくて,ちゃんと内容が伴っていないと,本当に時間の無駄なんですよね.会社は営利企業であるので,社員全員の意志を最大に汲むことはできないので,自分の意志にそぐわない仕事しかない場合,異動なり転職なりをしっかりと選択肢に持たないといけないと感じる1年でした.経歴や業績が貧相ですが,一応ここに私のLinkedin貼っておきます.

実力の謙遜は無駄

これは日本人の文化性もあると思うんですが,経験が浅いからとか,他分野からだとか,で「私は○○なので」とわざわざアピールして話す人がいるのですが,これ本当に勿体ないなぁと思います.周りからの期待値が下がってしまいますし,そのうち声すらかけられなくなります.また,勝手な偏見ですが,こういう人はtakeは多いけどgiveが圧倒的に少ない傾向があると思っています.「しっかりと勉強して理解してから発信します」と返されたりするのですが,その到達点というのに達してる人なんて世界に一握りしかいないので,さっさと何か出しましょう.Done is better than perfect! 私がやってるポッドキャストも,未熟なリサーチャーがやる番組ですが,多くの人が聞いてくれてますし,叩かれるどころか,フィードバックたくさんもらえてとても楽しい場になってます.inputもいいけどoutputもしていきましょう(自分への言い聞かせ).

まとめ

留学から帰って来て,今思うこと私的感情を書かせてもらいました.個人の日記にしまっとけ!と思われそうですが,心情状態を記録しておくのは悪くないと思うので,書かせていただきました.何かに挑戦するときは,やはりやらない理由を考えがちなのですが,挑戦した後の素敵な世界を想像して進みましょう.

明日は,Yuki Matsudaさんです(あとでリンク更新します)

ヘッドレス環境でもplotlyでpng書き出ししたい

Pythonでグラフを書くとき,Plotlyを私はよく使います.別にmatplotでもいいんですが,scatterの図でWebブラウザ上で,インタラクティブに各ポイントにラベルを確かめられる機能が気に入ってます. plot.ly

ただし私の揮発環境はGUIを入れないヘッドレスな環境なので,グラフのHTML書き出しは出来たんですが,png書き出しができませんでした.そこで見つけたのがorcaというElectronのアプリで,これを経由するとうまくいきそうでした.インストール方法はconda, npm, Docker, スタンドアロンバイナリと色々あるのですが,結論から言うとDocker以外,ヘッドレス環境のためかうまくいきませんでした.ヘッドレス環境では,xvfbなどの仮想ディスプレイを使う必要があるのですが,この辺うまくインストールできませんでした.

結果として,うまくいったのがDockerでplotly-orcaのサービスを起動しておき,URL request経由で画像生成のAPIを呼ぶスタイルです.仮想ディスプレイ周りなど沼だと思うので,コンテナで切り離した環境を使うこのスタイルが何だかんだでスッキリします.

使うためには,予めDockerでplotly-orcaのサービスを起動します.

docker run -dp 9091:9091 quay.io/plotly/orca

あとは,下記のような形でpostする形でうまくいきました.これでhtmlとpngの両方がサーバーのような環境でも出力できるようになったはずです. gist.github.com

ゼロから作った形態素解析器Taiyakiで学ぶ形態素解析

本記事は,自然言語処理 Advent Calendar 2019 - Qiitaの1日目の記事です.

はじめに

今回の記事では,去年末ごろからPythonとCythonだけでチマチマ作った形態素解析器Taiyakiをベースに,形態素解析器の解説をしようかなと思います.この形態素解析器の完成はまだ程遠いんですが,ひとまず簡単な形態素解析はできるようになったのでここでお披露目しておきます.本記事は実質,Double-Arrayの辞書引きと最小コスト法に基づく形態素解析器の解説記事となっています.

なぜ今更に形態素解析器を作ったかと問われると困ってしまうのですが,NLPerなら1つぐらい自作しても良いのかなってことと.形態素解析がどう動いているかって意外と知らなかったのが動機です.解説内容間違えてる可能性はあるので,見つけた方はコメント欄でご指摘いただけると嬉しいです.

作っているものは下記リポジトリで管理しています.近いうちにちゃんとした形でリリースしたいです.

github.com

自作するにあたって読んだ本はこちらです.MeCabやSentencePieceの開発者として知られる工藤さんの本で,形態素解析器を開発する人にとっては(どれぐらいいるのか知らんけど)紛れもない神本です. 本記事は,この本から得られる以上の情報を提供できるとは思いませんが,自作するに当たり苦労した点やグラフィカルに解説して初学者でも楽しめるように紹介できればと思います.

目次

めっちゃ長くなったので先に目次書いておきます.

  • 形態素解析とは
  • Taiyakiの全体構成
  • Double-Arrayによる辞書引き
    • 共通接頭辞検索と最長一致法
  • 最小コスト法
    • ラティス構築
    • ビタビアルゴリズムによる最小コスト経路の算出
    • 未知語処理
  • まだやっていないこと
  • まとめ

形態素解析とは

形態素解析とは,文を入力した際に,単語分割及び品詞推定を行う処理を指します.形態素(morpheme)は言語学上の最小限の意味の単位を表し,その最小単位で区切ってあげようというのが形態素解析です.

形態素解析を実現する手法は,基本的に2種類.本記事で上げるラティスを構築して最小コスト法を解く手法と,点予測と呼ばれる手法があります.Taiyakiでは前者を採用しています.最小コスト法を採用している形態素解析器としてMeCabやJumanが上げられ,一方点予測ではKyTeaなどがあります.先に点予測を説明しておくと,点予測では文字の系列データ "吾輩は猫である" を入力した際に,各文字の間に境界があるかを予測する単純なモデルです.つまり,"吾"と"輩"は区切られているか?"輩"と"は"は区切られているか?といった形で1文字ずつ処理していきます.この例だと後者で区切れて"吾輩"というのが1つの単位として抽出できるのが良さそうですね.この例では単語分割だけですが,品詞も含めた予測(多クラス分類)を解けば品詞推定もいけます.なんだかRNNとかでできそうだな,って思う人がいるかもいれませんが,実際にJumanなどで知られる黒橋・河原研の方がニューラルネットを使った手法を,NLP2019やNAACLで手法を発表しています.Podcastでも説明しているので興味がある人はぜひ

点予測に対して,最小コスト法は実装項目が多く作るのは面倒ではあるのですが,人による言語知識をうまく組み込めるような仕組みになっており,作っていて面白そうだなと思いこちらを採用しました.また経路選択による解候補(N-best)が出せるというのも点予測にはない魅力です.

Taiyakiの全体構成

Taiyakiの全体構成は下記のようになっています.まず最初に大規模語彙データから,辞書を構築する必要があります.文の解析時にこの構築した辞書を参照します.この辞書をどう作るのかというと,Double-Arrayという仕組みを採用します.

f:id:jonki:20191119203246p:plain

Double-Arrayによる辞書引き

入力文を単語分割するためには,辞書データが必要になります.幸いにも多くのデータが公開されているのですが,まずはmecabのIPA辞書というのを使うことにしました.このIPA辞書には,様々な語彙が品詞や読み情報を伴い記録されています.またCRFで計算されたコスト(後述)が既に付与されており使いやすいです.一部例を以下に示します.何だかよくわからん数字とか記号が入っていますが,とりあえず気にしないで雰囲気を感じてください.

鯛焼,1285,1285,5618,名詞,一般,*,*,*,*,鯛焼,タイヤキ,タイヤキ
太公望,1285,1285,5622,名詞,一般,*,*,*,*,太公望,タイコウボウ,タイコーボー
土盛り,1285,1285,5622,名詞,一般,*,*,*,*,土盛り,ドモリ,ドモリ
祝い事,1285,1285,5601,名詞,一般,*,*,*,*,祝い事,イワイゴト,イワイゴト

このようにIPA辞書に登場する単語単位で入力文を区切れば,単語分割ができてきそうです(コストや未知語はひとまず忘れる).このように与えられた入力文から辞書に登録された単語を抽出する作業を辞書引きといいます. 非常にナイーブに実装すると,まず語彙をPythonの辞書型などでロードします.そして,入力文字列に対してすべての部分文字列を計算し,その部分文字列が辞書に登場するかを計算すれば,単語の区切り候補はすべて分かるわけです.ただ問題になるのは計算コストです.すべての部分文字列を考えるのにN2,さらに文字列のハッシュ値計算にNかかるので,O(N3)という非現実的な計算量になります.数十万,数百万の語彙を扱うことを考えると,さすがに厳しいです.

そこで語彙情報を効率的に保持・探索できるデータ構造のDouble-Arrayが登場します.これは1989年に登場した技術ですが,未だにメモリ使用効率が高く,最も高速なトライの実装方法として現役のアルゴリズムのようです.これからDouble-Arrayの解説を,探索と構築に分けて説明しますが,構築手段の方は探索と比べてかなり複雑であるためスキップして大丈夫です.またDouble-Arrayを実際に使うときは,Darts-cloneを使ってしまえば,一瞬ですが,今回は勉強も兼ねて自分で実装してみました.さすがにPythonだと構築時間が馬鹿にならなかったのでCythonで書いています.実業務ではおとなしくDarts-cloneを使いましょう.

Double-Array 検索編

Double-Arrayはその名の通り,2つの同じ長さの配列(int)から構成されます.それぞれの配列をbaseとcheckと呼びます.文字列を入力した際に1文字単位で文字コードを計算し,baseとcheckを使うことで,その入力文字列が辞書に含まれているかどうか?を判定させることができます.baseは,文字コードのオフセット値がセットされており,そのbaseのオフセット値と文字コード値を足し合わせた値をインデックス値に持つcheckの値が,その遷移元のbaseのインデックス値と一致する場合,遷移が成功し,そこまでの文字が登録されていると判断できる仕組みです.checkの値が遷移元のbaseのインデックス値でない場合,それは未知の遷移ということで辞書には登録されていないということが言えます. 言葉で説明すると難しいので以下にスライドを作ったのでこれで説明します.

この例では非常にシンプルに,文字はa, b, c, #の4文字が存在します.#というのは終端コードを指し,語彙の終端を判断できます.語彙はaとbcの2つだけ登録されたDouble-Arrayが存在し,既に構築済みのものを用意しました,例として,a, bc, cの3つの文を入力したときに,Double-Array上でどう検索されるか示しています.最初の2つは語彙辞書に登録されているので,見つかりますが,最後のcは語彙にはないので検索が失敗しているのが分かると思います.

speakerdeck.com

どうでしょう?入力文字列に比例した処理時間で,高速に検索できるのが分かるでしょうか?生の語彙辞書ではなく,2つのint配列だけで辞書引きできるのは感動的です.

ところでこの文字コードの値は,Taiyakiの実際の実装では,書籍に書いてあるとおり,UTF-8マルチバイト値で行いました.これにより遷移距離は255以下になるため配列の構築がコンパクトに実現できます.例えば"あ"という文字コード値は,[227, 129, 130]という3つ(3バイト)に分割されます.つまり"あ"を調べるためには,3回の遷移が必要になるわけですが,入力文字列長は高々しれた長さなので,処理時間を気にする必要はないでしょう.それよりも遷移距離を短くしたことでDouble-Arrayをコンパクトに作れることの方が重要です.

In [1]: s = 'あ'.encode('utf-8')
    ...:print(s)
    ...:for code_val in s:
    ...:    print(code_val)
    ...:
b'\xe3\x81\x82'
227
129
130

Double-Array 構築編(スキップしてええよ)

先程の例では,すでに構築済みのDouble-Arrayが登場しましたが,あれをゼロから作ってみましょう.Double-Arrayは一度構築してしまえば単純な話ですが,それをどうコンパクトに高速に構築するのか,というのは結構難しいです.また語彙を追加していくと,コンフリクトが発生してしまうので,ノードの付替をする部分が複雑です.ここは決して難しいものではないんですが,手順が多いので,じっくりと紙に書いたりして動作原理を理解する必要が私にはありました. また,Double-Arrayの構築には時間がかかりますが,一般的に形態素解析を行う際は,動的に語彙を追加することはあまり考えていないため,オフラインで時間をかけて1度流すもの,という前提を置く場合が多いかもしれません.といってもDarts-cloneの構築は割と一瞬なんですけどね.

辞書構築もスライドを使って説明しましょう.基本的には0リセットされたDouble-Arrayの値をソート済み語彙辞書を最初から登録して値を更新していくだけです.ただし,コンフリクトが容易に発生するので,コンフリクト時にはコンフリクトが発生したインデックスとそのコンフリクトが発生したインデックスを参照しているインデックスの値を入れ替えていく必要があります.ここまでの説明で分かったらあなたの空間把握能力は天才的だと言っていいでしょう.

構築したDouble-Arrayは検索のときに使った値に最終的になります.

speakerdeck.com

実装のコツ

最初ナイーブに上記をPythonで実装していたのですが,これが遅くて遅くてとてもじゃないですが,数十万単語を1日経っても構築できないような状況でした.というのもコンフリクト発生時に,遷移元を親に持つノードを入れ子で探す必要があったりするので,毎度全探索してると指数関数的に遅くなります.マルチバイト単位で遷移するため,base値から255程度しか移動できないので,そのことをうまく使って対象ノードからの探索範囲を絞る必要があります.またある程度,配列の空きがなくなってきたタイミングで,下手にセコセコ配列を使わずに,配列のメモリを増やしてスカスカの領域を作って再配置しやすくするのも手です.ただこのあたりは私もチューニングしきれていないので,別途高速化できたタイミングでまた記事を書こうと思います.

自作Double-ArrayとDarts-cloneの比較

せっかく自作したので,Darts-cloneと比較してみます.Darts-cloneはC++でできているのに対して,私の自作はCython製です.私の実装がヘボいとはいえ,どれぐらいの性能差があるのか,気になって調べてみました.mecab-ipadicに登録されていたおよそ33万語を登録したDouble-Arrayを構築した結果が以下です.

構築時間 Double-Arrayサイズ
Darts-Clone 1.5秒 5.2 MB
Taiyaki 93.3秒 25.7 MB

うーん,これはひどいです.C++とCythonとか言う前に,実装力の差がありそうですね.実際に構築はオフラインで行うので構築時間はそこまで気にしなくても良いかなとは思いますが,改善の余地は大きそうです.ファイルサイズも私のは生テキストで保存しているとはいえ差が大きく開きました.ノードのアサイン・再アサイン時に時間がかかっていたのでガバガバと配列サイズを大きくする実装を入れたのが原因で,かなりスパースになってしまいました.

今回実装したDouble-Arrayの実装はこちらです.Cython知らん!という方もほぼ見た目Pythonなので何となく分かるかと.ただ実装は結構複雑というか汚くなってしまったので,最適化と合わせてまだまだ改善していきたいです.

Taiyaki/double_array.pyx at master · jojonki/Taiyaki · GitHub

共通接頭辞検索と最長一致法

Double-Arrayによる辞書引きという形態素解析における非常に重要な機能を実装することができました.実は既にこの段階で最長一致法という単純な単語分割器は作れてしまいます.最長一致法とは,その名の通り長い単位で区切れるパーツで単語分割を行う手法です.そのために必要な共通接頭辞検索(Common Prefix Search)について説明しましょう.共通接頭辞検索とは,入力文から考えられる様々な接頭辞(prefix)が辞書に登場するものかを検索します.例えば「東京都」という文にに対しては「東」「東京」「東京都」という単語が共通接頭辞になります.

この共通接頭辞検索によって,「アイスカフェオレ飲みたい」を最長一致法で分割してみましょう.この文に対して共通接頭辞検索は,「ア」「アイ」「アイス」「アイスカ」...「アイスカフェオレ飲みた」「アイスカフェオレ飲みたい」...「イ」「イス」「イスカ」...という接頭辞候補ができるので,それらが辞書に含まれるのかを調べればよいのですが,Double-Arrayがあるわたしたちは,この共通接頭辞もスマートに抽出できます.例えば「アイス」「アイスカフェオレ」「飲」「飲み」「たい」という語彙が登録されているDouble-Arrayが使えるとしましょう.そうするとDouble-Arrayによる共通接頭辞検索を使った最長一致法は下記のように実装できます.

  1. 「アイスカフェオレ飲みたい」を対象文にセット
  2. 「ア」に遷移.また「#」に遷移不可,「ア」は接頭辞でない.
  3. 「イ」に遷移.また「#」に遷移不可.「アイ」は接頭辞でない.
  4. 「ス」に遷移.また「#」に遷移可能.「アイス」を接頭辞に追加
  5. (中略.「ス」に戻って,「カ」「フェ」「オ」で遷移)
  6. 「レ」に遷移.また「#」に遷移可能.「アイスカフェオレ」を接頭辞に追加
  7. 「飲」に遷移不可.ここでDouble-Arrayの探索を打ち切る(「アイスカフェオレ飲」を接頭辞に持つ単語はもう辞書に登場しないので打ち切れる).
  8. 共通接頭辞「アイス」と「アイスカフェオレ」をreturn.
  9. 最長一致法に基づき,「アイス」と「アイスカフェオレ」は「アイスカフェオレ」の方が長いのでこちらを採用.
  10. 【アイスカフェオレ】まで区切れたので,次に「飲みたい」を対象文にセットして共通接頭辞検索を行う.
  11. (これを文末まで繰り返す)
  12. 【アイスカフェオレ,飲み,たい】と最長一致法で区切れる.

このように共通接頭辞検索の結果を,貪欲に長い方を採用していくだけで,簡単に文を区切ることができます.また基本的には1文字ずつ調べていくだけで(1文字目から調べ直さなくても)複数の共通接頭辞を出せるのも効率的ですね.工藤さんの書籍によると単純な手法ですが,90%以上の精度が得られる結果も出てるようです.品詞などの単語に付属する情報がいらないアプリケーションを考える場合は,Double-Arrayだけで実装できるので非常に軽量・高速・シンプルに出来上がります.

最小コスト法

最長一致法では,貪欲に長いトークンを採用する形で分割をしていきましたが,実際に分割パターンはたくさんあるはずです.そこで使われるのが全分割パターンを表現したラティス構造を作り,そのラティスから最小コストの経路を探し,その経路を通るノードの単位(単語・形態素の単位)を採用することで,最適な形態素解析を行おうという手法を,最小コスト法と呼びます.コストとは何かというと,その語の発生のしやすさや,語と語の繋がりやすさ,になり,前者を生起コスト,後者を遷移コストと呼びます.

まずはラティス構築に関して説明していきましょう.

ラティス構築

形態素解析器におけるラティスとは,下記のように形態素解析及び単語分割の全解候補です(単純のため省略していますが,もっと多くのノードがあります).共通接頭辞検索により,入力文を1文字ずつずらして共通接頭辞検索をすることで,共通接頭辞をすべて洗い出せるので,下記のようなデータ構造で表現できます.ラティスの実装は自由に行えると思いますが,書籍に倣うと,各文字位置(0〜8)において,BeginNodes,EndNodesという形で保持する形を選びました.BeginNodesはそこから後ろにつながるノード群を保持し,EndNodesは反対に前につながるノード群を保持します.下記の例だと,BeginNodes[3]には(ネコ,ネ),EndNodes[3]には(輩は,は)のノードが格納されます.ノードには,文の中での文字位置,生起コスト,文脈ID,品詞,などの情報をもたせます.

ラティス上に数字が書いていますが,これはmecab-ipadで計算済みのコストになります.矢印上の数字は連接コスト,ノード内の数字を生起コストと呼びます.連接コストはその経路を通るためのコスト,生起コストはそのノードの文字を通るためのコストです.つまり経路を選ぶ(次のノードを決定する)度に,連接コストと生起コストの和がコストとして発生します.縦線のグリッドで書いている0から8の数字は文字位置を表します.ラティスの特徴として,どのような経路を通っても元の文が復元できるようになっているので,あとはどう最小コストの経路を出すかという話になります.

f:id:jonki:20191121215107p:plain

ところでこの数字はどこから持ってきたかというと,mecabIPA辞書に定義されたものを使っています.記事冒頭で出てきたあの謎の時系列データを見てみましょう.品詞.csvに色々定義されています.左から4つを順に説明すると,見出し語,左文脈ID,右文脈ID,生起コスト,です.左文脈IDとはその単語を左から見たときのIDで,右文脈IDはその逆です.

鯛焼,1285,1285,5618,名詞,一般,*,*,*,*,鯛焼,タイヤキ,タイヤキ
太公望,1285,1285,5622,名詞,一般,*,*,*,*,太公望,タイコウボウ,タイコーボー
土盛り,1285,1285,5622,名詞,一般,*,*,*,*,土盛り,ドモリ,ドモリ
祝い事,1285,1285,5601,名詞,一般,*,*,*,*,祝い事,イワイゴト,イワイゴト
は,261,261,3865,助詞,係助詞,*,*,*,*,は,ハ,ワ

左・右文脈IDがなぜいるかというと,連接コストを見るためです.連接コストはmatrix.defに定義されています. 例えば,【鯛焼(名詞), は(助詞)】という2つのノードの連接コストを調べるには,【鯛焼】の右文脈ID,【は】の左文脈IDを見ればよいのです.(右左混乱しますが,気をつけてください.鯛焼は左側のノードですが,連接エッジ側(右側)から見たコストになるので,右文脈IDとなります).matrix.defの該当行は下記のように,単語A,Bが連接する際に,Aの右文脈ID,Bの左文脈ID,連接コストの順で記述されます.つまり【鯛焼(名詞)】の右文脈ID,【は(助詞)】の左文脈IDが並んでいる場所を探すと下記のようになっているので,この連接コストは-3845と負の値であることが分かります.要は負のコストなので,選ばれやそうな経路です.

1285 261 -3845

では,【鯛焼, 太公望】はどうでしょうか?いずれも文脈IDが1285なので,matrix.defの1285 1285の行を探せばよいのです.先程よりもコストが上がったので,連接コストだけの観点で言えば,【鯛焼太公望】よりも【鯛焼は】の方が自然なつながりであるというのが機械的に分かります(意外と鯛焼太公望のコストが低いですが笑).

1285 1285 62

このようにして,生起コストと連接コストが分かったので,あとは最小コストの経路パスを競プロよろしくのテクニックで好きに解けばよいのです. ちなみにこれらのスコアはBCCWJなどの大規模コーパスでCRFなどによって学習されるのですが,今回の記事ではそのあたりまで間に合わなかったので,今度の記事で新語追加などの話と合わせてお話できればと思います.

ビタビアルゴリズムによる最小コスト経路の算出

最小コストの経路を出すには,動的計画法であるビタビアルゴリズムを使うのが楽です.ビタビアルゴリズムは順方向と逆方向の2種類の探索で,最小コストの経路が見つかります.順方向では各ノードにおいて,そのノードに到達するための最小コストを計算していき,更に最小コストを通る直前の経路を保存します.逆方向では,その各ノードで保存されている直前の最小コストの経路を選択していくだけ,最小コストの経路が辿れます.これもスライドを用意したので見てみてください.前方向計算時に,いくつかのノードでは複数の経路がありますが,最小コストの経路を選択していることに注意してみてください. speakerdeck.com

ここまでで一通り形態素解析ができるようになったので,形態素解析をしてみた例がこちらになります.

$ python examples/run_tokenizer.py
Loading dictionaries...
Loaded!
Query (press "end" to exit)>> 吾輩は猫である
Tokenized tokens (min cost):
表層系  品詞    発音    未知語
__BOS__ None    None    None
吾輩    名詞    ワガハイ        False
は      助詞    ワ      False
猫      名詞    ネコ    False
で      助動詞  デ      False
ある    助動詞  アル    False
__EOS__ None    None    None

Query (press "end" to exit)>> 形態素解析エンジンTaiyakiできたぞ
Tokenized tokens (min cost):
表層系  品詞    発音    未知語
__BOS__ None    None    None
形態素  名詞    ケイタイソ      False
解析    名詞    カイセキ        False
エンジン        名詞    エンジン        False
Taiyaki 名詞    *       True
でき    動詞    デキ    False
た      助動詞  タ      False
ぞ      助詞    ゾ      False
__EOS__ None    None    None

Query (press "end" to exit)>> すもももももももものうち
Tokenized tokens (min cost):
表層系  品詞    発音    未知語
__BOS__ None    None    None
すもも  名詞    スモモ  False
も      助詞    モ      False
もも    名詞    モモ    False
も      助詞    モ      False
もも    名詞    モモ    False
の      助詞    ノ      False
うち    名詞    ウチ    False
__EOS__ None    None    None

未知語処理

ここまでで形態素解析はできるようになったのですが,それは既知語が登場する場合です.mecab-ipadicには33万語登録されていますが,実際に語彙というのはロングテールなので,マイナーな語彙は登録されていなかったり,そもそも語はどんどん追加されていくので(流行語,地名,ユニット名とか色々),これに対応しないといけません.では,実際に知らない語,未知語を含む文を入力されたらどうすべきでしょうか?極端な話,私が今思いついたような語彙とかにも対応してほしいわけです.実は先程の形態素解析の例で「Taiyaki」という未知語があるのですが,うまく単語区切りできているのは,未知語処理のおかげです.

TaiyakiではMeCab処理を模倣しているので,解析時の未知語処理もMeCabさんに倣うとしましょう.MeCabでは,解析する文字種毎にヒューリスティックなルールが設定されており,それをベースに未知語処理していきます.そのルールはmecab-ipaadicのchar.defに記述されています.char.defは冒頭,下記のような記述があります.

DEFAULT        0 1 0  # DEFAULT is a mandatory category!
SPACE          0 1 0                                                                                                                                                                                                                                                        KANJI          0 0 2
SYMBOL         1 1 0                                                                                                                                                                                                                                                        NUMERIC        1 1 0
ALPHA          1 1 0                                                                                                                                                                                                                                                        HIRAGANA       0 1 2
KATAKANA       1 1 2
KANJINUMERIC   1 1 0
GREEK          1 1 0                                                                                                                                                                                                                                                        CYRILLIC       1 1 0

左から順に,文字種,動作タイミング(0/1),グルーピング(0/1),長さ(0,1,..,n)です.

  • 文字種
  • 動作タイミング
    • 1であれば常に未知語処理を働かせ,0であればその位置から開始する既知語がない場合に動作させる
  • グルーピング
    • 同じ文字種でまとめるかどうか
  • 長さ
    • 1以上である場合,その長さで未知語をまとめる.0である場合,まとめる長さ制約は設けない(ただしグルーピングが1のときのみ有効)

文字種とは,Unicodeのコードポイントの値から判定するものとしてchar.defで以下のように定義されています.漢字とか平仮名のコードポイントの範囲が書かれています.

  # HIRAGANA
  0x3041..0x309F  HIRAGANA
  
  # KATAKANA
  0x30A1..0x30FF  KATAKANA
  0x31F0..0x31FF  KATAKANA  # Small KU .. Small RO

この文字種を判定するコードは,Pythonで下記のように書けます.int値に変換して範囲に収まっているか見るだけです.

# 便宜上char.defより部分的に抜粋してここで定義.実際はchar.defから読み込んでパース.
char_def = {
    'ALPHA': [('0x0041', '0x005A'), ('0x0061', '0x007A')],
    'HIRAGANA': [('0x3041', '0x309F')],
    'KATAKANA': [('0x30A1', '0f30FF'), ('0x31F0', '0x31FF')],
    'NUMERIC': [('0x0030', '0x0039')]
}
def analyzeCharCategory(char):
  code_pt = ord(char) # ordはUnicode コードポイントを表す整数を返す
  for char_category, char_range_list in char_def.items():
    for char_range in char_range_list:
      char_range_beg, char_range_end = char_range
      if code_pt >= int(char_range_beg, 16) and code_pt <= int(char_range_end, 16):
        return char_category

for char in ['a', 'あ', 'ア', '3']:
  print(char, ':', analyzeCharCategory(char))

結果は下記のようになり,文字種が判定できていることが分かります.

a : ALPHA
あ : HIRAGANA
ア : KATAKANA
3 : NUMERIC

文字種判定ができるようになったので,いくつか未知語を含む文の形態素解析をしてみましょう.

最初は未知語「GAFA」です.ALPHAのルールは,グルーピングが1かつ未知語長が0(制限なし)なので,下記のようにまとまって捉えることができます.

表層系  品詞    発音    未知語
__BOS__ None    None    None
GAFA    名詞    *       True
に      助詞    ニ      False
転職    名詞    テンショク      False
し      動詞    シ      False
たい    助動詞  タイ    False
人生    名詞    ジンセイ        False
だっ    助動詞  ダッ    False
た      助動詞  タ      False
__EOS__ None    None    None

次にカタカナである「タピりたい」と「タピタピりたい」を形態素解析してみるとこんな感じです.カタカナのルールはグルーピングが1かつ未知語長が2であるため,長さ2で区切られています.本質的な解ではないですが,まぁなんとなくうまくいくケースがそれなりにある,という感じでしょうか.

表層系  品詞    発音    未知語
__BOS__ None    None    None
タピ    名詞    *       True
り      助動詞  リ      False
たい    助動詞  タイ    False
__EOS__ None    None    None


表層系  品詞    発音    未知語
__BOS__ None    None    None
タピ    名詞    *       True
タピ    名詞    *       True
り      助動詞  リ      False
たい    助動詞  タイ    False
__EOS__ None    None    None

最後に漢字である「卍解」を含む文を形態素解析してみるとこんな感じです.漢字はグルーピングなしで未知語長が2なのでうまくいきそうですが,漢字1文字が既知語として登場すること,動作タイミングが0であることから,うまく卍解できません.この例からも未知語処理が一筋縄ではいかないことが分かります.ゲームや漫画では特に未知語が多いですし,SNSなどでは言葉が砕けすぎていて形態素解析と実に相性が悪く,未だに研究の余地は大きく残っていると言えます.

表層系  品詞    発音    未知語
__BOS__ None    None    None
卍      名詞    マンジ  False
解し    動詞    カイシ  False
たい    助動詞  タイ    False
__EOS__ None    None    None

速度計測

ここまでで一通り動くようになりました.せっかくなのでpure pythonで書かれたjanomeさんと速度対決をしてみましょう.適当におーぷん2chの文,1000文をtokenizeしたときの速度を比較してみると下記のようになりました.

実行時間
Janome 1.2秒
Taiyaki 76.1秒

はい,圧倒的な遅さを誇ります.何故なんでしょう...Double-Arrayの構築が遅いのは多少理解できるのですが,推論時も遅いのは正直ようわかりませんね.Double-Arrayの検索部は,早くなるようなものではない気がするので,ラティスの構築や探索が下手なんでしょうか.これはfuture workにしておきます.

語彙追加と評価

語彙追加に関しては,今回はタイムアップだったので,次回の記事で書こうと思います.今回はmecab-ipadicで既にCRFで計算済みのコストをそのまま利用しましたが,Taiyakiではゼロから作ることを目標としているので,ここのコストも自前で計算したいです.そのために,現代日本語書き言葉均衡コーパス(BCCWJ)を買おうかなと思いましたが,流石に22万円するのでお財布には辛いです(誰か買って).まだ方針は決めてないですが,京大ウェブ文書リードコーパスなどの無料で使えるものや,既存の形態素解析器が大規模文書に対して形態素解析した結果を擬似的なコーパスとして利用する方法(シルバーコーパス)などがあるかなぁと思っています.このあたり評価方法と含めて,現在検討中です.

まとめ

この記事では,ゼロから作った形態素解析器Taiyakiを教材に,最小コスト法に基づく形態素解析器の解説をしました.だいぶ長い記事になったので,ここまで読んでくださった方には感謝です.スライドなどを使って極力わかりやすくなるよう頑張りました.まだ語彙追加,評価,高速化など大事なところをやっていないですし,ライブラリ化するために必要なテストやパッケージングもできていないので,進捗出たらまた報告します.

明日の自然言語処理Advent Calendar2日目は,下記お二人になります.