言語処理100本ノック 2020 (Rev2)の「第9章: RNN, CNN」の80本目「ID番号への変換」記録です。
Pythonあるあるですが、横着しようとしてすごく簡単にできるパッケージがないか調べた結果、逆に時間かかってしまった系です。多分4時間くらい解くのに時間かけてしまいました。また、少し思うところがあってノック内容に反することもしています。
記事「まとめ: 言語処理100本ノックで学べることと成果」に言語処理100本ノック 2015についてはまとめていますが、追加で差分の言語処理100本ノック 2020 (Rev2)についても更新します。
参考リンク
| リンク | 備考 |
|---|---|
| 80_ID番号への変換.ipynb | 回答プログラムのGitHubリンク |
| 言語処理100本ノック 2020 第9章: RNN, CNN | (PyTorchだけど)解き方の参考 |
| 【言語処理100本ノック 2020】第9章: RNN, CNN | (PyTorchだけど)解き方の参考 |
| まとめ: 言語処理100本ノックで学べることと成果 | 言語処理100本ノックまとめ記事 |
| TextVectorization | ID化のkeras関数 |
| FreqDist | 辞書作成のnltk関数 |
環境
後々GPUを使わないと厳しいので、Google Colaboratory使いました。Pythonやそのパッケージでより新しいバージョンありますが、新機能使っていないので、プリインストールされているものをそのまま使っています。TensorFlowはいつの間にか2.6から2.7に上がっています。
| 種類 | バージョン | 内容 |
|---|---|---|
| Python | 3.7.12 | Google Colaboratoryのバージョン |
| 2.0.3 | Google Driveのマウントに使用 | |
| tensorflow | 2.7.0 | ディープラーニングの主要処理 |
| nltk | 3.2.5 | Tokenの辞書作成に使用 |
| pandas | 1.1.5 | 行列に関する処理に使用 |
第8章: ニューラルネット
学習内容
深層学習フレームワークを用い,再帰型ニューラルネットワーク(RNN)や畳み込みニューラルネットワーク(CNN)を実装します.
80. ID番号への変換
問題51で構築した学習データ中の単語にユニークなID番号を付与したい.学習データ中で最も頻出する単語に1,2番目に頻出する単語に2,……といった方法で,学習データ中で2回以上出現する単語にID番号を付与せよ.そして,与えられた単語列に対して,ID番号の列を返す関数を実装せよ.ただし,出現頻度が2回未満の単語のID番号はすべて0とせよ.
回答
回答結果
適当なサンプル文を入力します。
print(vectorize_layer([["bar unknown_word(1)"]]).numpy)
辞書化されて、Numpy配列が返ってきます。これ見ただけでは意味わからないですね。
<bound method _EagerTensorBase.numpy of <tf.Tensor: shape=(1, 18), dtype=int64, numpy=
array([[2081, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0]])>>
今回は以下の2点について違反をしています。
- 学習データ中で最も頻出する単語に1,2番目に頻出する単語に2,……といった方法で,学習データ中で2回以上出現する単語にID番号を付与せよ.
- 出現頻度が2回未満の単語のID番号はすべて0とせよ.
1に関しては、特に頻出順位とID番号は関連付けていません。特に関連付けるメリットを見いだせず、逆に使っている関数にその機能がなかったためです。
2. に関しては0はmask用にしています。これが一般的なのではないでしょうか(kerasだけ?)。代わりに1をUnkownとして使っています。
回答プログラム 80_ID番号への変換.ipynb
GitHubには確認用コードも含めていますが、ここには必要なものだけ載せています。
import nltk
import pandas as pd
import tensorflow as tf
from google.colab import drive
drive.mount('/content/drive')
BASE_PATH = '/content/drive/MyDrive/ColabNotebooks/ML/NLP100_2020/06.MachineLearning/'
max_len = 0
def get_vocabulary(type_):
global max_len
df = pd.read_table(BASE_PATH+type_+'.feature.txt')
df.info()
sr_title = df['title'].str.split().explode()
max_len_ = df['title'].map(lambda x: len(x.split())).max()
if max_len < max_len_:
max_len = max_len_
fdist = nltk.FreqDist(sr_title)
print(f'Top 3 tokens: {fdist.most_common(3)}')
return [k for k, v in fdist.items() if v > 1]
vocabulary = get_vocabulary('train')
vocabulary.extend(get_vocabulary('valid'))
vocabulary.extend(get_vocabulary('test')) # あまりこだわらずにテストデータセットも追加
# setで重複削除し、タプル形式に設定
tup_voc = tuple(set(vocabulary))
print(f'vocabulary size before removing duplicates: {len(vocabulary)}')
print(f'vocabulary size after removing duplicates: {len(tup_voc)}')
print(f'sample vocabulary: {tup_voc[:10]}')
print(f'max length is {max_len}')
vectorize_layer = tf.keras.layers.TextVectorization(
output_mode='int',
vocabulary=tup_voc,
output_sequence_length=max_len)
print(f'sample vocabulary: {vectorize_layer.get_vocabulary()[:10]}')
model = tf.keras.models.Sequential()
model.add(tf.keras.Input(shape=(1,), dtype=tf.string))
model.add(vectorize_layer)
# いまいち仕様を調べていないが、一度modelに追加すると下記が有効になる
print(vectorize_layer([["bar unknown_word(1)"]]).numpy)
回答解説
辞書作成
今回の肝となるコードです。
df['title'].str.split().explode()で単語分割をして1列のSeriesを作り、nltk.FreqDist(sr_title)で辞書を作成しています。ifの条件文で*「学習データ中で2回以上出現する単語にID番号を付与」*を実現しています。
また、max_len_ = df['title'].map(lambda x: len(x.split())).max()で1文の最大Token数を求めています。最大Token数はTextVectorization関数に渡すために求めています。
def get_vocabulary(type_):
global max_len
df = pd.read_table(BASE_PATH+type_+'.feature.txt')
df.info()
sr_title = df['title'].str.split().explode()
max_len_ = df['title'].map(lambda x: len(x.split())).max()
if max_len < max_len_:
max_len = max_len_
fdist = nltk.FreqDist(sr_title)
print(f'Top 3 tokens: {fdist.most_common(3)}')
return [k for k, v in fdist.items() if v > 1]
テストデータセットも辞書化しています。実運用を考えると辞書化から除外すべきかとも思いますが、こだわらずにやっています。
vocabulary = get_vocabulary('train')
vocabulary.extend(get_vocabulary('valid'))
vocabulary.extend(get_vocabulary('test')) # あまりこだわらずにテストデータセットも追加
辞書のモデル組込み
KerasのTextVectorization関数を使っています。今回、ノックの学習データ中で2回以上出現する単語にID番号を付与せよがなければ、本当にただテキストを渡すだけでした。しかし、1回以下の単語を除去するために事前に辞書を作ってパラメータvocabularyに渡しています。パラメータmax_tokensを使えば、覚え込む単語数の上限は指定できるのですが、頻度での指定はできないです。
vectorize_layer = tf.keras.layers.TextVectorization(
output_mode='int',
vocabulary=tup_voc,
output_sequence_length=max_len)
print(f'sample vocabulary: {vectorize_layer.get_vocabulary()[:10]}')
model = tf.keras.models.Sequential()
model.add(tf.keras.Input(shape=(1,), dtype=tf.string))
model.add(vectorize_layer)
# いまいち仕様を調べていないが、一度modelに追加すると下記が有効になる
print(vectorize_layer([["bar unknown_word(1)"]]).numpy)
以下の出力をします。1行目の[UNK]は未知語です。小さい息子の影響で「う○こ」に見えてしまいます。
sample vocabulary: ['', '[UNK]', 'Changes', 'rampage', 'Politics', 'Gawker', 'mood', 'Quiznos', 'friendless', 'end']
<bound method _EagerTensorBase.numpy of <tf.Tensor: shape=(1, 18), dtype=int64, numpy=
array([[2081, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0]])>>
こぼれ話
今回、ノックの学習データ中で2回以上出現する単語にID番号を付与せよに非常に苦しみました。
普通にPythonで組めばそんなに手間がかからないのですが、関数を色々探してしまいました。
没関数1: Tokenizer
Keras の Tokenizer関数を調べました。パラメータdocument_countを使えばできるかも、と思いましたがそんなことは無かった。
没関数2: corpora.dictionary
Gensim の corpora.dictionary関数を調べました。もう忘れましたが、パラメータか関数を使ってできるかも、と思いましたがそんなことは無かった。