自然言語処理の前処理の話(記号の削除、stopwordsの削除、語形の統一など)

まずは実際のコードを掲載します。
(今回は英語に対してなので注意)

```python
#テキトーに引っ張ってきた文章
sentence = """
'"Let me look, Billie," and Donald reached out his hand for
the field glass through which Broncho Billie was gazing down
from the summit of Real del Monte upon the plain of Quesco,
through which the Pachuca river winds its way.
"Maybe I can make out who they are."\n
Billie handed over the glass without a word and stood expectant,
while Donald scrutinized closely a body of horsemen—twenty or more
in number—which had halted beside the railroad that connects
the little city of Pachuca with the City of Mexico.\n
"They are not soldiers, that\'s certain," was Donald\'s comment
after he had inspected the riders carefully for a couple of minutes.\n
"That\'s what I thought," from Billie.
"They look like a bunch of vaqueros to me;
 but what would a crowd of fifty cowpunchers be doing in a country where
 the only cattle are goats?"'
"""
        
# 1. 記号の削除
sentence = re.sub("[^a-zA-Z]", " ", sentence)
        
# 2. 小文字化
sentence = sentence.lower()
        
# 3. トークン化
sentence = nltk.word_tokenize(sentence)
        
# 4. stopwordsの削除
sentence = [word for word in sentence if not word in set(stopwords.words("english"))]

# 5. レマタイズ
lemma = nltk.WordNetLemmatizer()
sentence = [lemma.lemmatize(word) for word in sentence]

# 結合
sentence = " ".join(sentence)
```

# 手順
1. 記号の削除
2. 小文字化
3. トークン化
4. stopwordsの削除
5. レマタイズ

# 1. 記号の削除

記号の削除を踏む理由は言うまでもないですね。
reモジュールを使います。

```python
# アルファベットのaからzまでAからZまでを抽出
sentence = re.sub("[^a-zA-Z]", " ", sentence)

print(sentence)

# Let me look  Billie   and Donald reached out his hand for the field glass through which Broncho Billie was gazing down from the summit of Real del Monte upon the plain of Quesco  through which the Pachuca river winds its way   Maybe I can make out who they are    Billie handed over the glass without a word and stood expectant  while Donald scrutinized closely a body of horsemen twenty or more in number which had halted beside the railroad that connects the little city of Pachuca with the City of Mexico    They are not soldiers  that s certain   was Donald s comment after he had inspected the riders carefully for a couple of minutes    That s what I thought   from Billie   They look like a bunch of vaqueros to me   but what would a crowd of fifty cowpunchers be doing in a country where  the only cattle are goats 
```

クオーテーションや改行コードが消えていますね。

# 2. 小文字化

機械の上ではGetとgetの二つは別のものとして扱われます。
同じ単語が別のものになって分析されることを避けるため、小文字に統一します。
lower()メソッドによって小文字に置き換えることが可能です。

```python
sentence = sentence.lower()

print(sentence)

# let me look  billie   and donald reached out his hand for the field glass through which broncho billie was gazing down from the summit of real del monte upon the plain of quesco  through which the pachuca river winds its way   maybe i can make out who they are    billie handed over the glass without a word and stood expectant  while donald scrutinized closely a body of horsemen twenty or more in number which had halted beside the railroad that connects the little city of pachuca with the city of mexico    they are not soldiers  that s certain   was donald s comment after he had inspected the riders carefully for a couple of minutes    that s what i thought   from billie   they look like a bunch of vaqueros to me   but what would a crowd of fifty cowpunchers be doing in a country where  the only cattle are goats 
```

# 3. トークン化

トークン化というと聞きなれないかもしれませんが、
要するに単語で分割してlistに置き換えることです。

```python
sentence = nltk.word_tokenize(sentence)

print(sentence)

# ['let', 'me', 'look', 'billie', 'and', 'donald', 'reached', 'out', 'his', 'hand', 'for', 'the', 'field', 'glass', 'through', 'which', 'broncho', 'billie', 'was', 'gazing', 'down', 'from', 'the', 'summit', 'of', 'real', 'del', 'monte', 'upon', 'the', 'plain', 'of', 'quesco', 'through', 'which', 'the', 'pachuca', 'river', 'winds', 'its', 'way', 'maybe', 'i', 'can', 'make', 'out', 'who', 'they', 'are', 'billie', 'handed', 'over', 'the', 'glass', 'without', 'a', 'word', 'and', 'stood', 'expectant', 'while', 'donald', 'scrutinized', 'closely', 'a', 'body', 'of', 'horsemen', 'twenty', 'or', 'more', 'in', 'number', 'which', 'had', 'halted', 'beside', 'the', 'railroad', 'that', 'connects', 'the', 'little', 'city', 'of', 'pachuca', 'with', 'the', 'city', 'of', 'mexico', 'they', 'are', 'not', 'soldiers', 'that', 's', 'certain', 'was', 'donald', 's', 'comment', 'after', 'he', 'had', 'inspected', 'the', 'riders', 'carefully', 'for', 'a', 'couple', 'of', 'minutes', 'that', 's', 'what', 'i', 'thought', 'from', 'billie', 'they', 'look', 'like', 'a', 'bunch', 'of', 'vaqueros', 'to', 'me', 'but', 'what', 'would', 'a', 'crowd', 'of', 'fifty', 'cowpunchers', 'be', 'doing', 'in', 'a', 'country', 'where', 'the', 'only', 'cattle', 'are', 'goats']
```

私はstr.split()でもいいかと思っていましたが、word_tokenizeを利用することでdidn'tを["did", "n't"]と分割してくれたり単純なスペース分割よりも賢く分割してくれているようです。

```python
sentence = """At eight o'clock on Thursday morning
... Arthur didn't feel very good."""

tokens = nltk.word_tokenize(sentence)

# ['At', 'eight', "o'clock", 'on', 'Thursday', 'morning', 'Arthur', 'did', "n't", 'feel', 'very', 'good', '.']
```

# 4. stopwordsの削除

stopwordsとは？って感じですよね。
これはaやforやtheなど文法表現として文章を成り立たせるのには必要ですが、
単語一つとしてみたときにあまり意味を持たない単語になります。
様々な文章に現れるので文章中で出てくる単語の偏りも大きく出てしまいます。
機械学習を行うときにノイズになりがちなので削除してしまいます。

```python
sentence = [word for word in sentence if not word in set(stopwords.words("english"))]

print(sentence)

# ['let', 'look', 'billie', 'donald', 'reached', 'hand', 'field', 'glass', 'broncho', 'billie', 'gazing', 'summit', 'real', 'del', 'monte', 'upon', 'plain', 'quesco', 'pachuca', 'river', 'winds', 'way', 'maybe', 'make', 'billie', 'handed', 'glass', 'without', 'word', 'stood', 'expectant', 'donald', 'scrutinized', 'closely', 'body', 'horsemen', 'twenty', 'number', 'halted', 'beside', 'railroad', 'connects', 'little', 'city', 'pachuca', 'city', 'mexico', 'soldiers', 'certain', 'donald', 'comment', 'inspected', 'riders', 'carefully', 'couple', 'minutes', 'thought', 'billie', 'look', 'like', 'bunch', 'vaqueros', 'would', 'crowd', 'fifty', 'cowpunchers', 'country', 'cattle', 'goats']
```

stopwordsモジュールという便利なモジュールが用意されているのでそれを利用します。
english指定によって英語のstopwordsを参照し、その中に参照中の単語が含まれていれば、
stopwordsとして弾く、含まれていなかったら抽出の流れです。

# 5. レマタイズ

lemmatizeという聞きなれない単語ですが、
plan, planned, planningなどを語形をそろえて、planに統一してしまうことです。

```python
# レマタイザーのインスタンス化
lemma = nltk.WordNetLemmatizer()
# 先ほどトークン化してリストになっているので、リストから1単語ずつ取り出してレマタイズの実行
sentence = [lemma.lemmatize(word) for word in sentence]

# ['let', 'look', 'billie', 'donald', 'reached', 'hand', 'field', 'glass', 'broncho', 'billie', 'gazing', 'summit', 'real', 'del', 'monte', 'upon', 'plain', 'quesco', 'pachuca', 'river', 'wind', 'way', 'maybe', 'make', 'billie', 'handed', 'glass', 'without', 'word', 'stood', 'expectant', 'donald', 'scrutinized', 'closely', 'body', 'horseman', 'twenty', 'number', 'halted', 'beside', 'railroad', 'connects', 'little', 'city', 'pachuca', 'city', 'mexico', 'soldier', 'certain', 'donald', 'comment', 'inspected', 'rider', 'carefully', 'couple', 'minute', 'thought', 'billie', 'look', 'like', 'bunch', 'vaquero', 'would', 'crowd', 'fifty', 'cowpuncher', 'country', 'cattle', 'goat']
```

nltkの中にWordNetLemmatizerモジュールがあるのでこちらを利用してやっています。

# 注意点

以上自然言語処理の前処理の話でしたが、注意点があります。
近年流行しているBERTモデルなどは文章の前後、つまり文脈を判断するためにstopwordsを消したり語形をそろえることが返って予測を困難にしてしまう場合があります。(私が行った分析で、この前処理の後にBERTに通したのと、前処理を挟まなかった場合とで比べて前処理しないほうが誤差が少なく出たことがありました。)
使いたいモデルに応じて、行うべき前処理を吟味する必要がありそうです。

TfidfVectorizerなど文章中の単語一つ一つを計算して特徴量にするものに関しては、こういった前処理が最適解になると思っています。
（後日、実際この前処理の後にTfidfVectorizerに通す処理は他記事で掘り下げる予定です。）


記事を読んでくださった皆様ありがとうございました！
参考になれば幸いです。

# この後の展開
こちらの前処理後にTfidfVectorizerを使って特徴を出す記事を書きました。
https://qiita.com/fumifumitaro/items/b1f478a8999ccb98406b