0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

言語処理100本ノック(2020)-70: 単語ベクトルの和による特徴量(TensorFlow)

Last updated at Posted at 2021-10-03

言語処理100本ノック 2020 (Rev2)「第8章: ニューラルネット」70本目「単語ベクトルの和による特徴量」記録です。
TensorFlowのTFRecordsが理解し難く、非常に時間がかかってしまいました。大規模データではデータ読込などでTFRecords使うメリットありそうですが、中規模レベルでpickleに比べて大きなメリットあるか、理解できていません課題の数式は他の方のプログラムをカンニングしながらコーディングしているうちに理解できるので、最初に完全に理解しなくてもいいかと思います。
記事「まとめ: 言語処理100本ノックで学べることと成果」言語処理100本ノック 2015についてはまとめていますが、追加で差分の言語処理100本ノック 2020 (Rev2)についても更新します。

参考リンク

リンク 備考
70.単語ベクトルの和による特徴量.ipynb 回答プログラムのGitHubリンク
言語処理100本ノック 2020 第8章: ニューラルネット (PyTorchだけど)解き方の参考
【言語処理100本ノック 2020】第8章: ニューラルネット (PyTorchだけど)解き方の参考
まとめ: 言語処理100本ノックで学べることと成果 言語処理100本ノックまとめ記事
TFRecords と tf.Example の使用法 今回の課題メイン
言語処理100本ノック(2020)-50: データの入手・整形 前提ノック(元データ作成)
言語処理100本ノック(2020)-60: 単語ベクトルの読み込みと表示 前提ノック(単語ベクトル読込)

環境

後々GPUを使わないと厳しいので、Goolge Colaboratory使いました。Pythonやそのパッケージでより新しいバージョンありますが、新機能使っていないので、プリインストールされているものをそのまま使っています。

種類 バージョン 内容
Python 3.7.12 Google Colaboratoryのバージョン
google 2.0.3 Google Driveのマウントに使用
tensorflow 2.6.0 ディープラーニングの主要処理
pandas 1.1.5 タブ区切りファイル読込とその処理
gensim 3.6.0 Word2Vecのデータ読込処理
numpy 1.19.5 Numpy Arrayにする処理

第8章: ニューラルネット

学習内容

深層学習フレームワークの使い方を学び,ニューラルネットワークに基づくカテゴリ分類を実装します.

ノック内容

第6章で取り組んだニュース記事のカテゴリ分類を題材として,ニューラルネットワークでカテゴリ分類モデルを実装する.なお,この章ではPyTorch, TensorFlow, Chainerなどの機械学習プラットフォームを活用せよ.

70. 単語ベクトルの和による特徴量

問題50で構築した学習データ,検証データ,評価データを行列・ベクトルに変換したい.例えば,学習データについて,すべての事例$x_i$の特徴ベクトル$\boldsymbol{x}_i$を並べた行列$X$と正解ラベルを並べた行列(ベクトル)$Y$を作成したい.

X = \begin{pmatrix}
\boldsymbol{x}_1 \
\boldsymbol{x}_2 \
\dots \
\boldsymbol{x}_n \
\end{pmatrix} \in \mathbb{R}^{n \times d},
Y = \begin{pmatrix}
y_1 \
y_2 \
\dots \
y_n \
\end{pmatrix} \in \mathbb{N}^{n}

>
> ここで,$n$は学習データの事例数であり,$\boldsymbol x_i \in \mathbb{R}^d$と$y_i \in \mathbb N$はそれぞれ,$i \in \{1, \dots, n\}$番目の事例の特徴量ベクトルと正解ラベルを表す.なお,今回は「ビジネス」「科学技術」「エンターテイメント」「健康」の4カテゴリ分類である.$\mathbb N_{<4}$で$4$未満の自然数($0$を含む)を表すことにすれば,任意の事例の正解ラベル$y_i$は$y_i \in \mathbb N_{<4}$で表現できる.
> 以降では,ラベルの種類数を$L$で表す(今回の分類タスクでは$L=4$である).
>
> $i$番目の事例の特徴ベクトル$\boldsymbol x_i$は,次式で求める.
>
> $$\boldsymbol x_i = \frac{1}{T_i} \sum_{t=1}^{T_i} \mathrm{emb}(w_{i,t})$$
>
> ここで,$i$番目の事例は$T_i$個の(記事見出しの)単語列$(w_{i,1}, w_{i,2}, \dots, w_{i,T_i})$から構成され,$\mathrm{emb}(w) \in \mathbb{R}^d$は単語$w$に対応する単語ベクトル(次元数は$d$)である.すなわち,$i$番目の事例の記事見出しを,その見出しに含まれる単語のベクトルの平均で表現したものが$\boldsymbol x_i$である.今回は単語ベクトルとして,問題60でダウンロードしたものを用いればよい.$300$次元の単語ベクトルを用いたので,$d=300$である.
> $i$番目の事例のラベル$y_i$は,次のように定義する.
>
>```math
y_i = \begin{cases}
0 & (\mbox{記事}\boldsymbol x_i\mbox{が「ビジネス」カテゴリの場合}) \\
1 & (\mbox{記事}\boldsymbol x_i\mbox{が「科学技術」カテゴリの場合}) \\
2 & (\mbox{記事}\boldsymbol x_i\mbox{が「エンターテイメント」カテゴリの場合}) \\
3 & (\mbox{記事}\boldsymbol x_i\mbox{が「健康」カテゴリの場合}) \\
\end{cases}

なお,カテゴリ名とラベルの番号が一対一で対応付いていれば,上式の通りの対応付けでなくてもよい.

以上の仕様に基づき,以下の行列・ベクトルを作成し,ファイルに保存せよ.

  • 学習データの特徴量行列: $X_{\rm train} \in \mathbb{R}^{N_t \times d}$
  • 学習データのラベルベクトル: $Y_{\rm train} \in \mathbb{N}^{N_t}$
  • 検証データの特徴量行列: $X_{\rm valid} \in \mathbb{R}^{N_v \times d}$
  • 検証データのラベルベクトル: $Y_{\rm valid} \in \mathbb{N}^{N_v}$
  • 評価データの特徴量行列: $X_{\rm test} \in \mathbb{R}^{N_e \times d}$
  • 評価データのラベルベクトル: $Y_{\rm test} \in \mathbb{N}^{N_e}$

なお,$N_t, N_v, N_e$はそれぞれ,学習データの事例数,検証データの事例数,評価データの事例数である.

回答

回答結果

findコマンドでGoogle Driveへ出力したファイルを確認。ノック内容に少し反していますが、特徴量行列とラベルベクトルは同じファイルにまとめています。

結果
77   1640 -rw-------   1 root     root      1679352 Sep 23 01:09 /content/drive/MyDrive/ColabNotebooks/ML/NLP100_2020/08.NeuralNetworks/valid.tfrecord
82   1640 -rw-------   1 root     root      1679352 Sep 23 01:09 /content/drive/MyDrive/ColabNotebooks/ML/NLP100_2020/08.NeuralNetworks/test.tfrecord
72  13116 -rw-------   1 root     root     13429788 Sep 23 01:09 /content/drive/MyDrive/ColabNotebooks/ML/NLP100_2020/08.NeuralNetworks/train.tfrecord

回答プログラム 70_単語ベクトルの和による特徴量.ipynb

GitHubには確認用コードも含めていますが、ここには必要なものだけ載せています。

import string

import tensorflow as tf
import pandas as pd
import numpy as np
from google.colab import drive
from gensim.models import KeyedVectors

drive.mount('/content/drive')

BASE_PATH = '/content/drive/MyDrive/ColabNotebooks/ML/NLP100_2020/'
BASE_PATH08 = BASE_PATH + '08.NeuralNetworks/'

w2v_model = KeyedVectors.load_word2vec_format(BASE_PATH+'07.WordVector/input/GoogleNews-vectors-negative300.bin.gz', binary=True)

# 変換テーブル作成: 同じ長さでないとエラーがでるので第2引数の長さ調整(置換元と置換先文字は1:1だから)
table = str.maketrans(string.punctuation, ' '*len(string.punctuation))
def vectorize(title):

    # 句読点をスペースに置換してsplit
    tokens = title.translate(table).split()

    # 各Tokenをベクトル化
    vectors = [w2v_model[token] for token in tokens if token in w2v_model]
    
    # Tokenごとの平均ベクトルを算出(DataFrameの要素にNumpy Arrayを格納)
    vectors = np.array(sum(vectors)/len(vectors))
    return vectors


def make_example(title, category):
    # Dictionary形式でfeaturesを格納
    features={
       'title' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[title])),
       'category' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[category]))
    }

    # ExampleとFeaturesでまとめて返す
    return tf.train.Example(features=tf.train.Features(feature=features))


def process_data(type_):
    df = pd.read_table(BASE_PATH08+'input/'+type_+'.txt')

    # タイトル: Token化しベクトル化した要素を1列にしたSeries作成
    titles = df['title'].map(vectorize).explode().astype('float32')

    # タイトル: 配列をNumpy形式にしてshape変更
    titles = titles.values.reshape(len(df), 300)

    # ラベル: 文字を数字に変更
    categories = df['category'].replace({'b':0, 't':1, 'e':2, 'm':3})

    # 本当はScikit Learnの関数が望ましいが少ないラベル(4値)種類でそこそこデータ件数があるのでkeras使う
    categories = tf.keras.utils.to_categorical(categories, dtype='int32')

    # ファイル書込
    with tf.io.TFRecordWriter(BASE_PATH08+type_+'.tfrecord') as writer:
        
        # 1行ずつ取り出してExample形式にして書込
        for title, category in zip(titles, categories):
            ex = make_example(title.tobytes(), category.tobytes())
            writer.write(ex.SerializeToString())


process_data('train')
process_data('valid')
process_data('test')

回答解説

ノック解説

まずはノック内容自体の解説からします。

すべての事例$x_i$の特徴ベクトル$\boldsymbol{x}_i$を並べた行列$X$と,正解ラベルを並べた行列(ベクトル)$Y$を作成したい.

上記部分は以下のように整理できます。

項目 内容
事例$x_i$ i番目の事例 Jj Abrams - Star Wars: Episode VII has started filming
特徴ベクトル$\boldsymbol{x}_i$ i番目の特徴ベクトル(300要素の特徴ベクトル) [ 0.117, ..., 0.024] 300要素
行列$X$ 特徴ベクトル$\boldsymbol{x}_i$が事例数並んだ行列 [[ 0.117, ..., 0.024],...[0.117, ..., 0.024]]300要素×事例数
行列$Y$ 正解ラベル$y_i$が事例数並んだ行列 [0, 1, ..., 2]事例数と同じ要素

$\mathbb{R}^{n \times d}や\mathbb{N}^{n}$の$n$は事例数で$d$は次元数(今回はノック60で取得したWord2Vecモデルの次元数300)。

X = \begin{pmatrix} 
  \boldsymbol{x}_1 \\ 
  \boldsymbol{x}_2 \\ 
  \dots \\ 
  \boldsymbol{x}_n \\ 
\end{pmatrix} \in \mathbb{R}^{n \times d},
Y = \begin{pmatrix} 
  y_1 \\ 
  y_2 \\ 
  \dots \\ 
  y_n \\ 
\end{pmatrix} \in \mathbb{N}^{n}

特徴ベクトル$\boldsymbol{x}_i$ の計算についてです。

\boldsymbol{x}_i = \frac{1}{T_i} \sum_{t=1}^{T_i} \mathrm{emb}(w_{i,t})

仮に10番目の事例に「Chariklo asteroid has two RINGS」があったとします。

項目 内容
事例$x_{10}$ 10番目の事例 Chariklo asteroid has two RINGS
単語列数$T_{10}$ 10番目の事例の単語列数 5(事例の文は5単語)
単語列$w_{10,t}$ 10番目の事例の単語列 (Chariklo, asteroid, $\dots$, RINGS)
単語ベクトル$\mathrm{emb}(w_{10,t}$ 10番目の事例の単語ベクトル Word2Vecで取得する300次元の単語ベクトル

事例のベクトル化

関数vectorizeで事例をベクトル化しています。pandasSeriesの1要素に対してmap関数で実行されます。つまり、入力パラメータのtitleはstring型のテキスト(例: 「Jj Abrams - Star Wars: Episode VII has started filming」)です。
len(vectors)は単語数$T_i$です。300次元の各要素を全単語で平均化しているため、常に出力vectorsが300次元になります。私は途中まで、各単語ごとに平均ベクトルを求めるのかと勘違いをしていました。

# 変換テーブル作成: 同じ長さでないとエラーがでるので第2引数の長さ調整(置換元と置換先文字は1:1だから)
table = str.maketrans(string.punctuation, ' '*len(string.punctuation))
def vectorize(title):

    # 句読点をスペースに置換してsplit
    tokens = title.translate(table).split()

    # 各Tokenをベクトル化
    vectors = [w2v_model[token] for token in tokens if token in w2v_model]
    
    # Tokenごとの平均ベクトルを算出(DataFrameの要素にNumpy Arrayを格納)
    vectors = np.array(sum(vectors)/len(vectors))
    return vectors

TFRecord形式での保存

今回、理解するのに最も時間がかかった内容です。主にチュートリアル「TFRecords と tf.Example の使用法」を見て理解しましたが、以下の点が重要。データ読込が遅くなければ使わなくてもいい

tf.data を使っていて、それでもデータの読み込みが訓練のボトルネックであるという場合でなければ、既存のコードを TFRecords を使用するために変更する必要はありません。

以下の順序でTFRecord形式で保存します。

  1. tf.train.Feature形式に変換
  2. Dictionary型データにしてtf.train.Featuresメッセージに変換
  3. tf.train.Exampleメッセージに変換
  4. シリアライズしてTFRecord形式でファイル書込

事例およびラベルデータをtf.train.Feature形式に変換します。tf.train.Featureは以下の3種類の型を選びます。詳しくは「tf.Example 用のデータ型」を参照。

  1. tf.train.BytesList: Stringや複数のNumpy配列もこれ。
  2. tf.train.FloatList
  3. tf.train.Int64List

今回は、1行ずつ配列から取り出して、titleおよびcategoryは複数列なので、Byte化しています。

    # ファイル書込
    with tf.io.TFRecordWriter(BASE_PATH08+type_+'.tfrecord') as writer:
        
        # 1行ずつ取り出してExample形式にして書込
        for title, category in zip(titles, categories):
            ex = make_example(title.tobytes(), category.tobytes())
            writer.write(ex.SerializeToString())

で、関数make_example内でtf.train.Featureに渡しています。

def make_example(title, category):

    # Dictionary形式でfeaturesを格納
    features={
       'title' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[title])),
       'category' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[category]))
    }

    # ExampleとFeaturesでまとめて返す
    return tf.train.Example(features=tf.train.Features(feature=features))
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?