こんにちは。エクサウィザーズAIエンジニアの玉城です。
本やインターネットで調べ物をする際、情報量が多すぎてどこを見たら良いのか分からなくなってしまった、という経験はないでしょうか。このように情報量の豊かさが返って人の判断を鈍らせてしまう問題を情報オーバーロードと言います。
インターネットの普及に伴う情報オーバーロードに対して、自動文書要約の技術が注目されています。今回、exaBaseではディープラーニング技術を活用した自動文書要約モデルを公開致しました。こちらにてソースコードと学習済みモデルをダウンロードし、以下のように英文ニュース記事から簡潔な要約文が生成可能なのでぜひ試してみてください。
原文(学習時に使用していないデータ) :
モデルが出力した要約文:
spotify believes it has identified the average age of midlife crises at 42 . users aged around 42 drop their usual playlists which usually contain hits from their youth in favour of today 's chart toppers from the likes of rihanna and sam smith .
人間が作成した要約文:
spotify believes it has identified the average age of midlife crises at 42 . staff analysed data and found users aged 42 drop their usual playlists . start listening to today 's chart toppers , such as rihanna and sam smith .
本記事では以下のような構成で自動文書要約の種類などの基本的な概念やディープラーニングを中心とした研究動向、exaBase掲載モデルの詳細をご紹介していきたいと思います。
自動文書要約の種類
自動文書要約は以下のような観点で分類されます。
- 入力方式
- Single document:単一の文書
- Multi-document:複数の文書
- 条件付け
- Generic:特に指示を出さない
- Domain specific:特定の分野に特化
- Query-based:与えられた質問やキーワードに対する要約
- 要約範囲
- Informative:重要な内容を網羅
- Indicative:文章を読むべきか判断する情報を示す。本や映画のレビュー等
- 出力スタイル
- Keyword:キーワードのみ
- Headline:ニュース記事のヘッドラインのように、一行のみ出力
- その他指定された長さの要約文
- 要約手法
- Extractive:元の文章から抽出
- Abstractive:人による要約のように文章の言い換えを利用する
古典的手法
ディープラーニング以前は以下のような人によって定義された特徴を用いた手法が主流でした。
- 情報の出現頻度
- 重要な情報ほど高い頻度で出現すると仮定し、キーワードやキーコンセプトを抽出
- それらを多く含むセンテンスを高く評価
- センテンス間の類似度
- 他のセンテンスと類似するセンテンスはその文書の核となる情報を持っていると仮定
- 検索エンジンで用いられるPage Rankアルゴリズムなど、文章のグラフ構造を用いる
- その他
- title feature::タイトルに含まれる単語を含むセンテンスを高く評価
- sentence position:初めや最後のパラグラフに重要なコンセプトが含まれていると仮定
- keyword frequency:Query-based要約の場合、キーワードを多く含むセンテンスを高く評価
ディープラーニングを用いた手法
近年は機械翻訳などの自然言語処理タスクにおいてディープラーニングを用いるのが一般的になってきましたが、自動文書要約においても同様の傾向があります。ここではディープラーニングを用いたExtractive/Abstractive手法を紹介します。
Extractive Summarization (抽出型)
CNN
CNNはコンピュータービジョンの分野でよく用いられる手法ですが、自動文書要約にも応用されています。
Extraction of Salient Sentences from Labelled Documents
Word Embedding(単語の分散表現)は自然言語処理ではモデルへ単語を入力する手法として広く使われています。こちらの論文ではWord Embeddingを拡張したSentence EmbeddingとDocument Embeddingを導入しています。まず、センテンス単位でWord Embeddingを結合させたSentence Matrixに対してCNNを適用することでそのセンテンスの分散表現であるSentence Embeddingを生成、同様の手法でDocument Embeddingを生成します。最終的にはDocument Embeddingから元の文章のクラス分類ができるように学習します。
では上記モデルをどのように文書要約タスクへ役立てれば良いでしょうか?CNNを用いたコンピュータービジョンの分野では可視化技術を用いて、どの入力値がロス関数に対して大きく作用しているかを算出することが可能です。本論文ではSentence Embeddingを経由することで、単語単位ではなくセンテンス単位での影響度の数値化を可能にしました。この値をそのセンテンスの重要度と捉え、上位k個のセンテンスを抜き出すことで要約文を生成しています。
通常自動文書要約タスクは教師ラベル(センテンスを抽出すべきか否か)の作成コストが非常に高いですが、本手法では文書のクラス分類ラベルのみを使用することでコストを大幅に抑えています。また、生成された要約文書を入力としたクラス分類を実施することである程度要約文の質を測ることが可能なようです。
CNN + Seq2Seq + Attention
Sequence-to-Sequence + Attentionは機械翻訳の分野で多用される技術です。 通常Attentionは「デコードする際に入力のどの部分に着目すべきか」を判断するのに用いるのですが、こちらの論文ではAttentionの値を元に直接センテンスの抽出を行なっています。
Neural Summarization by Extracting Sentences and Words
まず、Word Embeddingで入力されたセンテンスに対してCNNを適用します(sentence encoder)。出力されたセンテンスのベクトルを順次LSTMに入力することでドキュメントを表現する隠れ層が生成されます(document encoder)。センテンスの抽出にはもう一つLSTMを準備します(sentence extractor)。
あるタイムステップにおけるsentence extractorの隠れ層は直前のタイムステップの隠れ層と値pによって重み付けされたセンテンスのベクトルによって決定されます。この値pこそがセンテンスが抽出されるべきか判断する指標となります(pが大きいほどそのセンテンスの重要度が高い)。pの値はdocumenter encoderとsentence extractorの隠れ層を結合したベクトルをMLP(多層のニューラルネットワーク)に入力し、シグモイド活性化関数を介することで0から1の間の数値として得ることができます。
Abstractive Summarization (抽象型)
抽象型の自動文書要約においてもやはりSequence-to-Sequenceモデルを使用するのが主流のようです。こちらの論文ではSequence-to-Sequence + Attentionをベースラインモデルとし、そこに二段階の改良を加えています。
Get To The Point: Summarization with Pointer-Generator Networks
まずは以下をご覧ください。
一番上の段がソーステキストで、要点が青字になっています。その下の段がベースラインのSequence-to-Sequence + Attentionの出力例です。純粋な抽象型の要約手法なので、人名などあらかじめ準備された辞書に存在しない単語(OOV)を出力することができません。また、要約精度もあまり高くなく、事実に反する要約内容が見られます。次の段はベースラインモデルにPointer-Genを加えた、抽象型と抽出型の良いとこ取りをしたモデルの出力です。ソーステキストから人名を抽出できており、要約内容も事実に沿ったものとなっています。ただし、同じ内容を繰り返すクセがあります。一番下の段にあるのがベースライン + Pointer-GenにCoverage機能を付け足たしたモデルの出力で、繰り返し問題が解消されています。
では、ベースラインモデルから一つずつ見ていきましょう。
ベースラインSequence-to-Sequence + Attention
ベースラインモデルは純粋な抽象型モデルです。まず、ソーステキストの単語のWord Embeddingが一つずつエンコーダーである双方向LSTMに入力され、Encoder Hidden Statesシーケンスが生成されます。文章内のある単語を推測する際、その単語の前だけではなく、後ろにあるテキストの情報があった方が推測しやすいですよね。なのでエンコーダーにはソーステキストを前向きと後ろ向きで入力する双方向LSTMを用いるのが一般的になっています。
デコーダー(片方向LSTM)側ではEncoder Hidden Statesと前ステップで出力した単語のWord Embeddingが入力され、Decoder Hidden Statesシーケンスが生成されます。
Attention Distribution at は以下のようにEncoder Hidden States とDecoder Hidden State を用いて計算され、「次の単語を出力する際にソーステキストのどの単語に着目すれば良いか」を示してくれます。
Encoder Hidden Statesに対応するAttention Distributionを掛け合わせることで重み付けをし、それらを足し合わせたものがContext Vectorとなります。Context VectorとDecoder Hidden Statesを結合させ、二つの層を経由し最終的にsoftmaxにかけることで準備された辞書から最適な単語を選択しています。
図を見ると"victorious"や"win"という単語へのAttentionによる重み付けが大きいため、最終的に辞書内からそれらに近い"beat"という単語が選択されていることがわかるかと思います。
Pointer-Gen
Pointer-Genモデルは抽象型と抽出型の手法をブレンドしたモデルとなっています。あらかじめ準備された辞書から単語を選択するだけではなく、ソーステキストからの単語の抽出を可能にしました。
ベースラインモデルと比較すると、Pgenというものが追加されています。このPgenが単語を生成するか抽出するかを判断するスイッチのような役割を担います。
Pgenの値はContext Vector とDecoder Hidden State 、さらにデコーダーへの入力を用いて以下のように 計算されます。
準備された辞書とソーステキストに存在する単語を結合させた新しい辞書Pから単語wが選ばれる確率は以下のように計算されます。
図を見るとソーステキストの"Argentina"という単語が着目されているため、Final Distributionでは辞書内(緑)とオリジナルテキスト内(青)の合計スコアが最も大きい"Argentina"が選択されています。尚、"2-0"のように辞書に存在しない単語においてはPvocabの値は0となります。
Pointer-Gen + Coverage
ベースライン + Pointer-Genのみでは同じ内容を繰り返してしまう問題が生じるので、それを解消するためにCoverage機能を追加します。
まず、タイムステップtまでのAttention Distributionを合計したCoverage Vectorを導入します。
Coverage Vectorはソーステキスト内のそれぞれの単語がどれだけ網羅されたかを知る指標になります。これを以下のようにAttention Distributionを決定する式に織り込みます。
また、以下に定義されるCoverage Lossを通常のロス関数に足し合わせることによって同じ内容の繰り返しに対してペナルティを与えています。
最終的なロス関数は以下のようになります。
モデル評価手法
自動文書分類モデルの評価にはROUGE、BLEU、METEORなど様々な手法が存在します。ここではよく用いられるROUGEによる評価手法を紹介します。
ROUGEにおけるRecall(再現率)とPrecision(同様適合率)
ROUGEでは生成された要約文をレファレンス要約文(多くの場合人の手で作成されたもの)と比較することで評価を実施します。二つの文の比較手法として、RecallとPrecisionという概念を理解する必要があります。
例えば以下のような要約文がモデルから出力されたとしましょう。
there is a pencil and an eraser on the desk
以下はレファレンス要約文です。
there is a pencil on the desk
Recallは以下のように計算されます。
しかし、これだけだとモデルから出力される要約文が長ければ長いほど高いRecallを達成し易くなることにお気づきでしょうか。なので以下のように計算されるPrecisionを考慮する必要があります。
評価には上記RecallとPrecisionの両方を考慮したF-scoreを用います。
ただし、モデルの出力文の長さが制限されているなどPrecisionの値をそれほど気にする必要がない場合にRecallの値のみ使用されることもあります。
ROUGE-N、ROUGE-S、ROUGE-L
- ROUGE-N:ROUGE-1, ROUGE-2などN-gram単位で生成された要約と正解要約を比較
- ROUGE-S:単語の組み合わせ(順番は問わない)で評価
- ROUGE-L:一致する最大のシーケンスで評価
データセット
- CNN/ Daily Mail
- 約30万のレファレンス要約付きの英文ニュース記事。各記事は平均39のセンテンスで構成される。
- DUC 2001~2007
- 英文ニュース記事の用いた最もメジャーなデータセット。タスクに合わせて以下のようなデータを提供
- それぞれの記事にSingle document用の短いレファレンス要約(各75byte以下)
- テーマ毎にクラスタリングされたMulti document用のレファレンス要約(各665byte以下)
- Annotated English Gigaword
- 約27GBのニュース記事。ヘッドライン生成タスクに利用可
exaBase掲載モデル
こちらで紹介するモデルは抽象型の要約手法にて紹介した論文Get To The Point: Summarization with Pointer-Generator NetworksのGitHubで公開されているソースコードをexaBase環境で実行できるように変更を加えたものです。ソースコードと学習済みモデルはexaBaseにてダウンロード可能です。
以下はモデル構築の一部です。
def _add_seq2seq(self): """Add the whole sequence-to-sequence model to the graph.""" hps = self._hps vsize = self._vocab.size() # 辞書のサイズ with tf.variable_scope('seq2seq'): # 初期化用 self.rand_unif_init = tf.random_uniform_initializer(-hps.rand_unif_init_mag, hps.rand_unif_init_mag, seed=123) self.trunc_norm_init = tf.truncated_normal_initializer(stddev=hps.trunc_norm_init_std) # Word Embeddingを追加(エンコーダーとデコーダーで共有) with tf.variable_scope('embedding'): embedding = tf.get_variable('embedding', [vsize, hps.emb_dim], dtype=tf.float32, initializer=self.trunc_norm_init) if hps.mode=="train": self._add_emb_vis(embedding) # tensorboard用 emb_enc_inputs = tf.nn.embedding_lookup(embedding, self._enc_batch) # エンコーダーへの入力となるshape (batch_size, max_enc_steps, emb_size)のテンソル。max_enc_stepsは文書あたりの最大単語数。 emb_dec_inputs = [tf.nn.embedding_lookup(embedding, x) for x in tf.unstack(self._dec_batch, axis=1)] # shape (batch_size, emb_size)のテンソルをmax_dec_steps個含むリスト。max_dec_stepsは出力する要約文の最大の長さ。 # エンコーダーを追加 enc_outputs, fw_st, bw_st = self._add_encoder(emb_enc_inputs, self._enc_lens) self._enc_states = enc_outputs # エンコーダーは双方向LSTMのため、片方向LSTMであるデコーダーに渡せるようにサイズ調整 self._dec_in_state = self._reduce_states(fw_st, bw_st) # デコーダーを追加 with tf.variable_scope('decoder'): decoder_outputs, self._dec_out_state, self.attn_dists, self.p_gens, self.coverage = self._add_decoder(emb_dec_inputs) # デコーダーの出力から辞書へのマッピング with tf.variable_scope('output_projection'): w = tf.get_variable('w', [hps.hidden_dim, vsize], dtype=tf.float32, initializer=self.trunc_norm_init) w_t = tf.transpose(w) v = tf.get_variable('v', [vsize], dtype=tf.float32, initializer=self.trunc_norm_init) vocab_scores = [] # softmax適用前のスコア。リスト内の各アイテムがデコーダーの1ステップ分の出力に対応。 for i,output in enumerate(decoder_outputs): if i > 0: tf.get_variable_scope().reuse_variables() vocab_scores.append(tf.nn.xw_plus_b(output, w, v)) # 線形レイヤーを適用 vocab_dists = [tf.nn.softmax(s) for s in vocab_scores] # 辞書へのスコア配分。(batch_size, vsize)の配列をmax_dec_steps個含むリスト。単語の順番はvocabulary fileと同じ。 # Pointer-generatorモデルの場合はソーステキストの単語を追加して最終スコア配分を計算する if DefineFlags.Flags.pointer_gen: final_dists = self._calc_final_dist(vocab_dists, self.attn_dists) else: # Pointer-generatorでなければvocab_distsが最終スコア final_dists = vocab_dists if hps.mode in ['train', 'validation']: # ロス計算 with tf.variable_scope('loss'): if DefineFlags.Flags.pointer_gen: # ステップ単位でのロス計算 loss_per_step = [] # shape (batch_size)を長さmax_dec_steps含むリスト batch_nums = tf.range(0, limit=hps.batch_size) # shape (batch_size) for dec_step, dist in enumerate(final_dists): targets = self._target_batch[:,dec_step] # shape (batch_size)。ターゲット単語のインデックス取得。 indices = tf.stack( (batch_nums, targets), axis=1) # shape (batch_size, 2) gold_probs = tf.gather_nd(dist, indices) # shape (batch_size)。 このステップの正しい出力単語に対する確信度 losses = -tf.log(gold_probs) loss_per_step.append(losses) # dec_padding_mask を適用してロスを取得 self._loss = _mask_and_avg(loss_per_step, self._dec_padding_mask) else: # ベースラインモデル self._loss = tf.contrib.seq2seq.sequence_loss(tf.stack(vocab_scores, axis=1), self._target_batch, self._dec_padding_mask) # this applies softmax internally tf.summary.scalar('loss', self._loss) # Attention distributionからCoverage lossを計算 if hps.coverage: with tf.variable_scope('coverage_loss'): self._coverage_loss = _coverage_loss(self.attn_dists, self._dec_padding_mask) tf.summary.scalar('coverage_loss', self._coverage_loss) self._total_loss = self._loss + hps.cov_loss_wt * self._coverage_loss tf.summary.scalar('total_loss', self._total_loss) if hps.mode == "summarization": # 推論時はbeam searchという手法を使い、ステップ毎にk個(beam size)の候補を保持しておき、最終的に確信度が最も高かった文を出力する。 assert len(final_dists)==1 # final_distsはshape (batch_size, extended_vsize)のシングルトンリスト final_dists = final_dists[0] topk_probs, self._topk_ids = tf.nn.top_k(final_dists, hps.batch_size*2) self._topk_log_probs = tf.log(topk_probs)
では早速学習を始めてみましょう。データセットは論文と同じCNN/ Daily Mailを用いています。まずはCoverage無しのベースラインSequence-to-Sequence + Pointer-Genでロスが収束するまで学習をさせます(Tesla K80 GPUにて24時間程度)。
python Document_Summarization_Driver_Main.py --mode=train --data_path=finished_files/chunked/train_* --exp_name=任意の実験名
この段階で推論させるとやはり内容の繰り返しが生じます。
maurice thibaux , 67 , caused quite a stir at sunday night 's opening show held at the carriageworks in sydney 's inner city . maurice thibaux , 67 , caused quite a stir at sunday night 's opening show held at the carriageworks in sydney 's inner city .
次に、学習したモデルをCoverage用に変換し、Coverageロスが収束するまで学習を継続します(Tesla K80 GPUにて30分程度)。
python Document_Summarization_Driver_Main.py --mode=train --data_path=finished_files/chunked/train_* --exp_name=coverageなし学習で指定した実験名 --coverage=True --convert_to_coverage_model=True
では推論させてみましょう。
maurice thibaux , 67 , caused quite a stir at sunday night 's opening show held at the carriageworks in sydney 's inner city . the french native who lives next door to the venue crashed the stage to make a complaint .
繰り返しが解消され、より中身の濃い要約文が出力されました。
最後に
今回は自動文書要約における代表的な手法を紹介させていただきました。この分野、特に抽象型の文書要約はいまだ発展途上にありますが、将来的には様々な社会課題の解決に役立つ技術となりそうです。尚、エクサウィザーズは優秀なエンジニア、社会課題を一緒に解決してくれる魔法使い”ウィザーズ”を募集しています。ご興味を持たれた方はぜひご応募ください。 採用情報|株式会社エクサウィザーズ
ExaWizards Engineer Blogでは、定期的にAIなどの技術情報を発信していきます。Twitter (https://twitter.com/BlogExawizards) で更新情報を配信していきますので、ぜひフォローをよろしくお願いします!