NLP 入門(1–2) Stop words

Gary Hsu
11 min readAug 2, 2019

--

本篇文章的colab 連結在這

前言:

繼上一篇: NLP入門 Bag of words + Naive Bayes Classifier,文中我有提到一些可以增進 NLP model 的效能的方法,由於篇幅的關係我就拆來這一篇講,希望能幫助大家更了解NLP,如果對 bag of words (詞袋) 沒有那麼熟悉的話,建議可以回到第一篇文章(看前半部就好,Naive Bayes 的細節不會在這篇出現)

Stop words

講到 Stop words 時我們不妨從這個 bag of words (bow) 的例子來看看,其中我們可以發現(下圖)右手邊中出現了諸如 the, who, and, it, to, a, for 這些單字,可以推測的是,在每篇文章中這些單字(定冠詞、介系詞、代名詞)都會是高頻單字,因此似乎沒什麼必要保留這些不「太」帶有資訊的單詞。

Bag of words (Bow)

有哪些stop words?

在NLP 兩大library: spaCy 和 NLTK之中都各自提供了stop words的列表,取得的方法分別是:

spaCy:

如果尚未安裝 spaCy:

# 利用 conda 安裝 spaCy
conda install -c conda-forge spacy
# spaCy 安裝完是一個空殼,要用它 model 裡面的功能必須先下載 model
python -m spacy download en_core_web_sm
  1. spaCy 本身是一個空殼,直到 load model 之後才能存取 model 的內容(更多spaCy的細節: https://spacy.io/usage/models
  2. 將spaCy model之中,英文的stop words抓出來:
import spacy

nlp = spacy.load('en_core_web_sm')
spacy_stopwords = spacy.lang.en.stop_words.STOP_WORDS
print('spaCy has {} stop words'.format(len(spacy_stopwords)))
print('The first five stop words are {}'.format(list(spacy_stopwords)[:5]))

結果:

spaCy has 326 stop words 
The first five stop words are [‘if’, ‘put’, ‘’ve’, ‘how’, ‘all’]

NLTK

如果尚未安裝 NLTK:

conda install -c anaconda nltk
  1. NLTK 在第一次存取 stop words 時,也需要下載
  2. 下載完之後,就可以取得NLTK提供的 stop words 列表, 更多關於 NLTK stop words 的細節 https://gist.github.com/sebleier/554280
import nltknltk.download('stopwords')nltk_stopwords = nltk.corpus.stopwords.words('english')print('NLTK has {} stop words'.format(len(nltk_stopwords)))print('The first five stop words are {}'.format(list(nltk_stopwords)[:5]))

結果

NLTK has 179 stop words 
The first five stop words are [‘i’, ‘me’, ‘my’, ‘myself’, ‘we’]

可以發現,在不同library之中會有不同的stop words,現在就來把stop words 從IMDB的例子之中移出吧(Colab link)

整理之後的 IMDB Dataset

我將提供兩種實作方法,並且比較兩種方法的性能。

1. 平鋪直敘的寫法:

1. 將整個dataframe iterate一遍
2. 當前這一列(row)的 text 取出,並使用word_tokenize來將整段文章轉換成 list of words
3. 如果這個 word 不在 spacy_stopwords裡面,就append進list之中
4. 利用 ‘ ‘.join(word list) 將 word list 轉換回string
5. 在 imdb_df 之中建立一個新的column,並把剛剛的結果assign給這個新的column
import nltk, time
from nltk import word_tokenize
nltk.download(‘punkt’)
start = time.time()
spacy_rmsw_result = []
for index, row in list(imdb_df.iterrows()): # 1
word_vector = []
for word in word_tokenize(row[‘text’]): # 2
if word not in spacy_stopwords: # 3
word_vector.append(word)
spacy_rmsw_result.append(‘ ‘.join(word_vector)) # 4
imdb_df[‘spacy_rmsw_text’] = spacy_rmsw_result # 5
print(“Finish remove stop words from {} example in {} seconds “.format(len(imdb_df), time.time() — start))

結果:

Finish remove stop words from 25000 example in 56.4 seconds

2. 使用dataframe之中apply的寫法:

1. imdb_df['text'].apply將每個row的text值分別取出2. apply(lambda x: rmsw_function(x, spacy_stopwords)) 這邊可以拆解成
- 將imdb_df['text'] 取出,當作lambda function的 x
- 然後丟進 rmsw_function 得到ruturn的值
- 然後放到 imdb['spacy_rmsw_text_fancy']之中
import time
from nltk.tokenize import word_tokenize
start = time.time()
def rmsw_function(text, stopword_list):
return ' '.join([word for word in word_tokenize(text) if word not in stopword_list])
imdb_df['spacy_rmsw_text_fancy'] = imdb_df['text'].apply(lambda x: rmsw_function(x, spacy_stopwords))print("Finish remove stop words from {} example in {} seconds".format(len(imdb_df), time.time() - start))

結果:

Finish remove stop words from 25000 example in 51.26 seconds

雖然和上面平鋪直敘的方式寫起來效能沒有差很多(5秒,約10%),但我認為有三個理由會讓 data scientist 想把這 “pythonic” 的方式學起來:

  • 可以不用再有額外的list [spacy_rmsw_result] 來儲存結果
  • 可以加快10%的速度
  • 最重要的一點:很潮、很潮、很潮

3. 殺手鐧:使用sklearn內建的功能

如果覺得自己一列一列把 stop words 取出來很麻煩,有一個小訣竅就是使用 Sklearn 之中 CountVectorizer(stop_words=’english’),偉哉sklearn:

from sklearn.feature_extraction.text import CountVectorizervectorizer_rmsw = CountVectorizer(stop_words='english')raw_sklearn_text = vectorizer_rmsw.fit_transform(imdb_df['text'])

上面那些 fancy 不 fancy 的方法都沒有這個 fancy,但別急著罵髒話,後面的例子會告訴你,根據不同 library (spaCy, NLTK, sklearn)提供的 stop words 列表,也會影響我們 model 的效能(一點點)!

來測試一下 Stop words是否對效能有影響吧?

1. 首先我們要像上篇一樣,使用 bag of words (詞袋)的方式來將文字轉換成 Machine learning model 可讀的「數值」:

from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer()
raw_text = vectorizer.fit_transform(imdb_df['text'])
rmsw_text = vectorizer.fit_transform(imdb_df['spacy_rmsw_text'])

2. 再將 raw text 餵給我們貪吃的 Naive Bayes Model:

(這邊我使用了 cross validation,之後會再寫一篇來補充,總之就是將data 切分成 training data, validation data, 然後 cv = 4 代表做四次, n_jobs = -1 代表使用所有處理器)

from sklearn.model_selection import cross_val_score
from sklearn.naive_bayes import MultinomialNB
import numpy as np
MNB = MultinomialNB()score_raw = cross_val_score(MNB, raw_text, imdb_df['label'], cv = 4, n_jobs = -1)print('The average accuracy "without" remove stop words is {}'.format(np.mean(score_raw)))

結果:

The average accuracy "without" remove stop words is 0.8488

3. 也將 rmsw_text (remove stop words) 餵給同樣的 Naive Bayes Model:

from sklearn.model_selection import cross_val_score
from sklearn.naive_bayes import MultinomialNB
import numpy as np
MNB = MultinomialNB()score_rmsw = cross_val_score(MNB, rmsw_text, imdb_df['label'], cv = 4, n_jobs = -1)print('The mean accuracy "with" remove stop words from "spaCy" is {}'.format(np.mean(score_rmsw)))

結果:

The mean accuracy "with" remove stop words from "spaCy" is 0.86
The mean accuracy "with" remove stop words from "NLTK" is 0.8591
The mean accuracy "with" remove stop words from "sklearn" is 0.8574

發現最好的結果(使用 spaCy stop words 列表)增加了1.12%的正確率。

結語

至於有一些觀點說明 remove stop words 會影響效能,例如:

This is not a great movie (negative)
This is a great movie (positive)

由於 This, is, not, a 都是 stop words 所以這兩句話會變成

great movie (positive)
great movie (positive)

也許這是一個極端的例子,大部分的情況 remove stop words 會讓 model 更專注在訊息量較大的單詞,那究竟要不要 remove stop words 呢?我個人的建議是交給資料(data)來決定吧!如果這個例子之中 remove stop words是有提升 model 正確率的話,何必跟正確率過不去。

如果覺得這篇文章對你有幫助的話,再麻煩幫我鼓掌囉!如果有什麼疑問也歡迎留言或者 E-mail 我討論: sfhsu29@gmail.com

--

--