Out-of-the-box https://yag-ays.github.io/ Recent content on Out-of-the-box Hugo -- gohugo.io ja Sat, 16 Oct 2021 11:13:49 +0900 PyCon JP 2021にて「Pythonで始めるドキュメント・インテリジェンス入門」で登壇しました https://yag-ays.github.io/article/pyconjp2021/ Sat, 16 Oct 2021 11:13:49 +0900 https://yag-ays.github.io/article/pyconjp2021/ 2021/10/15-16に開催されたPyCon JP 2021にて「Pythonで始めるドキュメント・インテリジェンス入門」という題で登壇しました。 ドキュメント・インテリジェンスとは、ビジネス書類を解析し内容を理解する技術やアプリケーションの総称です。紙に印字された文字をOCRで認識し、文書解析や意味理解などの技術を組み合わせて、タスクに応じて解析システムを構築する画像認識と自然言語処理の融合分野です。今回の私の発表では、ドキュメント・インテリジェンスの全体観を紹介するとともに、レシート読み取り機能の実装というシンプルな具体事例を元にして、OCRや情報抽出のイメージを掴んでもらうような内容にしました。 ソースコード: https://github.com/yagays/di-pyconjp2021 具体の内容は発表資料を参照いただくとして、この記事ではどのような経緯で登壇に至ったかを振り返ってみようと思います。 CfPへの応募 私が所属しているUbie Discoveryでは、ブログ執筆やカンファレンス登壇といった対外的なプレゼンス向上施策に力を入れています。そういった取り組みの中でPyCon JPのCall for Proposal(発表募集)が2021年6月頃に公開され、社内でも何かアクションを取れないかという話になりました。私も何か発表できるものがないかといろいろ検討した結果、Ubie入社から1年ほど仕事で取り組んでいたトピックである、紙に書かれた文書の解析について応募してみようと考えました。 ドキュメント・インテリジェンスという言葉 ドキュメント・インテリジェンスという言葉は、 Proposalの投稿に際して何か発表内容を一言で表す端的かつキャッチーな言葉が必要だと考え、NeurIPS 2019の“First Workshop on Document Intelligence”というワークショップの名前から持ってきたものです。このワークショップは2021年のKDDで2回目が開かれるほどまだ歴史が浅く、タイトルをGoogleで検索しても先進的なスタートアップが自社商品の説明に利用する例が散見されるくらいで、おそらくdocument intelligence自体アメリカでもまだ広く浸透していない単語ではないかと思います。当然ながら日本語でドキュメント インテリジェンスと検索しても、本家document intelligenceと同じ概念として説明されたウェブページなどは出てきません。こうした言葉を使うことは、新しい単語を無闇矢鱈に発明せず先例に倣うというアカデミックの流儀からは外れるものではありましたが、色々とサーベイする中で適する言葉が見つからなかったこともあり、最終的に採用するに至りました。 ちなみに、概念としてはDocument Analysis and Recognitionが最も近いと思いますが、ドキュメント・インテリジェンスを含むもう少し大きな概念かと思います。ビジネス文書の解析はコンピュータを使ったタイポグラフィにより生成された書類を対象とし内容理解に重点を置くあたり、情景画像からのOCRであったり、手書き文書や歴史文書の解析、小説などの文字情報が全てを表す文書の解析とは、用いる手法やタスク設計が少し異なる印象があります。 投稿そして採択 そんなことを考えながらProposalを締め切りギリギリで書き上げて7月中旬に投稿し、結果が出る8月中旬まで待つことに。その当時slackでは以下のような呑気なことを書いており、こういった目論見は途方もなく間違っていたことをこの後実感します。 登壇に向けて 採択結果が8月10日にわかると、これはいよいよ頑張らないといけないなと締め切り駆動で動き始めます。発表は2ヶ月後。発表内容はProposal時点で章立てレベルで提出していることもあって大まかに出来上がっていたものの、やはり入門と題して発表するからにはある程度体系立てた知識としてお伝えする必要があるだろうと考えて、分野全体のサーベイを始めました。と言っても、ドキュメント・インテリジェンスという真新しい言葉でサーベイ論文が出ているわけでもなく、ICDARなどの色々な学会の論文やビジネス文書解析がらみのブログ記事を漁ったり、前職の同僚に話を聞いたりと、散らばった情報をかき集めるのはかなり大変でした。特にOCRに関しては、古くからある技術で、ソフトウェアエンジニアに限らず多くの人にとって馴染みある技術だと思うのですが、その技術を日本語で解説した書籍は全くと言っていいいほど見当たりません(私の探し方が悪いのかもしれませんが)。見つけた書籍も出版が30年近く前のもので、逆に古典を知る機会になって興味深いということがありました。 最近OCRの歴史について調べていて、1994年出版の文献を見つけて入手できました。電気情報通信学会がまとめた音声や文字認識のパターン認識に関する本で手法自体はあまり書いてないですが、フォントどう対応するかなどの課題が書いてあって面白いです。 pic.twitter.com/RYKCuvIbvo — やぐ (@yag_ays) October 6, 2021 こうしたサーベイなどの取り組みの中で今回の資料作成に直接的に寄与した部分は少ないですが、それでも自分の中で分野全体を体系立てて理解するきっかけになったと思います。資料に盛り込めなかった細かな話などは、またどこかブログなどでアウトプットできればと思います。 レシート読み取りという題材 PyCon JPという場所で登壇するからには、あくまでPythonのコードが主役にならなければいけないと考え、チュートリアル的に分析するコードを紹介することにしました。ドキュメント・インテリジェンスは様々なビジネス書類を題材とする都合上、取り組む題材によってやることがかなり変わってきます。なるべく共通した要素を紹介でき、かつ解析の奥深さも伝えられるくらいに難しいもの、そして親しみを持てるトピックを考えた結果、レシートの解析を選択しました。レシートというのはインターネットでも様々な方がブログ記事にしているくらいありふれた題材であり、私が改めて解説するほどのものなのかとかなり悩んだのですが、いざストーリーを構築するなかで簡単な文字列操作から固有表現抽出、暗黙的な表形式などを盛り込むことができ、結果的には私が話したいことを一通り網羅できる良い題材だったと思います。 最後に 今回発表したトピックは自分自身のなかでまだ体系立てて理解していなかった領域だったため、サーベイをしたり説明のためのストーリーを練る作業が本当に大変でした。私にとっては未踏の地の開拓という感じで手探り状態でなんとか完成させた発表ですが、ビジネス書類の解析をふくめ、OCRや周辺領域がもっと活発になり、新しく取り組みたいと思った方にとって多くの情報が得られるようになればいいなと思います。私もサービス開発の中で得られた知見を公開できるよう、これからも頑張りたいと思います。 MNTSQ & UbieでVertical AI Startup Meetupを開催しました https://yag-ays.github.io/article/mntsq-ubie/ Wed, 11 Aug 2021 09:09:22 +0900 https://yag-ays.github.io/article/mntsq-ubie/ 2021/8/10にリーガルテックのMNTSQさんとヘルステックのUbieの2社で"Vertical AI Startup Meetup"というイベントを開催しました。Ubieからは私が登壇したので、当日のイベントの様子や感想をご紹介します。 【MNTSQxUbie】Vertical AI Startup Meetup - connpass Vertical AI Startupとはなにか @YotaroKatayama まずはじめにMNTSQ 堅山さんから、イベントのタイトルにもなっているVertical AI Startupの紹介と開催 趣旨の説明がありました。このイベントが企画されるまで私はVertical AI Startupという概念を知らなかったのですが、まさにMNTSQさんやUbieのようなスタートアップを表すのにふさわしい単語だと思います。 リーガルテックやヘルステックといった「Xテック」といった表現で形容されることも多い領域ですが、ただAIや機械学習を使ったピンポイントのソリューションではなく、分野全体をVerticalにカバーする形で課題を解決しにいくんだというところが、Vertical AIという表現のいいところですね(AIという表現に目を瞑れば 笑)。 Vertical AI製品の品質管理 @kzinmr MNTSQの稲村さんからは製品の品質管理についての発表がありました。少し難しいトピックではありましたが、私自身もぼんやりと考えている概念が簡潔にまとまっており、とても勉強になりました。 教師データを元に学習を行う機械学習においては、機械学習のロジックや特徴量といった要素に加えて、教師データの質も同様に重要です。スライドの中で登場するデータ品質保証というのはまさに予測精度やモデルの質の根幹となる大事な要素ですが、その質を数値として客観的に判断する手段はまだまだ発展途上でかつ、対象ドメインやデータの中身によっても大きく異なります。Uberの事例の引用ではETLのテストで欠損値の割合や重複率といった品質項目が挙げられていましたが、それはデータの質の一つの側面でしかありません。データが生成される背後にある潜在的な分布の変化やデータドリフトをどう捉えるかが重要といった話をされており、MNTSQさんでもかなり苦労されながら品質担保のための項目を充実させているとのことでした。 Ubieの医療においても、入ってくるデータの質が変わるというのは当たり前に起こります。具体のタスクではないですが、例えばインフルエンザは夏に比べて冬の方が多いとか、熱中症は夏によく起きるとか、病気には季節性があります。年単位で見なければデータの傾向を見逃してしまい、夏のデータで何らかの予測モデルを作ったら冬に全然性能が出ないとなってしまうことも考えられます。究極にはデータを見るということが大事という話ですが、人間の事前知識でカバーできない予想外のデータの変化なども含めて、データやそこから作られる機械学習プロダクトの品質に向き合っていく必要がありますね。 医者の言葉、患者の言葉、エンジニアの言葉 @yag_ays 私からはUbieでの実事例を交えながら、医療ドメインにおける言語処理についてお話させていただきました。前回の記事で書いたこの1年の振り返り内容とも少し重複しますが、医療という1つのドメインの中にある複雑さをご紹介できたのではないかと思います。 内容はスライドを見ていただくとして、ここではsli.doで頂いた質問に対して改めてお答えできればと思います。 医者言葉と患者言葉のマッピングに興味を持ちました。特に患者言葉の曖昧性解消はかなり難しいタスクだと思います(患者自身がどういう現象を表現しているのか理解できずに発した言葉が多そう)が,どういう問題設定でアプローチされているのでしょうか?素朴に文脈を見ても解消できない気がします。 なかなかガチめの質問で、その場では結構回答するのに苦労しました(笑)。 発表冒頭でも話したように、患者表現の「顔が赤い」を医師は「顔面紅潮」と表現することがあり、同じ対象や現象に対しても話す人によって言い方が異なります。それらを結びつけるためには、エンジニア観点では両者を一旦エンジニアの言葉であるID:42みたいなDB上の一つの項目(エンティティ)に紐付けることになりますが、では自然文からのID変換の名寄せをどうするかが次の課題になってきます。名寄せは前職を含めてここ数年ずっと自分の中で取り組んでいる課題ではあるんですが(昔書いたblog記事)、辞書を拡充させるといった方法以外で解くのは難しい印象です。つまりID:42に対して「顔が赤い」「顔面紅潮」「顔が赤みがかる」「顔が発赤している」みたいな表現をたくさん集めておくという方法ですね。自然言語処理的には単語の分散表現などから意味的な類似度を元に名寄せできると嬉しいのですが、それをするためにはたくさんの学習データを集めて対応関係を学習させるしかなくて、結局やっていることは辞書拡充と同じというのが実情だと思います。近年ではBERTに代表される大規模コーパスでpretrainさせたモデルがどのタスクにも有用な情報を学習できるという話もあり、そこから発展して人間の常識(common knowledge)のようなものが学習できて名寄せにも利用できるといいなと、ぼんやり思っています。 一方、いま現在は曖昧性解消をどうしているかというと、どうもしていないというのが率直な答えになります。今は目の前のタスクをいかに精度高く解くかに注力しており、その上で曖昧性解消を解く必要が出てこない限りは取り組まないです。小枝を切るのにチェーンソーを使う必要がないのと同様に、解きたいタスクに適した最小の技術を当てはめること、それと同時に不必要に難しいタスクを解かないことを意識して取り組んでいます。と、抽象的な話で若干煙に巻いている感はありつつも、実はそろそろ向き合わなければいけなくなってきた雰囲気がするので、今後の進展であったり私が苦労している様子にご期待ください(笑) We Are Hiring さて、今回はVertical AI Startupという軸でイベントを開催しましたが、MNTSQさんもUbieも機械学習や自然言語処理などの各種エンジニアを募集しております。どちらも採用サイトにてカジュアル面談のフォームがあるので、今回の発表がちょっとでも面白いと思ったら、ぜひ一度お声がけください。お待ちしております! MNTSQ, Ltd. | リーガルテックカンパニー Ubie Discovery 採用サイト 医療ドメインの自然言語処理に飛び込んで1年経って見えてきたこと https://yag-ays.github.io/article/mednlp/ Wed, 23 Jun 2021 09:00:00 +0900 https://yag-ays.github.io/article/mednlp/ 医療スタートアップのUbieに入社して1年が経ちました。これまでの人生で一番短かったんじゃないかというくらいのスピードで月日が過ぎ去っていき、主体的に携わるプロジェクトも1.5周くらいしたところかなと思います。この記事では機械学習エンジニアの私が、医療というドメインの自然言語処理に携わるなかで考えたことを紹介したいと思います。 最近ではリーガルテックをはじめ、HR、ファイナンス、そして医療など、様々な領域で自然言語処理の活用が広がっています。そうした専門ドメインでの自然言語処理に携わる人も増えてきていると思いますので、その中の一例として何かしら参考になれば幸いです。 【目次】 - 医療という専門領域の知識は必要 - 分野が違っても手法は同じ、研究が扱う題材を知っておく - 医療という特殊なデータ事情 - なぜ私はいま医療言語処理をやるのか? - まとめ 医療という専門領域の知識は必要 機械学習で何を解くのかというタスク設計において、医療ドメインの知識は必要不可欠だと感じます。どういった問題やデータに対して、どういった入力/出力を設計し、どうプロダクトに適用するかを、機械学習エンジニアは常日頃考えているわけです。そのあらゆる状況において、医療ドメインの知識は活かされています。 実際に私が遭遇した例を、幾つか紹介しましょう。詳しくは書けませんが、ある病気の症状やその状態を文書中から抜き出すタスクに取り組むことになりました。病気の症状というと「38度の熱がある」とか「昨日から頭が痛い」とか「足首を捻挫した」など、そういったものを想定してください。あまりきっちりとしたタスク定義ができない問題だったのですが、どういう情報を取得できれば価値があるかという問題設定からタスクを検討し始めたなかで見つけたのが、医師が問診の現場で利用する「OPQRST法」というフレームワークでした。これはOnset, Palliative/Provocative, Quality/Quantity, Region/Radiation, Symptoms associated(Severity), Time courseの頭文字を取ったもので、Oはいつから発症したか、Pはどういう時に痛くなる/痛くなくなるかといったように、この枠組みで患者の症状(病歴)を聞くことが良いとされています。これを踏まえて、このタスクではOPQRSTの各項目に対して情報抽出を行うというアプローチを取るということにしました。病気の症状というのは一般の人も知っている概念なので、どうしてもその知識の範囲内だけで捉えてしまいがちですが、医師の目線での体系を使うことでよりタスク定義が明確になったと思います。 また、推論結果のプロダクトへの適用という意味では、既に存在するID体系やオントロジー、リソースといった外部の知識とどう対応付けるかが一つ重要になってきます。つまり、すでに体系立てられているものに寄せて開発したほうがいいよという話です。例えば、文書中から病気の名前を抽出しましょうというタスクがあったときに、その病名の文字列だけを取得できればいいというケースは稀でしょう。その病気の詳細情報を表示したり、インデックスとして利用して検索システムに利用したり、病気に対して何かを紐付けたりと、なんらかダウンストリームのタスクに利用しますが、その時に必要になるのが病気の概念を表したIDです。日本では現在ICD-10と呼ばれる国際疾病分類に従った管理がされており、様々なデータがICD-10準拠になっています。自然言語処理のコーパスにおいても、万病辞書の病気の分類にも採用されています。この他にも様々な医療の概念に対してIDが付与されたり、標準化規格が策定されています。目の前のデータの中だけで精度高く解ければそれでいいということもありますが、既存のID体系やオントロジーに乗っかる方が正確であったり外部知識との接続も容易になることが多く、将来的な汎用性や網羅性に繋がります。こうしたシステムで利用する目的の規格などは医師も知らない場合が多く、エンジニアサイドが主体的に調べて知識を深めていく必要があります。 このように、解くべき問題の背後にある理論や知識は、機械学習タスクを設計する上で重要です。といっても医師と同じような知識が必要というわけではなく、病気や治療に対するモノの捉え方を理解したり、体系化された情報をどう活用するかがポイントと言えます。 分野が違っても手法は同じ、研究が扱う題材を知っておく 専門ドメインでの自然言語処理と言っても、解くべきタスクのデータや性質が異なるだけで、手法としての自然言語処理は共通している場合が多いです。文中からの情報抽出であればCRFやRNN/LSTM、Transformerが使えますし、他にも検索や名寄せ、ポジネガ判定、文書の類似度算出など、広く研究される自然言語処理のタスクの技術をそのまま利用できます。使える道具は共通しているので、そこに関して知識があれば別ドメインに移ることはそう難しいものではありません。 ちなみに分野を俯瞰するという意味では、実際に解かれているタスクを見るのが一番の近道です。私の場合は、はじめにNAIST荒牧先生の「医療言語処理」を読み、あとは適宜タスクが見えてきたら個別にサーベイをして補完していくという流れで進めました。言語処理学会の年次大会の論文から医療に関するものを見つけてきて一通り目を通したり、あとはカンファレンス内で開かれる医療系のワークショップなどは分野に明るくない身としてはとても役に立ちました。 ちなみにUbie入社後ちょっとしてから書いた下記の記事は、一通り実験したあとに類似研究があることに気づいて慌てて言及した記憶があります。何をするにしてもサーベイはやっぱり大事です。 医療分野の大規模テキストデータで学習した分散表現から、疾患の類似度を求める - Out-of-the-box 医療という特殊なデータ事情 ここが医療言語処理の一番の特徴だと思いますが、とにかくデータが無いです。なさすぎて困っています。 無いというのは少し語弊がありますが、もうすこし正確に記述すると、パブリックにアクセスできるコーパスや言語資源に乏しく、病院をはじめ医療関係の企業や研究室といった組織に偏在しているという感じでしょうか。そして色々な自然言語処理のドメインにおいて、医療ほどデータを集めにくい分野は他にないんじゃないかと思います。その理由としては、以下の2つがあります。 電子カルテや診療記録といった治療行為に関する情報は個人情報の塊であり、容易にアクセスできず、かつ利用が強く制限されるということ 医師や看護師といったドメインエキスパートの労働力は高単価であること つまり、元となるデータはないわ、作るのにコストがかかるわ、の二重苦なわけです。何かしようと思ってもそもそも機械学習のスタートラインに立てないというのが、毎度おなじみの光景になっています。なので、基本はデータを作るところから始めます。社内DBやウェブサイトなどから使えそうなデータがないか探したり、教師あり学習の場合はアノテーションも付与する必要があるので、こちらもクラウドソーシングや業務委託に依頼するといった形で準備する必要があります。distant supervisionのような半教師有りといった方向に持っていくこともできますが、評価データやベースラインが定まっていない開発初期に採用することはあまりないです。 そうしてデータを作る間にもまだまだやることはたくさんあります。プロダクト開発では、なるべく早い段階で動くモノがあると、依存する他のサービスを作りやすくなります。しかしデータ作成にはリードタイムがありますから、まずはルールベースや簡単なモデルから作り始めます。すると性能を評価をしたくなりますが、評価データもあったりなかったりで、モデルを評価するにも性能向上するにもデータ作成が律速になってしまいがちです。 一方で、医療ドメインでも集めやすいデータが存在するので、そちらを利用する場合はちょっと状況が異なります。例えばTwitterでユーザが発言する病気や症状のテキストであったり、企業の中であれば提供しているウェブサービスが利用される中で得られる各種情報。Googleの研究者らがユーザの検索履歴を元にインフルエンザの流行予測を試みた「Googleインフルトレンド」の話は有名ですね(ちなみに予測はのちに有効ではなかったと結論付けられています link)。他には論文のデータはpublishされているという理由で扱いやすく、英語圏ですと特にPubMedという生命科学に関する論文データベースから、大量の論文のアブストラクト取得することができます。また前節で言及したオントロジーなどの知識ベースも、うまく活用するとよい情報源となります。 このように医療というドメインの性質がデータ収集や利用に強く影響するなかで、なかなか機械学習という枠組みに乗りにくいのが実情です。 なぜ私はいま医療言語処理をやるのか? 最後に大事な話を一つ。なぜ私はいま医療言語処理に携わるのか?という、もっとも根本的な問いがあります。それは入社時と比べて、より明確になったと思います。 まず第一に社会的な意義。この1年は私にとってUbieの1年であったと同時に新型コロナウィルスの1年でもあったわけですが、医療や健康に対して特に考える機会が多かったと思います。一介の機械学習エンジニアの自分にできることはなんだろうと考えるなかで、医療に対して直接的な貢献はできなくても、医療ベンチャーの一員として手伝えることがあるだろうと改めて思い直しました。世間ではAIや人工知能といった流行で持て囃される業種ですが、真にユーザに価値を届けられる仕組みを作るのはとても大変なことです。そこに対して自然言語処理という領域で取り組むことで、少しでも医療に貢献できればと思っています。 そして第二の理由としては、医療言語処理のプレイヤーの少なさと将来性。特にプロダクト開発/エンジニアリングにおいてプレイヤーは少なく、手のつけられていない課題がたくさんあると思っています。もちろん医療というドメインはその性質上どうしても閉鎖的になりがちな分野で、実際には言語処理関係の仕事をされている方が多数いらっしゃるのは認識しています。そうした制約がある上で分野としての裾野を広げるためにも、共通した言語資源の整備やタスク特化の手法の開発、そして医療言語処理での成功事例の蓄積など、そこに好機を見出して挑戦していくのが楽しいフェーズだと思います。私はこうした知識の共有やアウトリーチ活動が好きなので、個人的な趣向とも一致しています。 まとめ 少し長くなってしまいましたが、この記事では私が医療言語処理という医療ドメインの世界に飛び込んだ中で感じたことを紹介しました。一言で言い表すと、制約だらけの未開の地で大変だけどそのぶん楽しいよ!という感じです。冒頭でも述べたように自然言語処理には医療に限らず色々なドメインが存在しており、医療とは違った形の難しさや挑戦があるのだと思います。そうした自然言語処理がより盛り上がっていくのが楽しみですし、医療言語処理に対しても少しでもそのお手伝いが出来ればと思っております。 さて、Ubieでは、こうした医療言語処理を使ったプロダクトを一緒に開発していく方を募集しております。ご興味がありましたら、ぜひ気軽にご連絡ください。お待ちしております! シニア機械学習エンジニアの募集 | Ubie株式会社 採用情報 万病辞書を形態素解析器Sudachiで利用する https://yag-ays.github.io/project/manbyo-sudachi/ Mon, 05 Apr 2021 20:45:14 +0900 https://yag-ays.github.io/project/manbyo-sudachi/ 概要 万病辞書とは、NAISTソーシャル・コンピューティング研究室から公開されている病名辞書です。様々な病名に対してICD10と呼ばれる疾患の標準規格が対応付いているほか、医療従事者による作業や計算機による自動抽出で得られた病名が列挙されています。また、形態素解析器で利用するための辞書データとして、MeCabに対応したものが配布されています。 今回は、この万病辞書を形態素解析器Sudachiで利用できるようにするために、万病辞書からSudachiのユーザ辞書を作成しました。ダウンロードして利用できるように辞書データも配布します。 レポジトリと辞書ファイル レポジトリ: yagays/manbyo-sudachi ユーザ辞書ファイル: manbyo20190704_all_dic.txt manbyo20190704_sabc_dic.txt 配布している辞書ファイルのライセンスは元のライセンスを継承してCC-BYです。また、この辞書ファイルにはSudachidictのシステム辞書の情報は含まれていません。 作成方法 ここでは、万病辞書からSudachiのユーザ辞書を作成する方法を解説します。もし上記の作成済みユーザ辞書ファイルを利用する場合は3.以降をお試しください。 1. 万病辞書の準備 万病辞書はMANBYO_201907を利用します。各病名には信頼度LEVELというものが設定されており、ICDとの対応やアノテーションの作業度合いによってS,A,B,C,D,E,Fと降順で割り振られています。今回は元となる万病辞書から、信頼度でフィルタして2種類の辞書を作成します。 信頼度がS,A,B,Cの、ある程度信頼できる病名 (*_sabcと表記) すべての病名 (*_allと表記) なお、登録されている病名や読みがななどは基本的にはそのまま利用しますが、見出し語に全角カンマ,が含まれていると正規化時に,となりcsvを破壊するため、日本語の句読点、に変換しています。 2. Sudachiのユーザ辞書の作成 Sudachiのドキュメントに従って、以下のようにユーザ辞書を作成します。 見出し語は、万病辞書の「出現形」にSudachiの文字正規化を行った文字列を使用 左連接ID,右連接ID,品詞1および品詞2は、4786,名詞,固有名詞を利用 読みは、万病辞書の複合文字列ラベルのしゅつげんけいがある場合に登録し、なければ空白 正規化表記は、万病辞書に登録されている表記を利用 3. ユーザ辞書のビルド 作成したユーザ辞書をビルドして、バイナリ辞書ファイルを作成します。今回はsudachidictのシステム辞書を利用するため、自身の環境に合わせてsystem.dicの場所を指定してください。 BASE_PATH=/path/to/sudachidict/resources/ sudachipy ubuild -s $BASE_PATH/system.dic manbyo20190704_sabc_dic.txt -o user_manbyo_sabc.dic sudachipy ubuild -s $BASE_PATH/system.dic manbyo20190704_all_dic.txt -o user_manbyo_all.dic 4. 設定ファイルに記述する Sudachiで指定するバイナリ辞書ファイルを利用するには、設定ファイルsudachi.jsonのuserDictに指定する必要があります。インストールしているsudachipyのsudachi.jsonを直接変更する方法もありますが、今回は自分でsudachi.jsonを作成してSudachi利用時に指定してみます。 以下のように、userDictに先ほど作成したバイナリ辞書ファイルを指定します。なお、設定ファイル内には他にもchar.defやunk.defのパスも指定されているため、そちらはインストールされているsudachipyのresources/以下のファイルを見に行くように修正します。 "userDict": ["/Users/yag_ays/dev/nlp/manbyo-sudachi/user_manbyo_sabc.dic"], 実行 それでは実行してみましょう。まずはコマンドラインから病名をsudachiに渡して実行してみます。 $ echo "間質性腎炎所見" | sudachipy -a -r config/sudachi_sabc.json | tr "\t" "\n" | nl -b a 1 間質性腎炎所見 2 名詞,固有名詞,一般,*,*,* 3 間質性腎炎所見 4 間質性腎炎所見 5 かんしつせいじんえんしょけん 6 1 7 EOS ちゃんと1つの名詞として分かち書きされていますね。品詞や読みもユーザ辞書に登録した通りに表示されています。 精度向上のために機械学習プロダクト全体をフルスクラッチで書き直した話 https://yag-ays.github.io/article/refactor-them-all/ Sun, 24 Jan 2021 14:58:15 +0900 https://yag-ays.github.io/article/refactor-them-all/ 2020年7月から医療スタートアップのUbieで機械学習エンジニアをしています。ようやく入社から半年くらいが経ちましたので、ここ最近やっていた仕事として、機械学習プロダクトの精度向上のためにシステム全体をフルスクラッチでかつ一人で実装し直した話をしたいと思います。 機械学習は既に様々な会社でプロダクトに組み込まれ始めていると思いますが、サービスとしてのリリースや長期運用、そして今回お話する継続的な精度向上とリファクタリングについては、公開されている知見はまだまだ少ないと思います。もし同じような境遇の機械学習エンジニアの方への参考になれば幸いです。 tl;dr 精度向上のために、機械学習プロダクト全体をフルスクラッチで書き直した 開発スピードを上げるためには、既存のコードを流用するより新規で書き直すほうが良いような特殊な状況だった 機械学習タスクの実装は、可視化やテストなどを活用しつつ小さく積み上げていくことが大事 はじまり 私が取り組んでいた機械学習プロダクトは、ドキュメントの画像をOCRしてテキスト中から情報を抽出するというサービスでした。画像処理と自然言語処理の領域が入り混じった、なかなかに複雑なタスクです。サービス全体はWebAPIとして実装されており、クラウド上の画像のURLがリクエストとして来ると、画像を読み込んで前処理、OCRに投げて文字を抽出、文字列とその座標位置からいい感じに情報抽出をして、最後に構造化した情報をレスポンスとして返すという構造でした。 今回お話するロジックとは、この中の情報抽出の部分を指します。機械学習プロダクトとしての精度は悪くありませんでしたが、まだまだ精度向上や機能開発の可能性がありました。 精度向上への課題 さて、そのようなプロダクトの精度向上を任されのですが、じゃあKaggleみたいに様々な機械学習モデルを駆使してテストセットの精度を上げていくぜ!となるかというと、そういう話ではありませんでした。よくある機械学習タスクに持ち込めない理由として、具体的には以下のようなものがありました。 複数の機械学習タスクが存在し、それらに依存関係がある 前段のOCRによる文字検出の結果が、後段の自然言語処理による情報抽出に強く影響する OCRで大きく間違えると、そもそも情報抽出の方ではどうしようもできない 一部のタスクはブラックボックスになっている OCRは自作しているわけではないため、ブラックボックスとして扱わざるを得ない システム全体で単一のロス関数を設定してそれを下げるみたいなEnd-to-Endなアプローチが難しい 教師データがそもそも少ない ドメインの性質上、学習データを大量に作ることが容易ではないため、使えるデータが少ない&増やせない 情報抽出の固有表現はバリエーションが非常に多く、全部を網羅するようなデータ集合を作ることは不可能 (分類タスク的に解こうとするとExtreme Multi-label Classificationみたいな設定) そのため、実際のシステムはディープラーニングでEnd-to-Endに推論するわけではなく、かなり泥臭い方法でロジックを組み立てていき、細かな微調整を繰り返しながら地道に精度を上げていくという感じでした。ディープラーニングが流行る前の、古き良き特徴量エンジニアリング時代の機械学習という感じですね。私が入社した時点ではそういった作業はある程度進んでいる状態でしたので、既存のロジック自体はかなり複雑かつ大きなものになっていました。短期的に精度向上する余地は、未知のパターンに対応したり、辞書を拡充したりといったことくらいでした。 この時点では、見えていた課題に対してこうアプローチすれば精度が改善する!という見立ては立てられていたものの、その手法は既存ロジックを拡張する形では実現できず、わりと根本からのロジックの修正が必要でした。実装の見通しも立っていなかったため、それに手を付けられないまま、細かな修正で少しずつ精度を上げることしかできていませんでした。いわゆる局所最適の谷に嵌っていた感じです。ちなみに他の精度向上施策として一時期はOCR自作も考えましたが、あまりにもRoIのInvestmentが大きいという理由で却下しています。 フルスクラッチで書き換える決意 大きな精度向上のために残された道としては既存ロジックを新しく置き換えるくらいでしたので、ある程度やり尽くした時点でやると決断します。この時に今回の記事の主題である、既存のコードベースはほぼ使わずにシステム全体をフルスクラッチで作ろうと判断しました。 ではなぜ既存のコードベースを活かしつつ該当する箇所だけ置き換えなかったのか?ですが、その理由としては 「開発スピードを上げるため」 これに尽きます。 精度向上を山登りに例えるならば、今登っている登山道を登り続けるよりも、一旦下山して別のルートから頂上にアタックするほうが最終的に頂上に着くのが早いと判断しました。 この時期にちょうど「レガシーコードからの脱却」「リファクタリング」といった本を読んでいたのですが、そこに書かれているアドバイスとしては「レガシーコードのフルスクラッチでの書き換えはやめろ」でした。先人がそうした警鐘を鳴らすなかで、この決定は自分でもかなり葛藤がありました。今でも多くのケースでは、フルスクラッチでの書き換えは悪手だと私は思っています。今回のケースではデメリットを上回るメリットがあると思い、このように決意しました。 開発スピードが落ちる理由 フルスクラッチで書き換える理由は開発スピードを上げるためと書きましたが、その理由としては大きく2つあります。 画像処理 × 自然言語処理というタスクの難しさ 既存システムの問題 1. 画像処理 × 自然言語処理という難しさ まず何より今回のシステムが、通常のシステム開発や機械学習タスクと比較して特殊だったということがあります。画像をOCRしてテキストから情報抽出するタスクですが、処理の途中でどういう値を扱うかというと、雰囲気はこんな感じです。 # 画像 array([[[243, 245, 244], [242, 244, 243], [241, 243, 242], . OpenCVとPythonで深層学習モデルの超解像を手軽に試す https://yag-ays.github.io/project/opencv-superresolution/ Thu, 24 Dec 2020 12:20:04 +0900 https://yag-ays.github.io/project/opencv-superresolution/ 概要 超解像とは、任意の画像を入力として解像度を上げた画像を出力するコンピュータービジョンのタスクです。近年の深層学習の発達により、より高精細で自然な超解像が可能になってきました。一方、そうした研究で提案された最新のモデルを動かすにはPytorchやTensorflow等のディープラーニングのパッケージを利用する必要があり、論文著者らにより公開されているコードを読み込んだり必要に応じて変更していく必要があります。モデル構造を深く理解したり自分のタスクに特化したものを作るにはそうしたコーディング技術が必須ですが、もう少し気軽に深層学習を用いた超解像を試して結果を見てみたい、どれくらい見た目が良くなるのかの感覚を掴みたい、ということがあると思います。 そこで今回はOpenCVとPythonを用いて、深層学習モデルの超解像を動かしてみようと思います。 方法 1. インストール 今回試す超解像は、OpenCVに加えてopencv-contrib-pythonというOpenCV準拠の拡張パッケージをインストールする必要があります。 $ pip install opencv-python $ pip install opencv-contrib-python 2. モデルのダウンロード 超解像に利用するモデルはpipではインストールされないため、手動でダウンロードします。利用できる各モデルのリンクは下記URLのREADME.mdに記載されています。2020/12/24現在では、以下のモデルに対応しており、拡大するときの倍率(scale)として2倍,3倍,4倍が用意されています。 EDSR ESPCN FSRCNN LapSRN opencv_contrib/modules/dnn_superres at master · opencv/opencv_contrib これらは2016年から2017年頃にかけて提案されたモデルです。近年のコンピュータービジョンの深層学習の発展を考えると少々古く感じられますが、主要なモデルが揃っている感じです。超解像の発展における各モデルの位置付けは、下記の記事が参考になります。 【Intern CV Report】超解像の歴史探訪 -2016年編- - Sansan Builders Blog トップ学会採択論文にみる、超解像ディープラーニング技術のまとめ - Qiita 3. コードを動かしてみる それでは深層学習モデルを使った超解像を試してみましょう。ここでは公式のTutorialの通りに動かします。 import cv2 from cv2 import dnn_superres # Create an SR object - only function that differs from c++ code sr = dnn_superres.DnnSuperResImpl_create() # Read image image = cv2. つい言い間違えてしまうような薬剤名を(半)自動で探しだす https://yag-ays.github.io/project/drug-name-ambiguity/ Mon, 14 Dec 2020 21:16:20 +0900 https://yag-ays.github.io/project/drug-name-ambiguity/ この記事はUbie Advent Calendar 2020の15日目の記事です。 昨日の記事はshigedoさんのこれからのDXを社会実装するスタートアップに必要な「パブリック・アフェアーズ」とは|yukishigedo|noteでした。 言い間違えを探す みなさんはこのツイートを見たことがありますか? 薬剤師の日常#今年もあと少しなので褒めてほしい pic.twitter.com/LaSfRnM3NY — 鮫島うさぎ (@usagi_samejima) December 27, 2019 患者さんがお薬の名前を言い間違えてしまうという薬剤師あるあるな話ですが、間違え方が独特で面白いですね。特に薬剤名は普段馴染みがなく横文字で覚えづらいので、記憶の中にある近い名称に引っ張られてしまうのかもしれません。このツイートのリプライ欄でも他の人が別の事例を紹介してくださっていますが、色々な言い間違えのパターンがあるみたいですね。 さて、今回はこのような言い間違えをしそうな単語とその薬剤名を自動で抽出してみようと思います。自然言語処理の力を使ってなかば総当り的に探していけば、まだ広く知られていない言い間違えが見つかるかも……? 言い間違えの問題 薬剤の言い間違いについてはこうした笑い話になる一方で、別の薬剤と間違えてしまうことによる医療事故にも繋がりかねない問題でもあります。薬局での薬剤の取り違えの約3割は名称類似によって起こることが報告されているほか、類似名称による医療事故が発生したことから薬剤名が変更される事例もあります。こうした医薬品の言い間違えや認識違いについては、医療事故の原因分析や予防の面で研究されているようです。 薬局での「薬剤取り違え」や「数量間違い」が5399件―日本医療機能評価機構2014年度調査 | GemMed | データが拓く新時代医療 「サクシン」が「スキサメトニウム注」に改名―薬の取り違え事故をどうなくすか:日経メディカル 製薬企業からの医薬品の安全使用(取り違え等)に関するお知らせ | 独立行政法人 医薬品医療機器総合機構 薬名類似度検索サービス 方法 今回は、以下の方法で薬剤名とその言い間違い候補を抽出しました。 候補となる薬剤名およびWikipediaの記事タイトルの一覧を取得する 編集距離などのフィルタリングを元に候補を絞り込む 最後は目検で確認する 1. 候補の名称の抽出 対象とする薬剤名の一覧を取得します。くすりのリストにて公開されているPMDAから取得した「医療用医薬品リスト」の中から、xx錠といったカタカナから始まり錠で終わる薬剤名を取得します。今回は一つの単語のまとまりとしての言い間違いを探したいため、薬剤名にありがちなxx酸yy錠といった漢字が含まれている名称は除外しました。その結果、887件の薬剤の名称が抽出できました。 次に、Wikipediaから言い間違え候補の一覧を取得します。Wikipedia:データベースダウンロードから日本語の全タイトルの一覧が含まれるダンプされたデータをダウンロードします。その結果、約200万件の言い間違え候補が抽出できました。 2. 候補を絞り込む ここから薬剤名と言い間違え候補の中で、お互いに似通った文字列を探し出します。ナイーブに実行すると(1000 × 200万)ペアの組に対して探索する必要があり計算コストが大きいため、薬剤名ごとに編集距離を用いて候補を絞り込み、その絞り込んだ候補に対して類似判定を行います。 2-1. 編集距離 まず最初の絞り込みですが、任意の薬剤名に対して編集距離が2以下の言い間違え候補を列挙しました。編集距離とは、ある文字列から別の文字列に変形するときに必要となる追加/削除/置換のステップの最小回数のことです。例えば「バルトレックス」と「バトルレックス」の編集距離は、2文字目のルを削除する→2文字目のトの後ろルを追加するの合計2ステップとなり、編集距離は2です。 編集距離の計算にはSymSpellを利用しました。事前に検索インデックスを計算することで薬剤ごとに探索する時の計算時間を抑えています。 2-2. 間違えやすさを測る ここから、より言い間違えやすい候補に絞り込んでいきます。編集距離による絞り込みでは文字列として近い候補は列挙できますが、実際に見てみると間違えないような事例が多数含まれます。そういった事例を弾くために文字列同士の言い間違えやすさを評価し、より現実的な候補を見つけます。今回は以下のサイトでまとめられている間違いパターンを利用しました。 第10回:「言語学からみた間違いやすい薬剤名」 - 医療安全推進者ネットワーク 利用したパターンは以下の2つです。 2字目と3字目は「転倒」がおきやすい 1字目と2字目が同じ、末尾が同じ、長さが同じ位 1つ目のパターンはバルトレックス/バトルレックスの事例、2つ目のパターンはエビリファイ/エビフリャイの事例そのものですね。ちなみに2つ目のパターンは、人間が文章を読む際に単語の先頭と末尾さえあっていれば自然と読めてしまうタイポグリセミアとしても有名です。 ここではその他にも、単語を構成する文字の種類に大幅な乖離がないなど、いくつかのヒューリスティックな絞り込みを含めています。 3. 目視で確認 こうして抽出した候補ですが、今回の言い間違い候補の辞書として利用したのはWikipediaですので、全く知らない単語も多く含まれています。また、間違えやすいパターンに合致しても人間が見たときに納得度合いの低いペアも含まれるため、最後は力技で人間(私)が目視で確認し、知ってそうな単語を見つけるとともに、その言い間違えやすさを主観で判断しました。 絵文字の日本語読み辞書をUNICODE 13.0対応に更新しました https://yag-ays.github.io/project/emoji-ja-update-13/ Sat, 07 Nov 2020 21:01:50 +0900 https://yag-ays.github.io/project/emoji-ja-update-13/ Unicode絵文字の日本語読み/キーワード/分類辞書を、Unicode 13.0に更新しました。 今回は@HeroadZにPull Requestを送っていただきました。ありがとうございます(PR放置してごめんなさい🙏)。 前回の記事:絵文字の日本語読み辞書をUnicode 12.0対応に更新しました 前々回の記事:📙Unicode絵文字の日本語読み/キーワード/分類辞書📙 🔖 リリース Githubレポジトリの20201107リリースからダウンロードするか、現在masterブランチに含まれている各種ファイルを利用ください。 Release 20201107 · yagays/emoji-ja 前回からのアップデートは、Unicode 13.0で追加された新規絵文字の対応のみです。 ➡ 括弧やコンマといった記号の読みの追加 今回のリリースから記号の読みが追加されており、これは辞書が参照しているUnicode CLDRのリストに追加されたためです。はたして記号は絵文字なのかという疑問が湧きますが、特に区別する必要性も感じられなかったので、元のソースに従ってすべて辞書に追加しています。 "«": { "keywords": [ "カッコ", "二重ギュメ", "山パーレン", "左ギュメ", "左山括弧", "引用符" ], "short_name": "左山括弧", "group": "", "subgroup": "" }, 📝 参考 Unicode 13.0.0 Unicode 13.0 Emoji List 医療分野の大規模テキストデータで学習した分散表現から、疾患の類似度を求める https://yag-ays.github.io/project/icd10-embeddings/ Wed, 05 Aug 2020 20:23:39 +0900 https://yag-ays.github.io/project/icd10-embeddings/ 概要 人間が記述した文章から特定の意味や関係性を抽出する行為は情報抽出と呼ばれ、自然言語処理におけるタスクの一つです。人間により収集された情報はオントロジーや知識グラフのような関係性を持つ構造として表現することで、抽出した概念の関係性を理解してきました。こうした知識の構築は、言語の文法構造を利用しルールベースで半自動で抽出する方法が広く用いられていますが、近年では単語の意味的な情報を活用し自動獲得する方法が出てきました。 私は最近Ubieという医療の問診AIを開発している会社にジョインしたのですが、医療自然言語処理の世界でもこうした情報抽出の研究が行われています。そうした特定のドメインでの自然言語処理ではデータやタスクにユニークなものが多く、そうした分野間の違いが現れるところが自然言語処理の面白いところです。 そこでこの記事では、ウェブから収集した医療分野における大規模テキストデータから、疾患(病気)の類似度を自動で獲得してみようと思います。例えば頭痛と腹痛は痛みという観点では似ていますが、痛みの部位が異なります。一方で、頭痛と蕁麻疹は症状も部位も異なり、腹痛よりは概念的には大きく離れていそうと言えます。こうした疾患の類似度を、なんとか文章から抽出しようというわけです。 手法 今回はword2vecと呼ばれる単語の分散表現を獲得する方法を使って、疾患に対応するベクトルを計算し、その数値的な類似度から疾患の関係性を評価します。実際に大規模テキストからは様々な単語の意味を学習するのですが、単語の分散表現では単語の意味というのは周囲の文脈により決定されます。疾患の単語でも同様に、周囲でどんな言葉が使われやすいかという共起情報を元にして、疾患の意味的な要素が学習されるというわけです。そうした周囲の単語の使われ方を元にして、疾患の類似度が計算されます。 1. 医療分野の大規模データ収集 まず初めに、インターネットで公開されている医療関連のウェブページからテキストを収集します。対象としては、医療情報が記載された辞典、医療に関連するニュースサイトやコラム記事、医学論文のアブストラクトなど、疾患に関連する単語が含まれていそうなテキストを選定しました。 2. 専門用語を考慮した分散表現を学習 今回はテキスト中から疾患名を正しく抽出する必要があるため、疾患名が広く網羅された万病辞書(Version:MANBYO_201907)を利用しました。この辞書による分かち書きで得られた約3,300万語ほどの文章を元に、単語の分散表現を計算します。分かち書きにはMeCabを、word2vecの計算にはgensimを用いました。 3. 疾患と分散表現の関連付けを行う 次に、疾患に対する分散表現の対応付けを行います。疾患をまとめる単位として、ここではICD-10という国際疾病分類に従ったコードを用います。例えば「頭痛」はICDコードではR51というコードが付与されており、この中には「炎症性頭痛」や「神経痛性頭痛」といった疾患も含まれています。 分かち書きで用いた万病辞書では、多くの単語にICDコードが付与されています。一つのICDコードに複数の単語が紐付いているため、ICDコードごとに含まれる単語の単語ベクトルの平均を計算し、そのICDコードに対応する疾患の分散表現としました。 万病辞書から単語と紐付いたICDコードは7,661件、そのうち上記方法で分散表現を計算できた疾患は2,398件でした。 4. 疾患の類似度を求める 疾患ごとの分散表現のベクトルを用いて、疾患間の類似度を計算します。今回は近似最近傍探索のFaissを用いました。なお、サンプルコードではgensimのmost_similar()を使っていますが、距離以外は同じような結果になります。 コードと疾患ベクトル ここで作成した疾患ごとのベクトルおよび利用のためのサンプルコードは、下記レポジトリで公開しております。 yagays/icd10_embeddings 結果 それでは結果を見てみましょう。ここではクエリとなる疾患に対して類似度が高い疾患上位10件を近い順に表示しています。疾患ごとにICDコードとそれに紐づく疾患名を最大で3個、そして疾患間の距離を表示しています。 例) 頭痛 クエリ:R51 頭痛/頭重感/後頭部痛 rank icd code disease_name distance 1 R42 めまい/めまい感/めまい発作 0.29512033 2 M5422 頚部痛/項部痛/頚性頭痛 0.31296897 3 R101 心窩部痛/胃痛/上腹部痛 0.34621543 4 R104 腹部圧痛/側腹部痛/腹痛症 0.3733242 5 M5456 腰痛症/殿部痛/急性腰痛症 0. Sansanを退職してUbieに入社します https://yag-ays.github.io/article/ubie/ Sun, 24 May 2020 23:50:56 +0900 https://yag-ays.github.io/article/ubie/ 2018年1月より2年半勤めたSansan株式会社を退社して、2020年7月よりUbie株式会社に入社します。現在は現職の有給消化中で少し気が早いですが、退職エントリを書きたいと思います。前回の転職エントリは手短だったので、今回は自分の仕事内容と絡めつつ互いの会社のことやデータサイエンスのキャリアのことについて長めに書きます。ちなみに、私のプロフィールは https://yag.xyz/ をご覧ください。 これまでの仕事 Sansanは法人/個人向けのクラウド名刺管理を提供している会社で、名刺のデータ化に関わる開発やデータ活用を推進しているDSOC (Data Strategy & Operation Center)という部署に入りました。Sansanに中途入社する前に在籍していたリクルートテクノロジーズでは機械学習のプロダクト開発を担当しており、Sansanでも引き続き機械学習でサービスに貢献するような技術開発を行っていました。特に、自然言語処理に関する機械学習に携わらせていただきました。 名刺管理のSaaSという特殊なサービスの性質上、自然言語処理や機械学習のタスクにおいても他社ではあまり取り組まれない特殊なタスクが多かったと思います。下記のような仕事をしていました。 名刺とニュースを紐付ける - 深層学習を用いた記事文章からの企業名抽出 名刺を超えて人や企業を検索する - Sansanにおける検索システムへの取り組み 文字のゆらぎをどう扱うか? - Sansanにおける自然言語処理の活用 これらの中でも特に惹かれたのは「名寄せ」と「情報抽出」です。具体的なタスクとしては、様々な表記ゆれがある単語を正規化するものであったり、文章中から企業名や日付など特定の概念(エンティティ)を抽出するものだったりするのですが、少量のデータしかなかったり、一般常識や固有の知識が必要だったり、正解が厳密に定義できないようなタスクなど、ビジネスの現場で遭遇するものは特に難しいと感じます。 Sansanの中でこうした困難なタスクにどっしりと向き合えたのは、とても貴重な経験でした。一方で、ビジネス上高い精度が求められるがために機械学習の機能を導入できなかったりと、周囲の期待に答えられないもどかしさもありました。こうした経験を通じて、これらのテーマは私の中で人生をかけて向き合うタスクになったと思います。 また、アカデミックとの接点では、Wikipedia構造化プロジェクトである森羅に参加させてもらったり、NLPやIBISにポスターを出したり、DEIMやCCSEで講演する機会もいただきました。部署的にも対外的な発信を推奨されている文化であり、かつ社内チェックなどの体制もしっかりしているので、安心して発表しつつ資料をパブリックに公開することができました。 あと、気がつけば新卒/中途採用を任されたり、長期インターンのメンターを受け持ったりしました。2年半しかいなかったのですが、チームの中で中堅社員くらいの立ち位置&役割だったと思います。 Sansan DSOCの環境 R&Dチームとして SansanのR&Dには数多くの優秀なメンバーがいて刺激になりました。この規模の会社で、画像処理から機械学習、自然言語処理、そして社会科学に至る多様なメンバーが在籍している組織は、周りを見渡してもそうそう無いのではないかと思います。博士号を持つ人間も多く、社会人博士に通いながら仕事をしている人がいるのもレベルの高さを感じさせます。他にわかりやすい例としては、Kaggle Grandmasterの方だったり、東大の助教からSansanに入社した方もいらっしゃいます。 特に、同僚で一緒に自然言語処理のタスクに取り組んだ@kanji250trは、エンジニアリングも出来て研究もできるというフルスタック人材でした。自然言語処理タスクに関して議論したり、一緒にプロダクトを作ったり、NLPにポスターを出したりと、彼と一緒に仕事が出来て本当に良かったと思います。 このR&D組織が特にユニークだと思うのは、社会科学系の方々がめざましい活躍をしているところです。名刺交換の先にある「人と人との出会い」というデータに対して、科学的アプローチで構造を理解したり、サービスの価値向上のための分析を行ったりと、Sansanにしかないデータで研究し価値を出しているのは素晴らしいと思います。コンピューターサイエンス側からするとあまり想像がつかないと思いますので、私が面白いと感じた活動を貼っておきます。 Sansanの名刺ネットワーク分析からみえてきた「成功するビジネスの出会い」の要件 | BizHint(ビズヒント)- 事業の課題にヒントを届けるビジネスメディア 「SocSci Meetup~社会科学をブートする~」イベントレポート - Sansan Builders Box また、私が入社したときには中途採用の人間の割合が多かったのですが、ここ数年で優秀な新卒も続々入社してきており、年齢的にも若い組織になってきていると思います。 DSOC全体として 所属していた部署全体としても恵まれた環境でした。DSOCの中には、先に紹介したR&Dチームの他にも、開発やインフラ、運用などサービス全体に関わるチームが所属しています。そのため、データサイエンティストは分析だけするみたいな閉じた状態にならず、様々な方と接する中で仕事のバランスが取れたと思います。 機械学習エンジニアとして一番ありがたかったのは、社内でデータのアノテーションが完了するところでしょうか。Sansanは名刺をデータ化する上でクラウドソーシング先を多く抱えており、DSOCにはデータ化体制を運用するチームがいます。機械学習タスクの学習データを大規模に作れる体制が整っているということです。以前そのチームと仕事をしていたときは、学習データを用意する必要がありますねと言った瞬間にはそれ作ろうという話が進み、数日するとアノテーションガイドラインと対象データの準備が完了し、数週間する頃には学習データが完成していました。機械学習エンジニア側はデータセットの内容にだけ注力し、その他の発注作業や取りまとめ、クオリティコントロール等は専属チームに任せられたので、本当に仕事がやりやすかったです。 まだまだ紹介したいのですが最後に一つだけ。私がいた部署にはクリエイティブグループという部署全体のブランディングやデザインを担当するチームがあり、私が作ったポンチ絵とゴミスライドを投げると、統一感あるハイクオリティな資料に仕上げてくれました。これは思った以上に感動的な体験だったので、いろんな会社で普及するといいんじゃないかと真面目に思います。 福利厚生 あとはこんな福利厚生が良かったです。 フルスペックMacBook Pro & 昇降机 & 曲面ディスプレイ PCはWindows/macOS、ディスプレイは複数/42inch/曲面など選択できます 本や学習に年6万円、ハードウェアに年3万円、ソフトウェアに年2万円の支給 書籍は躊躇なく買えましたし、AirPods Proやリモートワーク用の良いマイクを買いました 土日出勤して休みを平日に振り替える制度 振替休日と土日と繋げて、国内/海外旅行に行けました リファラル採用に関する諸々の支援 紹介する側もされる側もインセンティブがあります 転職活動 このような業務や環境でわりかし自由に仕事させてもらっていたのですが、自身の成長を客観視したり、界隈の機械学習人材の凄さみたいなものを知れば知るほど、今の自分はコンフォートゾーンに入ってしまいっているのではという不安が常にありました。つい2,3年前に最先端だった技術が今では新しいものに置き換わってしまうこの分野においては、安定すればするほど不安になるというのは仕方がないことかもしれません。 Flairを使ってSWEMによる文章埋め込みを計算する https://yag-ays.github.io/project/swem_flair/ Mon, 30 Dec 2019 14:27:32 +0900 https://yag-ays.github.io/project/swem_flair/ 概要 Flairは、Pytorchで書かれた自然言語処理用のフレームワークです。固有表現抽出や品詞タグ付け、文章分類などの機能を提供しているほか、文章埋め込み (Sentence Embedding) も簡単に計算することができます。以前に本ブログで紹介したSWEMも扱うことができたので、ここで使い方を紹介したいと思います。 記事:SWEM: 単語埋め込みのみを使うシンプルな文章埋め込み - Out-of-the-box 方法 単語ベクトルの読み込み まずFlairで学習済みの単語埋め込みベクトルを読み込みます。あらかじめ学習済み単語ベクトルのファイルを用意しておく必要はなく、以下のコードを初めて動かす際に自動でウェブからダウンロードされます。日本語の場合は、fastTextが提供しているja-wiki-fasttext-300d-1Mが選択されます。 from flair.embeddings import WordEmbeddings, DocumentPoolEmbeddings, Sentence from flair.data import build_japanese_tokenizer ja_embedding = WordEmbeddings("ja") ここでダウンロードしたファイルは$HOME/.flair/embeddings/に保存されます。 文章埋め込みの選択 次に、文章埋め込みの手法を選択します。SWEMは各単語ベクトルに対して各種Poolingの操作を行うことで文章埋め込みを計算するため、DocumentPoolEmbeddings()を利用します。この引数には、さきほど読み込んだWordEmbeddingsのインスタンスを選択します。 document_embeddings = DocumentPoolEmbeddings([ja_embedding]) 文章埋め込みを計算する 最後に文章埋め込みを計算します。まず対象となる文章のSentenceオブジェクトを作成し、それを上記で作成したdocument_embeddings.embed()で埋め込みます。 sentence = Sentence("吾輩は猫である。名前はまだ無い。", use_tokenizer=build_japanese_tokenizer("MeCab")) document_embeddings.embed(sentence) そして、文章埋め込みのベクトルはsentnece.get_embedding()から取得することができます。 In []: sentence.get_embedding() Out[]: tensor([ 2.1660e+00, -1.9450e+00, -1.9782e+00, -1.0372e+01, -7.4274e-01, -1.6262e+00, 2.3832e+00, 1.3668e+00, 4.2834e+00, -3.4007e+00, [...] 3.6956e+00, -4.1554e+00, 4.7224e+00, 4.1686e+00, -4.3685e+00], grad_fn=<CatBackward>) カスタマイズ 指定した学習済みの単語ベクトルを使う Flairがデフォルトで指定している学習済みの単語ベクトルではなく、ローカルにあるファイルを指定することもできます。ファイル形式はgensimのバイナリフォーマットで用意する必要があります。 own_embedding = WordEmbeddings("path/to/own_vector.bin") flair/CLASSIC_WORD_EMBEDDINGS.md at master · flairNLP/flair 文書分類においてデータ内に現れる特定のパターンを見つける https://yag-ays.github.io/project/leakage_pmi/ Wed, 11 Dec 2019 09:20:57 +0900 https://yag-ays.github.io/project/leakage_pmi/ この記事はSansan Advent Calendar 2019 - Adventarの11日目の記事です。 概要 自然言語処理における教師有り学習では、対象となる文書から何らかの意味やパターンを見つけ、正解ラベルとの対応関係をモデルが学習することで、未知の文書に対するラベルの予測を行います。このとき、学習データの文書内に何らかの不適切な情報が混入することにより、意図せずモデルの精度が向上することがあります。これをリーク (Leakage)と呼び、自然言語処理のみならず機械学習が陥りやすい問題として広く認識されています。 2つの文章の意味の関係性(意味が同じか違うか)を推論するタスクで用いられているThe Stanford Natural Language Inference (SNLI)コーパスにおいては、特定のラベルにおいてある単語が多く現れやすいということが報告されています。例えば「nobody」や「no」などの否定形が入る文章は、3値分類のなかの「Contradiction(矛盾)」というラベルに多く含まれています。否定形があればとりあえずこのラベルに割り振れば正答率が高まるといったように、機械学習モデルが本来学習してほしい意味的な部分以外で答えを出してしまいかねないという問題があります。SNLIデータセットは人手で作成されていることからこのようなバイアスが生じており、こういった隠れた傾向を表す情報は Annotation Artifacts と呼ばれています。 https://www.aclweb.org/anthology/N18-2017/ 今回は、自然言語処理においてリークとなるような、特定のラベルと強く関係している単語を見つける方法を紹介します。また、こういった人間が知る情報を使って前処理や評価を行う意義についても議論してみたいと思います。 自己相互情報量PMIによる特定パターンの抽出 今回は自己相互情報量(以降PMI)を使います。PMIは単語の共起を表す指標として自然言語処理で広く使われている方法です。このPMIを特定のラベルの特定の単語に対して計算することにより、そのラベルと強く関連している単語を抽出します。計算式としては以下のようになります。 Img: Annotation Artifacts in Natural Language Inference Data (NAACL 2018) より 分子にはあるラベルの文書内に存在するある単語の頻度、分母には全ラベルの文書内に存在する単語の頻度と、ラベルの文書数があります。式の気持ちとしては、特定のラベルに偏った単語はp(word, class)とp(word, .)がほぼ同じようになるためPMIが大きくなり、全ラベルにまたがって存在する単語はp(word, class)よりもp(word,.)の方が大きくなるため、PMIが小さくなるようになっています。 このPMIは任意の単語に対して各ラベルぞれぞれに計算されるため、そのラベル間のPMIの値の差が大きいラベルとその単語に注目することで、リークと判断されうる単語や特性のパターンを確認することができます。 実験 「Livedoorニュースコーパス」を対象に、特定ラベルに現れるパターンを検知してみます。このコーパスは、「livedoor ニュース」が配信していたメディアのうち、9つのカテゴリのニュース記事本文が含まれています。 結果 Livedoorニュースコーパスのそれぞれのラベルに対して全単語のPMIを計算し、各ラベルのPMI平均との差が大きいそれぞれ上位3件の単語を以下に示します。 label \ rank 1 2 3 dokujo-tsushin オフィスエムツー はるひ ゆるっ it-life-hack 虎の巻 イケショップ 紺子 kaden-channel ビデオSALON MIYUKI KOMATSU livedoor-homme 求人情報 type @type movie-enter ベルセルク ジョン・カーター 映画批評 peachy 兼美 韓国コスメ Write smax ICS 開発コード名 IceCream sports-watch 週刊アサヒ芸能 すぽると! サッカー解説者 topic-news 韓国ニュース ビッグダディ デヴィ夫人 具体事例 それでは、実際に上記の単語がどのように文書中に表れるかを見てみましょう。まずはdokujo-tsushinのPMI値が高い単語を文中から抽出してみます。 Elasticsearchで分散表現を使った類似文書検索 https://yag-ays.github.io/project/elasticsearch-similarity-search/ Mon, 02 Sep 2019 15:25:26 +0900 https://yag-ays.github.io/project/elasticsearch-similarity-search/ 概要 Elasticseachに分散表現のベクトルに対する類似文書検索が実装されたということで、以下のElasticのブログ記事を参考に類似文書検索を試してみました。 Text similarity search in Elasticsearch using vector fields | Elastic Blog 類似文書検索とは、与えられたクエリの文書と似ている文書を文書集合内から検索する技術です。この際に必要となるのが「似ている」という概念で、計算機上でどうやって2つの文書間の類似度を数値として表現するかがポイントになります。例えば、互いの文書に出現する単語の一致度や重複度合いを測ったり、TF-IDFやBM25などで文書をベクトル化して比較する方法があります。ただしこれらの方法では、言い換え表現や表記の違いにより同じ意味の単語が異なる単語だと判定されたり、文書の中では重要でない単語に強く影響されるなどの問題点がありました。 そこでこの記事では、意味的な空間上に単語や文書を埋め込む分散表現という手法を用いて、任意の文書を数百次元のベクトルに変換します。そして類似度の計算では、2つの文書それぞれの分散表現のベクトルのコサイン類似度を計算することによって、文書間の意味的な近さ/遠さを表現します。検索の際には、与えられたクエリ文書をベクトル化したあと、文書集合内のすべての文書のベクトルと類似度を計算し降順に並べ替えることで、類似文章が得られるというわけです。 それではElasticsearchの環境構築から類似文書検索までを順に解説していきます。本記事では重要なコードの部分のみ提示していますが、全体の実験コードはこちらのレポジトリにありますので必要に応じて参照ください。 tl;dr Elasticsearchでベクトルの類似文書検索機能が実装された Wikipedia日本語記事全116万エントリーに対して検索時間は約0.8秒 Elasticsearchの既存の検索機能と組み合わせることが可能 方法 1. Elasticsearchの設定 Dockerを使ってElasticsearchを立ち上げます。Elasticsearchでの高次元ベクトルのフィールドタイプ対応はバージョン7.0、検索機能はバージョン7.3からサポートされているため、docker.elastic.coで提供されているバージョン7.3.1を利用しました。以下のようにdocker-compose.yamlを定義してdocker-compose upで起動します。 # docker-compose.yaml version: '3.3' services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.3.1 ports: - "9200:9200" volumes: - es-data:/usr/share/elasticsearch/data tty: true environment: discovery.type: single-node volumes: es-data: driver: local 立ち上がった後はcurl http://127.0.0.1:9200/_cat/healthで結果が帰ってくればOKです。 2. Wikipediaの本文を文ベクトルに変換してElasticsearchに投入する 今回はある程度現実的な状況を再現するため、データセットはWikipediaの日本語のすべてのエントリーを使用しました。Wikipediaのエントリーに含まれるテキストをダンプデータから抽出し、分散表現を計算するとともにElasticsearchにインポートします。文書の分散表現の計算には、SWEMのaverage-poolingを利用します。手法の詳細はこちらの記事を参考ください。 まず index.jsonでインデックスのマッピングタイプを指定します。文書ベクトルには"type": "dense_vector"、次元数を"dims": 200で指定します。 # index.json "properties": { "title": { "type": "text" }, "text": { "type": "text" }, "text_vector": { "type": "dense_vector", "dims": 200 } } あとはElasticsearchに文書およびその文書の分散表現を登録していくだけです。注意点として、PythonのElasticsearchパッケージを利用する場合は、dense_vectorにはnumpyのndarrayではなくPythonのリスト型に変換したものを渡す必要があります。 絵文字の日本語読み辞書をUnicode 12.0対応に更新しました https://yag-ays.github.io/project/emoji-ja-update-12/ Fri, 26 Jul 2019 11:45:37 +0900 https://yag-ays.github.io/project/emoji-ja-update-12/ 以前に公開した「Unicode絵文字の日本語読み/キーワード/分類辞書」ですが、Unicode 12.0が公開され絵文字も追加されたので、辞書を更新しました。 前回の記事:📙Unicode絵文字の日本語読み/キーワード/分類辞書📙 - Out-of-the-box 🔖 リリース Githubレポジトリの20190726リリースからダウンロードするか、現在masterブランチに含まれている各種ファイルを利用ください。 Release 20190726 · yagays/emoji-ja 前回からの変更点は以下の通りです。 - [update] Unicode 12.0の新しい絵文字を追加 - [update] Unicode 12.0で変更されたグループ名/サブグループ名の翻訳を更新 - [fix] サブグループ名において、スペース区切りをハイフンに変更 (e.g.動物 鳥類→動物-鳥類) 絵文字の追加がメインですが、サブグループ名でこれまでスペース区切りで表していたものをハイフン区切りに変更しておりますので、以前のバージョンを利用していた方はご注意下さい🙏 👀 追加された絵文字 せっかくなので、追加された絵文字を少し見てみましょう。 カワウソ 各種メディアでも取り上げられていたカワウソ(otter)ですが、日本語のキーワードを見るとラッコ(Sea otter)も付与されているようです(参考)。Emojipediaの🦦 Otter Emojiを見てみると、カワウソかラッコかイマイチ区別が付かないですが、Microsoftのotterは貝を持っているのでラッコを意識してそうな雰囲気があります。 "🦦": { "keywords": [ "カワウソ", "ラッコ", "動物", "遊び好き", "魚を食べる" ], "short_name": "カワウソ", "group": "動物と自然", "subgroup": "動物-哺乳類" } 玉ねぎとにんにく 玉ねぎとにんにくも今回から追加されました。どちらも似たような外見ですが、Emojipediaの🧅 Onion Emojiと🧄 Garlic Emojiを見比べてみると、丸みや色で区別しているようですね。Googleは唯一玉ねぎの絵文字に輪切りの状態のものを載せていて、頑張って区別しました感があります。 "🧄": { "keywords": [ "におい", "ニンニク", "薬味", "野菜", "香り" ], "short_name": "ニンニク", "group": "飲み物と食べ物", "subgroup": "食べ物-野菜" }, "🧅": { "keywords": [ "タマネギ", "ねぎ", "玉ねぎ", "薬味", "野菜" ], "short_name": "タマネギ", "group": "飲み物と食べ物", "subgroup": "食べ物-野菜" } 📝 参考 Emoji Recently Added, v12. pytorchでBERTの日本語学習済みモデルを利用する - 文章埋め込み編 https://yag-ays.github.io/project/pytorch_bert_japanese/ Wed, 05 Jun 2019 20:11:43 +0900 https://yag-ays.github.io/project/pytorch_bert_japanese/ 概要 BERT (Bidirectional Encoder Representations from Transformers) は、NAACL2019で論文が発表される前から大きな注目を浴びていた強力な言語モデルです。これまで提案されてきたELMoやOpenAI-GPTと比較して、双方向コンテキストを同時に学習するモデルを提案し、大規模コーパスを用いた事前学習とタスク固有のfine-tuningを組み合わせることで、各種タスクでSOTAを達成しました。 そのように事前学習によって強力な言語モデルを獲得しているBERTですが、今回は日本語の学習済みBERTモデルを利用して、文章埋め込み (Sentence Embedding) を計算してみようと思います。 環境 今回は京都大学の黒橋・河原研究室が公開している「BERT日本語Pretrainedモデル」を利用します。 BERT日本語Pretrainedモデル - KUROHASHI-KAWAHARA LAB BERTの実装は、pytorchで書かれたpytorch-pretrained-BERTがベースになります。また形態素解析器は、学習済みモデルに合わせるためJUMAN++を利用します。 方法 今回はBertWithJumanModelという、トークナイズとBERTによる推論を行うクラスを自作しています。ソースコード自体は下記レポジトリにあり、また各ステップでの計算方法を本記事の後半で解説しています。 yagays/pytorch_bert_japanese In []: from bert_juman import BertWithJumanModel In []: bert = BertWithJumanModel("/path/to/Japanese_L-12_H-768_A-12_E-30_BPE") In []: bert.get_sentence_embedding("吾輩は猫である。") Out[]: array([ 2.22642735e-01, -2.40221739e-01, 1.09303640e-02, -1.02307117e+00, 1.78834641e+00, -2.73566216e-01, -1.57942638e-01, -7.98571169e-01, -2.77438164e-02, -8.05811465e-01, 3.46736580e-01, -7.20409870e-01, 1.03382647e-01, -5.33944130e-01, -3.25344890e-01, -1.02880754e-01, 2.26500735e-01, -8.97880018e-01, 2.52314955e-01, -7.09809303e-01, [...] これでBERTによる文章埋め込みのベクトルが得られました。あとは、後続のタスクに利用したり、文章ベクトルとして類似度計算などに利用できます。 また、BERTの隠れ層の位置や、プーリングの計算方法も選択できるようにしています。このあたりの設計はhanxiao/bert-as-service を参考にしています。 In []: bert.get_sentence_embedding("吾輩は猫である。", . SWEM: 単語埋め込みのみを使うシンプルな文章埋め込み https://yag-ays.github.io/project/swem/ Wed, 29 May 2019 09:59:24 +0900 https://yag-ays.github.io/project/swem/ 単語埋め込み (Word Embedding) のみを利用して文章埋め込み (Sentence Embedding) を計算するSWEM (Simple Word-Embedding-based Methods) を実装しました。 概要 文章に対する固定次元の分散表現を得る手法としては、doc2vecやSkip-thoughts、テキスト間の含意関係を学習することで分散表現を得るinfersent、最近では強力な言語モデルとなったBERTといった方法があります。これらの手法は、単語ベクトルに加えて文章ベクトルを得るためのニューラルネットワーク自体を、大規模コーパスから学習させる必要があります。 そこで、より単純ながらも後続タスクへの精度がでる文章埋め込みの計算方法として、追加学習やパラメータチューニングを必要とせず単語埋め込みだけを利用するSWEMが提案されました。これはACL2018 “Baseline Needs More Love: On Simple Word-Embedding-Based Models and Associated Pooling Mechanisms”にて発表された方法で、複数のデータセットにおける評価において、既存のCNN/RNNモデルと同等またはそれ以上の精度となっています。ロジックは単純ながらもある程度良い性能を示すことから、強力なベースラインとして利用することができると考えられます。 方法 SWEMでは以下の4つの方法が提案されています。 SWEM-aver:単語の分散表現に対してaverage poolingする SWEM-max:単語の分散表現に対してmax poolingする SWEM-concat:SWEM-averとSWEM-maxの結果を結合する SWEM-hier:n-gramのように固定長のウィンドウでaverage-poolingした結果に対してmax poolingする これらは基本的に、文章に含まれる単語の分散表現全体に対して、どういう操作で固定時点のベクトルに集約するかといった操作の違いでしかありません。それぞれのaverage poolingやmax poolingは、element-wiseにaverageやmaxを取ります。Out-of-Vocabulary (OOV) な単語に対しては、[-0.01, 0.01]の範囲の一様乱数を用いて初期化します。なお、aver, max, concatに関してはパラメータはありませんが、SWEM-hierはn-gramのウィンドウの幅nを決める必要があります。 ちなみに、結局のところどれが一番いいの?という話ですが、論文中の評価ではタスク/データ依存という結果になっており、一概にどれが良いかは断定できないようです。 コード https://github.com/yagays/swem from gensim.models import KeyedVectors from swem import MeCabTokenizer from swem import SWEM w2v_path = "/path/to/word_embedding.bin" w2v = KeyedVectors.load_word2vec_format(w2v_path, binary=True) tokenizer = MeCabTokenizer("-O wakati") swem = SWEM(w2v, tokenizer) In []: text = "吾輩は猫である。名前はまだ無い。" # SWEM-aver In []: swem. 深層学習時代の言語判定の最新動向 https://yag-ays.github.io/project/language_identification_in_dl_era/ Sun, 05 May 2019 12:10:06 +0900 https://yag-ays.github.io/project/language_identification_in_dl_era/ 概要 言語判定(Language identification)とは、与えられた文字列が何語で書かれているかを判定するタスクです。例えば「こんにちは」なら日本語、「Hello World.」なら英語といったように、世界各国で話されている言語のうち何に属するかを推定するというものです。 これだけ聞くと非常に簡単な問題のように思えますよね。出てくる単語を辞書で探せば何語か分かりそうなものですし、書かれている文字を見ても容易に判別できそうな気がします。Google翻訳のような機械翻訳が高精度に文章を翻訳できる現在において、言語を判定するなんて行為はより基本的なことで、できて当たり前とも思えます。実際に2010年時点でサイボウズ・ラボの中谷さんが作成された言語判定エンジンlanguage-detectionは、49言語で99.77%の正解率で判定することができています(source)。他の言語処理タスクでは考えられないくらい高い正解率ですし、ここからの向上余地なんてほぼ無いんじゃないかと考えてしまいます。 しかしながら、言語判定は今でも様々な論文が発表される分野です。極端な例を出すならば、Googleは自然言語処理において1st tierな学会であるEMNLPで2018年に言語判定の論文を出しています。このように現在でも研究され論文が通る分野であり、大学のみならず企業からも論文が発表される領域です。では、どこに研究の課題が残されているのでしょうか?また近年大きく発展した深層学習は、言語判定にどのように影響しているのでしょうか? ここでは、近年発表された3種類の言語判定の論文をもとに、深層学習時代の言語判定について見ていきたいと思います。 複数の言語が混ざった文章の言語判定 A Fast, Compact, Accurate Model for Language Identification of Codemixed Text まずは冒頭でも紹介したEMNLP 2018のGoogleの論文です。論文タイトルにあるように複数言語が混ざった文章を対象にした言語判定は、既存の言語判定モデルでは無視されてきた領域でした。特にユーザが投稿するようなサービスにおいては同じメッセージを複数言語で記載することが多く、そのような言語判定に対応するため、より粒度の細かい単位で言語判定することが必要となります。 例えば、以下のようなTweetはその代表例でしょう。 新しい御代の幕開けに心からお祝い申し上げます。『令和』が日本の国に平和と繁栄をもたらす祝福された時代となるよう祈念致しております。🇯🇵 Congratulations on the beginning of the new era! We hope 令和 will be blessed with peace and prosperity for everyone in Japan. — Tim Cook (@tim_cook) April 30, 2019 この論文で提案している手法CMXは、まずトークン(単語)単位で言語判定をしたのちに、貪欲法を用いて文章全体での言語判定を行うというものです。前半の言語判定には文字特徴量とトークンの特徴量の両方を用いたシンプルなニューラルネットワークを用いています。 (Ref. https://arxiv.org/abs/1810.04142) 文字特徴量は従来の言語判定と同じように文字n-gramを用いており、n=[1,4]を計算したのちにfeature hashingで語彙数をコントロールしています。これは、n-gramのnを増やすごとに生成される文字列のパターン数が指数的に増加することから、計算コストやモデルサイズを削減するためです。その他にも、ひらがなやハングルのように特定文字に対応する言語の特徴量や、辞書ベースの特徴量などを加えたモデルになっています。 単語や文字単位での言語判定 LanideNN: Multilingual Language Identification on Character Window 単一文章に対して複数言語を判定できるようになれば、次に知りたくなるのは文章中での言語の出現位置や、どこで言語が変わったかといった文章内の情報です。さきほどのGoogleの論文ではトークン単位で推定しつつ全体の文章の言語判定をしていましたが、EACL 2017で発表されたLanideNNでは入力文を文字単位で言語判定するモデルを提案しています。 fasttextを用いた言語判定 https://yag-ays.github.io/project/fasttext_language_identification/ Sun, 21 Apr 2019 03:45:17 +0900 https://yag-ays.github.io/project/fasttext_language_identification/ Facebookが提供するfasttextの公式サイトにて、fasttextを用いた言語判定モデルが公開されていたので、実際に利用してみました。 概要 fasttextはFacebookが公開している単語埋め込みの学習方法およびそのフレームワークです。word2vecとは違い、サブワードを利用した手法が特徴となっています。 こちらの公式ブログの記事によると、fasttextによる言語判定は軽量でかつ高速に言語予測することができると述べられています。言語判定において広く使われるlangid.pyとの評価実験では、高い精度でかつ計算時間が1/10程度であることが示されています。またモデルファイルはオリジナルのサイズでは126MB、圧縮されたモデルは917kB (0.9MB)と、既存の単語埋め込みの学習済みモデルと比較してもかなり軽量になっています。 なお「言語判定」(Language Identification)とは、与えられた文章がどの自然言語により書かれているかを判定するタスクを指します。例えば本記事に対して「日本語」(ja)であることを自動で判定するのが、言語判定です。 使い方 まずは公開されているモデルを実際に動かしてみましょう。 1. モデルのダウンロード fasttextのモデルをダウンロードします。下記ページにてlid.176.binまたはlid.176.ftzをダウンロードします。 Language identification · fastText 2. fasttextのpythonバインディングをインストール Pythonからfasttextを利用するためのPythonバインディングをインストールします。いくつかパッケージは存在しますが、pyfasttextはすでにメンテナンスされていないため、ここではFacebook公式のfastTextをインストールします。 fastText/python at master · facebookresearch/fastText $ git clone https://github.com/facebookresearch/fastText.git $ cd fastText $ pip install . 3. モデルを読み込んで利用 さて、ようやく言語判定のモデルをロードして利用してみます。 In []: from fastText import load_model In []: model = load_model("lid.176.bin") # or lid.176.ftz In []: model.predict('こんにちは') Out[]: (('__label__ja',), array([1.00002694])) In []: model.predict('你好') Out[]: (('__label__zh',), array([0.98323345])) In []: model. MeCabの形態素解析の結果から正規表現を使って品詞列を抜き出すmecabpr https://yag-ays.github.io/project/mecab_pos_regex/ Mon, 15 Apr 2019 21:44:12 +0900 https://yag-ays.github.io/project/mecab_pos_regex/ MeCabの形態素解析の結果から、正規表現を使って品詞列を抜き出すためのパッケージmecabpr(mecab-pos-regexp)を作成しました。 概要 キーフレーズ抽出などのタスクにおいて、MeCabの形態素解析した文字列の中から「形容詞に続く名詞」や「任意の長さを持つ名詞の系列」といった特定のパターンを持つ品詞列を取り出したいことがあります。そのようなパターンを正規表現の記法を用いて表現し、一致する品詞列を抜き出すためのパッケージを作成しました。 ソースコード https://github.com/yagays/mecabpr 使い方 インストール mecabprはpipを使ってインストールできます。 $ pip install mecabpr 準備 mecabprを読み込みます。 import mecabpr mpr = mecabpr.MeCabPosRegex() sentence = "あらゆる現実をすべて自分のほうへねじ曲げたのだ。" MeCabPosRegex()にはMeCabに渡すオプションを指定できます MeCabPosRegex("-d /path/to/mecab-ipadic-neologd")でNEologdの辞書を使うことができます mpr.findall()の引数には、対象とする文字列と、正規表現で表した品詞列を指定します 品詞には任意の階層を指定することができ、階層間を-で区切って入力します (e.g. 名詞-固有名詞-人名-一般) あとは、以下のように品詞のパターンを指定すると、目的の品詞列を取得できます。 例)「名詞」を抽出する In []: mpr.findall(sentence, "名詞") Out[]: [['現実'], ['すべて'], ['自分'], ['ほう'], ['の']] 例)「名詞に続く助詞」を抽出する In []: mpr.findall(sentence, "名詞助詞") Out[]: [['現実', 'を'], ['自分', 'の'], ['ほう', 'へ']] ちなみに、"名詞助詞"のように一続きに表現しても問題ないですが、可読性のために"(名詞)(助詞)"のように品詞を括弧で括っても同様の結果が得られます。 例)「名詞に続く助詞が2回続くパターン」を抽出する 通常の正規表現と同様の記法を使うことができます。ここでは{2}を指定することで、2回の繰り返しを表現しています。 In [42]: mpr. 単語埋め込みにおけるout-of-vocabularyの対応 - magnitudeの初期化 https://yag-ays.github.io/project/out-of-vocab-magnitude/ Wed, 27 Feb 2019 22:36:40 +0900 https://yag-ays.github.io/project/out-of-vocab-magnitude/ 概要 magnitudeという単語埋め込みを扱うライブラリには、単語を構成する文字列を考慮したout-of-vocabularyの初期化の方法が実装されています。EMNLP 2018の論文と実際のコードを元に、その初期化の方法を実装して試してみました。 背景 KaggleのQuora Insincere Questionsコンペを終えて KaggleのQuora Insecure QuestionsのコンペではOOVの対応が重要だったっぽいけど、magnitudeはランダムベクトルの付与とかミススペルの対応とかしてくれるみたいだ。ロジック確認しないと何してるのかわからないけど…… https://t.co/d8tteqwwCp — やぐ (@yag_ays) February 26, 2019 KaggleのNLPコンペであるQuora Insincere Questions Classificationが終わって上位陣の解法を眺めていたのですが、その中で目に止まったのがout-of-vocabulary(以降OOVと表記)の対応です。今回のコンペでは主催側が定めた幾つかの学習済み単語埋め込みしか使うことができないので、大規模コーパスから新しく学習することができません。そのため、データセットには含まれているが学習済み単語ベクトルには含まれていない単語であるout-of-vocabularyをどう扱うかが、前処理の重要な要素だったようです。それぞれの解法には以下のようなコメントが記載されています。 “The most important thing now is to find as many embeddings as possible for our vocabulary. (中略) For public test data we had around 50k of vocab tokens we did not find in the embeddings afterwards. " (1st place solution) “I applied spell correction to OOV words. 後処理のみで単語ベクトルの性能を向上させるALL-BUT-THE-TOPを使った日本語学習済み分散表現 https://yag-ays.github.io/project/all-but-the-top/ Sat, 23 Feb 2019 00:49:58 +0900 https://yag-ays.github.io/project/all-but-the-top/ 概要 ICLR2018で発表されたAll-but-the-Top: Simple and Effective Postprocessing for Word Representationsの後処理を実装し、日本語学習済み分散表現に対して適用し評価を行いました。また、作成した単語ベクトルを配布します。 All-but-the-Top All-but-the-Topは、学習済みの分散表現に対して特定の後処理を加えることにより、分散表現の評価に用いられるタスクにおいて性能向上を達成した手法です。単語ベクトル内に存在する偏りを無くすために、平均で標準化し、主成分分析で幾つかの方向の主成分を引くという処理をするというのものです。たったこれだけという感じですが、SIF Embeddingの研究と同様に理論的な裏付けがあるようです。こういった背景や英語での実験結果は論文を参考ください。日本語での解説はこちらの論文紹介スライドが参考になります。 単語ベクトルのダウンロード 以下の2つの学習済み分散表現に対してAll-but-the-Topの後処理を適用したファイルです。配布するモデルは、元のファイル名に加えてabttという名前が付与されています。 ファイル 次元数/語彙数 ファイルサイズ jawiki.word_vectors.100d.abtt.bin 100 / 727,471 285M jawiki.word_vectors.200d.abtt.bin 200 / 727,471 563M jawiki.word_vectors.300d.abtt.bin 300 / 727,471 840M cc.ja.300d.abtt.bin 300 / 2,000,000 2.3G jawiki.word_vectors: 日本語 Wikipedia エンティティベクトル 20181001版のjawiki.word_vectorsを使用 鈴木正敏, 松田耕史, 関根聡, 岡崎直観, 乾健太郎. Wikipedia 記事に対する拡張固有表現ラベルの多重付与. 言語処理学会第22回年次大会(NLP2016), March 2016. 語彙を限定して単語ベクトルのモデルサイズを小さくするminify_w2v https://yag-ays.github.io/project/minify-w2v/ Tue, 19 Feb 2019 10:03:37 +0900 https://yag-ays.github.io/project/minify-w2v/ 概要 最近では単語埋め込みのベクトルをニューラルネットの埋め込み層に利用する手法が多く使われていますが、学習済みの単語埋め込みは多くの語彙を含んでおり、ファイルサイズが大きくロード時間もかかります。再現性のために学習に使う外部データを固定したり、APIのためにDockerコンテナに同梱する際には、こういったリソースはなるべくサイズを減らしたいという場合があります。そこで、必要な語彙に限定することで学習済み単語埋め込みのモデルサイズを小さくするコードを書きました。 yagays/minify_w2v: Minify word2vec model file 使い方 動作は至ってシンプルで、読み込んだ学習済み単語ベクトルの中から指定された単語のベクトルのみを抜き出して出力するだけです。 学習済みモデルを読み込む 必要な語彙を指定する 出力する load_word2vecとsave_word2vecにはそれぞれbinaryオプションがあり、入力および出力をバイナリフォーマットかテキストフォーマットか選択できます。 mw2v = MinifyW2V() mw2v.load_word2vec("/path/to/model.txt") mw2v.set_target_vocab(target_vocab=["cat", "dog"]) mw2v.save_word2vec("/path/to/output.bin", binary=True) ベンチマーク 日本語 Wikipedia エンティティベクトルの単語ベクトルを元に、そこから語彙を10,000に減らしたときのファイルサイズや読み込み時間を比較した結果です。テキストファイルの場合はファイルフォーマットの構造上、語彙を減らせば線形でファイルサイズも小さくなりますし読み込み時間も小さくなります。 Name Vocab. size File size Load time jawiki.word_vectors.200d.txt 727,471 1.6G 1min 27s jawiki.word_vectors.200d.bin 727,471 563M 5.9 s jawiki.word_vectors.200d.10000.txt 10,000 22M 1.18 s jawiki.word_vectors.200d.10000.bin 10,000 7.7M 190 ms plasticityai/magnitudeとの関連 単語ベクトルを効率よく扱う方法として、magnitudeという軽量で遅延読み込み等をサポートしたパッケージがあります。Magnitude Formatsという形式で保存することで読み込み速度を大幅に短縮することができるほか、単語埋め込みで利用される類似度検索や、KerasやPytorchなど他のパッケージとのインターフェイスも提供されています。 日本語Wikipediaで学習したdoc2vecモデル https://yag-ays.github.io/project/pretrained_doc2vec_wikipedia/ Tue, 22 Jan 2019 15:55:40 +0900 https://yag-ays.github.io/project/pretrained_doc2vec_wikipedia/ 日本語Wikipediaを対象にdoc2vec学習させたモデルを作成したので、学習済みモデルとして公開します。 概要 doc2vecは2014年にQuoc LeとTomas Mikolovによって発表された文章の埋め込みの手法です。今更doc2vecかという感じではありますが、日本語のdoc2vecの学習済みモデルは探した限り容易に利用できるものがなかったこともあり、せっかくなので作成したモデルを配布します。 word2vecのような単語の分散表現においては学習済みモデルとして配布されたものを利用することが多いですが、文章の埋め込みに関しては対象とするドキュメント集合やそのドメインに特化した学習モデルを作成することが多い印象です。なので、学習済みモデルファイルの配布自体にそれほど意味があるわけではなさそうですが、既存手法との比較に利用したり、とりあえず何かしらの手法で単語列から文章ベクトルにしたいといった場合には便利かと思います。まあ何も無いよりかはマシかなという雰囲気です。今回の作成の経緯として、別の手法を実装する際にdoc2vecが内部で使われていたということで副次的に必要になったからだったのですが、ふと利用したいときに気軽に利用できるというのは結構良いのではないかと思います。 モデル ここでは2つの学習アルゴリズムでdoc2vecを学習しました。dbow300dはdistributed bag of words (PV-DBOW)を、dmpv300dはdistributed memory (PV-DM)を用いています。なお、モデルファイルはサイズが大きいため、Googleドライブに配置してあります。下記リンク先からダウンロードしてください。 dbow300d https://www.dropbox.com/s/j75s0eq4eeuyt5n/jawiki.doc2vec.dbow300d.tar.bz2?dl=0 圧縮ファイルのサイズ:5.48GB dmpv300d https://www.dropbox.com/s/njez3f1pjv9i9xj/jawiki.doc2vec.dmpv300d.tar.bz2?dl=0 圧縮ファイルのサイズ:8.86GB モデルの学習パラメータ param dbow300d dmpv300d dm 0 1 vector_size 300 300 window 15 10 alpha 0.025 0.05 min_count 5 2 sample 1e-5 0 epochs 20 20 dbow_words 1 0 dbow300dのパラメータは、An Empirical Evaluation of doc2vec with Practical Insights into Document Embedding GenerationにおけるEnglish Wikipeiaの学習時のパラメータを利用しました。dmpv300dのパラメータは、Gensim Doc2Vec Tutorial on the IMDB Sentiment Datasetの設定を参考にしました。 Wikipediaの記事ごとのページビューを取得する https://yag-ays.github.io/project/wikipedia-pageview/ Sun, 16 Dec 2018 12:42:50 +0900 https://yag-ays.github.io/project/wikipedia-pageview/ 自然言語処理においてWikipediaのテキストコーパスは広く利用されており、各記事のページビュー(閲覧数)の情報もトレンド分析やエンティティリンキング等で用いられています。この記事では、Wikipediaの記事ごとのページビューを取得する方法を紹介します。 tl;dr ウェブから簡単に調べるなら → Pageviews Analysis 少数の記事についてプログラムから利用したいなら → Pageview API 大量の記事についてプログラムから利用したいなら → Wikimedia Analytics Datasets 1. Pageviews Analysisを利用する 手軽にページビューを確認するには「Pageviews Analysis」というウェブサイトがもっとも容易です。 Pageviews Analysisではグラフによる時系列の可視化、複数記事の比較、編集履歴の回数、csvやjsonによるダウンロードなど、多様な機能を備えています。また現在どのようなページが多く閲覧されているかといった言語ごとの閲覧数ランキングなどの機能もあり、とりあえず何か調べるなら大抵のことはPageviews Analysisで完結すると思います。 ちなみに日本語の記事を検索する場合は「プロジェクト」をja.wikipedia.orgに指定するのを忘れずに。 2. Pageview APIを利用する Wikimediaにはページビューを取得するREST APIが用意されています。指定できるパラメータや得られる情報ははPageviews Analysisと大差ありませんが、json形式で取得できるのでプログラムへの連携が簡単になります。幾つかリストアップした記事ごとに手軽にページビューを得たいという場合には最適な方法です。ただし100リクエスト/秒という制限があるので、日本語の記事全部のページビューを得たいといった用途には不向きです。 Wikimedia REST APIドキュメント こちらもREST APIのドキュメントからパラメータを指定してリクエストを送り、ウェブ上で結果を確認する機能があります。同時にcurlのコマンドやURLのエンドポイントも自動で生成してくれるので、サンプルで動かす際には便利です。 例えばプロジェクトja.wikipedia.orgにおける機械学習という記事の2018/12/01から2018/12/03ページビューを得るときのコマンドは以下のようになりました。 $ curl -X GET --header 'Accept: application/json; charset=utf-8' 'https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/ja.wikipedia.org/all-access/all-agents/%E8%87%AA%E7%84%B6%E8%A8%80%E8%AA%9E%E5%87%A6%E7%90%86/daily/20181201/20181203' 返ってくる結果は以下のようになります。 { "items": [ { "project": "ja.wikipedia", "article": "機械学習", "granularity": "daily", "timestamp": "2018120100", "access": "all-access", "agent": "all-agents", "views": 210 }, [...] ] } なお、ドキュメントにも記載されていますが、複数のクエリを機械的にリクエストするときにはUser-AgentやApi-User-Agentをヘッダに含めてリクエスト送り元がわかるようにしましょう。Pythonのrequestsパッケージで実行する場合は以下のようにヘッダで情報を付与します。 A La Carte Embeddingの実装 https://yag-ays.github.io/project/alacarte/ Fri, 07 Dec 2018 08:56:02 +0900 https://yag-ays.github.io/project/alacarte/ ACL2018にて発表された“A La Carte Embedding: Cheap but Effective Induction of Semantic Feature Vectors”を実装しました。未知語やngramなどの単語埋め込みを既知の学習済みベクトルから計算する手法です。 この記事はSansan Advent Calendar 2018 の8日目の記事です。 概要 “A La Carte Embedding"は、文脈における周囲の単語埋め込みを平均したものが学習済みの単語埋め込みと一致するように線形変換を学習することで、未知語に関しても単語埋め込みのベクトルを推定する手法です。これにより、通常の単語埋め込みでは学習が難しいような低頻度語であったり、複合名詞などの複数の単語からなる語においても、分散表現を得ることができます。 本論文の著者らは、これまでにSIF Embeddingなど理論寄りな方面から単語埋め込みや文章のベクトル表現を研究しているプリンストン大学のSanjeev Aroraのグループです。 ロジック A La Carte Embeddingに必要なのは、大規模コーパスと学習済みの単語ベクトルです。 学習の流れとしては、 単語埋め込みを計算したい低頻度語やngramなどの語彙を用意する 学習済みの単語埋め込みの語彙と1.で用意した語彙を対象に、学習コーパス内で文脈内に存在する単語埋め込みの和や出現数から文脈における埋め込みを得る これらのうち学習済みの単語埋め込みに関しては正解がわかっているので、2.で作成した文脈における埋め込みのがこの正解と一致するような線形変換を学習する 学習した線形変換を利用して、2.で計算した低頻度語やngramなどのベクトル表現に関しても線形変換を行って単語埋め込みを得る という感じです。 例えば("単語","埋め込み")というbigramのベクトル表現を得たいと思ったとしたら、コーパス内でこのbigramの周囲の単語埋め込みのベクトルを足し合わせてその出現頻度で割ることで、2.の文脈埋め込みが得られます。なお、ここで言う文脈はword2vecと同じく、特定のウィンドウ幅に含まれる単語の集合のようなものです。それをコーパス内で先頭から順に、すべての語彙で行います。線形変換の学習には、既知の単語埋め込みにある単語ベクトル(上図でいう吾輩のような既知の単語など)と、今回作成した文脈埋め込みが等しくなるように学習させます。あとはこの線形変換をbigramの文脈埋め込みに掛けることによって目的とするベクトル表現が得られます。 ソースコード yagays/alacarte_embedding: Python implementation of A La Carte Embedding なお、本ソースコードで精度評価やオリジナル実装との比較は行っていません。バグや細部の実装の違いが含まれている場合がありますのでご注意ください。 オリジナルの実装 著者らがオリジナルの実装を公開しています。こちらは(当然ながら)スペースで単語が分割できる英語などの言語を対象にしています。 NLPrinceton/ALaCarte 使い方 ALaCarteEmbeddingを作成し、コーパスに対して実行します。パラメータは以下の通りです。 word2vec: 学習済み単語埋め込み gensimのWord2VecKeyedVectorsを前提としています tokenize: トークナイザ min_count: 対象とする単語のコーパス内での頻度の最小値 ngram: 対象とするngram alc = ALaCarteEmbedding(word2vec=w2v, tokenize=tokenize, min_count=10, ngram=[1, 2]) alc. Word Embedding based Edit Distanceの実装 https://yag-ays.github.io/project/wed/ Mon, 12 Nov 2018 21:54:12 +0900 https://yag-ays.github.io/project/wed/ Leading NLP Ninjaのep.12で紹介されていたWord Embedding based Edit Distanceを実装してみました。 Word Embedding based Edit Distance Word Embedding based Edit Distance(WED)は、文字列間の類似度を計算する編集距離(Edit Distance)を拡張して、単語埋め込みの類似度を使うことによって文章間の意味的な距離を編集距離的に計算しようというものです。編集距離では文字の追加/削除/置換のコストが1なのに対し、WEDではそれぞれのコストは以下のように設定しています。問題は単語埋め込みの類似度をどう使うかですが、追加と置換のコストに関しては対応する文中に近い単語があればそっちの類似度分をコスト1から差し引く、置換に関しては単語間の類似度を単純にコストから差し引くといった形で拡張しています。 ここで、単語間の類似度は以下のように計算しています。 なので、このアルゴリズムに出てくるパラメータはw,b,λ,μの4つです。 詳しくはLeading NLP Ninjaの解説の方を参照ください。 実装 yagays/wed: Python implementation of Word Embedding based Edit Distance 基本的には編集距離の実装に用いる動的計画法をそのままに、コスト部分を論文通りに拡張すればOKです。ただし、実装が合っているかどうかすごく不安なので、あくまで参考適度に……。 注意点として、DPの配列の0行目と0列目では片方の文字列を削除したときのコストが入るので、単純に行/列ごとに1をインクリメントしていくのではなく、上記コストを計算する必要があります。 評価 流石に論文のような評価はしていませんが、手元でSTAIR Captionsを対象に類似度計算をした結果、まあまあ妥当そうな結果になりました。ここでは15,000件くらいの文章を対象に、馬が走っているという文章に似た文章を近い順に10件列挙しています。 [['一頭の茶色い馬が走っている', 0.6571005980449359], ['芝生の上を人がのった馬が走っている', 1.073464996815989], ['黄色いスクールバスが走っている', 1.0986647573718564], ['道路を首輪をした白い馬が走っている', 1.1281587567774887], ['尻尾の長い茶色の馬が歩いている', 1.1786151622459524], ['汽車のミニチュアが走っている', 1.2233853716174816], ['車の外を白や茶色の馬が走っている', 1.2432452098145987], ['大きな道路をトラックが走っている', 1.2456263623812807], ['フリスビーをくわえた犬が走っている', 1.2533987344007116], ['曲がりくねった道をレーサーが走っている', 1.2657003314558781]] 感想 実装/評価する上で気になった点を幾つか。 1. 追加/削除のコスト まずはじめに気になる点として、WEDの追加/削除のコスト計算がそれで良いのか問題。今回の論文の計算方法では、文章中の全単語の中から最も近い単語の類似度を用いるので、文中でどれだけ離れていても一番意味的に近い単語が採用される点が気になります。文章が長ければ長いほど似た単語が現れやすいでしょうし、編集距離を拡張している意味が何なのかよくわからなくなってきます。一度類似した単語として利用したら以降は使わないとか、前後n単語のみの類似度を使うとか、もう少し改良が必要な気がします。 2. パラメータ調整 そもそも最適なパラメータ調整がかなり難しいです。追加/削除と置換のコストがバランス取れていないと一単語置換するより1単語削除して1単語追加したほうが良いみたいになることもあり、この辺ピーキーすぎて調整が難しいところ。単語間類似度を計算するときにcos類似度→[0,1]の類似度に変換している部分も、改良の余地があると思います。 3. 文字列間の文章長の影響 また、極端に短い文章を対象にすると、意味的に関係が無くてもトータルのコストが低くなる場合があります。このあたりはコスト周りのパラメータの調整でも限界があると思うので、Quoraの質問ペアのようなある程度整ったデータセットでないと辛い部分でしょう。といってもそこは従来の編集距離でも同じことなので、的外れな意見ではあります。 4. 計算コスト 論文中には計算量は編集距離と同じとありますが、追加/削除の類似度計算は比較する文章中の単語数だけ必要です。また、基本的には与えられた文章ペアの類似度を計算する方法なので、ある一つのクエリ文章に対して類似した文章を大量の候補の中から検索するといった用途には活用しにくいと思います。 学習済み分散表現をTensorBoardで可視化する (gensim/PyTorch/tensorboardX) https://yag-ays.github.io/project/embedding-visualization/ Tue, 28 Aug 2018 21:37:26 +0900 https://yag-ays.github.io/project/embedding-visualization/ word2vecや系列モデル等で学習した分散表現の埋め込みベクトル(word embeddings)は、単語の意味をベクトル空間上で表現することが可能です。最も有名な例では「King - Man + Woman = Queen」のように意味としての加算や減算がベクトル計算で類推可能なこともあり、ベクトル空間の解釈として低次元へ写像する形で分散表現の可視化が行われています。 可視化の際に用いられるツールとしては、TensorFlowのツールの一つであるTensorBoardが、豊富な機能とインタラクティブな操作性を備えていて一番使い勝手が良いと思います。ただ、TensorFlowと組み合わせた可視化は容易なのですが、他のツールやパッケージで作成したコードをそのまま読み込めないなど、かゆいところに手が届かないと感じる部分もあります。 そこで今回は、すでに学習された単語の分散表現を可視化するために、 gensimを用いてベクトルを読み込み、 PyTorchのTensor形式に変換したうえで、 tensorboardXを用いてTensorBoardが読み込めるログ形式に出力する ことで、TensorBoard上で分散表現を可視化します。いろいろなステップがあって一見して遠回りに思えますが、コード自体は10行に満たないほどで完結します。個人的には、Tensorflowで学習済み分散表現を読み込むよりも、これらを組み合わせたやり方のほうが簡潔に書くことができて好きです。 方法 準備 必要な外部パッケージは、gensim/pytorch/tensorboardX/tensorflowの4つです。インストールされていない場合はpipなどであらかじめインストールしておきます。 $ pip isntall gensim torch tensorboardX tensorflow 分散表現の読み込みからtensorboard形式のログ出力まで TensorBoardで可視化するまでに必要な本体のコードです。これだけ! import gensim import torch from tensorboardX import SummaryWriter vec_path = "entity_vector.model.bin" writer = SummaryWriter() model = gensim.models.KeyedVectors.load_word2vec_format(vec_path, binary=True) weights = model.vectors labels = model.index2word # DEBUG: visualize vectors up to 1000 weights = weights[:1000] labels = labels[:1000] writer.add_embedding(torch.FloatTensor(weights), metadata=labels) コード内のvec_pathは、任意の学習済みベクトルのファイルに置き換えてください。 📙Unicode絵文字の日本語読み/キーワード/分類辞書📙 https://yag-ays.github.io/project/emoji-ja/ Thu, 23 Aug 2018 07:41:44 +0900 https://yag-ays.github.io/project/emoji-ja/ emoji_jaは、Unicodeに登録されている絵文字に対して、日本語の読みやキーワード、分類を付与したデータセットです。Unicodeで定められている名称やアノテーションを元に構築しています。 TwitterやInstagramなどのSNSを通じた絵文字の普及により、emoji2vecやdeepmojiなどの絵文字を使った自然言語処理の研究が行われるようになりました。絵文字を含む分析においては、絵文字の持つ豊富な情報や多彩な利用方法により、従来の形態素分析などのテキスト処理では対応できない場合があります。例えば、「今日は楽しかった😀」という文章では感情表現として絵文字が使われていますが、「今日は🍣を食べて🍺を飲んだ」ではそれぞれの対象を表す単語として用いられることもあります。[佐藤,2015]では絵文字の品詞を名詞/サ変名詞/動詞/副詞/記号/感動詞の6種類に分類しており、形態素解析に用いるNEologd辞書にも絵文字に対応する言葉が複数登録されています。 このように、絵文字を機械的な処理や研究対象として扱うには、絵文字の読み方であったり意味を表す単語、または意味的な種類で分類したカテゴリが必要になります。こうした辞書は、英語においてはemojilibがありますが、絵文字は文化的に異なった意味として用いられる場合があるため、それらの対訳をそのまま利用できないことがあります。 そのため、日本語で容易に使えるリソースとしてemoji_jaを作成しました。 (追記:2019/07/26) 絵文字の日本語読み辞書をUnicode 12.0対応に更新しました - Out-of-the-box 💻 ダウンロード 以下のGitHubレポジトリからjson形式のファイルをダウンロードできます。data/配下にある各種jsonファイルが、データセットの本体です。 yagays/emoji-ja: 📙UNICODE絵文字の日本語読み/キーワード/分類辞書📙 📁 データセット emoji-jaには下記の3種類のデータが含まれています。 emoji_ja.json: 絵文字に対応するキーワードやメタ情報 group2emoji_ja.json: 絵文字のグループ/サブグループに対応した絵文字のリスト keyword2emoji_ja.json: 絵文字のキーワードに対応した絵文字のリスト 1️⃣ emoji_ja.jsonデータ emoji_ja.jsonには、絵文字に対応する以下のメタデータが含まれています。 カラム 概要 取得元 keywords 絵文字に関連したキーワード CJK Annotations (CLDR Version 33) short_name 絵文字を表す短い名前 CJK Annotations (CLDR Version 33) group 絵文字を意味的に分類したときのグループ Emoji List, v11.0を元に翻訳 subgroup 絵文字を意味的に分類したときのサブグループ Emoji List, v11.0を元に翻訳 { "♟": { "keywords": [ "チェス", "チェスの駒", "捨て駒", "駒" ], "short_name": "チェスの駒", "group": "活動", "subgroup": "ゲーム" }, "♾": { "keywords": [ "万物", "永遠", "無限", "無限大" ], "short_name": "無限大", "group": "記号", "subgroup": "その他 シンボル" }, . 漢字を構成する部首/偏旁のデータセット https://yag-ays.github.io/project/kanjivg-radical/ Mon, 06 Aug 2018 08:43:23 +0900 https://yag-ays.github.io/project/kanjivg-radical/ kanjivg-radicalは、漢字を構成する部首や偏旁を容易に扱えるように対応付けしたデータセットです。 「脳」という漢字は、「月」「⺍」「凶」のように幾つかのまとまりごとに細分化できます。このように意味ある要素に分解しデータセットにすることで、漢字を文字的に分解して扱ったり、逆に特定の部首/偏旁を持つ漢字を一括して検索することができます。 このデータセットは、KanjiVGで公開されているsvgデータを抽出および加工して作成されています。そのため、本データセットに含まれる部首/偏旁のアノテーションはすべてKanjiVGに準拠します。 ダウンロード 以下のGitHubレポジトリからjson形式のファイルをダウンロードできます。data/配下にある各種jsonファイルが、データセットの本体です。 yagays/kanjivg-radical データセットの詳細 kanjivg-radicalには4種類のデータが含まれています。 漢字と部首/偏旁の変換データ 漢字と要素の変換データ 漢字と部首/偏旁の変換データのうち、左右に分割できる漢字 漢字と部首/偏旁の変換データのうち、上下に分割できる漢字 以下では、部首/偏旁はradical、要素はelement、左右はleft_right、上下はtop_buttomと表現しています。 1. 漢字と部首/偏旁の変換データ 漢字と部首/偏旁を対応付けしたデータセットです。漢字から部首/偏旁と、部首/偏旁から漢字の2種類のデータがあります。 kanji2radical.json : 漢字から部首/偏旁への変換 radical2kanji.json : 部首/偏旁から漢字への変換 # kanji2radical.jsonのサンプル "脳": [ "月", "⺍", "凶" ] # radical2kanji.jsonのサンプル "月": [ "肝", "育", "胆", "朦", "脱", ... 2. 漢字と要素の変換データ 漢字と要素を対応付けしたデータセットです。漢字から要素と、要素から漢字の2種類のデータがあります。 kanji2element.json : 漢字から要素への変換 element2kanji.json : 要素から漢字への変換 ここで使用している「要素」いう言葉は、部首/偏旁を構成する更に細かい単位での漢字のことを指しています。言語学的に定義された単語ではなく、KanjiVGで利用されていたelementの対訳として用いています。 このデータでは構成している要素をすべて列挙しているので、結果の中には重複が含まれます。以下の例だと脳には「凶」という要素が含まれていますが、同時に「乂」という要素も含まれているため、どちらもkanji2elementの結果として出力されます。 # kanji2element.jsonのサンプル "脳": [ "乂", "凶", "丿", "凵", "⺍", "月" ] # element2kanji. Wikipedia CirrusSearchのダンプデータを利用する https://yag-ays.github.io/project/cirrus/ Mon, 30 Jul 2018 21:07:52 +0900 https://yag-ays.github.io/project/cirrus/ Wikipediaのデータを容易に利用できるCirrusSearchのダンプデータについて紹介します。これを利用することにより、Wikipediaの巨大なXMLデータをパースしたり、Wikipedia Extractorなど既存のツールで前処理することなく、直にWikipediaの各種データにアクセスすることができます。 tl;dr 細かいことは置いておいて、素直にWikipediaの日本語エントリーに書かれているテキストを取得したい場合、 ここのCirrusSearchの任意の日付のダンプデータjawiki-YYYYMMDD-cirrussearch-content.json.gzを落としてくる 中に入っているjsonデータをパースして、偶数行の"text"を取得するコードを書く とすることで、簡単にWikipediaのテキストデータを取得することができます。 CirrusSearchダンプデータの概要 CirrusSearchは、ElasticSearchをバックエンドに構成された検索システムです。このシステムに利用されているデータがダンプデータとして公開されており、そのファイルを利用することで、整形されたテキストを始めとして、外部リンクのリストやカテゴリのリスト等のメタデータが容易に利用できます。また、言語ごとにダンプファイルが分かれているため、日本語のWikipediaのデータだけを対象にすることが可能です。 CirrusSearchのダンプデータは以下から取得します。 Index of /other/cirrussearch/ Wikipediaに関するダンプデータは以下の2つです。 ファイル 内容 jawiki-YYYYMMDD-cirrussearch-content.json.gz Wikipediaの本文(namespaceが0) jawiki-YYYYMMDD-cirrussearch-general.json.gz Wikipediaのその他情報(namespaceが0以外) その他の接頭辞の対応関係は以下の通りです。 jawiki: ウィキペディア jawikibooks: ウィキブックス jawikinews: ウィキニュース jawikiquote: ウィキクォート jawikisource: ウィキソース jawikiversity: ウィキバーシティ CirrusSearchのデータ CirrusSearchのダンプデータは、1行が1つのjsonとなっており、2行で1つのエントリーを表しています。 奇数行 奇数行にはエントリーに固有のidが記載されています。この_idから該当のエントリーにアクセスするには、https://ja.wikipedia.org/?curid=3240437のようにcuridのパラーメータを指定します。 { "index": { "_type": "page", "_id": "3240437" } } 偶数行 偶数行にはエントリーの情報が記載されています。下記の例では、複数の要素が入った配列や長い文字列は...で省略しています。 { "template": [ "Template:各年の文学ヘッダ", ... ], "content_model": "wikitext", "opening_text": "1972年の文学では、1972年(昭和47年)の文学に関する出来事について記述する。", "wiki": "jawiki", "auxiliary_text": [ " 1972年 こちらもご覧下さい 社会 政治 経済 . Home https://yag-ays.github.io/home/ Thu, 26 Jul 2018 04:28:29 +0900 https://yag-ays.github.io/home/ yag_aysの資材置き場。out-of-the-boxなデータセット/コーパス/ノウハウを公開していきたい。 記事一覧はこちら→ PROJECT プロフィール→ Yuki Okuda 文字の図形的な埋め込み表現 Glyph-aware Character Embedding https://yag-ays.github.io/project/char-embedding/ Wed, 25 Jul 2018 12:30:41 +0900 https://yag-ays.github.io/project/char-embedding/ 「文字の図形的な埋め込み表現」は、文字の図形的な情報から埋め込み表現を学習したデータセットです。文字の意味や文章中の文脈などのセマンティクスから構成する分散表現とは違い、文字の形状という視覚的な特徴を学習しています。それぞれの文字に対する埋め込み表現の近さを計算することで、似た形の文字を推定することができます。 ダウンロード 下記のGitHubレポジトリからダウンロード可能です。以下のURLを開いて「Download」をクリックしてください。 convolutional_AE_300.tar.bz2 (解凍前:88MB, 解凍後:180MB) 以下の2つのファイルが入っています。フォーマットが異なるだけで、どちらも同じベクトルデータです。 convolutional_AE_300.bin convolutional_AE_300.txt その他サンプルコードなどのすべてのファイルは、以下のレポジトリにあります。 yagays/glyph-aware-character-embedding 詳細 ベクトル次元:300 文字の種類数:44,637 学習データに用いたフォント:Google Noto Fonts NotoSansCJKjp-Regular 使い方 gensimを用いた利用方法を例示します。なお、ここではword2vecのように単語の分散表現として扱っていますが、本リソースで扱う文字の図形的な埋め込み表現には加法性がありません。図形としての文字の類似度は計算できますが、部首の足し算引き算といったような操作はできないのでご注意下さい。 from gensim.models import KeyedVectors model = KeyedVectors.load_word2vec_format("data/convolutional_AE_300.bin", binary=True) most_similar()を用いて、図形的な類似文字を検索します。以下の例では一番類似度が高い文字に「а」が来ていますが、これはasciiの「a」ではなくキリル文字の「a」です。 In []: model.most_similar("a") Out[]: [('а', 1.0000001192092896), ('ả', 0.961397111415863), ('ä', 0.9610118269920349), ('ā', 0.9582812190055847), ('á', 0.957198441028595), ('à', 0.9558833241462708), ('å', 0.938391923904419), ('ầ', 0.9370290040969849), ('ǎ', 0.9368112087249756), ('ấ', 0.9365179538726807)] Google Noto Fonts NotoSansCJKjp-Regularに含まれるすべての文字に対して操作が可能です。 In []: model.most_similar("油") Out[]: [('汕', 0.9025427103042603), ('泊', 0.