fastTextでサクッと単語の分散表現を使ってみよう！

本稿ではfastTextを使って**サクッと**単語の分散表現を獲得する方法について見ていきます。[前日の記事](https://qiita.com/BlueRayi/items/eb7949594839771c1b70)と少しリンクするお話に出来れば良いなあと思って書きました。

# fastTextとは

[fastText](https://github.com/facebookresearch/fastText#text-classification)はFacebookが発表した単語の分散表現（単語を数値で表現したもの）を獲得する手法です。基となっているのはお馴染みWord2Vec（CBOW / skip-gram）です。Word2Vecについては今更も今更なので説明は不要でしょう。

論文：[Enriching Word Vectors with Subword Information](https://arxiv.org/pdf/1607.04606v1.pdf)

Word2VecとfastTextの違いは、ベクトルの取り方です。サブワードという仕組みを入れることによって、活用形等の近い単語同士を汲み取っています。

Word2Vecではgoとgoesは全く別の単語でした。しかしfastTextではこれを考慮し、同じ構成要素としてgoとgoesに意味を持たせます。当然、未知語にも強くなりますね！

詳細は論文や次の資料をお勧めします。

- [FacebookのfastTextでFastに単語の分散表現を獲得する - Qiita](https://qiita.com/icoxfog417/items/42a95b279c0b7ad26589)
- [高速と噂のfastTextの仕組み - Qiita](https://qiita.com/n_kats_/items/2691c08639468e30abdd)

# FastTextで遊ぼう

今回の目的は「PythonとFastTextを使ってサクッと単語の分散表現で遊ぶ」ことです。

## 環境構築

え？Pythonの導入すらサクッと出来ない？では[Google Colaboratory](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)を使いましょう。（以下colab）

colabはGoogleアカウントさえあれば誰でも簡単にPythonやGPUが使える最強の無料環境です。

## サクッとfastText

fastTextを使うには学習モデルが必要になります。これは自分でデータを収集して、学習させても良いのですが、ここでは学習済みモデルを使わせてもらいます。

学習済みモデル：[fastTextの学習済みモデルを公開しました - Qiita](https://qiita.com/Hironsan/items/513b9f93752ecee9e670)

モデルはそのままGoogleドライブの中に放りこんでください。せっかくなのでcolabの使い方説明も含めて、ドライブから解凍してしまいましょう！

colabでドライブの中を触るにはマウントが必要です。これは以下のコードを実行すればよいのですが、「ドライブをマウント」ボタンを押せば同じコードが出てくるはずなのでそれを実行してください。

```
from google.colab import drive
drive.mount('/content/drive')
```

あとはURLにアクセスして、アカウントにログイン、するとauthorization codeが出てくると思うのでそれをcolabでコピペするだけです。簡単。

あとは先程のモデルを解凍するだけ！

```python
%cd /content/drive/My Drive/data
!unzip vector_neologd.zip
```

pathはドライブに上げた学習済みモデルの場所を指定してくださいね。

それでは類似する言葉を探してみましょう。

```python

import gensim

model = gensim.models.KeyedVectors.load_word2vec_format('model.vec', binary=False)

model.most_similar(positive=['自然言語処理'])
```

結果。

```
[('自然言語理解', 0.7600098848342896),
 ('自然言語', 0.7503659725189209),
 ('計算言語学', 0.7258570194244385),
 ('自動プログラミング', 0.6848069429397583),
 ('テキストマイニング', 0.6811494827270508),
 ('コンピュータ言語', 0.6618390083312988),
 ('メタプログラミング', 0.658093273639679),
 ('Webプログラミング', 0.6488876342773438),
 ('形態素解析', 0.6479052901268005),
 ('コーパス言語学', 0.6465639472007751)]
```

色々遊ぶと意外と楽しい。


```python

model.most_similar(positive=['友人'], negative=['友情'])
```

```
[('知人', 0.4586910605430603),
 ('自宅', 0.35488438606262207),
 ('知り合い', 0.329221248626709),
 ('入り浸り', 0.3212822675704956),
 ('親戚', 0.31865695118904114),
 ('知り合っ', 0.3158203959465027),
 ('宅', 0.31503963470458984),
 ('誘わ', 0.302945077419281),
 ('繁く', 0.30250048637390137),
 ('同僚', 0.29792869091033936)]
```

## サクッと？fastText

あまりにサクッとし過ぎたので、続いてボキャブラリーを制限して同じことをしてみます。こんなやり方もあるよというオプションです。

今回扱うのは日本語のテキストですが、日本語は英語と違い各単語がスペース等で分割されていないので、初めに前処理を行わなければなりません（分かち書き）。

尚、実装は[書籍「つくりながら学ぶ! PyTorchによる発展ディープラーニング](https://github.com/YutaroOgawa/pytorch_advanced)を参考にしています。

ここではjanomeを使います。

```python
!pip install janome
```

では分かち書きをしましょう。

```python
from janome.tokenizer import Tokenizer

j_t = Tokenizer()

def tokenizer_janome(text):
    return [tok for tok in j_t.tokenize(text, wakati=True)]
```

janomeだけで分かち書きがサクッと出来ますが、加えて簡単な前処理をしましょう。

```python
import re
import unicodedata
import string


def format_text(text):
    text = unicodedata.normalize("NFKC", text)
    table = str.maketrans("", "", string.punctuation  + "「」、。・")
    text = text.translate(table)

    return text


def preprocessing(text):
    """
    前処理をする関数
    """
    text.translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)}))  # 全角→半角

    text = text.lower()  # 大文字→小文字

    text = re.sub('\r', '', text)
    text = re.sub('\n', '', text)
    text = re.sub(' ', '', text)
    text = re.sub('　', '', text)

    text = re.sub(r'[0-9 ０-９]', '', text)  # 数字の除去
    text = re.sub(r'[!-/:-@[-`{-~]', '', text)  # 半角記号の除去
    text = re.sub(r'/[！-／：-＠［-｀｛-～、-〜”’・]', '', text)  # 全角記号の除去
    text = format_text(text)  # 全角記号の除去

    return  text


def tokenizer_with_preprocessing(text):
    text = preprocessing(text)
    text = tokenizer_janome(text)
    
    return text
```

コメントに書いた通りですが、いくつかの前処理を行っています。もちろんタスクや用途によって前処理で行うべきことは変わるはずです。

では試しに適当な文章を入れてみましょう。

```python

text = '今回は　fastTextを　使って 分散表現を獲得したい！！！?'
print(tokenizer_with_preprocessing(text))
```

結果。

```
['今回', 'は', 'fasttext', 'を', '使っ', 'て', '分散', '表現', 'を', '獲得', 'し', 'たい']
```

上手くいっているようですね。

続いて[torchtext](https://torchtext.readthedocs.io/en/latest/vocab.html#fasttext)を使って諸々を楽にします。torchtextについては[torchtextで簡単にDeepな自然言語処理 - Qiita](https://qiita.com/itok_msi/items/1f3746f7e89a19dafac5)が詳しいです。

```python

import torchtext

max_length = 25
TEXT = torchtext.data.Field(sequential=True, tokenize=tokenizer_with_preprocessing,
                            use_vocab=True, lower=True, include_lengths=True,
                            batch_first=True, fix_length=max_length)
LABEL = torchtext.data.Field(sequential=False, use_vocab=False)

train_ds, val_ds, test_ds = torchtext.data.TabularDataset.splits(
    path='./tsv/', train='train.tsv',
    validation='val.tsv', test='test.tsv', format='tsv',
    fields=[('Text', TEXT), ('Label', LABEL)])
```

train.tsv / test.tsv / val.tsvを用意しました。これは本稿の「fastTextとは」の文章を適当に3分割してtsvファイルとしました。これも上記のリンクが詳しいのでそちらをお勧めします。

```python
from torchtext.vocab import Vectors

vectors = Vectors(name='model.vec')

# ベクトル化したバージョンのボキャブラリーを作成します
TEXT.build_vocab(train_ds, vectors=vectors, min_freq=1)

# ボキャブラリーのベクトルを確認します
print(TEXT.vocab.vectors.shape)  # 52個の単語が300次元のベクトルで表現されている
TEXT.vocab.vectors

# ボキャブラリーの単語の順番を確認します
TEXT.vocab.stoi
```

これで準備が出来たのでボキャブラリーの各単語の類似度を計算してみます。「単語」に3つの単語が類似するか見てみましょう。

```python

import torch.nn.functional as F

tensor_calc = TEXT.vocab.vectors[TEXT.vocab.stoi['単語']]

# コサイン類似度
print("論文", F.cosine_similarity(tensor_calc, TEXT.vocab.vectors[TEXT.vocab.stoi['論文']], dim=0))
print("ワード", F.cosine_similarity(tensor_calc, TEXT.vocab.vectors[TEXT.vocab.stoi['ワード']], dim=0))
print("ベクトル", F.cosine_similarity(tensor_calc, TEXT.vocab.vectors[TEXT.vocab.stoi['ベクトル']], dim=0))
```

結果。

```
論文 tensor(0.3089)
ワード tensor(0.3704)
ベクトル tensor(0.3265)
```

# もっと詳しくやりたい人は

fastTextについてはWord2Vecほどではありませんが、色んな記事がありますので、特におすすめなもの（参考文献）を紹介して締めます。

## fastTextで文書分類

追記箇所。以前は色んな記事を紹介しましたが、最近投稿された以下の記事が最もおすすめ。タイトルにWatsonとの比較とありますが、Google colabで動作するfastTextのコードがあるのでポチポチするだけ。学習データの形式についての説明も丁寧ですし、記事とコードをにらめっこすると良いと思います。

- [fasttextとWatson Natural Language Classifierの文書分類比較](https://www.cresco.co.jp/blog/entry/11577/)

## fastText以外の単語埋め込み

- [いますぐ使える単語埋め込みベクトルのリスト - Qiita](https://qiita.com/Hironsan/items/8f7d35f0a36e0f99752c)

もっと最近の単語埋め込みを知りたい方はELMoとかBERTでググって貰えれば沢山出ると思います。当然ですが最近のモデルになるほど難しくなります。

# 次回は？

@takashi1029さんです！

- [GiNZA+Elasticsearchで係り受け検索の第一歩 - Taste of Tech Topics](http://acro-engineer.hatenablog.com/entry/2019/12/06/120000)