문서분류❓
주어진 문서에 대해
미리 정의된 클래스로 분류하는 작업을 의미
예를 들어 몇개의 뉴스가 있다면
이 뉴스들을 어떤 카테고리의 뉴스인지,
가령 정치, 환경 등등으로
분류하는 것을 말한다.
머신러닝으로도 문서 분류할 수 있는데
문서분류는 지도학습으로 분류가 된다.
즉, 모델 학습을 위해서는
모든 문서 또는 텍스트에 대해서
라벨이나 분류 카테고리가 있어야 한다는 뜻이다.
뉴스 데이터를 가지고 한번 실습해보고자 한다.
from sklearn.datasets import fetch_20newsgroups
#20개의 토픽 중 선택하고자 하는 토픽을 리스트로 생성
categories = ['alt.atheism', 'talk.religion.misc', 'comp.graphics', 'sci.space']
#학습 데이터셋을 가져옴
newsgroups_train = fetch_20newsgroups(subset='train',
#메일 내용에서 hint가 되는 부분을 삭제 - 순수하게 내용만으로 분류
remove=('headers', 'footers', 'quotes'),
#해당 카테고리의 뉴스만 가져오겠다!
categories=categories)
#검증 데이터셋을 가져옴
newsgroups_test = fetch_20newsgroups(subset='test',
remove=('headers', 'footers', 'quotes'),
categories=categories)
print('#Train set size:', len(newsgroups_train.data)) #.data는 텍스트의 내용을 가져옴
print('#Test set size:', len(newsgroups_test.data))
print('#Selected categories:', newsgroups_train.target_names)#숫자에 따른 토픽이름
print('#Train labels:', set(newsgroups_train.target)) #.target은 숫자로 표시된 라벨을 가져옴
사이킷런에서는 fetch_20newsgroup으로
뉴스 데이터를 제공해주는데
여러가지 설정을 하여 데이터 필터링을 할 수 있다.
1. subset
: train 또는 test를 넘겨주면
저절로 train set, test set을 반환해준다.
2. categories
원하는 카테고리를 넘겨주면
해당 카테고리의 뉴스만 반환해준다.
3. remove
삭제하고 싶은 데이터를 넘겨주면
해당 데이터를 삭제해준 데이터를 반환해준다.
.target은 뉴스의 라벨(숫자)을 반환해주고,
(이때 라벨은 0부터 시작)
.data는 뉴스의 원본(텍스트)를 반환해준다.
그리고 target_names는 라벨에 따른 타겟 이름을 반환해준다
아래는 위 코드의 결과이다.
#Train set size: 2034
#Test set size: 1353
#Selected categories: ['alt.atheism', 'comp.graphics', 'sci.space', 'talk.religion.misc']
#Train labels: {0, 1, 2, 3}
위에서 머신러닝을 통해 문서분류를 할 수 있다고 했는데,
문서분류 머신러닝 알고리즘에는
라쏘,릿지,로지스틱 회귀분석
나이브 베이즈
의사 결정 나무, 랜덤포레스트, 그레디언부스팅 등
다양한 알고리즘이 있지만
나이브 베이즈가 문서분류 알고리즘으로 가장 많이 사용되어왔다
머신러닝을 이용한 문서분류 순서는 아래와 같다
1. 데이터 정제(전처리)
텍스트 전처치 과정 거친다
(토큰화, 불용어 제거 등)
2. 특성 데이터로 변환
1번에서 전처리한 데이터를 가지고
카운트 벡터를 만들어 준다.
💡 이때 주의할 점은 train set에 대해서 fit_transform을 진행하고
test set에 대해서는 transform만 적용해야 한다.
< 동일한 특성집합에 대해서
train set과 test set의 카운트 벡터가 생성되어야 하기 때문에!!
만약 test set에 대해서 fit_transform을 적용하면
test set에 대해서 새로운 특성집합을 만들고
그것을 기준으로 카운트 벡터를 생성하게 되기때문에
제대로 된 학습 및 평가가 이루어질 수 없음
3. 데이터 분류
모델 학습을 위한 train data와
모델 평가를 위한 test data로 데이터를 분류한다
4.모델 학습
train데이터로 모델을 학습
5.모델 평가
4번에서 학습된 모델을 가지고 test data로 평가를 진행
6.최종 모델 도출
성능이 가장 좋은 모델을 채택
<성능 개선을 위해 여러가지 시도를 해야 함
예를 들어, 하이퍼파라미터 튜닝, 다른 알고리즘 선택등이 있다.
(이때 지표는 주로 정확도를 사용)
7.최종 예측 진행
실제 문제에 대해 6번에서 채택된 모델을 가지고 예측 진행
(이때 데이터는 1~2번과 동일한 전처리를 거친 데이터이어야 함)
나이브 베이즈
문서 분류에 가장 많이 사용된 알고리즘이다.
사이킷런은 sklearn.naive_bayes에서
나이브 베이즈를 위한 클래스 제공
1. MultinomialNB
>이산적인 특성값들을 이용하여 분류하고자 할때 이용
즉, 보통의 카운트벡터는 이산적인 값들로 이루어져 있기 때문에
MultinomialNB를 이용한다
MultinomialNB를 이용하여
문서분류하는 절차는 아래와 같다.
(거의 모든 머신러닝 알고리즘의 순서가 동일)
<순서>
1. 객체 생성 : MultinomialNB()
2. 모델 훈련 : model.fit()
> train set으로 모델을 훈련 시킴
>이때, 인수로 text와 label 둘 다 넘겨줘야 한다.
3. 모델 평가
#model.predict()
> 인수로 test set의 특성데이터를 넘겨주면
그에 대한 예측 라벨 값을 반환
# accuracy_score()
>인수로 실제 test set에 대한 라벨값과
predict을 통해 예측한 값을 인수로 넘겨주면
정확도를 반환
4. 성능 개선
정확도를 높이기 위해서
모델의 하이퍼 파라미터 튜닝을 진행하거
또는 토큰화하는 방법을 개선
from sklearn.naive_bayes import MultinomialNB #sklearn이 제공하는 MultinomialNB 를 사용
from sklearn.metrics import accuracy_score
NB_clf = MultinomialNB() # 분류기 선언
NB_clf.fit(X_train_cv, y_train) #train set을 이용하여 분류기(classifier)를 학습
#문서분류기에서 fit은 카운트벡터와 그에 해당하는 라벨을 인수로 건네줘야 한다.
nb_preds=NB_clf.predict(X_test_cv)
#score는 정확도를 반환 : 인수로 카운트벡터와 라벨을 건네주면 된다.
print('Train set score: {:.3f}'.format(NB_clf.score(X_train_cv, y_train))) #train set에 대한 예측정확도를 확인
print('Test set score: {:.3f}'.format(accuracy_score(y_test,nb_preds))) #test set에 대한 예측정확도를 확인
Train set score: 0.824
Test set score: 0.732
여기서 모델을 성능을 더 개선해보고 싶다면❓
1. 모델의 매개변수 조절
2. 카운트 벡터라이저의 매개변수 조절
* max_df, min_df, max_features 등등
3. CountVectorizer가 아닌 TF-idf 사용
✔️ 성능 개선을 위해
CountVectorizer대신 TFidfVectorizer를 사용하여
카운트벡터를 생성해보자!
이때도 마찬가지로 train set에 대해서만 fit_transform을 적용
test set에 대해서는 transform만 적용
from sklearn.feature_extraction.text import TfidfVectorizer
#CountVectorizer와 동일한 인수를 사용
tfidf = TfidfVectorizer(max_features=2000, min_df=5, max_df=0.5)
#X_train에 대한 특성집합 생성한 뒤, 카운트벡터 반환
X_train_tfidf = tfidf.fit_transform(X_train) # train set을 변환
#X_trian에 대한 특성집합을 기준으로 X_test에 대한 카운트 벡터 반환
X_test_tfidf = tfidf.transform(X_test) # test set을 변환
NB_clf.fit(X_train_tfidf, y_train) #tfidf train set을 이용하여 분류기(classifier)를 새로 학습
print('Train set score: {:.3f}'.format(NB_clf.score(X_train_tfidf, y_train))) #train set에 대한 예측정확도를 확인
print('Test set score: {:.3f}'.format(NB_clf.score(X_test_tfidf, y_test))) #test set에 대한 예측정확도를 확인
Train set score: 0.862
Test set score: 0.741
=> TF-idf를 사용하니 CountVectorizer보다
성능이 개선되었다는 것을 확인할 수 있음
✔️ 이번에는 모델의 매개변수를 조절해보자
MultinomialNB 는 alpha라는 매개변수를 가지고 있음
→ 알파 값을 조정하여 가장 성능이 좋은 값을 찾아보고자 한다
이때 하이퍼파라미터 튜닝은 GridSearchCV로 할 수 있음
params_grid에
튜닝하고자 하는 하이퍼파라미터의 이름과 값들을
딕셔너리 형태로 넘겨주면 된다.
ex ) {’alpha’ : [1,2,3,4] } : 값을 넘겨줄 때는 리스트 형태로 넘겨줘야 함!
또한 인수로 튜닝하고자 하는 모델을 넘겨주면 된다
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
nb_clf=MultinomialNB()
#모델, 하이퍼 파라미터(딕셔너리형태로)을 인수로
nb_grid=GridSearchCV(nb_clf,param_grid={'alpha':np.arange(0.1,10,0.1).tolist()}, refit=True,cv=7)
#위에서 설정한 하이퍼파라미터로 계속 훈련
#최고의 성능을 나타내는 하이퍼파라미터의 조합과 그때의 평가지표를 저장
nb_grid.fit(X_train_tfidf,y_train)
#GridSearchCV결과 보기
best_params_
: 최적의 하이퍼 파라미터 조합을 반환
best_estimator_
: 최적의 하이퍼파라미터로
이미 훈련(GridSearchCV 디폴트가 refit=True라서 자동으로 훈련시켜줌) 시킨
모델 반환
print('Best params: ', nb_grid.best_params_)
print('Best score :', nb_grid.best_score_)
Best params: {'alpha': 0.1}
Best score : 0.8117193980329424
> 그리드서치 결과 최적의 알파값 : 0.1
✔️ 그리드서치를 통해 튜닝한 모델을 가지고 다시 평가해보자
#그리드서치로 하이퍼파라미터 튜닝한 후 다시 모델을 평가해보기
#최적의 하이퍼 파라미터로 훈련시킨 모델을 불러옴
best_nb=nb_grid.best_estimator_
grid_preds=best_nb.predict(X_test_tfidf)
print('best score :', accuracy_score(y_test,grid_preds))
best score : 0.7524020694752402
> 하이퍼파라미터 튜닝을 안 한 것보다 0.01정도 개선되었다는 것을 확인할 수 있었음
여기서 어떤 단어들이 라벨에 영향을 주었는지 알고싶으면
coef_를 통해 확인할 수 있다
coef_는 결과로 각 카테고리별로 각각의 특성 단어들이
얼마나 영향을 끼쳤는지 수치로 반환해준다.
✔️ 문서 분류에서 coef_를 통해 각 라벨에 대해 특성 단어들이 기여한 정도를 행렬로 반환해줌
크기 : (라벨의 개수, 특성 단어의 개수)
> 각 행이 라벨을 의
즉, 1번째 행은 0번 라벨에 대한 값들
> 각 칼럼이 특성 단어를 의미
즉, 첫번째 칼럼은 첫번째 순서에 해당하는 특성 단어를 의미
> 값들이 기여도를 의미
즉, (0,0)에 위치한 값은
1번째 라벨에 대해서 1번째 특성 단어가 기여한 정도를 의미
import numpy as np
def top10_features(classifier, vectorizer, categories):
feature_names = np.asarray(vectorizer.get_feature_names_out())
for i, category in enumerate(categories):
# 역순으로 정렬하기 위해 계수에 음수를 취해서 정렬 후 앞에서부터 10개의 값을 반환
top10 = np.argsort(-classifier.coef_[i])[:10] #argsort는 요소들을 내림차순으로 정렬하고 인덱스 반환
# 카테고리와 영향이 큰 특성 10개를 출력
print("%s: %s" % (category, ", ".join(feature_names[top10])))
top10_features(NB_clf, tfidf, newsgroups_train.target_names)
alt.atheism: you, not, are, be, this, have, as, what, they, if
comp.graphics: you, on, graphics, this, have, any, can, or, with, thanks
sci.space: space, on, you, be, was, this, as, they, have, are
talk.religion.misc: you, not, he, are, as, this, be, god, was, they
2.BernoulliNB
주로 이진값들을 분류하고자 할 때 이용
회귀 알고리즘
1. 로지스틱
로지스틱 회귀는 회귀를 기반으로 한 알고리즘이다.
보통 회귀분석이라고 하면 연속적인 값을 예측할 때 사용하지만
로지스틱 회귀분석은 라벨이 연속적인 값이 아니라 분류에 해당할 때 사용
나이브 베이즈보다는 일반적으로 성능이 안 좋음
from sklearn.linear_model import LogisticRegression #sklearn이 제공하는 logistic regression을 사용
#count vector에 대해 regression을 해서 NB와 비교
LR_clf = LogisticRegression() #분류기 선언
LR_clf.fit(X_train_tfidf, y_train) # train data를 이용하여 분류기를 학습
#예측
lr_preds=LR_clf.predict(X_test_tfidf)
lr_preds_train=LR_clf.predict(X_train_tfidf)
print('Train set score: {:.3f}'.format(LR_clf.score(X_train_tfidf, y_train))) # train data에 대한 예측정확도
print('Train set score: {:.3f}'.format(accuracy_score(y_train,lr_preds_train)))
print('Test set score: {:.3f}'.format(LR_clf.score(X_test_tfidf, y_test))) # test data에 대한 예측정확도
print('Test set score: {:.3f}'.format(accuracy_score(y_test,lr_preds)))
Train set score: 0.930
Train set score: 0.930
Test set score: 0.734
Test set score: 0.734
*나이브 베이즈보다 성능이 안 좋음
❓ 왜?
1. 나이브베이즈가 텍스트 분류에 특화되어 있음
2. LR모델을 보면 나이브베이즈보다 과적합이 심함
(train 예측 점수를 보면 0.93으로 높음.
나이브 베이즈는 0.8정도 나옴)
따라서..
과적합을 줄이기 위해서는 특성의 수를 줄이거나 특성 정규화를 하면 된다.
→ 텍스트 분석에서는 특성이 많음에도 불구하고 좋은 성능을 보이는 경우가 많음
⇒ 그래서 일반적으로 정규화를 하면서 과적합 문제 해결
2. 릿지
릿지는 회귀분석에 정규화를 사용하는 알고리즘이다.
> alpha라는 매개변수를 통해 정규화 조절이 가능
alpha값이 커질수록 정규화 비중이 커짐
>너무 많이 커지면 학습 자체가 잘 안되므로
적절한 알파값을 찾는 것이 중요!
from sklearn.linear_model import RidgeClassifier
ridge_clf = RidgeClassifier() #릿지 분류기 선언
ridge_clf.fit(X_train_tfidf, y_train) #학습
ridge_preds=ridge_clf.predict(X_test_tfidf)
ridge_preds_train=ridge_clf.predict(X_train_tfidf)
print('Train set score: {:.3f}'.format(ridge_clf.score(X_train_tfidf, y_train)))
print('Train set score: {:.3f}'.format(accuracy_score(y_train,ridge_preds_train)))
print('Test set score: {:.3f}'.format(ridge_clf.score(X_test_tfidf, y_test)))
print('Test set score: {:.3f}'.format(accuracy_score(y_test,ridge_preds)))
Train set score: 0.960
Train set score: 0.960
Test set score: 0.735
Test set score: 0.735
로지스틱보다는 성능이 좋아지긴 함
적절한 alpha값을 찾아 성능을 더 개선해보자
#하이퍼파라미터 튜닝
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
ridge_clf=RidgeClassifier()
#모델, 하이퍼 파라미터(딕셔너리형태로)을 인수로
ridge_grid=GridSearchCV(ridge_clf,param_grid={'alpha':np.arange(0.1,10,0.1).tolist()}, refit=True,cv=4)
#위에서 설정한 하이퍼파라미터로 계속 훈련 -> 최고의 성능을 나타내는 하이퍼파라미터의 조합과 그때의 평가지표를 저장
ridge_grid.fit(X_train_tfidf,y_train)
#GridSearchCV결과
→최적의 alpha값이 1.8로 나옴
print('Best params: ', ridge_grid.best_params_)
print('Best score :', ridge_grid.best_score_)
Best params: {'alpha': 1.8000000000000003}
Best score : 0.8072809507603298
#하이퍼 파리미터 튜닝 후 최종 예측 진행
→ 정확도가 약 0.74로 성능이 약간 개선 되었음을 확인(0.73→0.74)
#최적의 하이퍼 파라미터로 훈련시킨 모델을 불러옴
best_ridge=ridge_grid.best_estimator_
grid_preds=best_ridge.predict(X_test_tfidf)
print('best score :', accuracy_score(y_test,grid_preds))
best score : 0.738359201773836
#하이퍼 파리미터 튜닝 후 과적합 문제 개선
→ alpha값을 1.8로 설정한 후 과적합 수치가 0.98에서 약 0.95로 내려갔음을 확인
→ alpha를 설정했더니 과적합 정도가 줄어듬 ⇒ 정규화가 되었다는 뜻
즉, 과적합을 줄이기 위해서는 정규화하는 것이 일종의 방법!
from sklearn.linear_model import RidgeClassifier
ridge_clf = RidgeClassifier(alpha=1.8) #릿지 분류기 선언
ridge_clf.fit(X_train_tfidf, y_train) #학습
ridge_preds=ridge_clf.predict(X_test_tfidf)
ridge_preds_train=ridge_clf.predict(X_train_tfidf)
print('Train set score: {:.3f}'.format(ridge_clf.score(X_train_tfidf, y_train)))
print('Train set score: {:.3f}'.format(accuracy_score(y_train,ridge_preds_train)))
print('Test set score: {:.3f}'.format(ridge_clf.score(X_test_tfidf, y_test)))
print('Test set score: {:.3f}'.format(accuracy_score(y_test,ridge_preds)))
Train set score: 0.946
Train set score: 0.946
Test set score: 0.738
Test set score: 0.738
#회귀 모델에서의 coef_
나이브 베이즈 보다는 성능이 떨어지지만
분류에 크게 영향을 준 단어들을 보면 나이브 베이즈 보다는 더 유의미함
→ 로지스틱 회귀 계열의 장점
⇒💡 coef_는
나이브 베이즈보다는 회귀계열에서
주로 사용하기를 권장 (나이브 베이즈에서는 coef_를 해석하기 복잡)
위에서 만든 함수를 통해 반환된 결과를 보면..
top10_features(ridge_clf, tfidf, newsgroups_train.target_names)
alt.atheism: bobby, atheism, religion, atheists, islam, motto, deletion, islamic, punishment, atheist
comp.graphics: graphics, computer, file, 3d, image, hi, using, 42, screen, looking
sci.space: space, orbit, nasa, spacecraft, moon, launch, sci, flight, idea, funding
talk.religion.misc: christian, christians, fbi, blood, order, jesus, objective, children, christ, hudson
> 나이브 베이즈보다 유의미한 단어들이 많이 보이는 것을 확인할 수 있다
3. 라쏘
라쏘 또한 릿지와 마찬가지로
과적합을 이용한 회귀분석 알고리즘이다.
다만 다른 점은 특성의 계수가 0과 가까워지면
이를 완전히 0으로 바꾼다는 점이다.
즉, 라쏘와 다른 점은 정규화 항의 차이가 있음
릿지는 L2 (RidgeClassifier)를 사용.
라쏘는 L1을 사용
L1
: 특성의 계수가 0에 가까워지면 이를 완전히 0으로 바꿈.
→ 계수가 0이라는 것은 분류에 전혀 영향을 안 줌
⇒ 그 특성 값이 사용 x ⇒ 특성의 수가 줄어듬(특성 선택)
>> 정보량이 줄어듬
라쏘는 정규화를 통해 과적합을 줄이지만 동시에
특성의 수도 줄이므로 성능이 항상 향상된다고 보기 어렵다
또한 라쏘는 릿지와 달리 사이킷런에서 라쏘만의 분류기 제공 x
⇒ 로지스틱 회귀에서 따로
penalty 매개변수를 'L1'으로 설정을 해야 함 : penalty='l1'
⇒ 뿐만 아니라 알고리즘도 liblinear를 선택해야 함 : solver='liblinear'
릿지 회귀에서의 alpha와 같은 기능이 매개변수 C도 있음 , 단 C는 alpha의 역수
⇒ 릿지에서는 알파값이 커질수록 정규화가 강해짐,
그러나 라쏘에서는 C값이 작을수록 정규화가 강해짐
(alpha값이 커지는 것이기 때문에)
lasso_clf = LogisticRegression(penalty='l1', solver='liblinear',C=1) # Lasso는 동일한 LogisticRegression을 사용하면서 매개변수로 지정
#C=1이기 ㄸㅐ문에 alpha가 1이라는 뜻
lasso_clf.fit(X_train_tfidf, y_train) # train data로 학습
lasso_pred=lasso_clf.predict(X_test_tfidf)
print('#Train set score: {:.3f}'.format(lasso_clf.score(X_train_tfidf, y_train)))
print('#Test set score: {:.3f}'.format(accuracy_score( y_test, lasso_pred)))
#Train set score: 0.819
#Test set score: 0.724
분류 알고리즘
왜 그렇게 분류했는지 시각화 가능. 단, 특성의 수가 너무 많으면 시각화하기 힘들다
따라서 특성의 수를 비롯한 여러가지 고려
결정트리 기반 알고리즘 : DecisionTree, RandomForest, GradientBoosting
#DecisionTree
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
tree = DecisionTreeClassifier(random_state=7)
tree.fit(X_train_tfidf, y_train)
t_pred=tree.predict(X_test_tfidf)
print('#Decision Tree train set score: {:.3f}'.format(tree.score(X_train_tfidf, y_train)))
print('#Decision Tree test set score: {:.3f}'.format(accuracy_score(y_test,t_pred)))
#RandomForest
forest = RandomForestClassifier(random_state=7)
forest.fit(X_train_tfidf, y_train)
forest_pred=forest.predict(X_test_tfidf)
print('#Random Forest train set score: {:.3f}'.format(forest.score(X_train_tfidf, y_train)))
print('#Random Forest test set score: {:.3f}'.format(accuracy_score(y_test, forest_pred)))
#GradientBoosting
gb = GradientBoostingClassifier(random_state=7)
gb.fit(X_train_tfidf, y_train)
gb_pred=gb.predict(X_test_tfidf)
print('#Gradient Boosting train set score: {:.3f}'.format(gb.score(X_train_tfidf, y_train)))
print('#Gradient Boosting test set score: {:.3f}'.format(accuracy_score(y_test,gb_pred)))
⇒ 텍스트 분류에서는 결정트리 알고리즘이 성능이 안 좋음
✔️ 분류 알고리즘에서는 coef_대신 feature_importances_를 제공
→ coef_ : 각 문서에 대해서 각각의 단어들이 기여한 정도를 반환
크기 : (라벨의 개수, 특성단어의 개수)
→ feature_importances_ : 각각의 단어들이 분류에 기여한 정도를 반환
크기 : (1, 특성단어의 개수)
> 아래 코드 참고
gb.feature_importances_
array([0. , 0. , 0.00018793, ..., 0.00321606, 0. ,
0. ])
#feature_importances_ 상위 40개의 단어들 추출
→ 즉, 분류에 크게 기여한 top40 단어들을 반환
sorted_feature_importances = sorted(zip(tfidf.get_feature_names_out(), gb.feature_importances_), key=lambda x: x[1], reverse=True)
for feature, value in sorted_feature_importances[:40]:
print('%s: %.3f' % (feature, value), end=', ')
space: 0.126, graphics: 0.080, atheism: 0.024, thanks: 0.023, file: 0.021, orbit: 0.020, jesus: 0.018, god: 0.018, hi: 0.017, nasa: 0.015, image: 0.015, files: 0.014, christ: 0.010, moon: 0.010, bobby: 0.010, launch: 0.010, looking: 0.010, christian: 0.010, atheists: 0.009, christians: 0.009, fbi: 0.009, 3d: 0.008, you: 0.008, not: 0.008, islamic: 0.007, religion: 0.007, spacecraft: 0.007, flight: 0.007, computer: 0.007, islam: 0.007, ftp: 0.006, color: 0.006, software: 0.005, atheist: 0.005, card: 0.005, people: 0.005, koresh: 0.005, his: 0.005, kent: 0.004, sphere: 0.004,
성능 개선 방법
성능을 높이는 방법에는
토큰화 또는 정규화 방법을
좀 더 세심하게 하는 법이 있다.
CountVectorizer나 Tf-idf와 같은 토큰화 방법을 사용할 수도 있지만
RegexpTokenizer와 같이 정규표현식을 이용하여
사용자 임의로 토큰화를 하여 성능을 개선시킬 수도 있다
1. 정규표현식 토큰화 진행
2. 불용어 제거
3. 단어의 길이가 3이상
4. 어간추출(PorterStemmer)
# 필요한 library들을 import
from nltk.corpus import stopwords
#불용어 불러오기
cachedStopWords = stopwords.words("english")
from nltk.tokenize import RegexpTokenizer
from nltk.stem.porter import PorterStemmer
import re
RegTok = RegexpTokenizer("[\\w']{3,}") # 정규표현식으로 토크나이저를 정의
english_stops = set(stopwords.words('english')) #영어 불용어를 가져옴
def tokenizer(text):
tokens = RegTok.tokenize(text.lower())
# stopwords 제외 및 단어의 길이가 2이상인 애들만 가져옴
words = [word for word in tokens if (word not in english_stops) and len(word) > 2]
# portr stemmer 적용
#words에 대해서 porterStemmer()을 적용해라
features = (list(map(lambda token: PorterStemmer().stem(token),words)))
return features
tfidf = TfidfVectorizer(tokenizer=tokenizer, max_features=2000, min_df=5, max_df=0.5) # 새로 정의한 토크나이저 사용
X_train_tfidf = tfidf.fit_transform(X_train) # train set을 변환
X_test_tfidf = tfidf.transform(X_test) # test set을 변환
#tfidf vector를 이용해서 분류기 학습
LR_clf = LogisticRegression() #분류기 선언
LR_clf.fit(X_train_tfidf, y_train) # train data를 이용하여 분류기를 학습
LR_preds=LR_clf.predict(X_test_tfidf)
print('#Train set score: {:.3f}'.format(LR_clf.score(X_train_tfidf, y_train))) # train data에 대한 예측정확도
print('#Test set score: {:.3f}'.format(accuracy_score(y_test,LR_preds))) # test data에 대한 예측정확도
#Train set score: 0.930
#Test set score: 0.751
> 위의 4가지 과정을 걸치니
릿지 회귀의 정확도가 0.739에 불과하던 것이
토큰화 과정을 섬세하게 하니 0.75로 상승
⚙️ 성능을 더 높이기 위해 특성 단어의 개수 제한을 없애보자
→max_features 설정 x
from sklearn.feature_extraction.text import TfidfVectorizer
#max_features에 제한을 안둠 -> 특성단어의 개수에 제한을 안둔다
tfidf = TfidfVectorizer(tokenizer=tokenizer)
X_train_tfidf = tfidf.fit_transform(X_train) # train set을 변환
print('#Train set dimension:', X_train_tfidf.shape) # 실제로 몇개의 특성이 사용되었는지 확인
print('#Train set dimension:', len(tfidf.get_feature_names_out()))
X_test_tfidf = tfidf.transform(X_test) # test set을 변환
print('#Test set dimension:', X_test_tfidf.shape)
#Train set dimension: (2034, 20085)
#Train set dimension: 20085
#Test set dimension: (1353, 20085)
⚙️ 그런 다음 하이퍼파라미터 튜닝을 하여 최적의 알파값 도출
#GridSearchCV로 하이퍼 파라미터 튜닝
print('best params: ', rg_grid.best_params_)
print('best score: ', rg_grid.best_score_)
best_rg=rg_grid.best_estimator_
grid_rg_preds=rg_grid.predict(X_test_tfidf)
print('best predict score: ', accuracy_score(y_test,grid_rg_preds))
#최적의 알파값
from sklearn.model_selection import GridSearchCV
ridge=RidgeClassifier()
rg_grid=GridSearchCV(ridge, param_grid={'alpha':list(np.arange(1,10,0.5))},cv=5,refit=True)
rg_grid.fit(X_train_tfidf,y_train)
best params: {'alpha': 1.0}
best score: 0.826936251074182
best predict score: 0.7642276422764228
#최종 예측
→최적의 알파값을 찾은 다음 직접 설정
ridge_clf = RidgeClassifier(alpha=1.0)
ridge_clf.fit(X_train_tfidf, y_train) #학습
ridge_preds=ridge_clf.predict(X_test_tfidf)
print('#Train set score: {:.3f}'.format(ridge_clf.score(X_train_tfidf, y_train)))
print('#Test set score: {:.3f}'.format(accuracy_score(y_test,ridge_preds)))
#Train set score: 0.968
#Test set score: 0.768
> 특성 단어의 제한을 없애고 하이퍼파리미터 튜닝한 결과,
정확도가 0.768로 약 0.02정도 상승했음
N-gram
카운트 기반은 단어 문맥의 순서가 아닌 사용된 횟수를 기반으로 문서에 대한 벡터를 생성
즉 예를 들어, '나는 학교에 간다' 와 '간다 나는 학교에'가 똑같은 벡터로 인코딩
위와 같이 짧은 문장에 대해서는 상관이 없지만
문장이 길어질수록 문맥을 파악하기 어려움
위와 같은 문제점을 해결하기 위해 나온 방법이 N-gram
N-gram은
BOW방식을 그대로 쓰면서 단어가 쓰여진 순서 또한 반영할 수 있음
#N-gram
n개의 연속적인 단어들의 나열
지금까지는 토큰화할 때, 토큰은 하나의 단어로 만들어짐
하지만, n-gram에서는 하나의 토큰이 두개 이상의 단어로 구성될 수 있음
* n=1 : uni-gram : 지금까지의 토큰화 방법
* n=2 : bi-gram : 이 경우 하나의 토큰이 두개의 단어로 구성
* n=3 : tri-gram : 세개씩 구성
예를 들어 'the future depends on what we do in the present.' 가 있다
⇒ uni-gram : the/future/depends/on/what/we/do/in/the/present
>즉 the, future, depends가 특성 집합을 구성하는 토큰들이 된다
⇒ bi-gram : the future/future depends/depends on/on what/what we/we do/do in /in the/the present
> the future, future depends 가 특성 집합을 구성하는 하나의 토큰이 된다
#N-gram설정
객체 생성할 때(TfidfVectorizer) 매개변수 ngram_range를 이용하여 n-gram설정 가능
>> ngram_range=(a,b)
ex) ngram_range=(1,2) :: uni-gram과 bi-gram을 사용하겠다는 뜻!
>> 만약 bi-gram만 사용하고 싶은면 ngram_range=(2,2)로 설정하면 된다.
ngram_range=(1,3) :: uni-gram과 bi-gram, tri-gram까지 사용하겠다는 뜻!
<n-gram사용 x>
cachedStopWords = stopwords.words("english")
reg=RegexpTokenizer("[a-zA-Z']{3,}")
tfidf = TfidfVectorizer(tokenizer=reg.tokenize, # 토큰화를 위한 정규식
decode_error ='ignore',
lowercase=True,
stop_words = stopwords.words('english'),
max_df=0.5,
min_df=2)
X_train_tfidf = tfidf.fit_transform(X_train)
X_test_tfidf = tfidf.transform(X_test)
print(X_train_tfidf.shape)
(2034, 11483)
<n-gram을 사용하지 않고 모델 평가>
from sklearn.linear_model import RidgeClassifier
ridge_clf = RidgeClassifier() #릿지 분류기 선언
ridge_clf.fit(X_train_tfidf, y_train) #학습
ridge_preds=ridge_clf.predict(X_test_tfidf)
print('Train set score: {:.3f}'.format(ridge_clf.score(X_train_tfidf, y_train)))
print('Test set score: {:.3f}'.format(accuracy_score(y_test,ridge_preds)))
Train set score: 0.976
Test set score: 0.766
<n-gram 사용>
tfidf = TfidfVectorizer(token_pattern= "[a-zA-Z']{3,}",
decode_error ='ignore',
lowercase=True,
stop_words = stopwords.words('english'),
#ngram_range=(1,2) : uni-gram과 bi-gram 두개 다 사용, 만약 bi-gram만 사용하고 싶으면 (2,2)를 넘겨주면 된다.
ngram_range=(1, 2), # 바이그램 설정
max_df=0.5,
min_df=2)
X_train_tfidf = tfidf.fit_transform(X_train)
X_test_tfidf = tfidf.transform(X_test)
print(X_train_tfidf.shape)
(2034, 26550)
> 특성의 개수가 11482 에서 26550으로 증가 (bi-gram을 사용했기 때문)
<n-gram사용한 후 모델 평가>
bigram 적용했더니 성능이 좋아짐 (0.766 → 0.773)
ridge_clf.fit(X_train_tfidf, y_train) #학습
rid_preds=ridge_clf.predict(X_test_tfidf)
print('Train set score: {:.3f}'.format(ridge_clf.score(X_train_tfidf, y_train)))
print('Test set score: {:.3f}'.format(accuracy_score(y_test,rid_preds)))
Train set score: 0.976
Test set score: 0.773
+ tri-gram도 적용
bigram보다 더 특성집합의 수가 증가
tfidf = TfidfVectorizer(token_pattern= "[a-zA-Z']{3,}",
decode_error ='ignore',
lowercase=True,
stop_words = stopwords.words('english'),
#tri-gram도 추가
ngram_range=(1, 3),
max_df=0.5,
min_df=2)
X_train_tfidf = tfidf.fit_transform(X_train)
X_test_tfidf = tfidf.transform(X_test)
print(X_train_tfidf.shape)
(2034, 32943)
bigram을 적용한 것보다 성능이 더 좋아짐 (0.773 → 0.775)
#tri gram은 하나의 토큰에 세개의 단어가 있기 때문에 split하면 길이가 3이상이 된다
trigram_features = [f for f in tfidf.get_feature_names_out() if len(f.split()) > 2]
print('tri-gram samples:', trigram_features[:10])
ridge_clf.fit(X_train_tfidf, y_train) #학습
ridge_preds=ridge_clf.predict(X_test_tfidf)
print('Train set score: {:.3f}'.format(ridge_clf.score(X_train_tfidf, y_train)))
print('Test set score: {:.3f}'.format(accuracy_score(y_test,ridge_preds)))
tri-gram samples: ["'em better shots", "'expected errors' basically", "'karla' next one", "'nodis' password also", "'official doctrine think", "'ok see warning", "'what's moonbase good", 'aas american astronautical', 'ability means infallible', 'able accept donations']
Train set score: 0.976
Test set score: 0.775
'✍️ STUDY > NLP' 카테고리의 다른 글
[Text Mining] 감성분석 (1) | 2023.03.22 |
---|---|
[Text Mining] 토픽모델링 (0) | 2023.03.08 |
[Text Mining] 차원 축소 (0) | 2023.02.27 |
[Text Mining] CountVectorizer와 TF-IDF (0) | 2023.02.13 |
[Text Mining] 텍스트 전처리, 자연어 전처리 (0) | 2023.02.06 |