Open9

NLPについて学ぶ

nabetsunabetsu

NLPでの前処理

語の表記ゆれに対応することで、できるだけ機械が意味(特徴)を正しく理解できるようにする。

以下のようにテキストの文章にはさまざまな表記の違いがあり、人間からすると意味に影響は与えないが、機械はそれを同じと認識できないので、正規化により表記のゆれを抑える

  • スペースの有無
  • スペースの全角半角
  • 文字の全角半角
  • 記号の全角半角

1. 正規化

正規化処理をまとめたneologdnというライブラリがあるので、これを使用する

pip install neologdn

以下のサンプルの通り、上記で上げた表記の揺れについてはneologdnのnormalizeで解消できる。
ただし、アルファベットの大文字・小文字変換は含まれていないので、別途実施する必要がある。

neologdn.normalize('「初めてのTensorFlow」は定価2200円+税です')
'「初めてのTensorFlow」は定価2200円+税です'
neologdn.normalize('「初めての TensorFlow」は定価2200円+税です')
'「初めてのTensorFlow」は定価2200円+税です'
neologdn.normalize('「初めての TensorFlow」は定価2200円+税です')
'「初めてのTensorFlow」は定価2200円+税です'
  • 大文字小文字の解消
    • Pythonのstr型の組み込みメソッドであるlower()またはupper()を使って小文字または大文字に表記を統一できる
neologdn.normalize('「初めての TensorFlow」は定価2200円+税です').lower()
'「初めてのtensorflow」は定価2200円+税です'

2. Unicode正規化

以下の例のようにUnicodeには様々な文字と仕様が定義されているのでバイト表現が異なることがある。

㈱リックテレコム
(株)リックテレコム

同じ意味を表しているがバイト表現が異なるものを正規化するにはPythonのStandard Libraryであるunicodedataが使える

import unicodedata

# 変換のルールとしてNFKCを指定
>>> normalized = unicodedata.normalize('NFKC', '㈱リックテレコム')
>>> assert normalized == '(株)リックテレコム'
>>> print(normalized)
()リックテレコム

詳細は# image参照

3. 見出し語化(lemmatization)

動詞の活用によって同じ意味を表す単語であっても別々の語彙として扱われてしまうケースがあり、これを避けるための手段が見出し語化

具体的にはわかち書きの結果得られる単語を原型に戻し、以下のような結果を得ること。

  - before:
  - 本 を 読ん だ
  - 本 を 読み まし た
  - 
  - after:
  - 本 を 読む だ
  - 本 を 読む ます た

※上記の例では同じ意味である「読ん」と「読み」が別の語彙と判定されてしまう

 import MeCab
 
 tagger = MeCab.Tagger()
 
 def tokenize(text):
     node = tagger.parseToNode(text)
     result = []
     while node:
         features = node.feature.split(',')
         
         if features[0] != 'BOS/EOS':
             token = features[6] if features[6] != '*' else node.surface
             result.append(token)
         
         node = node.next
     
     return result
     
    >>> print(tokenize('本を読んだ'))
    ['本', 'を', '読む', 'だ']
    >>> print(tokenize('本を読みました'))
    ['本', 'を', '読む', 'ます', 'た']

※形態素解析の結果をつかう

4. ストップワード

文の特徴を捉えるのに役立たず、特徴量抽出前に除外すべき単語がストップワード(stopword)
例えば「です」、「ます」などの言葉はあってもなくても文章の内容にあまり影響をあたえないため

ストップワードの候補

  • 「です」「ます」のような文末につく助動詞
  • 「あれ」「それ」などの指示語
  • 「。」「、」などの句読点

辞書ベースのストップワード

あらかじめストップワードをリストアップしておいてそれを除去する方法。
辞書は自分で作ってもいいし、ネット上で公開されているものを使っても良い。

参考ページ
簡易的な日本語ストップワードの取得メソッド

以下のプログラムは見出し語化とストップワードを組み合わせたもの

 import MeCab
 
 tagger = MeCab.Tagger()
 
 def tokenize(text, stop_words):
     node = tagger.parseToNode(text)
     result = []
     while node:
         features = node.feature.split(',')
         
         if features[0] != 'BOS/EOS':
             token = features[6] if features[6] != '*' else node.surface
             if token not in stop_words:
                 result.append(token)
         
         node = node.next
     
     return result
     
 >>> stop_words = ['て', 'に', 'を', 'は', 'です', 'ます']
 >>> tokenize('本を読んだ', stop_words)
 ['本', '読む', 'だ']
 >>> tokenize('本を読みました', stop_words)
 ['本', '読む', 'た']

品詞ベースのストップワード

単語をすべて指定するのではなく、「て」「に」「を」「は」などの助詞や「です」「ます」などの助動詞をまとめてストップワードとして扱う方法

※文章の内容を特徴量として抽出するには

 import MeCab
 
 tagger = MeCab.Tagger()
 
 def tokenize(text, stop_words):
     node = tagger.parseToNode(text)
     result = []
     while node:
         features = node.feature.split(',')
         
         if features[0] != 'BOS/EOS':
             if features[0] not in ['助詞', '助動詞']:
                 token = features[6] if features[6] != '*' else node.surface
                 result.append(token)
         
         node = node.next
     
     return result
 
 >>> tokenize('本を読んだ', stop_words)
 ['本', '読む']
 >>> tokenize('本を読みました', stop_words)
 ['本', '読む']

※ストップワードの結果として2つの文章はまったく同じ意味になった

5. 単語置換

数値や日次などの詳細には影響を与えない情報を無視する方法。

以下の例では、卵を何個買ったかという文意に影響を与えない部分を特定の文字列で置き換えている
- 前後にスペースを入れることでわかち書きの際に前後の文字と結合して認識されることを防ぐ
- ただし、その判定は連続する半角数字だけなので、もっと条件を追加したほうがいいかもしれない

 import re
 
 def tokenize_numbers(text):
    return re.sub(r'\d+', ' SOMENUMBER ', text)
 
 >>> print(tokenize_numbers('卵を1個買ったよ'))
 >>> print(tokenize_numbers('卵を2個買ったよ'))
 >>> print(tokenize_numbers('卵を10個買ったよ'))
 卵を SOMENUMBER 個買ったよ
 卵を SOMENUMBER 個買ったよ
 卵を SOMENUMBER 個買ったよ

以下のように0に置き換えることもできるが、どちらが効果的なのだろうか?
また、文字数を変化させたくない場合には正規表現で数字一文字を表す\dを使う。

def normalize_number(text):
    replaced_text = re.sub(r'\d+', '0', text)
    return replaced_text

$ text = '2 万 0689・24ドル'
$ normalize_number(text)
000ドル
def normalize_number(text):
    replaced_text = re.sub(r'\d', '0', text)
    return replaced_text

$ text = '2 万 0689・24ドル'
$ normalize_number(text)
0000000ドル

具体的な活用

以下のコードのようにインプットとなるテキストデータに前処理を行うことでモデルの精度を0.05向上させることに成功した

 import unicodedata
 
 import MeCab
 import pandas as pd
 from sklearn.feature_extraction.text import CountVectorizer
 from sklearn.pipeline import Pipeline
 from sklearn.svm import SVC
 import neologdn
 
 
 class DialogueAgent:
     def __init__(self):
         self.tagger = MeCab.Tagger()
         
     def _tokenize(self, text):
         # Unicode正規化
         text = unicodedata.normalize('NFKC', text)
         # 正規化
         text = neologdn.normalize(text)
         # 大文字を小文字に統一
         text = text.lower()
         
         node = self.tagger.parseToNode(text)
         
         tokens = []
         while node:
             features = node.feature.split(',')
             if features[0] != 'BOS/EOS':
             	# 品詞ベースでストップワードを除去
                 if features[0] not in ['助詞', '助動詞']:
                     token = features[6] \
                             if features[6] != '*' \
                             else node.surface
                     tokens.append(token)
             
             node = node.next
             
         return tokens
     
     def train(self, texts, labels):
         pipeline = Pipeline([
             ('vectorizer', CountVectorizer(tokenizer=self._tokenize)),
             ('classifier', SVC())
         ])
         
         pipeline.fit(texts, labels)
         
         self.pipeline = pipeline
         
     def predict(self, texts):
         return self.pipeline.predict(texts)
     
 training_data = pd.read_csv('./data/training_data_added.csv')
 dialogue_agent = DialogueAgent()
 dialogue_agent.train(training_data['text'], training_data['label'])
 
 with open('./data/replies_added.csv') as f:
     replies = f.read().split('\n')
 
 
 >>> input_text = '名前を教えてよ'
 >>> predictions = dialogue_agent.predict([input_text])
 >>> predicted_class_id = predictions[0]
 
 >>> from sklearn.metrics import accuracy_score
 
 >>> test_data = pd.read_csv('./data/test_data.csv')
 >>> predictions = dialogue_agent.predict(test_data['text'])
 >>> print(accuracy_score(test_data['label'], predictions))
 0.43617021276595747 # 前処理前は0.3723404255319149


nabetsunabetsu

文字コード

コンピュータと人間の違い

  • 文字コード

    • 人は「あ」や「線」など文字の形で情報を表すが、コンピュータは人が行っているように形から情報を読み取ることはできない
    • コンピュータが文字の種類を判別するために文字コードというものがある
  • フォント

    • 文字コードの番号に対応する文字の形がフォントデータを使って画面に描画される

文字コードとは

文字とそれに対応するビット組み合わせを対応付ける規則を文字コードという

文字 ビット組み合わせ
A 001
B 010
C 011
D 100
E 101

文字の集合

上記の例のように5つしか文字を表せない文字コードが現実にあっても使い物にならない。

そのため、どの文字に対応するかを決めなければならない。

文字コードの実装とは

コンピュータ上では以下のような動きが行われ、人が文字をみることができる。

  • Aというキーが入力されたら、「A」に対応するビット組み合わせをメモリ上に発生させる。
  • 表示装置は発生したビット組み合わせに応じたフォントデータを画面に描画する

外部コードと内部コード

外部とのやりとりで使用するコードが正しければ、コンピュータの内部でどのような文字コードを使っていても問題はない。

実用上の文字コード

HTTP

HTTPにおいても伝送するテキストデータの文字コードを明示できる

具体的にはHTTPヘッダにContent-Typeというフィールドがあるが、その中でcharset=utf-8のように文字コードの指定ができる

以下はhttps://www.w3.org/を表示したときのResponse Headerの一部

...
content-type: text/html; charset=utf-8
date: Wed, 01 Jan 2020 12:51:55 GMT
etag: "8af2-599e258eae140;89-3f26bd17a2f00-gzip"
expires: Wed, 01 Jan 2020 13:01:55 GMT
last-modified: Tue, 17 Dec 2019 08:45:17 GMT
status: 200
...

参考資料

unicodeとは?文字コードとは?UTF-8とは?
https://qiita.com/hiroyuki_mrp/items/f0b497394f3a5d8a8395

Unicode

Unicodeは文字コードの一種。文字コードとはコンピュータ内部で文字をどのようなバイト列で表現するかを定めたルール。

例)「あ」→0x3042

上記の「0x3042」をコードポイントという(Unicodeのコードポイントであることを示すために「U+3042」と表記する)

ord()
Unicode文字を受け取り、コードポイントを表す整数を返す
chr()
 コードポイントを表す整数を受け取り、Unicode文字を返す

>>> hex(ord('あ'))
'0x3042'
>>> chr(0x3042)
'あ'

Unicodeにおける文字の表現方法

Unicodeで文字を表現するには以下の2つの方法がある。

  1. 結合文字列(combining character sequence)
    複数の文字(コードポイント)の組み合わせで表現される文字

  2. 合成済み文字列(precomposed character)
    ひとつの文字(コードポイント)で表現される文字

# 合成済み文字列での表現
>>> hex(ord('デ'))
'0x30c7'
>>> chr(0x30c7)
'デ'
 
# 結合文字列での表現
>>> chr(0x30C6)
'テ'
>>> chr(0x3099)
'゙'
>>> chr(0x30C6) + chr(0x3099)
'デ'

同じ文字に複数の表現方法が存在するのは不便なので、Unicodeでは「同じ文字として扱うべきコードポイントの組を定義する」という方法で対応した(Unicodeの等価性という)

上記の例で言うと、合成済み文字列「U+30C7」と結合文字列「U+30C6 U+3099」は等価であるというルールが定義されているためにこの2つが同じ文字を表しているということがわかる。(変換も可能)

Unicode正規化には以下の4種類があり、unicodedata.normalizeの引数として指定ができる( [NLPでの前処理] 参照)

No. Name Description
1 NFD(Normalization From Canonical Decomposition 正準等価性による分解
2 NFC(Normalization From Canonical Composition 正準等価性による分解 → 正準等価性による合成
3 NFCD(Normalization From Compability Decomposition 互換等価性による分解
4 NFCK(Normalization From Compability Composition 互換等価性による分解 → 正準等価性による合成
  • 正準等価性
    見た目も機能も同じ文字を等価とみなす等価性

  • 互換等価性
    正準等価性よりも範囲の広い等価性。

Unicodeエスケープ

Unicodeの符合位置を16進数で表したもの。
キーボードから直接入力できない文字やソースコードの文字コードでは表現できない文字を書きたい場合には、Unicodeエスケープを使うことで表現できる。

  • 例) スペードマークをUnicodeエスケープで表す
>>> unicode = "\u2660"
>>> unicode
'♠'
nabetsunabetsu

NLP 環境構築

Google Colaboratory

# 形態素分析ライブラリーMeCab と 辞書(mecab-ipadic-NEologd)のインストール 
!apt-get -q -y install sudo file mecab libmecab-dev mecab-ipadic-utf8 git curl python-mecab > /dev/null
!git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git > /dev/null 
!echo yes | mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n > /dev/null 2>&1
!pip install mecab-python3 > /dev/null

# シンボリックリンクによるエラー回避
!ln -s /etc/mecabrc /usr/local/etc/mecabrc

参考資料

Google ColabにMeCabとipadic-NEologdをインストールする
Google Colab で MeCab と CaboCha を使う最強の方法

Docker

Kaggleが提供しているイメージを使用する簡易版(データサイエンスで必要なライブラリがだいたい入っているので、初回は結構時間かかる)

# Kaggleが提供しているDockerイメージをベース
FROM gcr.io/kaggle-images/python:v67

RUN pip install -U pip && \
    pip install fastprogress japanize-matplotlib

# mecabとmecab-ipadic-NEologdの導入
RUN apt-get update \
    && apt-get install -y mecab \
    && apt-get install -y libmecab-dev \
    && apt-get install -y mecab-ipadic-utf8 \
    && apt-get install -y git \
    && apt-get install -y make \
    && apt-get install -y curl \
    && apt-get install -y xz-utils \
    && apt-get install -y file \
    && apt-get install -y sudo


RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git \
    && cd mecab-ipadic-neologd \
    && bin/install-mecab-ipadic-neologd -n -y

RUN pip install mecab-python3

# nodejsの導入
RUN curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - \
    && sudo apt-get install -y nodejs

## JupyterLabの拡張機能

# 変数や行列の中身を確認
RUN jupyter labextension install @lckr/jupyterlab_variableinspector

# 自動整形
RUN pip install autopep8 \
    && pip install jupyterlab_code_formatter \
    && jupyter labextension install @ryantam626/jupyterlab_code_formatter \
    && jupyter serverextension enable --py jupyterlab_code_formatter
version: "3"
services:
  jupyterlab:
    build: .
    volumes:
      - $PWD:/content
    working_dir: /content
    ports:
      - 8888:8888
    command: jupyter lab --ip=0.0.0.0 --allow-root --no-browser

上記のファイルを作成した状態でdokcer-compose up --buildを実行すればOK

Docker + MeCab + JupyterLabによる分析環境の構築

グローバルにインストールする場合

1-1. Mac

 # CRF++のインストール
 brew install crf++
 
 # mecabと辞書のインストール
 brew install mecab mecab-ipadic
 To enable mecab-ipadic dictionary, add to /usr/local/etc/mecabrc:
   dicdir = /usr/local/lib/mecab/dic/ipadic
 
 # mecabのインストール確認  
 mecab
 きょうはいい天気ですね。
 きょう	名詞,副詞可能,*,*,*,*,きょう,キョウ,キョー
 は	助詞,係助詞,*,*,*,*,は,ハ,ワ
 いい	形容詞,自立,*,*,形容詞・イイ,基本形,いい,イイ,イイ
 天気	名詞,一般,*,*,*,*,天気,テンキ,テンキ
 です	助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
 ね	助詞,終助詞,*,*,*,*,ね,ネ,ネ
 。	記号,句点,*,*,*,*,。,。,。
 EOS
 
 # mecab-ipadic-NEologdのインストール
 git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
 cd mecab-ipadic-neologd
 # 標準設定
 ./bin/install-mecab-ipadic-neologd -n
 # -dオプションを設定して使用
 mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/
 
 # CaboChaのインストール
 brew install cabocha
 # CaboChaのインストール確認
 cabocha
 今日はいい天気ですね。
       今日は---D
           いい-D
     天気ですね。
 EO
 
 # cabochaのPythonバインディングをインストール
 curl -OL https://github.com/taku910/cabocha/archive/master.zip
 unzip master.zip
 cd cabocha-master
 pip install python/
 # インストール確認
 python
 >>> import CaboCha

MeCabとCaboChaをMacに導入してPythonから使ってみる
https://qiita.com/musaprg/items/9a572ad5c4e28f79d2ae

mecab-ipadic-NEologd : Neologism dictionary for MeCab
https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md

1-2. Amazon Linux

 $ sudo yum update
 $ sudo yum install gcc make glibc gcc-c++ python-devel
 
 # CRFのインストール
 $ wget -O CRF++-0.58.tar.gz 'https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7QVR6VXJ5dWExSTQ'
 $ tar xvzf CRF++-0.58.tar.gz
 $ cd CRF++-0.58
 $ ./configure
 $ make
 $ sudo make install
 $ cd ../
 
 # MeCabのインストール
 $ wget -O mecab-0.996.tar.gz 'https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7cENtOXlicTFaRUE'
 $ tar xvzf mecab-0.996.tar.gz
 $ cd mecab-0.996
 $ ./configure
 $ make
 $ make check
 $ sudo make install
 $ cd ../
 
 # 辞書(IPADIC)のインストール
 $ wget -O mecab-ipadic-2.7.0-20070801.tar.gz 'https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7MWVlSDBCSXZMTXM'
 $ tar xvzf mecab-ipadic-2.7.0-20070801.tar.gz
 $ cd mecab-ipadic-2.7.0-20070801
 $ ./configure -with-charset=utf-8 –enable-utf8-only
 $ make
 $ sudo make install
 $ cd ../
 
 # Cabochaのインストール
 $ FILE_ID=0B4y35FiV1wh7SDd1Q1dUQkZQaUU
 $ FILE_NAME=cabocha-0.69.tar.bz2
 $ curl -sc /tmp/cookie "https://drive.google.com/uc?export=download&id=${FILE_ID}" > /dev/null
 $ CODE="$(awk '/_warning_/ {print $NF}' /tmp/cookie)"  
 $ curl -Lb /tmp/cookie "https://drive.google.com/uc?export=download&confirm=${CODE}&id=${FILE_ID}" -o ${FILE_NAME}
 
 $ tar -jxf cabocha-0.69.tar.bz2
 $ cd cabocha-0.69
 $ ./configure -with-charset=utf-8
 $ make
 $ sudo make install
 $ cd ../
  • Pythonバインディングのインストール
 # mecab-pythonのインストール
 pip install mecab-python3 --user
 
 # Cabochaのインストール
 git clone https://github.com/taku910/cabocha.git
 cd cabocha
  pip install python/ --user
  
 # ライブラリにパスを通す
 sudo vi /etc/ld.so.conf #文末に/usr/local/libを追加
 sudo ldconfig

Amazon Linux に MeCab と CaboCha をインストール
https://qiita.com/january108/items/85c80769ea870c190eaa
https://qiita.com/osyou-create/items/4e2f686d82bf9e1166e8

1-3. Ubuntu

sudo apt install mecab libmecab-dev mecab-ipadic mecab-ipadic-utf8


# Pythonバインディングのインストール
pip install mecab-python3

エラー対応

以下のエラーが出てしまう

Failed initializing MeCab. Please see the README for possible solutions:

    https://github.com/SamuraiT/mecab-python3#common-issues

If you are still having trouble, please file an issue here, and include the
ERROR DETAILS below:

    https://github.com/SamuraiT/mecab-python3/issues

issueを英語で書く必要はありません。

------------------- ERROR DETAILS ------------------------
arguments: 
error message: [ifs] no such file or directory: /usr/local/etc/mecabrc

以下に記載があるようにmecabにおける辞書の指定方法に起因する問題のよう。以下でバージョンを落とすことでとりあえず解決

pip install mecab-python3==0.996.5で解決した

mecab-python3 が 1.0 にバージョンアップして、辞書ライブラリを別途インストールすることをを求めるようになったことに起因するようです。

mecabrc が見つからないというエラー

fedora30 python3 から MeCabを使う → エラー「 [ifs] no such file or directory: /usr/local/etc/mecabrc」が出たら sudo cp /etc/mecabrc /usr/local/etc/ で解決

ローカルディレクトリにmecabをインストールする方法(2017)

2. ipadic-NEologdのインストール

# リポジトリのクローン
git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
cd mecab-ipadic-neologd
# インストールの実行
./bin/install-mecab-ipadic-neologd -n

# 途中でインストールするか聞かれるのでyesと応える
[install-mecab-ipadic-NEologd] : Do you want to install mecab-ipadic-NEologd? Type yes or no.
yes
...
[install-mecab-ipadic-NEologd] : Install completed.
[install-mecab-ipadic-NEologd] : When you use MeCab, you can set '/usr/local/lib/mecab/dic/mecab-ipadic-neologd' as a value of '-d' option of MeCab.
[install-mecab-ipadic-NEologd] : Usage of mecab-ipadic-NEologd is here.
Usage:
 $ mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd ...

[install-mecab-ipadic-NEologd] : Finish..
[install-mecab-ipadic-NEologd] : Finish..

https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md

※1GBメモリのインスタンスなどでインストールを行うと以下のエラーが出るかも

reading ./Verb.csv ... 130750
terminate called after throwing an instance of 'std::bad_alloc'         | 
   what():  std::bad_alloc
/home/ec2-user/environment/mecab-ipadic-neologd/bin/../libexec/make-mecab-ipadic-neologd.sh: line 525:  6769 Aborted                 ${MECAB_LIBEXEC_DIR}/mecab-dict-index -f UTF8 -t UTF8
nabetsunabetsu

特徴ベクトル化

文章をプログラムで扱うために、コンピュータで計算可能な形式に変換する。
具体的には1つの文章を1つのベクトルで表す。

機械学習の分野では、文章をベクトル化したものを特徴量(feature)や特徴ベクトル(feature vector)という(元となった文章の特徴が反映されたベクトルという意味)

特徴ベクトル化でこの手法が正しいという絶対的なものはなく、それぞれの手法の性質(この手法は語順が無視される等)を理解して、取り扱う問題に応じて選択していく必要がある。

Bag of Words

特徴抽出の方法の1つで一般的に使われているのがBag of Words。
具体的には以下の手順でベクトル化したものをBag of Wordsと呼ぶ

  1. 単語にインデックスを割り当てる
  2. 文ごとに単語の登場回数を数える
  3. 文ごとに各単語の登場回数を並べる

上記の手順の通り、単語の出現頻度をベクトル化したものであるので、語順による意味の違いは区別されない。以下の例のように

example
 # 語順によって意味が変わらないケース
 明日友達と遊園地に遊びに行く
 友達と遊園地に明日遊びに行く
 
 # 語順によって意味が変わるケース
 犬が人をかんだ
 人が犬をかんだ

Bag of Wordsの性質

語順による意味の違いは無視される
 文章を特徴づける単語と特徴づける単語が同等に扱われる

From Scratch

一から実装した場合には以下のような実装になる

 from tokenizer import tokenize
 
 def calc_bow(tokenized_texts):
     # 引数の文章すべてから単語のリストを作成する
     vocabulary = {}
     for tokenized_text in tokenized_texts:
         for token in tokenized_text:
             if token not in vocabulary:
                 vocabulary[token] = len(vocabulary)
     n_vocab = len(vocabulary)
     
     ### Build BoW Feature Vector
     # 0で埋めたベクトルを作成する
     bow = [[0] * n_vocab for i in range(len(tokenized_texts))]
     # 文章の中で単語の出現回数をカウントする
     for i, tokenized_text in enumerate(tokenized_texts):
         for token in tokenized_text:
             index = vocabulary[token]
             bow[i][index] += 1
     return vocabulary, bow
 
 texts = [
     '私は私のことが好きなあなたが好きです',
     '私はラーメンが好きです',
     '富士山は日本一高い山です'
 ]
 tokenized_texts = [tokenize(text) for text in texts]
 vocabulary, bow = calc_bow(tokenized_texts)

アウトプットとして、単語ごとのインデックス(vocabulary)と特徴ベクトルがアウトプットとして得られる

Using Scikit-Learn

Scikit-learnでBoWを作成する機能が提供されており、これと自分で作成したTokenizerを組み合わせることで簡単に実装ができる。

 from os.path import dirname, join, normpath
 
 import pandas as pd
 from sklearn.feature_extraction.text import CountVectorizer
 
 from tokenizer import tokenize
 
 # データ読み込み
 # BASE_DIR = normpath(dirname(__file__))
 # csv_path = join(BASE_DIR, './data/training_data.csv')
 training_data = pd.read_csv('./data/training_data.csv')
 training_texts = training_data['text']
 
 ### bag of wordsの計算
 # モデルの定義
 vectorizer = CountVectorizer(tokenizer=tokenize)
 
 # 語彙の獲得(対象のテキスト内のすべてのトークンから辞書を生成する)
 vectorizer.fit(training_texts)
 
 # 特徴ベクトル化
 bow = vectorizer.transform(training_texts)

# 上記の2行はfit_transformを使って1行にまとめることもできる
# vectorizer.fit_transform(training_texts)

注意点としてBowはほとんどの要素がゼロのベクトルになるため、メモリ効率化のためにtransformで返されるデータはscipy.sparse.csr.csr_matrixになっていること。
(学習する際もscipy.sparse.csr.csr_matrixのまま使えるので特段意識する必要はない)

また、ライブラリによっては上記の形式に対応していない場合もあるが、toarrayメソッドでnumpy配列に変換することができる。

print(bow.toarray())

TF-IDF

TF-IDFはBoWからさらに改良を加えようとして作られた特徴の抽出方法。
BoWの問題点の1つはすべての語彙を同じ重要度で扱ってしまうことであり、TD-IDFの背景にあるのは以下の考え。

  • ある文の中で、他の単語に比べて頻繁に登場する単語は、その文の意味を表現するために重要な単語である
  • いろいろな文に登場する単語は一般的な単語であるため、個々の文の意味を表現する上では重要ではない

BoWとは違った形式の特徴ベクトルが得られる。

Using Scikit-Learn

Scikit-LearnのTfidfVectorizerクラスを使ってIT-IDFの計算ができる

 from sklearn.feature_extraction.text import TfidfVectorizer
 
 from tokenizer import tokenize
 
 texts = [
     '私は私のことが好きなあなたが好きです',
     '私はラーメンが好きです。',
     '富士山は日本一高い山です'
 ]
 
 # TF-IDF計算
 vectorizer = TfidfVectorizer(tokenizer=tokenize)
 vectorizer.fit(texts)
 tfidf = vectorizer.transform(texts)
 >>> tfidf
 <3x15 sparse matrix of type '<class 'numpy.float64'>'
 	with 22 stored elements in Compressed Sparse Row format>
 >>> tfidf.toarray()
 array([[0.        , 0.29312473, 0.44585783, 0.29312473, 0.17312419,
         0.29312473, 0.29312473, 0.17312419, 0.        , 0.44585783,
         0.        , 0.        , 0.        , 0.44585783, 0.        ],
        [0.47496141, 0.        , 0.3612204 , 0.        , 0.28051986,
         0.        , 0.        , 0.28051986, 0.47496141, 0.3612204 ,
         0.        , 0.        , 0.        , 0.3612204 , 0.        ],
        [0.        , 0.        , 0.        , 0.        , 0.27249889,
         0.        , 0.        , 0.27249889, 0.        , 0.        ,
         0.46138073, 0.46138073, 0.46138073, 0.        , 0.46138073]])

BM25

TF-IDFにさらに文の長さを考慮するよう修正を加えたもの

単語N-gram

bi-gramとuni-gram

n-gramとはテキストを連続するn個のトークン(単語など)で表すこと。

これまでつかってきた1単語は1単語として扱う手法をuni-gramという。

連続する2単語をまとめて1つの単語のように扱う手法をbi-gramという。

bi-gramではuni-gramでは無視していた語順の情報をある程度反映できる特徴がある。

 # uni-gram
 東京 / から / 大阪 / に / 行く
	
# bi-gram
東京から / から大阪 / 大阪に / に行く
大阪から / から東京 / 東京に / に行く

上記のbi-gramの例を辞書にまとめると以下の通りとなり、「どこからどこに行くのか」というそれぞれの文章の特徴をとらえられていることがわかる

bi-gram(from Tokyo to Osaka)
  - 単語	登場回数
  - 東京から	1
  - から大阪	1
  - 大阪に	1
  - に行く	1
  - 大阪から	0
  - から東京	0
  - 東京に	0
bi-gram(from Osaka to Tokyo)
  - 単語	登場回数
  - 東京から	0
  - から大阪	0
  - 大阪に	0
  - に行く	0
  - 大阪から	1
  - から東京	1
  - 東京に	1

nを増やしていくと...

uni-gram、bi-gramの考え方を発展させ、任意の数の単語をまとめて考えることができる。
3単語ならtri-gram、それ以上ならN-gramと一般的に呼ばれる

N-gramを使う際に考慮すべき事項

語順情報を無視すべきか、考慮すべきか

  • nを増やすほど次元が増える

    • 次元が増えるのに応じてデータ量も多くなるので、メモリや計算負荷が増加する
  • nを増やすほど特徴がSparse(疎)になる

    • 汎化能力が下がるが、細かな特徴をとらえることも可能になる

実装

Scikit-Learnで簡単に実装が可能
n-gramはngram_rangeで最大値、最小値を指定できる。
(2, 2)を指定すればunigram、(1, 2)を指定すればbigramとunigramの両方

 from sklearn.feature_extraction.text import CountVectorizer
 
 from tokenizer import tokenize
 
 texts = [
     '東京から大阪に行く',
     '大阪から東京に行く'
 ]
 
 # bi-gram
 >>> vectorizer = CountVectorizer(tokenizer=tokenize, ngram_range=(2, 2))
 >>> vectorizer.fit(texts)
 >>> bow = vectorizer.transform(texts)
 >>> bow.toarray()
 array([[1, 0, 1, 0, 1, 1, 0],
        [0, 1, 1, 1, 0, 0, 1]])
  • TF-IDFにも対応しているので、引数でn-gramを指定すればベクトル化が簡単にできる
 from sklearn.feature_extraction.text import TfidfVectorizer
 
 from tokenizer import tokenize
 
 texts = [
     '東京から大阪に行く',
     '大阪から東京に行く'
 ]
 
 # bi-gram
 vectorizer = TfidfVectorizer(tokenizer=tokenize, ngram_range=(2, 2))
 vectorizer.fit(texts)
 bow = vectorizer.transform(texts)

文字N-gram(character N-gram)

文字単位での適用、つまりわかち書きなしに文中で連続するn文字をひとまとまりの語彙としてBoWを構成させる

例えば「東京から大阪に行く」を文字数によるtri-gramでBoW化すると以下のようになる

tri-gram
  - 東京か	1
  - 京から	1
  - から大	1
  - ら大阪	1
  - 大阪に	1
  - 阪に行	1
  - に行く	1

文字N-gramの特徴

  • 単語の表記ゆれに強い

  • タイプミスや活用、送り仮名の有無などの表記ゆれについては、単語ベースの手法では

  • 複合語・未知語に強い

  • 次元数が大きくなりやすい

実装

  • CountVectorizerでanalyzer='word'を指定するとtokenizerが使われるようになる
  • 英文の場合にはanalyzer='char_wb'も使える
 from sklearn.feature_extraction.text import CountVectorizer
 
 texts = [
     '東京から大阪に行く',
     '大阪から東京に行く'
 ]
 
 
 >>> vectorizer = CountVectorizer(analyzer='char', ngram_range=(3, 3))
 >>> vectorizer.fit(texts)
 >>> bow = vectorizer.transform(texts)
 >>> vectorizer.vocabulary_
 {'から大': 0,
  'から東': 1,
  'に行く': 2,
  'ら大阪': 3,
  'ら東京': 4,
  '京から': 5,
  '京に行': 6,
  '大阪か': 7,
  '大阪に': 8,
  '東京か': 9,
  '東京に': 10,
  '阪から': 11,
  '阪に行': 12}

特徴抽出手法の結合

これまでに学んできた各特徴ベクトル化の方法について、それぞれの方法を組み合わせて特徴ベクトルを作り出すこともできる。

  • 注意点

    • 次元が多くなる
  • 逆に精度が下がる場合がある

  • 値の範囲が大きく違う

  • 疎性(値が0である次元がどの程度多いか)が大きく違う

スクラッチでの実装

 import scipy
 from sklearn.feature_extraction.text import CountVectorizer
 
 from tokenizer import tokenize
 
 texts = [
     '私は私のことが好きなあなたが好きです',
     '私はラーメンが好きです。',
     '富士山は日本一高い山です'
 ]
 
 # 2種類の特徴量を定義
 word_bow_vectorizer = CountVectorizer(tokenizer=tokenize)
 char_bigram_vectorizer = CountVectorizer(analyzer='char', ngram_range=(2, 2))
 
 # 学習
 word_bow_vectorizer.fit(texts)
 char_bigram_vectorizer.fit(texts)
 
 # 特徴量を抽出
 word_bow = word_bow_vectorizer.transform(texts)
 char_bigram = char_bigram_vectorizer.transform(texts)
 
 feat = scipy.sparse.hstack((word_bow, char_bigram))

※`scipyを使っているのは結合前のそれぞれの特徴ベクトルがscipy.sparseの疎行列インスタンスであるため。numpy.ndarrayであればnp.stack等を使えばいい。

Scikit-Learnを使った実装

 from sklearn.feature_extraction.text import CountVectorizer
 from sklearn.pipeline import FeatureUnion
 
 from tokenizer import tokenize
 
 texts = [
     '私は私のことが好きなあなたが好きです',
     '私はラーメンが好きです。',
     '富士山は日本一高い山です'
 ]
 
 word_bow_vectorizer = CountVectorizer(tokenizer=tokenize)
 char_bigram_vectorizer = CountVectorizer(analyzer='char', ngram_range=(2, 2))
 
 estimators = [
     ('bow', word_bow_vectorizer),
     ('char_bigram', char_bigram_vectorizer)
 ]
 
 combined = FeatureUnion(estimators)
 combined.fit(texts)
 feat = combined.transform(texts)

その他アドホックな特徴量

上述の一般的な特徴量とは別に以下のような値を特徴量として加える(新たな次元として追加する)と識別性能が上がることがある。

  • 文の長さ
  • 読点で区切った場合の文の数
  • 特定の単語の出現回数

こうした特徴量は自分で取り扱う問題に合わせて設計するので、用途にそぐわない特徴量を設計しても性能が下がってしまうことがあるので、ある程度の試行錯誤が必要

Scikit-learnによる実装

  • FeatureUnionで結合させるため、アドホックな特徴量を求めるTextStatsはScikit-learnのBaseEstimatorTransformerMixinを継承する
  • TextStatsはDictのListを返す
  • DictVectorizerはDictのListを一つの特徴量としてまとめる機能を持つ
 import re
 
 from sklearn.base import BaseEstimator, TransformerMixin
 from sklearn.feature_extraction import DictVectorizer
 from sklearn.feature_extraction.text import CountVectorizer
 from sklearn.pipeline import FeatureUnion, Pipeline
 
 rx_periods = re.compile(r'[.。.]+')
 
 class TextStats(BaseEstimator, TransformerMixin):
     def fit(self, x, y=None):
         return self
     
     def transform(self, texts):
         return [
             {
                 'length': len(text),
                 'num_sentences': len([sent for sent in rx_periods.split(text)
                                       if len (sent) > 0])
             }
             for text in texts
         ]
 
 
 combined = FeatureUnion([
     ('stats', Pipeline([
         ('stats', TextStats()),
         ('vect', DictVectorizer()),
     ])),
     ('char_bigram', CountVectorizer(analyzer='char', ngram_range=(2, 2))), 
 ])
 
 texts = [
     'こんにちは。こんばんは',
     '焼き肉が食べたい'
 ]
 
 >>> combined.fit(texts)
 >>> feat = combined.transform(texts)
 >>> feat
 array([[11.,  2.,  1.,  0.,  0.,  2.,  0.,  1.,  1.,  1.,  1.,  0.,  1.,
          1.,  1.,  0.,  0.,  0.],
        [ 8.,  1.,  0.,  1.,  1.,  0.,  1.,  0.,  0.,  0.,  0.,  1.,  0.,
          0.,  0.,  1.,  1.,  1.]])
          
 >>> model = TextStats()
 >>> model.transform(texts)
 [{'length': 11, 'num_sentences': 2}, {'length': 8, 'num_sentences': 1}]

nabetsunabetsu

自然言語処理

全体像

  • 前処理
    • 正規化
    • 見出し語化
    • ストップワード
    • 単語置換
  • 形態素解析
  • 特徴ベクトル化
    • 機械学習等の分析手法を適用するためにはテキスト情報を特徴ベクトル化する必要がある
    • 特徴ベクトル化することでText Classification等の分析手法を適用できる
      • TF-IDF
      • bag-of-words
      • N-Gram
      • Word2vec
  • 特徴量変換
    • 特徴ベクトル化によって生成されたベクトルに対して、より一層識別器等で処理しやすい形に変換するための処理を行う
    • 次元削減が有名で、具体的な方法について以下がある。
      • LSA(潜在意味解析)
      • PCA(主成分分析)
  • 識別器等モデルの作成
    • SVM
    • RandomForest
    • LSTM
    • seq2seq
      • 機械翻訳

基本的な流れ

  • わかち書き(単語分割)
  • 特徴抽出(特徴ベクトル化)
  • 識別器等の作成(分析の実行やモデルの作成)

基本的な流れは上記の通りだが、コンピュータがデータをこちらの意図した通りに処理するためには表記揺れの対応等の前処理が必要になる。

分析手法

  • Text Classification
    • 有名なのはメールがスパムかどうかを判定するもの
    • 基本的にSupervised Learningで分類の判定をさせる?
  • Syntactic Similarity
    • 類似文書の検索
  • Topic Modeling
    • テキストデータ(例:ニュース記事、口コミ)のクラスタリングでよく使われるモデル
  • Text Summarization
    • 要約
    • extractionとabstractionの2つの方法に分けられる
  • Word Embedding
    • bag-of-wordsでは単語の登場回数が特徴量を作るが、使う単語が全く異なっていても同じような意味を表現していることはよくある
    • こうしたbag-of-wordsの欠点を補う方法としてWord Embeddingがある
  • Sentiment Analysis
    • 対象の文書がポジティブかネガティブか
  • Knowledge Graph

アプリケーション

  • チャットボット
  • ナレッジグラフ
  • 要約
nabetsunabetsu

テキスト解析の基礎

  • テキストデータはコンピュータからすればバイト列に過ぎない
  • プログラムで扱うための最初のステップは文を単語に分解すること
  • 文を単語に分割することができれば各単語に番号を割り当てて数値の配列として扱えるようになる

わかち書き

  • 英語のように単語と単語の間にスペースがあればほとんど処理は不要だが、日本語は単語の境界を判定して文を分解する必要がある
  • 文を単語に分割することをわかち書きという
  • わかち書きを行うために広く利用されているのがMecab
  • Mecabでは文を単語に分解するだけでなく、品詞や読みなどの情報を付与してくれる(品詞情報の付与まで含んだわかち書きを形態素解析と呼ぶ)

Mecab

インストール

インストールにあたっての注意点として、MecabをPythonで使用するには主に以下3つの要素が必要。

辞書はMecab本体をインストールした時にデフォルトのものが設定されているので、特にインストール作業は不要だが、mecab-ipadic-neologdなど別の辞書をインストールして使用することも可能

Docker

Google Colaboratory

Google ColabにMeCabとipadic-NEologdをインストールする
Google Colab で MeCab と CaboCha を使う最強の方法

使い方

parse

parseで形態素解析の結果が得られます。

import MeCab

tagger = MeCab.Tagger()
print(tagger.parse('国境の長いトンネルを抜けると雪国であった。'))
>>> 
国境	名詞,一般,*,*,*,*,国境,コッキョウ,コッキョー
の	助詞,格助詞,一般,*,*,*,,,ノ
長い	形容詞,自立,*,*,形容詞・アウオ段,基本形,長い,ナガイ,ナガイ
トンネル	名詞,一般,*,*,*,*,トンネル,トンネル,トンネル
を	助詞,格助詞,一般,*,*,*,,,ヲ
抜ける	動詞,自立,*,*,一段,基本形,抜ける,ヌケル,ヌケル
と	助詞,接続助詞,*,*,*,*,,,ト
雪国	名詞,一般,*,*,*,*,雪国,ユキグニ,ユキグニ
で	助動詞,*,*,*,特殊・ダ,連用形,,,デ
あっ	助動詞,*,*,*,五段・ラ行アル,連用タ接続,ある,アッ,アッ
た	助動詞,*,*,*,特殊・タ,基本形,,,タ
。	記号,句点,*,*,*,*,,,。
EOS

使用する辞書を指定するにはpathを指定します。

import MeCab

# mecab-ipadic-neologdを指定
path = "-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd"
tagger = MeCab.Tagger()
print(tagger.parse('国境の長いトンネルを抜けると雪国であった。'))
node

parseでは情報が全て文字列で返されるため改行で区切ったりパースする必要があります。
parseToNodeメソッドを使用して、得られるnodeオブジェクトを利用することで単語や品詞の情報に簡単にアクセスできます。

import MeCab

tagger = MeCab.Tagger()
node = tagger.parseToNode('国境の長いトンネルを抜けると雪国であった。')

while node:
    print('単語: ', node.surface)
    print('品詞: ', node.feature)
    node = node.next
>>> 
単語:  
品詞:  BOS/EOS,*,*,*,*,*,*,*,*
単語:  国境
品詞:  名詞,一般,*,*,*,*,国境,コッキョウ,コッキョー
単語:  の
品詞:  助詞,格助詞,一般,*,*,*,の,ノ,ノ
単語:  長い
品詞:  形容詞,自立,*,*,形容詞・アウオ段,基本形,長い,ナガイ,ナガイ
単語:  トンネル
品詞:  名詞,一般,*,*,*,*,トンネル,トンネル,トンネル
単語:  を
品詞:  助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
単語:  抜ける
品詞:  動詞,自立,*,*,一段,基本形,抜ける,ヌケル,ヌケル
単語:  と
品詞:  助詞,接続助詞,*,*,*,*,と,ト,ト
単語:  雪国
品詞:  名詞,一般,*,*,*,*,雪国,ユキグニ,ユキグニ
単語:  で
品詞:  助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
単語:  あっ
品詞:  助動詞,*,*,*,五段・ラ行アル,連用タ接続,ある,アッ,アッ
単語:  た
品詞:  助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
単語:  。
品詞:  記号,句点,*,*,*,*,。,。,。
単語:  
品詞:  BOS/EOS,*,*,*,*,*,*,*,*

わかち書き関数の実装

import MeCab

tagger = MeCab.Tagger()

def tokenize(text):
    node = tagger.parseToNode(text)

    tokens = []
    while node:
        if node.surface != '':
            tokens.append(node.surface)
        
        node = node.next
    return tokens

tokenize('国境の長いトンネルを抜けると雪国であった。')
>>> 
['国境', 'の', '長い', 'トンネル', 'を', '抜ける', 'と', '雪国', 'で', 'あっ', 'た', '。']

特徴ベクトル化

わかち書きした文章をコンピュータで処理しやすくするため、ベクトル形式への変換を行う。
具体的には1つの文章を1つのベクトルで表す。

具体的には以下のような形への変換を行うが、ここで重要なのは元となった文章の特徴をどのようにベクトルに反映させるか。

ベクトル化というのは以下のようなイメージで数値形式への変換を行う。

# 文章
国境の長いトンネルを抜けると雪国であった。

# わかち書き
['国境', 'の', '長い', 'トンネル', 'を', '抜ける', 'と', '雪国', 'で', 'あっ', 'た', '。']

# 特徴ベクトル
[0, 1, 2, 0, 0, ..., 3, 1, 0]

Bag of Words(BoW)

特徴ベクトル化で一旦的な手法

特徴
  • ベクトルの次元数は語彙の数になる
手順
  1. 単語にインデックスを割り当てる
  2. 文ごとに単語の登場回数を数える
  3. 各単語の登場回数を並べる
nabetsunabetsu

形態素解析について

辞書の活用

MeCabによるわかち書きは辞書に基づいて行われる。

MeCabによる解析を行ったときに表示される情報は、辞書にとのような情報が登録されているかに依存する。
また、辞書が保持しているのは、形態素解析に関する情報だけではなく、「ある形態素の出現しやすさ」や「形態素同士の連結しやすさ」といった情報(生起コスト、連接コスト)も保持している。

  • IPAdic
    • MeCabが公式に推奨している辞書であり、基本的な語彙をカバーしている
    • IPAコーパスというデータに基づいている
  • UniDic
    • UniDicというデータに基づいた辞書
  • jumandic
    • MeCabとは別のJUMANという形態素解析器で使われている辞書をMeCab用に移植した辞書
  • ipadic-NEologdn
    • IPA辞書をもとに単語の数を大幅に拡張した辞書
    • 基本的な形式はIPAdicを踏襲しているが、インターネットからクローリングして語彙を拡張しているため新語への対応力がとても高い

形態素

Mecab等を利用して形態素解析を行った結果として単語ごとに色々な情報が得られるため、目的に応じてその結果を使える。

例えば以下のようにシンプルなTokenizerを作ってプログラムで形態素解析を行ってみた場合

def tokenize(text):
    node = tagger.parseToNode(text)
    result = []
    while node:
        features = node.feature.split(',')
        print(features) # 形態素解析の結果を表示

以下のように単語ごに形態素解析の結果が得られる。

['名詞', 'サ変接続', '*', '*', '*', '*', '予定', 'ヨテイ', 'ヨテイ']
['助詞', '格助詞', '一般', '*', '*', '*', 'の', 'ノ', 'ノ']
['形容詞', '自立', '*', '*', '形容詞・アウオ段', '基本形', 'ない', 'ナイ', 'ナイ']
['名詞', '副詞可能', '*', '*', '*', '*', '休日', 'キュウジツ', 'キュージツ']
['助詞', '係助詞', '*', '*', '*', '*', 'は', 'ハ', 'ワ']
['名詞', '副詞可能', '*', '*', '*', '*', 'お昼', 'オヒル', 'オヒル']
['助詞', '格助詞', '一般', '*', '*', '*', 'から', 'カラ', 'カラ']
['動詞', '自立', '*', '*', '五段・マ行', '連用タ接続', '飲む', 'ノン', 'ノン']
['動詞', '非自立', '*', '*', '五段・ワ行促音便', '未然ウ接続', 'じゃう', 'ジャオ', 'ジャオ']
['助動詞', '*', '*', '*', '不変化型', '基本形', 'う', 'ウ', 'ウ']
['記号', '句点', '*', '*', '*', '*', '。', '。', '。']
['名詞', '副詞可能', '*', '*', '*', '*', '毎年', 'マイトシ', 'マイトシ']
['名詞', '一般', '*', '*', '*', '*', '恒例', 'コウレイ', 'コーレイ']
['助詞', '連体化', '*', '*', '*', '*', 'の', 'ノ', 'ノ']
['名詞', '固有名詞', '地域', '一般', '*', '*', '鎌倉', 'カマクラ', 'カマクラ']
['名詞', '接尾', '地域', '*', '*', '*', '産', 'サン', 'サン']
['助詞', '連体化', '*', '*', '*', '*', 'の', 'ノ', 'ノ']
['接頭詞', '名詞接続', '*', '*', '*', '*', '青', 'アオ', 'アオ']
['名詞', '固有名詞', '一般', '*', '*', '*', 'みかん。', 'ミカン', 'ミカン']
['助詞', '格助詞', '一般', '*', '*', '*', 'を', 'ヲ', 'ヲ']
['名詞', 'サ変接続', '*', '*', '*', '*', '投入', 'トウニュウ', 'トーニュー']
['動詞', '自立', '*', '*', 'サ変・スル', '連用形', 'する', 'シ', 'シ']
['助動詞', '*', '*', '*', '特殊・タ', '基本形', 'た', 'タ', 'タ']

例えば、Wordcloudで文書の内容を図示したいなら名詞だけをピックアップしたり、動詞や形容詞の言葉を揃えるため見出し語化を行うなどが考えられる。

nabetsunabetsu

特徴量変換

BoWやTF-IDF等の手法によって得られた特徴ベクトルを後続で処理を行う識別器等にとってより望ましい特徴を持った特徴ベクトルに変換する手法について。

潜在意味解析(Latent Semantic Analysis, LSA)

BoW等で作成した特徴ベクトルをもとにして、単語の背後にある意味のレベルで文章を表現するベクトルを得る手法

具体的には入力されたベクトルの次元数を削減(圧縮)することで文章が表現している意味を表す。

圧縮によって作成された各次元が表現していると考えられる抽象的な意味をトピックと呼ぶ。

LSAにおけるトピックとは各単語で文中での単語の登場の仕方から求まる概念であり、同じような使われ方をしている単語は似たような意味であると考えられ、その意味ことがトピックとして解釈できるというのがLSAの基本的なアイデア。

次元削減の効果

  • LSAによって意味的に関連のある単語が同じ次元にマッピングされるようになるので、同義語への対応力向上が期待できる
  • 意味の似た単語が同じ次元にまとめられ次元数が減るため、識別器等での処理におけるメモリ効率の向上が期待できる
  • その一方で、次元削減により元の特徴ベクトルが持っている情報はある程度削ぎ落とされるので、場合によっては識別器等の性能低下につながる可能性もある。

実現方法

特異値分解(Singular Value Decomposition, SVD)という手法でLSAを実現できる。
数学的な概念は置いておいて、scikit-learnから提供されているsklearn.decomposition.TruncatedSVDで実装が可能。

# SVDを実行

from sklearn.decomposition import TruncatedSVD
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer

vectorizer = CountVectorizer(tokenizer=tokenize)
vectorizer.fit(texts)
bow = vectorizer.transform(texts)

bow_table = pd.DataFrame(bow.toarray(), columns=vectorizer.get_feature_names())
svd = TruncatedSVD(n_components=4, random_state=42)
svd.fit(bow)

decompose_features = svd.transform(bow)

主成分分析(Principal Component Analysis, PCA)

LSAとは別の次元削減手法。

nabetsunabetsu

その他のライブラリのインストール

nltk

Python の自然言語処理用ライブラリ

インストール方法

  • nltk自体
    pipでインストールする

    pip install nltk

  • stopwords
    コンソールよりエラーメッセージに出力されたコマンドを入力する

LookupError: 
**********************************************************************
    Resource stopwords not found.
    Please use the NLTK Downloader to obtain the resource:

    >>> import nltk
    >>> nltk.download('stopwords')
    
    For more information see: https://www.nltk.org/data.html

    Attempted to load corpora/stopwords
 
    Searched in:
        '/home/deepstation/nltk_data'
        '/home/deepstation/Documents/venv/nltk_data'
        '/home/deepstation/Documents/venv/share/nltk_data'
        '/home/deepstation/Documents/venv/lib/nltk_data'
        '/usr/share/nltk_data'
        '/usr/local/share/nltk_data'
        '/usr/lib/nltk_data'
        '/usr/local/lib/nltk_data'
**********************************************************************
Python 3.6.6 (default, Oct 11 2018, 12:51:46) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import nltk
>>> nltk.download('stopwords')
[nltk_data] Downloading package stopwords to
[nltk_data]     /home/deepstation/nltk_data...
[nltk_data]   Unzipping corpora/stopwords.zip.
True

公式ページ
https://www.nltk.org/

spacy

自然言語処理ライブラリ。特徴は、 事前に訓練された統計モデルと単語ベクトルが付属している点です。現在33言語をサポート、8言語に対する13個の統計モデルを利用できる。TensorFlow、PyTorch、scikit-learn、Gensim、その他のPythonのAIエコシステムとシームレスに相互運用可能

インストール方法

  • Spacy自体

pip install spacy

  • モデルのダウンロード

python -m spacy download en_core_web_sm

Models & Languages
https://spacy.io/usage/models

公式ページ
https://spacy.io/

textracy

spacyを利用して構築されているライブラリ。
Information Extractionに利用できる。

displaCy Named Entity Visualizer

Named Entityをビジュアル化して表示できる

https://explosion.ai/demos/displacy-ent

Unidic

MeCabとUNIDICをUbuntu 14.04にインストール

自然言語処理

gensim

トピック分析のためのライブラリ

gensim入門

genism Official Documentation