2018年12月14日SHAREツイートシェアはてブLINEPocketどうもこんにちは。Aidemy研修生の片岡(@ShotaroKataoka)です。今回は小説をベクトルに変換して、自分好みの小説を見つけたいと思います。みなさん、小説を無料で投稿できるサイトをご存知でしょうか?小説家になろうやカクヨムなどが有名ですね。小説家になろうで小説を漁るのが好きです。最近はこういうサイトからプロになる人が多いみたいで、アマチュア小説家や出版社にとって機会獲得の場として重要視されているようです。僕たち消費者にもありがたいサービスなのですが、不満もあります。それは、面白い小説を見つけるのが難しいことです。いうのも、ランキングがあてにならない。(個人的に)面白いと思った小説は他の読者に評価されていない。検索機能は充実しているが、うまく機能しない。ジャンル検索に関して、もう少し詳しく指定したい。SF⇒超能力?タイムマシン?小説のキーワードが内容に即していない場合があるのでキーワード検索もあてにならない。「ランキングやキーワードに頼らずに自分好みの小説を探せないか?」と思ったので、小説の内容から直接似ている小説を探してみます。小説家になろうから小説データをスクレイピングする。集めた小説をDoc2Vecによってベクトル化する。階層的クラスタリングやコサイン類似度を用いて似ている小説を探す。こんな感じでやってみます。Colabratryを用いました。notebookが動かせちゃうというサービスです。無料でTPUまで使える(!)ので、貧弱なPCしか持ってない僕にとっては神のようなサービスです。driveにアクセスできるようにしておきます。gensimこんな感じで適宜インストールします。小説家になろうから小説をスクレイピングしてきます。スクレイピングについては今回の本題ではないので詳細は割愛します。以下の関数を用いて本文のダウンロードを行いました。novel[取得した小説について]まずは以下の条件で検索を行い、検索ページから該当する小説のリンクをスクレイピングしました。ファンタジー(異世界含む)小説である。小説家になろうにおいてファンタジー小説は圧倒的に人気があるため。内容に一定のパターン、いくつかのバリエーションがありクラスタリングしやすそう。10000文字以上の小説である。更新が放棄された小説を除くため。昔と今では流行が違うはずなので、ある程度のまとまりを担保するために最近の小説に絞った。検索ページでは2000タイトル以上は遡れなかったため、取得件数を2000タイトルとしました。スクレイピングしたリンクを辿って、各タイトル5話ずつ×2000タイトルの内容を取得しました。取得した小説データを解析するためにまずは形態素解析を行います。以下に該当する場合は除外する。どれかであり、かつストップワードに該当しない単語を選別する。連結すべき名詞は連結する。単語間を半角スペースで区切って出力する。いう処理を行っています。......(以下略)’こんな感じで学習データを整形できました。Doc2VecDoc2Vecについての概要さて、本日の本題であるDoc2Vecをやっていきましょう。名前のとおり、文書をベクトルに変換することができます。Documentsをご参照ください。単語の並び順を考慮してベクトル化できる。単語の意味を考慮してベクトル化できる。いう利点があるからです。「魔物」と「モンスター」という単語があったとき、Doc2Vecではこの2つの単語の意味は近いと判断してベクトル化してくれるので表現のブレに対して頑健なモデルになると考えられます。Doc2Vecを使ってみるDoc2Vecはgensimのライブラリを用いることで容易に実行することができます。qiita.comこちらのサイトを参考に(というかそのまま使って)Doc2Vecを行いました。文書ごとに単語を分割してリストにする。Drive/colab/narou/doc2vec.model")projector.tensorflow.org上記サイトでは埋め込みベクトルを主成分分析して可視化してくれます。学習の結果得られた文書ベクトルの分布がどうなっているか見てみましょう。偏りがあるかなと思ったのですが、思ったよりものっぺりと分布しています。第1主成分から第3主成分までの累積寄与率を調べてみると31.8%になっていました。累積寄与率は100%に近いほど次元削減したデータが次元削減前のデータの散らばり具合をよりよく説明できていることを表し、70~80%以上が目安だといわれています。今回は31.8%だったので、可視化したグラフはあまりあてにならないということが分かります。タイトルからだけでは詳細な内容は分からないですが、とりあえず似た感じの小説は集まって分布しているようです。次は得られたベクトルをクラスタリングしていこうと思います。クラスタリング階層的クラスタリングについて今回は文書ベクトルをクラスタリングするためにWard法による階層的クラスタリングを用いました。採用理由は以下の通りです。事前にクラスタ数を決定しなくてもクラスタリングができる。デンドログラムによってクラスタリングの過程を可視化できる。乱数を用いないアルゴリズムなので、コントロールしやすい。具体例で学ぶ数学このサイトが分かりやすいです。下記のプログラムによって、文書ベクトルをクラスタリングします。clustered)クラスタリングの結果、以下のようなデンドログラムが出力されました。今回は類似度0.6までを1つのクラスタと設定したため、全部で8つのクラスタができました。この図では低い位置で結合している部分ほど類似度が高いということを表しています。図からクラスタの大きさはクラスタによってばらばらだということが分かります。クラスタの分析・評価ここまでで小説をクラスタリングすることに成功しました。クラスタリングしたはいいもののそれぞれのクラスタがどのような集まりなのかは分かっていません。ここでは小説がジャンル(小分類)ごとにクラスタリングされていると仮定して、それぞれのクラスタがどのようなジャンルを表しているのかを分析します。他のクラスタには少ないが特定のクラスタには頻出である単語は、クラスタを表す重要な特徴語であると考えられます。この仮説のもとでクラスタごとにtf-idfを行い、スコア上位の単語をクラスタのジャンル推定語とします。今回は、tf:クラスタ内出現頻度idf:逆文書頻度として計算しました。したがってtf-idf値はクラスタ内で高頻度かつクラスタ外では低頻度であるような単語が大きい値を持つようになります。以下のプログラムでtf-idfの算出を行いました。0.0021027100914477945こうなりました。クラスタごとにある程度の傾向はみられますが、人名らしきものが多く分かりづらいです。データの前処理の段階で人名は除外しているのですが、一般名詞のフリをした人名がたくさん残っていました。これでは、ちゃんとしたジャンル推定ができないので手法を少し改良する必要があります。ジャンル推定手法の改良いろいろ分析した結果、上位にくる人名は1つの作品中で連呼されている、ということが分かりました。クラスタ全体のtf-idfが1つの小説の表現に引っ張られているということです。これを改善するためにはどうすればよいでしょう。作品数が増えれば、1つの作品の影響力は弱まるでしょうが、現在手元には2000作品しかなく増やすのにも時間がかかります。そこで今回はクラスタ内の単語の出現回数をカウントするときに、1つの作品中では何度出現しても1回として数えることにしました。これにより小説の独特な繰り返し表現などに結果が引っ張られないようになると考えられます。この手法でジャンル推定をした結果を以下に示します。0.0005856936537451811人名がきれいになくなり、よりクラスタの特徴らしいものが上位に来るようになりました。クラスタ[0,5,6,7]はこれだけでどういうクラスタなのかが想像できます。観測によるジャンルの推定ここまで、小説の文章中の特徴語を見つけることでクラスタのジャンル推定を行ってきましたが、クラスタ[1,2,3,4]は特徴語が一般的で何のクラスタなのか分かりませんでした。そこで、実際にクラスタに属する小説をいくつか(5~10作品ずつ)読むことで傾向を測りました。これまでの結果と合わせて考えると、クラスタ0:お嬢様への転生,異世界,恋愛,乙女ゲームクラスタ1:ローファンタジークラスタ2:ハイファンタジークラスタ3:ローファンタジー,地の文でキャラクター名を使っている小説が多い。クラスタ4:あまりまとまっていないクラスタ5:ハイファンタジー,現地の人が主人公?,冒険者とかダンジョンとかクラスタ6:ハイファンタジー,異世界転生,ゲーム風世界観クラスタ7:ハイファンタジー,異世界転生,ゲーム的ではない感じこのようなクラスタになっていると推測しました。全体数に対して調査件数が少ないですが、クラスタごとに小説の内容の傾向が明確に違うことは確認できました。小説のジャンルによるクラスタリングは一応できましたが、8つのクラスタに大きく分類したためクラスタ内でも内容の散らばりがありました。そこで、小説のID(以降、Nコードと呼びます)を与えたときに、類似度の高い順に小説名を表示するプログラムを作りました。これで気に入った小説と似た小説を探せるようになるはずです!以下にプログラムの主要部分のみを載せておきます。文書ベクトルデータを返す。指定した小説が学習済みかを調べる。小説をスクレイピングしてくる。print("未知の小説です。print()似ている小説を探してみる実際に学習に用いた小説を使って類似小説を検索してみます。追放されたワケあり魔術師~伝説の賢者として世界を救うこちらの小説について似ている小説を探してみます。similarity=0.7337366938591003タイトル:パーティーを追放されました。similarity=0.7283002734184265検索に使った小説とヒットした類似小説は多くが共通して以下の特徴を持っていました。ハイファンタジー、現地主人公(異世界転生や転移でない)、成り上がり、冒険者、1話くらいで冒険者パーティから追放されるかなり細かい内容まで類似した小説を探すことに成功しました。未知の小説に適用してみるDoc2Vecは未知の文書も学習済みモデルを用いてベクトル化することができます。実装した関数は未知の小説のNコードを指定した場合、自動で小説をスクレイピングして類似小説を検索することができるようにしています。–に類似した小説を探してみます。