텍스트 분류를 위한 나이브 베이즈 (2) - 분류기 훈련 및 성능평가
나이브 베이즈 분류기 훈련
sklearn.naive_bayes 모듈의 MultinomialNB 클래스와 3개의 벡터라이저를 각각 복합해 서로 다른 3개의 분류기를 만들고 기본 매개변수를 사용해 어떤 것이 더 낫게 수행하는지 비교한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | >>> from sklearn.naive_bayes import MultinomialNB >>> from sklearn.pipeline import Pipeline >>> from sklearn.feature_extraction.text import TfidfVectorizer, >>> HashingVectorizer, CountVectorizer >>> >>> clf_1 = Pipeline([ >>> ('vect', CountVectorizer()), >>> ('clf', MultinomialNB()), >>> ]) >>> >>> clf_2 = Pipeline([ >>> ('vect', HashingVectorizer(non_negative=True)), >>> ('clf', MultinomialNB()), >>> ]) >>> >>> clf_3 = Pipeline([ >>> ('vect', TfidfVectorizer()), >>> ('clf', MultinomialNB()), >>> ]) | cs |
분류기와 특화된 X, y 값을 입력받아 K-중첩 교차 검증을 수행하는 함수를 정의한다.
1 2 3 4 5 6 7 8 9 10 | >>> from sklearn.cross_validation import cross_val_score, KFold >>> from scipy.stats import sem >>> >>> def evaluate_cross_validation(clf, X, y, K): >>> # k=5 인 중첨 교차 검증 생성기를 만든다 >>> cv = KFold(len(y), K, shuffle=True, random_state=0) >>> # 기본적으로 점수 함수는 에스터메이터의 점수 함수로 반환된 함수를 사용한다(정확도). >>> scores = cross_val_score(clf, X, y, cv=cv) >>> print scores >>> print ("Mean score: {0:.3f} (+/-{1:.3f})").format(np.mean(scores), sem(scores)) | cs |
각 분류기에 5-중첩 교차 검증을 수행한다. 결과는 다음과 같다. 결과에서 보는대로 CountVectorizer 와 TfidfVectorizer 는 HashingVectorizer 보다 뛰어나며 서로 비슷한 성능을 보이고 있다.
1 2 3 4 5 6 7 8 9 10 | >>> clfs = [clf_1, clf_2, clf_3] >>> for clf in clfs: >>> evaluate_cross_validation(clf, news.data, news.target, 5) [ 0.85782493 0.85725657 0.84664367 0.85911382 0.8458477 ] Mean score: 0.853 (+/-0.003) [ 0.75543767 0.77659857 0.77049615 0.78508888 0.76200584] Mean score: 0.770 (+/-0.005) [ 0.84482759 0.85990979 0.84558238 0.85990979 0.84213319] Mean score: 0.850 (+/-0.004) | cs |
그럼 TfidfVectorizer 을 가지고 정규표현식으로 텍스트를 파싱해 결과를 향상시켜보자. 기본 정규표현식: ur"\b\w\w+\b" 는 알파벳 문자와 _ 를 고려한다. / 와 . 에 대한 고려는 토큰화를 향상시킬 수
있고, Wi-Fi 와 site.com 과 같은 토큰을 고려하기 시작한다. 새로운 정규표현식은
ur"\b[a-z0-9_\-\.]+[a-z][a-z0-9_\-\.]+\b" 가 된다. 정규표현식 정의에 대한 질문이 있다면 파이썬
re 모듈 문서를 참고한다. 새로운 분류기로 시도해보자.
1 2 3 4 5 6 7 8 9 10 11 | >>> clf_4 = Pipeline([ >>> ('vect', TfidfVectorizer( >>> token_pattern=ur"\b[a-z0-9_\-\.]+[a-z][a-z0->>> 9_\-\.]+\b", >>> )), >>> ('clf', MultinomialNB()), >>> ]) >>> >>> evaluate_cross_validation(clf_4, news.data, news.target, 5) [ 0.80450928 0.81029451 0.81082515 0.82143805 0.80817193] Mean score: 0.811 (+/-0.003) | cs |
사용할 수 있는 또 다른 매개변수는 stop_words 이다. 이 값은 너무 빈도가 높은 단어이거나 특정 주제의 정보를 제공하지 않는 단어와 같은 고려할 필요가 없는 단어의 리스트를 전달한다. 다음과 같이 텍스트 파일에서 stop_words 를 얻는 함수를 정의한다.
1 2 3 4 5 | >>> def get_stop_words(): >>> result = set() >>> for line in open('stopwords_en.txt', 'r').readlines(): >>> result.add(line.strip()) >>> return result | cs |
Stop Word List 는 아래의 링크 등을 참고해서 생성한다.
http://www.lextek.com/manuals/onix/stopwords2.html
다음과 같이 새로운 매개변수로 새로운 분류기를 생성한다. 0.89 로 결과치가 향상되었다.
1 2 3 4 5 6 7 8 9 10 11 12 | >>> clf_5 = Pipeline([ >>> ('vect', TfidfVectorizer( >>> stop_words= get_stop_words(), >>> token_pattern=ur"\b[a-z0-9_\-\.]+[a-z][a-z0-9_\-\.]+\b", >>> )), >>> ('clf', MultinomialNB()), >>> ]) evaluate_cross_validation(clf_5, news.data, news.target, 5) [ 0.88222812 0.89599363 0.88591138 0.89599363 0.88485009] Mean score: 0.889 (+/-0.003) | cs |
이 벡터라이저를 가지고 MultinomialNB 의 매개변수를 확인하자. 이 분류기는 변경할 수 있는 일부 매개변수를 가진다. 가장 중요한 것은 평활화를 조절하는 매개변수인 alpha 이다. 우선은 낮은 값인 0.01 로 설정한다. 결과는 0.89 에서 0.92 로 더 높아졌음을 볼 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 | >>> clf_7 = Pipeline([ >>> ('vect', TfidfVectorizer( >>> stop_words= get_stop_words(), >>> token_pattern=ur"\b[a-z0-9_\-\.]+[a-z][a-z0-9_\-\.]+\b", >>> )), >>> ('clf', MultinomialNB(alpha=0.01)), >>> ]) >>> >>> evaluate_cross_validation(clf_7, news.data, news.target, 5) [ 0.92175066 0.92040329 0.91907668 0.92491377 0.91881136] Mean score: 0.921 (+/-0.001) | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | >>> from sklearn import metrics >>> >>> def train_and_evaluate(clf, X_train, X_test, y_train, y_test): >>> >>> clf.fit(X_train, y_train) >>> >>> print "Accuracy on training set:" >>> print clf.score(X_train, y_train) >>> print "Accuracy on testing set:" >>> print clf.score(X_test, y_test) >>> y_pred = clf.predict(X_test) >>> >>> print "Classification Report:" >>> print metrics.classification_report(y_test, y_pred) >>> print "Confusion Matrix:" >>> print metrics.confusion_matrix(y_test, y_pred) | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | >>> train_and_evaluate(clf_7, X_train, X_test, y_train, y_test) Accuracy on training set: 0.996957690675 Accuracy on testing set: 0.91935483871 Classification Report: precision recall f1-score support 0 0.95 0.88 0.91 216 1 0.86 0.85 0.85 246 2 0.91 0.84 0.87 274 3 0.82 0.86 0.84 235 4 0.88 0.90 0.89 231 5 0.89 0.92 0.90 225 6 0.88 0.80 0.84 248 7 0.92 0.93 0.93 275 8 0.96 0.98 0.97 226 9 0.97 0.94 0.96 250 10 0.97 1.00 0.98 257 11 0.96 0.97 0.97 261 12 0.92 0.91 0.91 216 13 0.94 0.96 0.95 257 14 0.94 0.97 0.96 246 15 0.91 0.97 0.94 234 16 0.91 0.97 0.94 218 17 0.97 0.99 0.98 236 18 0.95 0.91 0.93 213 19 0.87 0.80 0.83 148 avg / total 0.92 0.92 0.92 4712 Confusion Matrix: [[190 0 0 0 1 0 0 0 0 1 0 0 0 1 0 9 2 0 0 12] [ 0 209 5 3 2 13 3 0 0 0 0 2 3 2 3 0 0 1 0 0] [ 0 11 230 22 1 5 1 0 1 0 0 0 0 0 1 0 1 0 1 0] [ 0 6 7 201 11 3 4 0 0 0 0 0 2 0 1 0 0 0 0 0] [ 0 2 3 3 209 1 5 0 0 0 2 0 5 0 1 0 0 0 0 0] [ 0 8 2 1 1 207 0 1 1 0 0 0 0 2 1 0 0 1 0 0] [ 0 2 4 9 6 0 199 14 1 2 1 1 4 2 2 0 0 1 0 0] [ 0 1 1 1 1 0 6 257 4 1 0 0 0 1 0 0 2 0 0 0] [ 0 0 0 0 0 1 1 2 221 0 0 0 0 1 0 0 0 0 0 0] [ 0 0 0 0 0 0 1 0 2 236 5 0 1 3 0 1 1 0 0 0] [ 0 0 0 1 0 0 0 0 0 0 256 0 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 1 0 1 0 0 0 254 0 1 0 0 3 0 1 0] [ 0 1 0 2 5 1 3 1 0 2 1 1 196 1 2 0 0 0 0 0] [ 0 1 0 1 1 0 0 0 0 0 0 2 1 246 3 0 1 0 0 1] [ 0 2 0 0 0 0 0 1 0 0 0 0 0 1 239 0 1 0 1 1] [ 1 0 1 2 0 0 0 1 0 0 0 1 0 0 1 226 0 1 0 0] [ 0 0 1 0 0 0 1 0 1 0 0 1 0 0 0 0 211 0 3 0] [ 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 234 1 0] [ 1 0 0 0 0 0 1 0 0 0 0 2 1 1 0 1 7 3 193 3] [ 8 0 0 0 0 1 0 0 0 1 0 0 0 0 0 11 4 1 4 118]] | cs |
벡터라이저 내부를 보면 딕셔너리를 만든 토큰을 볼 수 있다.
딕셔너리는 145,598개의 토큰으로 이루어져 있다. 속성 이름을 출력해보자.
1 2 3 | >>> print len(clf_7.named_steps['vect'].get_feature_names()) 145598 | cs |
의미적으로 같은 일부 단어를 볼 수 있다. 이를테면, sand 와 sands, sanctuaries 와 sanctuary 이다. 복수형 단어와 단수형 단어를 같은 단어로 처리한다면 문서를 좀 더 좋게 대표할 수 있을 것이다. 이 작업은 같은 어근을 갖는 두 단어의 어근 추출stemming을 사용해 해결한다(stemming 에 관해서는 http://nltk.org 를 참조한다).
1 | >>> clf_7.named_steps['vect'].get_feature_names() | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | [u'0-.66d8wt', u'0-04g55', u'0-100mph', u'0-13-117441-x--or', u'0-3mb', u'0-40mb', u'0-40volts', u'0-5mb', u'0-60mph', u'0-8.3mb', u'0-a00138', u'0-byte', u'0-defects', u'0-e8', u'0-for-4', u'0-hc', u'0-ii', u'0-uw', u'0-uw0', u'0-uw2', u'0-uwa', u'0-uwt', u'0-uwt7', u'0-uww', u'0-uww7', u'0.-w0', u'0..x-1', u'0.00...nice', u'0.02cents', u'0.0cb', u'0.1-ports', u'0.15mb', u'0.2d-_', u'0.5db', u'0.6-micron', u'0.65mb', u'0.97pl4', u'0.b34s_', u'0.c0rgo5kj7pp0', u'0.c4', u'0.jy', u'0.s_', u'0.tprv6ekj7r', u'0.tt', u'0.txa_', u'0.txc', u'0.vpp', | cs |
'프로그래밍 Programming' 카테고리의 다른 글
Numpy로 수치계산하기 (2) (0) | 2016.11.19 |
---|---|
Numpy로 수치계산하기 (1) (0) | 2016.11.18 |
우분투에 스칼라 설치하기 Install Scala on Ubuntu 14.04 (0) | 2016.11.14 |
텍스트 분류를 위한 나이브 베이즈 (1) - 데이터 전처리 (0) | 2016.11.12 |
ImportError: The pandas.io.data module is moved to a separate package (pandas-datareader) (0) | 2016.11.12 |