BrainPad株式会社ブレインパッドのデータ活用に関する取り組みや製品・サービス開発の裏側、社員の日常などをご紹介します。分析エントリーこんにちは、アナリティクスサービス本部の三浦です。本日はSkip-thoughtというアルゴリズムを用いた、テキストの数値ベクトル化についてご紹介したいと思います。Kirosらによって2015年に考案された、文書中の文の表現を数値ベクトル化する、深層学習のアルゴリズムです。アルゴリズムの特徴として教師なし学習であることが挙げられ、学習する際にラベル付けやアノテーションされたテキストは必要ありません。順序付けられた文で構成された文書*1さえ存在すれば、それを元に学習を行いモデルを構築することが可能です。数年前に単語をベクトル化できるWord2vec*2が話題になりましたが、Skip-thoughtは単語ではなく文やフレーズをベクトル化すると思っていただくと、わかりやすいかと思います。本ブログは、ある程度RNNやLSTMについて理解していることを前提としています。Qiitaなどに詳しくまとまっていますので、ご存じない方はまずそちらをご覧ください。本文中では、Skip-thoughtアルゴリズムの紹介Wikipediaデータの取得とテキストの前処理ライブラリの使い方と順番に説明します。最後にWikipediaの文書の数値ベクトル化を行い、ベクトル同士のコサイン類似度を計算することで、入力文に類似した文の検索を行います。説明は少し長くなりますので、Skip-thoughtでどんなことができるのか知りたい!という方は、「類似した文の検索」という項目からご一読ください。■アルゴリズムの紹介Skip-thoughtは文のベクトル化を行いますが、そのアルゴリズムはWord2vecとは大きく異なります。Word2vec(skip-gram)では入力単語に対して、周辺に位置する単語を予測することで、単語同士の共起関係を学習します。一方でSkip-thoughtは、エンコーダー-デコーダーと呼ばれる形式を取っており、入力文の単語の系列をエンコードし、前後の文の単語を出力として順番に予測します。Skip-thoughtでは文とその前後の文の共起関係を学習することで、文のエンコード結果である中間層の値を数値ベクトル(Skip-thoughtベクトル)として得ます。従来の手法ではWord2vecで得られた単語ベクトルの和を取るなど、単語ベクトルの合成方法を工夫することで文やフレーズのベクトルを得ていました。Skip-thoughtは文をエンコードすることでベクトル化を行っており、その意味でSkip-thoughtモデルの学習は、単語ベクトルの合成方法自体について学習していると言うことができます。言葉だけで説明してもわからないと思いますので、論文の実装を元にSkip-thoughtのネットワーク構造を図で説明したいと思います。洗濯が捗る」という3つの文で構成される文章があるとし、いま入力文が「今日/も/晴れ/だ」であるとします(スラッシュは分かち書きの結果を表します)。このときSkip-thoughtでは、前の文「5月/に/なっ/た」と後ろの文「洗濯/が/捗る」を次のように予測します。図の各矢印は重み行列を表し、色が同じ矢印間では重み行列の値が共有されています。Skip-thoughtモデルは図のように、RNNを用いた入力文のエンコーダーと、前後の文を出力する2つのデコーダーで構成されます。このネットワーク構造について、エンコーダーとデコーダーについてそれぞれ説明します。図のカッコ内は、論文内で設定された重み行列のサイズを表します。エンコーダーでは、one-hotで表現された単語を入力として受け取り*3、単語のベクトル(以下、単語embeddingと呼びます)化を行います。単語embeddingを入力とし、隠れ層のノードにGRU(図中の塗りつぶされたノード)を用いたRNNにより、入力文のエンコードを行います。入力文の単語をRNNに順次与え、最後の単語が入力された次の時点の隠れ層の出力値が、Skip-thoughtベクトルとして得られます。単語embedding層の重み行列は、エンコーダーとデコーダーで共通のものを用います。入力層の次元は20,000しかありませんが、テキスト中の出現回数が上位20,000単語*4に入力層のノードを割り当て、あまり出現しない単語は未知語としてまとめて扱います。RNNの隠れ層のノードとして用いられるGRUはLSTM同様の働きをしますが、ゲート数が2つとLSTMより少ないという特徴があります。通常のRNNで必要な重み行列だけでなく、GRUの2つのゲート制御用にも重み行列は必要です。隠れ層の重み行列は、それぞれ3つずつ存在します。ここではGRU自体の詳細は述べませんので、GRUについて気になる方は上で紹介した「わかるLSTM」の説明をご覧ください。デコーダーデコーダーでは、入力文をエンコードして得られたSkip-thoughtベクトルを隠れ層の初期状態として、エンコーダー同様GRUを用いたRNNにより前後の文の出力を行います。入力層から単語embedding層までの流れは、エンコーダー時と同様です。RNNの出力隠れ層と出力層間は全結合となっており、出力層の活性化関数はsoftmaxで、各ノードに対応する単語の出現確率を出力します。次の式で与えられます。入力文のエンコード結果、つまりSkip-thoughtベクトルを表します。入力文のSkip-thoughtベクトル、およびその前後の文の出現単語を与えた際の、出力単語の確率の対数の和が最大化されるよう最適化が行われます。以上、簡単にではありますが、Skip-thoughtモデルのネットワーク構造を紹介しました。ちなみにエンコーダー-デコーダー形式をとる同様のモデルに、機械翻訳で有名なSequence-to-Sequenceが存在します。入力文の次の文だけでなく、前の文もデコードするRNNのノードにLSTMではなくGRUを用いる隠れ層は多層ではなく1層などが挙げられます。モデル適用時モデルの適用時は入力した文のエンコード結果を、Skip-thoughtベクトルとして得ます。目的がSkip-thoughtベクトルを得るだけならば、デコーダーは必要ありません。学習時に入力層のノード数は20,000しか用いず、出現回数上位の20,000単語以外は未知語として扱いました。そのためモデル適用時の入力文に対しても、多くの単語が未知語として扱われ、同一の単語embeddingが与えられてしまいます。この問題を防ぐために、Word2vecなど別の方法で学習した単語embeddingモデルを用いて、このモデルで得られる単語embeddingから、Skip-thoughtモデル内の単語embeddingへのマッピングを行います。単語embedding間のマッピングを行うのには、異なる方法により得られた単語embeddingであっても、それぞれの空間内での位置関係は類似しているという背景があります。引用)上の図は左が英語のWord2vec、右がスペイン語のWord2vecにより得られた単語embeddingを、可視化のため2次元に圧縮して回転させたものです。英語とスペイン語という異なる言語間であっても、単語同士の位置関係が類似していることがわかります。それぞれの単語間の距離は各言語でほぼ共通であると考えられるため、単語間の共起関係を学習したWord2vecにおいても、位置関係がある程度保たれています。図では可視化のために回転などを行っていますが、いま行列を用いた線形変換により単語embedding同士を対応付けます。両モデルに共通して含まれる単語*5のembeddingを用いて、リッジ回帰により学習します*6。単語embeddingの次元は同じでなくても構いませんが、変換後の単語embeddingが得られなくなるので、事前学習したWord2vecの語彙数は多くなければなりません。用いて頻出単語以外の単語をマッピングすることで、それらの単語のSkip-thoughtモデル空間内の単語embeddingが与えられ、RNNの入力とすることが可能になります。■使用するデータそれでは実際にテキストデータを用いて、Skip-thoughtモデルの学習を行いたいと思います。手持ちのデータが存在しないため、ここでは日本語版Wikipediaをダウンロード*7し、前処理を行ってコーパスとして用います。データのダウンロードデータのダウンロードは「Wikipedia:データベースダウンロード」ページ内に存在するリンク先から行います。ページのタイトルのみ、概要のみなど、取得できるファイルの種類は多数存在するので、ダウンロードページ内の記載やこちらのページをご覧ください。データがダンプされた日付が入ります)をダウンロードして用います。容量が2.3GBあるため、ダウンロードには数十分かかります。XMLタグの除去ダウンロードしたファイルはXMLファイルとなっており、本文のテキスト以外にもさまざまなタグを含みます。WP2TXTというツールを用いてこれらのタグを除去し、テキストへの変換を行います。WP2TXTではタグ除去時のオプションをいろいろと指定できます。表を除去しないなどです。いま本文テキストと関係ない記事内の表やリスト、各セクションの見出しは除去しておきます。タイトルは記事内容の識別に必要なので残しておきます。jawiki-yyyymmdd-pages-articles.txt上記コマンドにより、1時間半程度かけてタグの除去を行います。1ファイルにまとめておきます。文字列の前処理WP2TXTを用いてタグを除去しても、テキスト中に記事のタイトルや画像ファイルなどのマークアップが含まれているため、これらの除去を行います。詳しくは説明しませんが、前処理の方針としては以下のとおりです。以上により句点単位で1行に分割し、1行を一つの文としてコーパスの作成を行います。記事タイトルは抽出して別ファイルとし、Skip-thoughtのアルゴリズムを考慮して、3文未満で構成される記事については今回は除去します。2,277,912となります。■Skip-thoughtモデルの学習Skip-thoughtは論文の著者により、GitHub上にその実装が公開されています。著者らの実装はTheanoを用いていますが、TensorFlowでの実装も存在します。GitHubここでは著者らのレポジトリをクローンして学習を行います。ディレクトリ内に存在します。より学習したモデルをロードし、新規の文に対してSkip-thoughtベクトルを付与となります。以下、GitHub上のREADMEに沿って順に説明します。toolsしていることを前提とします。学習に用いるデータは、あらかじめ以下のような分かち書きされた文のリストにしておく必要があります。...]プログラム実行の際には、Theanoの設定が必要となります。notebookなどとします。メモリサイズの割合を指定します。入力データから単語辞書の作成を行い、保存します。単語がキー、出現回数の順位が値となった辞書です。保存先のパスを指定します。saveto='model.npz')でモデルの学習を行います。ステップ1で作成した辞書のパスとなります(カッコ内はデフォルト値)。