医療用語に注目した文書の類似度計算

nishiba氏(以下、nishiba):こんばんは、nishibaです。upuraさんのプレゼンがあんなにうまいとは知らなくて、プレッシャーを感じています。

(会場笑)

みなさんあたたかく見守ってください。プレゼンテーションは場の雰囲気なので、みんなで作っていきましょう(笑)。

今日は『医療用語に注目した文書の類似度計算』について発表します。僕は機械学習エンジニアという肩書きを名乗っています。さっきアンケートを見たらけっこうデータサイエンティストの人も多かったので、一応ポジション的なことを話しておきます。

機械学習といっても、僕は探索的データ分析みたいなこともやっていて、KPIの設計にも関わっていました。うちのエムスリーという会社はテックブログですごく有名なんですが、エンジニアは60人くらいしかいません。60人であれだけブログを書いてる会社ってやばいだろという感じですが(笑)。

人数が少ないのでデータに関わることは全部やっています。機械学習チームは全員が機械学習を専門にやってるわけではなくて、データに関わる仕事を全部引き受けるようなチームです。僕は機械学習のアルゴリズム開発をメインにやっているんですが、分析をやったりもします。

チームメンバーには、データ基盤を作ったりデータエンジニアリングをやる人もけっこういます。あとは普通にWebアプリケーションも作っています。今は画像診断が流行っていると思うんですけど、画像診断はアルゴリズムだけではサービスにならないので、そのアプリケーションを作る人とかもチーム内にいます。

僕は自然言語処理系の推薦システムを作っています。大学時代の話を追記したんですけど、数理ファイナンスの研究をやっていました。応用数学ですね。Malliavin calculusというのをやったんですが、そこで博士取って、金融機関に入りまして、ある程度満足したのでエムスリーにやってきました。絶賛採用中なのでよろしくお願いします。

あとここに新規プロダクト立ち上げの勉強会をPLAIDさんでやるというリンクを貼ったので、よかったら来てください。

これは単に宣伝というわけではなくて。機械学習やデータサイエンスをやっている人はわかると思うんですが、やっぱりこれらはサービスがおもしろくてなんぼ、サービスに組み込んでなんぼというところがあります。やっぱり機械学習「だけ」、データサイエンス「だけ」でビジネスになることはなくて、サービスをいかに作っていくかというところを最近すごく課題に感じていて、こういう勉強会も企画しています。

文書の類似度を計算

今日話すことは、実際のビジネスの課題を機械学習で解決した事例です。どういう課題かと言うと、タイトルのとおり、医療用語という観点で文書の類似度をちゃんと計算するということです。資料は公開済みで、connpassを見ていただいたら資料が貼ってありますし、#d3mのところでもあとのツイートにあると思います。すごくいい勉強会だなぁと思うので、みなさんもどんどんハッシュタグでつぶやいてください。ポジティブなことを書いてくれると僕はうれしいです(笑)。

データ基盤などについては今日は話しません。データパイプラインも、あそこにいるイトウさんとData Pipeline Casual Talkという勉強会を企画中で、応募がたくさん来ているので、そこで話します。あとチームマネジメントの話とかも来週のMLCTとかでやるので話さないです。

ソースコードは公開してあります。機械学習のモデルとか、みなさんの会社ではあまり公開とかしないと思うんですが、うちはバンバン公開していいよっていうルールで、実務に使うソースコードとかもなぜか公開されています(笑)。

(会場笑)

ビジネスロジックとかは剥いであるんですが、「公開していいよね」ということで、今月からどんどんオープンソース化を進めています。例えばうちはニュース記事のレコメンデーションもやっていますが、たぶんニュース配信を行っている他社はアルゴリズムを公開することはないと僕は思ってます。僕らは公開しても問題ないので、今公開に向けて絶賛準備中です。

そのアルゴリズム自体は日本でも使われているし、うちはUSにブランチがあるんですけど、そこでも使われる予定のもので、ちゃんと実務で動いているものを公開していきます。

問題の背景

課題に入っていきます。弊社エムスリーは医療系のIT屋さんです。今回の課題は、医療用語に関するアイテムを探したいということです。

例えば今流行っている「インフルエンザ」のキーワードにマッチするアイテムを全部探したいとします。普通だったらキーワードマッチングとかの検索になると思うんですけど、あとで紹介するように、それではあまりうまくいきません。

うちは20サービスくらいあって、それぞれにコンテンツがあるわけです。そのコンテンツの中からインフルエンザに関連するものを全部ピックアップして一覧ページを作っていきます。

問題になるのが、別のサービスになると、テキストのアイテムのフォーマットや情報量が違ったりするので、なかなか横断で情報を探すのが難しいということです。

今回はテキストメインなので、よく使われているテキストの類似度計算をここに挙げたんですが、Doc2Vecが有名ですね。ただ精度的に課題はあるので他の方法等をちゃんと知っておいたほうが良いと思います。。

Skip-Thoughtとか、Average取ったWord2Vecとか、TFIDFで重み付けしたAverage取ったWord2Vecとかがあります。今回は、今日取り扱うSCDVという方法を採用しています。

こういった方法でテキストを特徴化したあとに、教師ありでやるんだったらXGBoostとかで類似を計算したり、教師なしだったらコサイン類似度を使っちゃいましょうみたいなことをよくやってると思います。今回それとよく似たことをやるんですが、問題としていろいろ工夫が必要なので、まずは問題設定を出します。

問題の課題点

医療系ワードで、例えば「糖尿病」で説明します。

「糖尿病患者に実践してほしい〇〇」というWeb上での講演会のコンテンツがあります。こういうコンテンツって、糖尿病というキーワードがすごく少なくて、ほかは告知の文章なんです。一方で、「糖尿病は、ナンタラカンタラ……」みたいな解説記事もあります。この両方の文章を「似ている」と判定したいです。

でも、これらを「似ている」と判定するのは非常に難しいんです。なぜかというと、ここの「糖尿病」というキーワードだけに注目しろということなんですね。この告知文のようなところは要らないんです。病気単位で考えると、こういうことが難しくなります。

もう1つ類似度の例として、「インスリン」ってどう考えても糖尿病と近い話ですよね。多分「糖尿病」のコンテンツを見ているお医者さんは、「インスリン」というキーワードにも興味を持っているはずですが、この「インスリン」の記事に「糖尿病」というワードは入っていないので、これもきちんと探さなくてはいけません。

初めの例でもっと難しくなるのが、普通にテキストを分類すると、(スライドを指して)この文章とこの文章は「似ている」と判定されてしまいます。なぜならキーワードが、ここ(「糖尿病」と「喘息」)しか変わらないで、ほかのところも大学とか全部似ているので、Word2Vecとかで重み付けをしてからやっちゃうと、だいたい「似ている」と判定されてしまいます。

しかも最悪なのが、「TFIDFでやればいいでしょ?」みたいな話にすぐなるんですけど、先生の名前とかの頻度を下げたとしても、どこかでそこから影響が来たりしてうまく取れない。専門用語もうまく取れなかったりします。いろいろがんばったらできないこともないですけど、やっぱり大変で、こういうのをちゃんと「似てない」と判定したい。

実際に作ったモデル

実際作ったモデルでどういうことをやっているかと言うと、データの流れとかをイメージして書いてるので上から見ていくと、最初にアイテムのクリックを見ます。

アイテムのクリックをどうするかと言うと、協調フィルタリングでMatrix Factorizationをします。Matrix Factorizationをやると、潜在変数としてアイテムの埋め込みみたいなものができるので、それで類似度を計算します。

なぜいきなりここでアイテムのクリックが出てきたかと言うと、「『糖尿病』と『インスリン』は似ているよね」というときの「似ている」の定義がすごく難しいからです。なので僕が今回考えた「似ている」の定義の仕方として、ユーザーがよく見ているコンテンツには、何かしらの「似ている」キーワードを含まれているはずで、そういう特徴をがんばれば掴めるというはずだと考えました。

これは本当に難しくて、例えば「子宮頸がんの新薬発売」という記事と「子宮頸がんのメカニズム解明」という記事があったときに、子宮頸がんって聞いたら「あ、女性のがんの病気だな、両方似てるだろう」と僕最初思ってたんですけど、実は似てませんでした。

なぜかと言うと、うちのユーザーはお医者さんとかなので、「新薬発売」といったら現場の人が見て、「メカニズム解明」といったらがんの研究者とかが見ちゃうから、単純に医療の言葉だけでも選べない。だからそこの違いをうまく出すために、協調フィルタリングとかでテキストは一切見ずに、まずはユーザーの行動だけを見て、行動で似ているアイテム群というのをここ(スライドの「類似度①」)で1個作ります。

もう1つは、やっぱりテキストは似ているほうがいいので、アイテムのテキストで医療キーワードはある程度わかっているので、それでキーワードマッチングをして、「糖尿病」を両方とも含んでいたら、お互い「似ている」といったかたちで作っています。それが「類似度②」というところです。

この類似度を予測するようなモデルを1個構築します。それはどうやっているかと言うと、SCDVという方法でテキストを分散表現に変えます。その分散表現を得て、2つのアイテムが似ているかどうかをXGBoostを使って予測します。

このときのインプットが、2つのアイテムの様相の積のベクトルを特徴量にして類似度を当てにいくみたいなことをやっています。これが学習時です。「次元削減」はあとで触れます。

予測時は何をやるかと言うと、2つのアイテムと、あと「糖尿病」というキーワードのところの分散表現で見ていて、この2つを入れるとそれが似ているのかどうかというのを判定してくれます。

ポイントは、ここで協調フィルタリングをやって、上でユーザーの行動を見てたんですが、こっちはもう見る必要はなくて、テキストだけでオッケーということです。インプットがテキストだけなんだけど、ユーザーから見て似ているものがちゃんと選ばれるようになった。こういうことをやっています。

なぜ予測時にクリックデータを使いたくないかと言うと、コールド・スタートに対応したいからです。コールド・スタートというのは、ユーザーが誰も評価していない新しいアイテムを評価しなきゃいけないときに、アイテムのクリックを予測時に使うと対応できないので、使わなくていいようなモデルにしています。

モデルの詳細ですが、クリックデータからMatrix Factorizationして、その潜在変数をコサイン類似度を計算して0.8以上のものと0以下のものに分けて。そこからサンプリングします。

数がアンバランスになっちゃうんですが、もちろん似てるコンテンツがすごく数が少なくて、似てないものがすごく多いので、同じ数になるようにサンプリングします。キーワードも同じです。

それで、SCDVって知ってる人います?

(会場挙手)

けっこういますね。けっこうと言っても少ないですけどね(笑)。

この論文(SCDV : Sparse Composite Document Vectors using soft clustering over distributional representations)とか、あとは僕がQiita記事とかブログも書いているので、よかったら見てください。

どういう話をしているのかと言うと、SCDVのアイデアは2年くらいの論文で、僕読んだのが3ヶ月くらい前で、それまで知らなかったんですけど、これは僕らの問題にはすごく適した論文です。

何をやるかと言うと、Word2VecとかfastTextを使って単語の埋め込みをします。単語自体をGaussian Mixture Modelを使ってクラスタリングするんです。クラスタに分けます。単語自体を重み付けされたクラスタに分けることができます。そうすると単語自体が分類されてうまく出るんですよ。

なぜかと言うと、「糖尿病」という単語とほかの単語でクラスタが変わるので、自分たちが注目したいことだけにフォーカスが当たる。「糖尿病」とか「喘息」とかっていうところだけにフォーカスが当たる次元が生まれるわけです

ただクラスタリングなので、僕が明示的に「糖尿病」っていう医療系のキーワードのところの次元を知ることができるかと言うと、調べなくとできなくて、調べるのも実際には難しいんですけど、XGBoostに任せてうまい感じに、ここのこういう(「糖尿病」や「喘息」などの)キーワードの次元のところだけを探してもらおうかなというような作戦でやってます。それをやるとかなりいいです。

ただ問題なのが、例えば単語をWord2Vec使って200次元にして、GMMの次元を、クラスタの数を60とかにしちゃうと。200×60の次元のすごく長いベクトルになっちゃうんですけど、そこは医療用語の一部だけ僕らが把握しているので、その一部を使って医療用語っぽいところのクラスタっていうのを探して、そこの次元しか使わないみたいなことをやってます。

それやると次元が削減されてXGBoostとか入れてもスムーズに計算ができます。結果、けっこううまくいきました。

結果

医療関係の人いないですよね? ときどきインターンの学生で「もともと薬学部に所属してました」みたいな人がいて、その人はめちゃくちゃ自然言語に強いのでわかるんですけど、僕はわからないのでいろいろGoogleで検索しながらやってます。

この「肺がん」というキーワードに似てるコンテンツで、例えば「がん医療」。これはコンテンツの中に肺がんっていうキーワードが入ってます。あとは「オプジーボ」。この前のノーベル賞の関連の薬だったんですけど、これは肺がんの薬なんですね。そういうのが全部取れてます。

「白内障」って目の病気なんですけど。白内障による視力低下の記事とか、「白内障」ってキーワードが入ってるものが全部取れるようになってます。

このへんでちょっとおもしろいなと思うのが、キーワードマッチングとかだとやっぱりけっこう無理があって、「オプジーボ」の話って取れなかったりするんですよ。

なぜかと言うと、「オプジーボ」が肺がんの薬だっていうことを(医療関係者は)みんな知ってるんですよね。僕らは知らないから「これは肺がんの薬だ」って書いてくれないとわからないんですけど、お医者さんから見たらそんなの常識なので、わざわざ記事内に「これは肺がんの薬です」みたいに書かれないから、肺がんがヒットしないんですよ。

あと文書が同時に出てくるかって言うと、例えばWord2Vecとか使えば「周りの単語が似ているから近くなるんでしょ」と思う人も多いと思うんですけど、完璧に薬剤の文章とかを大量に入れて学習させちゃうと、一切そんなのは出てこないです。こういうものが、Word2Vecとかで「似ている」ってならないんです。

ただこれが「似ている」ってちゃんと言われるのは、お医者さんのほうの協調フィルタリングで、このキーワード(肺がん)に反応する人と「オプジーボ」に反応する人たちのデータを取り込んでるからできるんです。

「白内障」のほうは目の話が絶対出てくるので、このへんはやりやすいんですけど、「肺がん」は非常に難しい問題になっていますが、できるようになってます。それができるのが、初めに協調フィルタリングを使ってるから、「ユーザーから見て似ているもの」がちゃんとデータとして入ってきている。

XGBoostを入れる前にSCDVとかでテキストの分散表現を入れたときに、医療っぽい用語っていうのだけピックアップするようになっているので、こういう(講演資料のような)文章が来たときに、予測するにあたって「ここ(告知)の文章はまったく聞いてません」「大学名とかたぶん聞いてません」みたいなことになれば、こういうところ(「糖尿病」や「喘息」)だけの次元にフォーカスが当たって、「似ている」「似ていない」のモデルになるので、ちゃんと予測できているという話です。

まとめ

最後にまとめですが、SCDVを3ヶ月くらい前に知ったんですけど、けっこういい感じで、僕としてはすごくうれしいので、ブログに書いたりQiitaに書いたりしています。

あと1番言いたいのは「新しい論文、自分の知らない論文を読んで実務に応用できる機械学習は楽しいですね!」という話です(笑)。けっこう楽しく仕事しています。

これは2年前に出てる論文なんですけど、自分がぜんぜん知らない知識があって、自分の課題を解決するためにいろんな論文を漁るわけですよね。それでいい論文が見つかったりとか、ほかの人と議論して教えてもらったりして、こうやってそのまま実務に応用できるというのが楽しいです。

このサービス自体はまだ出てないんですけど、2月から3月くらいの間にサービスとしてリリース予定のものです。ソースコードもGitHubで一部公開してます。そのうちすべて公開する予定です。以上です。ありがとうございました。

(会場拍手)

司会者:ありがとうございました。5分ほどあるので質問ある方?

(会場挙手)

アイテムのクリックデータについて

質問者1:学習のときに使う協調フィルタリングのところなんですけれども、こちらのアイテムのクリックのデータは予測で提示された内容がログのコード履歴に含まれているのか? もしくはまったく別の導線で得られたものなのか? そこをおうかがいしてもよろしいですか。

nisiba:鋭いですね。今はまだサービスが出てないので、実はここはないです。そこの依存をどうするかっていうのはけっこう考えなきゃいけない問題で。たぶんやり方を変えないといけないと思っているので、いいアイデアがあったら議論したいんですけど(笑)。

例えば「インフルエンザに興味ある人」と言ったときに、クリックされたものとされないものがたぶん出てきます。そこは本来であれば「予測間違ってたよね」としたいです。

ただおっしゃるとおり純粋にそのままデータを扱ってしまうと、実は(列挙されたアイテムのうちで、例えば)「アイテム2」ってインフルエンザがまったく関係ないのに表示されてるから、なんだろうと思ってクリックされることになって、間違った学習が入る可能性がある。

こういうことは防がなきゃいけないので、ここに表示されたものと、ほかのところから来たものとは区別をつけるとか、ここで異様にクリック率が低かったり、順位が上にも関わらずクリック率が低いものを使わないようにするような工夫は必要で、「大変だなあ」と思っていたところです(笑)。

質問者1:あと1点だけお願いします。例えばこのへんのサービスは実際の、プロダクションに投入しようとなると、わりと大きなサービスだと短いレスポンスで、レイテンシーが低い状態で返さなきゃいけないと。

実務的には例えばSolrとかElasticsearchみたいな、ああいう枯れたものを使わざるを得ないということもあると思うのですけれども、こちらの作り込んだなかで、そういうプロダクションの重み付けで工夫されたこととか、このへん辛かったなみたいなことをおうかがいできればありがたいなと思いまして。

nishiba:鋭いっすね。なんか質問の質が高いので、痛いところを突かれるというか課題どころをまんま突かれるんですけど。ここに今解決しなきゃいけない問題があって(笑)。1日中バッチ計算してある程度計算結果を残していたら、ある程度どうにかなるっていう逃げ道はあるんですけど。

みなさんどうやってるの? って聞きたいんですけど。XGBoostをかますじゃないですか。これが普通に簡単な行列演算とかだったらANNとか使って早いアルゴリズムっていうのが出ると思うんですけど、XGBoostを介す時点で探索が面倒くさくなるんですよね。そこってみんなどうしているんだろう? みたいな。誰か知見あったら教えて(笑)という感じです。

ここでXGBoostを使うんじゃなくて、ほかのニューラルネット系のやつに置き換えて、最終的にはコサイン類似度的な簡単なもので探索できるようにしといたほうがいいんじゃないかなと。

僕はあんま詳しくないんですけど、ANNはある程度関数自由に定義されてますけど、XGBoostとかたぶん無理ですよね。なのでここを置き換えないと、重いバッチ計算からは逃げられないですね(笑)。

質問者1:ありがとうございます。

日本語と英語でモデルと作る上で意識していること

質問者2:お話の中でアメリカにもブランチがあるという話だったと思うんですけど、日本語と英語でこういうモデルを作るときに、「このへん気をつけなきゃ」というところとか、実務に落とし込むうえで意識している点とかあれば教えていただきたいです。

nishiba:非常にまた難しい。

(会場笑)

英語のやつもちょっと試したときに、トークナイズは向こうのほうが圧倒的に楽なのでいいんですね。単語の前処理とかもある程度向こうもライブラリ揃ってたりとかするので。

日本の普通のフレームワークはそのまま載るんですよ。ただこっちがいろいろ手を入れている話とかは全部やり直さなきゃいけなくて、細かなところのやつを排除したり置き換えたりしなきゃいけないというのは問題として出てきます。

例えば糖尿病患者ってMeCabとかの辞書を使うともう登録されちゃってるんですよ。ただここでトークナイズしたいじゃないですか。糖尿病と患者を分けたいんですよ。僕のニーズ的にはね。

そういうトークナイズの処理とか、あと大学とか「後ろに載ってる大学だよ」みたいな話とか、そういうところは無くして、うまくいかないときにはまたがんばるという作業が発生しますね。それは言語の特徴というか……。

あと、今僕はfastTextを実際使ってるんですけど、日本語の糖尿病とかはめちゃくちゃやりやすいんですよ。漢字だと明確に分けてくれるので、2型糖尿病と1型糖尿病と糖尿病の距離がちゃんと近くなるんです。でも英語のときとか、薬や病気の名前については僕は語彙力がないので、そのへんは工夫が大変ですね。あと自然言語処理がいっぱいあって(笑)。

XGBoostを採用した経緯

質問者3:お話ありがとうございます。おっしゃっていたかもしれないんですけど、XGBoostを採用した経緯とかを知りたいなと思っているんです。というのは「アイテムのテキストを変換して、そこで読み込んで……」というところで、僕だと例えばクラスとかRNN、Recurrent Neural Networkを使ってテキストの前後関係まで含めたほうが精度が高いのかなとちょっと思ったんですが、そこはいかがですか?

nishiba:それをやると、ここの時点でもうRNNに変えなきゃいけなくなるんです。単語の順番とか関係なしにしてベクトル化しちゃうので。

RNNでもいいんですけど、文章の長さがコンテンツによってぜんぜん違うんですよ。例えばこれ(「〇〇患者に実践してほしい〇〇」)は短いんですけど、ニュースとかですごく長いやつがあったりとかして、そこでうまくいくのかなっていうところがもともと懐疑的だったのと。

正直文章の順番はあんまり関係ない気がするんです。すごく細かいところで、さっきの「メカニズム解明なのか、新薬発売なのか」みたいになると組み合わせがすごく大事になってきたり、位置関係も大事になってくるんですけど、今回のケースだとそこまで位置関係で見るよりは、キーワードをある程度含んでいるかくらいの荒いモデルなので。これでいいかなという感じですね。

質問者3:わかりました。ありがとうございます。

司会者:nishibaさん、発表ありがとうございました。

(会場拍手)