0
1

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 5 years have passed since last update.

言語処理100本ノック-82(Context Word):文脈の抽出

Last updated at Posted at 2019-12-31

言語処理100本ノック 2015の82本目「文脈の抽出」の記録です。
今回も後続のための前処理系で特に難しい処理をしておらず、技術的には解説すること少ないです。ただ、問題文が素人にはわかりずらく、理解に少し時間を要しました。

参考リンク

リンク 備考
082.文脈の抽出.ipynb 回答プログラムのGitHubリンク
素人の言語処理100本ノック:82 言語処理100本ノックで常にお世話になっています
言語処理100本ノック 2015年版 (80~82) 第9章で参考にしました

環境

種類 バージョン 内容
OS Ubuntu18.04.01 LTS 仮想で動かしています
pyenv 1.2.15 複数Python環境を使うことがあるのでpyenv使っています
Python 3.6.9 pyenv上でpython3.6.9を使っています
3.7や3.8系を使っていないことに深い理由はありません
パッケージはvenvを使って管理しています

課題

第9章: ベクトル空間法 (I)

enwiki-20150112-400-r10-105752.txt.bz2は,2015年1月12日時点の英語のWikipedia記事のうち,約400語以上で構成される記事の中から,ランダムに1/10サンプリングした105,752記事のテキストをbzip2形式で圧縮したものである.このテキストをコーパスとして,単語の意味を表すベクトル(分散表現)を学習したい.第9章の前半では,コーパスから作成した単語文脈共起行列に主成分分析を適用し,単語ベクトルを学習する過程を,いくつかの処理に分けて実装する.第9章の後半では,学習で得られた単語ベクトル(300次元)を用い,単語の類似度計算やアナロジー(類推)を行う.

なお,問題83を素直に実装すると,大量(約7GB)の主記憶が必要になる. メモリが不足する場合は,処理を工夫するか,1/100サンプリングのコーパスenwiki-20150112-400-r100-10576.txt.bz2を用いよ.

今回は*「1/100サンプリングのコーパスenwiki-20150112-400-r100-10576.txt.bz2」*を使っています。

82. 文脈の抽出

81で作成したコーパス中に出現するすべての単語tに関して,単語$ t $と文脈語$ c $のペアをタブ区切り形式ですべて書き出せ.ただし,文脈語の定義は次の通りとする.

  • ある単語$ t $の前後$ d $単語を文脈語$ c $として抽出する(ただし,文脈語に単語tそのものは含まない)
  • 単語$ t $を選ぶ度に,文脈幅$ d $は{1,2,3,4,5}の範囲でランダムに決める.

課題補足

「文脈語」とは

対象とする単語を**「対象語」(Target word)、対象語の前後を「文脈語」(Context word)と呼びます。対象語から何単語までを文脈語とするかを「文脈幅」(Context Window SizeまたはWindow Size)**と呼びます。

課題の元ファイルにある下記の例文で説明します。

No surface details of Adrastea are known due to the low resolution of available images

例えば上記でAdrasteaを対象語とすると、前後にある"details", "of", "are", "known"が文脈幅2の文脈語です。
では、仮に文脈幅を2として上記文に対して今回の課題を実行する場合、今回は下記のようなファイルを作成します。
※ *「単語$ t $を選ぶ度に,文脈幅$ d $は{1,2,3,4,5}の範囲でランダムに決める」*部分は無視して解説

1列名 2列目
No surface
No details
surface No
surface details
surface of
details No
details surface
details of
details Adrastea

回答

回答プログラム 082.文脈の抽出.ipynb

20行程度の短いプログラムですが、データ量が多いので処理に10分程度かかります。
また、作成されるファイルは800MB程度のサイズとなり大きいので注意が必要です。
ちなみに9割以上が記事「素人の言語処理100本ノック:82」のコピペです。

import random

with open('./081.corpus.txt') as file_in, \
     open('./082.context.txt', mode='w') as file_out:
    for i, line in enumerate(file_in):
        tokens = line.strip.split(' ')
        for j in range(len(tokens)):
            d = random.randint(1, 5)        # 文脈幅d
            
            # 前後d語以内の語の列挙
            for k in range(max(j - d, 0), min(j + d + 1, len(tokens))):
                
                # 自分自身の場合は出力しない
                if j != k:
                    file_out.writelines(tokens[j]+'\t'+tokens[k]+'\n')
        if i < 4:
            print(len(tokens), tokens)
        else:
            print('\r Processding line: {0}'.format(i), end='')

回答解説

下記コードがメインの部分です。
対象語の場所jから文脈幅dを増減させた数のループです。ただ単純に増減させると最初の方の単語は負の数になったり、最後の方の単語は合計単語数を超過してしまったりとするのでmaxmin関数を使って幅の調整をしています。

# 前後d語以内の語の列挙
for k in range(max(j - d, 0), min(j + d + 1, len(tokens))):
                
    # 自分自身の場合は出力しない
    if j != k:
        file_out.writelines(tokens[j]+'\t'+tokens[k]+'\n')

4行目まではコンソールに対象語の数と処理対象文を、それ以降は何行目を処理しているかを出力しています。

if i < 4:
    print(len(tokens), tokens)
else:
    print('\r Processding line: {0}'.format(i), end='')

こぼれ話(トークン化失敗)

文のトークン化に関する失敗談です。
最初はあまり考えずに下記のようにsplit関数を使っていました。

tokens = line.split()

しかし、結果がこんなになってしまったものもあり、後続でPandas使ったときにエラーで気づきました。

"b")("s"	"c
−	"b")("s"
−	"c

本当はこうなるべきでした。これは一見、スペース区切りになっているように見えてスペースっぽい部分には\xa0が使われています。前々回記事の「\xa0について」で少し触れています。

known	k" = √("s"("s" − "a")("s" − "b")("s" − "c

で、正しくすべくstrip関数を使ってスペースだけで区切っています。

tokens = line.strip.split(' ')
0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?