前回の記事では、日本語文の分散表現を計算するためのデータセットを作成した。
今回は、先日、SageMakerのObject2vecに追加された機能を使って文の分散表現を計算する。追加された機能には、ネガティブサンプリングの自動化、重み共有、学習の高速化が含まれている。いったん分散表現を計算できると、それを使って文をクラスタリングしたり、分類や回帰といった下流のタスクで使うことができる。
ここで、Object2vecのアーキテクチャについて簡単に説明しておこう。AWSの公式ブログによると、Object2vecのアーキテクチャは以下のようになっている。
まず、モデルには2つのエンコーダが存在し、これらを使って入力を固定長のベクトルに変換している。その後、Comparatorを使ってベクトルの連結や積を計算し、その結果を元にラベルを予測するということを行う。このモデルにより、文の分散表現を得られたり、ユーザ・アイテム行列から分散表現を得ることができる。
今回行う文の分散表現の計算では、片方のエンコーダに文S、もう一方のエンコーダに文SのコンテキストCを入力する。コンテキストというのは要するに文Sの周辺文のことを指している。Word2vecを学習する際に、ある単語とその周囲の単語を入力して学習させたが、それと同じ考え方を使って学習させることになる。つまり、コンテキストが似ているなら文Sも似ているだろうから、近いベクトルになるでしょうということだ。
ここまでで、Object2vecの説明を簡単にしたので、ここからは実際に触っていく。
準備
まずは、SageMakerでインスタンスを起動する必要がある。その際に、以下のリポジトリをクローンする設定にしておく。
インスタンスを起動すると、リポジトリをクローンした状態になっているので、notebooks/object2vec_document_embedding.ipynb
を開く。そうすると、ノートブックが開く。
これで準備は完了。あとは、動かすだけだが、ポイントだけ解説。
処理の流れ
大まかな処理の流れは以下のようになっている。
- ボキャブラリ作成
- 単語のインデックス化
- データの作成
- 学習
- デプロイ
この辺は一般的な自然言語処理と変わらない。
特徴的な点として、データを作成する際にポジティブサンプルだけ用意すれば済む点を挙げられる。以前はユーザ側でネガティブサンプルを用意してObject2vecに与えなければならなかったのだが、先日追加された機能により、ポジティブサンプルだけ用意すれば、あとはSageMaker側でネガティブサンプルを用意してくれるようになった。
ネガティブサンプルを生成させるために重要なのが、ハイパーパラメータに与えているnegative_sampling_rate
オプション。ここに、ネガティブサンプル数を設定することで、指定したサイズ分のネガティブサンプルを自動的に用意してくれるようになった。
hyperparameters['negative_sampling_rate'] = 3
negative_samling_rate
以外には以下の機能が追加されている。
hyperparameters['tied_token_embedding_weight'] = "true" hyperparameters['comparator_list'] = "hadamard" hyperparameters['token_embedding_storage_type'] = 'row_sparse'
tied_token_embedding_weight
は2つのエンコーダで重みを共有するパラメータ、comparator_list
はエンコーダから出力されたベクトルの組み合わせ方を指定するパラメータ、token_embedding_storage_type
は学習を高速化するパラメータとなっている。ハイパーパラメータの詳細は Object2Vec Hyperparameters を参照すると良い。ちなみに、記事を執筆している時点では日本語版のドキュメントには新しく追加されたハイパーパラメータの説明は載っていないので注意。
デプロイ
学習が終わったモデルはデプロイして使うことができる。
doc2vec_model = doc2vec.create_model( serializer=json_serializer, deserializer=json_deserializer, content_type='application/json') predictor = doc2vec_model.deploy(initial_instance_count=1, instance_type='ml.m4.xlarge')
予測はpredict
メソッドを使って行う。
sent = '今日 の 昼食 は うどん だっ た' sent_tokens = tokenizer.texts_to_sequences([sent]) payload = {'instances': [{'in0': sent_tokens[0]}]} result = predictor.predict(payload) print(result)
予測をすると、以下のように入力に対応した分散表現を得ることができる。
{ "predictions": [ {"embeddings":[0.057368703186511,0.030703511089086,0.099890425801277,0.063688032329082,0.026327300816774,0.003637571120634,0.021305780857801,0.004316598642617,0.0,0.003397724591195,0.0,0.000378780066967,0.0,0.0,0.0,0.007419463712722]}, ] }
分類や回帰の場合と分散表現を得る場合では、predict
に与えるフォーマットが若干違うので注意が必要。詳細は以下のドキュメントを参照。