本日はACL 2017のベストペーパーの1つである以下の論文で用いられている文書表現の方法を紹介します。
この論文は、固有表現認識をFeedForward Neural Networkを使って文書分類的に解くという論文です。手法としては、メンションと呼ばれる固有表現候補の左右に位置するコンテキストを固定長のベクトルで表現してネットワークに入力しています。これら左右のコンテキストを固定長のベクトルで表現する際に使われるのが本記事で紹介するFOFE(Fixed-size Ordinally Forgetting Encoding)です。
FOFEの特徴として、単語の位置情報を考慮しつつ文書を固定長で表現できることにあります。今日はこのFOFEを使って語順を考慮しつつ文書を固定長で表現する方法を紹介します。
FOFEとは?
FOFE(Fixed-size Ordinally Forgetting Encoding)は、可変長の系列を固定長の表現に変換する方法です。特徴としては、変換した系列は一意であり、また情報の損失なく変換できる点です。この辺の話は以下の論文を参照。
- A Fixed-Size Encoding Method for Variable-Length Sequences with its Application to Neural Network Language Models
- The Fixed-Size Ordinally-Forgetting Encoding Method for Neural Network Language Models
FOFEは自然言語処理で文書を表現するのによく使われるBag-of-Words(BoW)に似ています。違いとしては、位置情報を考慮するために忘却係数(forgetting factor)を使っている点です。BoWがどんな長さの文でも固定長で表現できたように、FOFEも任意長の文を固定長で表現することができます。
FOFEについて説明するために、まずはいくつかの記法を導入します。まず、各単語はボキャブラリをVとしたとき、次元数|V|のone-hotベクトルで表現することができます。ここで長さTの系列をで表し、 を S の t()番目の単語のone-hotベクトルとします。t番目の単語までをFOFEで表現した は以下のように再帰的に定義されます。
$$ \boldsymbol{z_t} = \left\{ \begin{array}{} \boldsymbol{0} & ( t = 0) \\ \alpha \cdot \boldsymbol{z_{t-1}} + \boldsymbol{e_t} & ( otherwise) \end{array} \right. $$
ここで ()は忘却係数(forgetting factor)と呼ばれます。ここで、の次元数が|V|であり、元の系列長であるTに無関係に表現されるのがポイントです。 はあらかじめ決めておきます。
ここまでの説明だとピンとこないと思うので、具体例を使ってみます。まず、ボキャブラリ数は3とします。その中身は{A, B, C}とでもしておきます。そして、それらのone-hot表現はそれぞれ [1, 0, 0]、[0, 1, 0]、[0, 0, 1] とします。このとき「ABC」を定義に従って計算すると [ ] になります。「ABCBC」なら [ ] で表現されます。
以上で、FOFEについての説明は完了しました。次はFOFEの実装をしていきます。
FOFEの実装
FOFEは以下のように実装できます。FOFEVectorizer
はscikit-learnのCountVectorizerを継承して書いています。継承して書いた関係で、初期化メソッドが少し長くなっていますが、中身に難しいところはないはずです。FOFEへの変換処理はtransform
メソッドに書いてあります。
import numpy as np from sklearn.feature_extraction.text import CountVectorizer class FOFEVectorizer(CountVectorizer): """Convert raw documents to FOFE matrix. Attributes: alpha: float. forgetting factor. """ def __init__(self, alpha=0.5, input='content', encoding='utf-8', decode_error='strict', strip_accents=None, lowercase=True, preprocessor=None, tokenizer=None, analyzer='word', stop_words=None, token_pattern=r"(?u)\b\w\w+\b", ngram_range=(1, 1), max_df=1.0, min_df=1, max_features=None, vocabulary=None, binary=False, dtype=np.int64): super(FOFEVectorizer, self).__init__( input=input, encoding=encoding, decode_error=decode_error, strip_accents=strip_accents, lowercase=lowercase, preprocessor=preprocessor, tokenizer=tokenizer, analyzer=analyzer, stop_words=stop_words, token_pattern=token_pattern, ngram_range=ngram_range, max_df=max_df, min_df=min_df, max_features=max_features, vocabulary=vocabulary, binary=binary, dtype=dtype) self.alpha = alpha def fit_transform(self, raw_documents, y=None): """Learn vocabulary, return FOFE matrix. This is equivalent to fit followed by transform. Args: raw_documents : iterable an iterable which yields either str, unicode or file objects Returns: X : FOFE matrix, [num_samples, num_vocabulary] """ super(FOFEVectorizer, self).fit_transform(raw_documents) return self.transform(raw_documents) def transform(self, raw_documents): """Transform documents to FOFE matrix. Uses the vocabulary learned by fit or fit_transform. Args: raw_documents : iterable an iterable which yields either str, unicode or file objects Returns: X : FOFE matrix, [num_samples, num_vocabulary] """ X = np.zeros((len(raw_documents), len(self.vocabulary_))) analyze = self.build_analyzer() for i, doc in enumerate(raw_documents): for word in analyze(doc): X[i, :] *= self.alpha try: j = self.vocabulary_[word] except KeyError: # Ignore unknown word. continue X[i, j] += 1 return X
FOFEVectorizer
は以下のようにして使うことができます。
>>> raw_documents = ['AA BB CC'] >>> vectorizer = FOFEVectorizer() >>> vectorizer.fit_transform(raw_documents) [[0.25 0.5 1. ]] >>> raw_documents = ['AA BB CC BB CC'] >>> vectorizer.transform(raw_documents) [[0.0625 0.625 1.25 ]]
文長が長くなると、アンダーフローを起こしそうですね。元の論文ではコンテキストのサイズがそれほど大きくなかったので、アンダーフローは問題にならなかったのだと思います。長めの文書分類で使う際には一工夫いりそうです。
おわりに
本記事では語順を考慮して文書を固定長で表現する技術であるFOFEを紹介しました。本記事が皆様のお役に立てば幸いです。
私のTwitterアカウントでも機械学習や自然言語処理に関する情報をつぶやいています。
この分野にご興味のある方のフォローをお待ちしています。