Amazon SageMakerのBlazingText(Word2Vec)で単語ベクトルを作ってみた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、大澤です。

当エントリではAmazon SageMakerの組み込みアルゴリズムの1つ、「BlazingText」を用いた単語ベクトルの生成方法についてご紹介していきたいと思います。

「組み込みアルゴリズム」の解説については下記エントリをご参照ください。

目次

概要説明:BlazingTextとは

BlazingTextは単語ベクトルを生成する手法であるWord2Vecの実装です。 単語ベクトルは名前の通り、単語をベクトルで表現したものです。似た意味の単語同士であれば、似た値の単語ベクトルになるようになっています。

BlazingTextによる単語ベクトル生成の用途としては、単語の分類や文章の意味解析の前段で使用することなどが考えられます。

また、BlazingTextでは教師あり学習として、文章の分類としても使うことができます。使い方についてはSageMaker Examplesを確認していただければと思います。

組み込みアルゴリズム:BlazingTextの解説と実践

Amazon SageMaker ExamplesのBlazingTextの例に沿って進めていきます。

今回の目的はBlazingTextのモデルを英語版Wikipediaの過去のデータの一部(text8)で学習させて、単語ベクトルを作成することです。

ノートブックの作成

SageMakerのノートブックインスタンスを立ち上げて、
SageMaker Examples

Introduction to Amazon algorithms

blazingtext_word2vec_text8.ipynb

use
でサンプルからノートブックをコピーして、開きます。
ノートブックインスタンスの作成についてはこちらをご参照ください。

環境変数とロールの確認

学習データ等を保存するS3のバケット名と保存オブジェクト名の接頭辞を決めます。

import sagemaker
from sagemaker import get_execution_role
import boto3
import json

sess = sagemaker.Session()

role = get_execution_role()
print(role) # This is the role that SageMaker would use to leverage AWS resources (S3, CloudWatch) on your behalf

bucket = sess.default_bucket() # Replace with your own bucket name if needed
print(bucket)
prefix = 'sagemaker/DEMO-blazingtext-text8' #Replace with the prefix under which you want to store the data if needed

データ取得

英語版wikipediaのデータに対して前処理を施してあるデータセットtext8を学習に使います。 そのデータをダウンロードし、解凍します。

!wget http://mattmahoney.net/dc/text8.zip -O text8.gz

# Uncompressing
!gzip -d text8.gz -f

学習データをS3にアップロードします。

train_channel = prefix + '/train'

sess.upload_data(path='text8', bucket=bucket, key_prefix=train_channel)

s3_train_data = 's3://{}/{}'.format(bucket, train_channel)

s3_output_location = 's3://{}/{}/output'.format(bucket, prefix)

学習

BlazingText用のコンテナを取得します。

region_name = boto3.Session().region_name
container = sagemaker.amazon.amazon_estimator.get_image_uri(region_name, "blazingtext", "latest")
print('Using SageMaker BlazingText container: {} ({})'.format(container, region_name))

学習に用いるEstimatorを作成します。

bt_model = sagemaker.estimator.Estimator(container,
                                         role,
                                         train_instance_count=2,
                                         train_instance_type='ml.c4.2xlarge',
                                         train_volume_size = 5,
                                         train_max_run = 360000,
                                         input_mode= 'File',
                                         output_path=s3_output_location,
                                         sagemaker_session=sess)

ハイパーパラメータを設定します。
ハイパーパラメータに関する詳細はドキュメントをご確認ください。

vector_dimevaluationという項目はこの後、参照するので確認しておきます。
vector_dimは単語ベクトルの長さで、evaluationは学習後にモデルの評価を行うかどうかです。

bt_model.set_hyperparameters(mode="batch_skipgram",
                             epochs=5,
                             min_count=5,
                             sampling_threshold=0.0001,
                             learning_rate=0.05,
                             window_size=5,
                             vector_dim=100,
                             negative_samples=5,
                             batch_size=11, #  = (2*window_size + 1) (Preferred. Used only if mode is batch_skipgram)
                             evaluation=True,# Perform similarity evaluation on WS-353 dataset at the end of training
                             subwords=False) # Subword embedding learning is not supported by batch_skipgram

学習データの登録を行います。

train_data = sagemaker.session.s3_input(s3_train_data, distribution='FullyReplicated',
                        content_type='text/plain', s3_data_type='S3Prefix')
data_channels = {'train': train_data}

学習!

bt_model.fit(inputs=data_channels, logs=True)

モデルの展開

モデルをエンドポイントに展開します。

bt_endpoint = bt_model.deploy(initial_instance_count = 1,instance_type = 'ml.m4.xlarge')

モデルの確認

まずは展開したモデルが正常に動いてるか確認してみます。
単語をモデルに渡して、単語ベクトルを受け取ることが出来ます。
この時学習していない単語を渡すと、0だけのベクトルが返ってきます。

words = ["awesome", "blazing"]

payload = {"instances" : words}

response = bt_endpoint.predict(json.dumps(payload))

vecs = json.loads(response)
print(vecs)

単語ベクトルが返って来ました。やはり、数字だけ見てもよくわかりません。
レスポンスは以下のような形式になっています。

[
    {
        'vector': [学習時にvector_dimで指定した長さのベクトル],
        'word' : 'vectorに対応する単語'
    }
]

次に、モデルの評価結果の確認と、モデルから得た各単語のベクトルを可視化を行なっていきます。   学習したモデルのデータをダウンロードして解凍します。

s3 = boto3.resource('s3')

key = bt_model.model_data[bt_model.model_data.find("/", 5)+1:]
s3.Bucket(bucket).download_file(key, 'model.tar.gz')
!tar -xvzf model.tar.gz

学習時にevaluationをTrueにしていた場合は単語同士の類似度について評価が行われます。評価の際の教師データとして、WordSim353というデータセットを用います。このデータセットには人によって設定された単語同士の類似度が登録されています。
モデルから作成した単語ベクトルによって計算した単語同士のコサイン類似度を計算して、単語同士を類似度によって順位付けします。その単語ペアの類似度ランキングがWordSim353のデータに含まれる類似度ランキングとどれだけ近いかをスピアマンの順位相関係数を用いて評価したものが、モデルデータのeval.jsonに含まれています。
spearmans_rho(スピアマンの順位相関係数)が1に近いほど良いモデルと言えます。

!cat eval.json

なかなか良さそうな結果です。

次に`vectors.txt`から各単語ベクトルを取り出します。

import numpy as np
from sklearn.preprocessing import normalize

# Read the 400 most frequent word vectors. The vectors in the file are in descending order of frequency.
num_points = 400

first_line = True
index_to_word = []
with open("vectors.txt","r") as f:
    for line_num, line in enumerate(f):
        if first_line:
            dim = int(line.strip().split()[1])
            word_vecs = np.zeros((num_points, dim), dtype=float)
            first_line = False
            continue
        line = line.strip()
        word = line.split()[0]
        vec = word_vecs[line_num-1]
        for index, vec_val in enumerate(line.split()[1:]):
            vec[index] = float(vec_val)
        index_to_word.append(word)
        if line_num >= num_points:
            break
word_vecs = normalize(word_vecs, copy=False, return_norm=False)

モデルから得た単語ベクトルをt-SNEという次元削減アルゴリズムを使って特徴量を抽出し、可視化します。

from sklearn.manifold import TSNE

tsne = TSNE(perplexity=40, n_components=2, init='pca', n_iter=10000)
two_d_embeddings = tsne.fit_transform(word_vecs[:num_points])
labels = index_to_word[:num_points]
from matplotlib import pylab
%matplotlib inline

def plot(embeddings, labels):
    pylab.figure(figsize=(20,20))
    for i, label in enumerate(labels):
        x, y = embeddings[i,:]
        pylab.scatter(x, y)
        pylab.annotate(label, xy=(x, y), xytext=(5, 2), textcoords='offset points',
                       ha='right', va='bottom')
    pylab.show()

plot(two_d_embeddings, labels)

上の方で team,game,player,character,computerなどが固まっていたり、右下ではarea,city,land,populationが近くに描画されていたりと、直感的なイメージとそんなに離れていない結果となりました。

エンドポイントの削除

余分なお金を使わないように、エンドポイントを削除します。

sess.delete_endpoint(bt_endpoint.endpoint)

まとめ

Amazon SageMakerの組み込みアルゴリズムの一つであるBlazingTextを用いて単語ベクトルの作成を行いました。

文章の意味解析や分類など文章に対して何かしらの解析を行う際には、単語ベクトル化はほぼ必須となります。ぜひとも使えるようになっておきたいものです。

以下シリーズではAmazon SageMakerのその他の組み込みアルゴリズムについても解説しています。宜しければ御覧ください。