# 22.02.15 머신러닝 스터디 8장. 텍스트 분석 - 3
<파이썬 머신러닝 완벽 가이드>¶
텍스트 분석 - 2 ( p528 ~ p561 )¶
08. 문서 유사도¶
문서 유사도 측정 방법 - 코사인 유사도¶
문서와 문서 간의 유사도 비교는 일반적으로 코사인 유사도(Cosine Similarity)를 사용한다. 코사인 유사도는 벡터와 벡터 간의 유사도를 비교할 때 벡터의 크기보다는 벡터의 상호 방향성이 얼마나 유사한지에 기반한다. 즉, 코사인 유사도는 두 벡터 사이의 사잇각을 구해서 얼마나 유사한지수치로 적용한 것이다.
두 벡터 사잇각¶
두 벡터의 사잇각에 따라서 상호 관계는 유사하거나, 관련이 없거나, 아예 반대 관계가 될 수 있다.
$$두 벡터 A와 B의 내적 값 : A * B = \left\| A \right\|\left\| B\right\|cos\theta $$따라서 유사도 cosθ는 두 벡터의 내적을 총 벡터 크기의 합으로 나눈 것이다.( 내적 결과를 총 벡터 크기로 L2 정규화한 것이다.)
$$similarity = cos\theta = \frac{A\cdot B}{\left\| A\right\|\left\| B\right\|} = \frac{\sum_{i=1}^{n}A_iB_i} {\sqrt{\sum_{i=1}^{n}A_i^2}\sqrt{\sum_{i=1}^{n}B_i^2}}$$코사인 유사도가 문서의 유사도 비교에 가장 많이 사용되는 이유
- 문서를 피처 벡터화 변환하면 차원이 매우 많은 희소 행렬이 되기 쉬운데 이러한 희소 행렬 기반에서 문서와 문서 벡터간의 크기에 기반한 유사도 지표는 정확도가 떨어지기 쉽다.
- 문서가 매우 긴 경우 단어의 빈도수도 더 많을 것이기 때문에 이러한 빈도수에만 기반해서는 공정한 비교를 할 수 없다.
import numpy as np
def cos_similarity(v1, v2):
dot_product = np.dot(v1, v2)
l2_norm = (np.sqrt(sum(np.square(v1))) * np.sqrt(sum(np.square(v2))))
similarity = dot_product / l2_norm
return similarity
from sklearn.feature_extraction.text import TfidfVectorizer
doc_list = ['if you take the blue pill, the story ends' ,
'if you take the red pill, you stay in Wonderland',
'if you take the red pill, I show you how deep the rabbit hole goes']
tfidf_vect_simple = TfidfVectorizer()
feature_vect_simple = tfidf_vect_simple.fit_transform(doc_list)
print(feature_vect_simple.shape)
(3, 18)
# TFidfVectorizer로 transform()한 결과는 희소 행렬이므로 밀집 행렬로 변환.
feature_vect_dense = feature_vect_simple.todense()
# 첫 번째 문장과 두 번째 문장의 feature vector 추출
vect1 = np.array(feature_vect_dense[0]).reshape(-1,)
vect2 = np.array(feature_vect_dense[1]).reshape(-1,)
# 첫 번째 문장과 두 번째 문장의 feature vector로 두개 문장의 코사인 유사도 추출
similarity_simple = cos_similarity(vect1, vect2)
print('문장 1, 문장 2 Cosine 유사도: {0:.3f}'.format(similarity_simple))
문장 1, 문장 2 Cosine 유사도: 0.402
vect1 = np.array(feature_vect_dense[0]).reshape(-1,)
vect3 = np.array(feature_vect_dense[2]).reshape(-1,)
similarity_simple = cos_similarity(vect1, vect3 )
print('문장 1, 문장 3 Cosine 유사도: {0:.3f}'.format(similarity_simple))
vect2 = np.array(feature_vect_dense[1]).reshape(-1,)
vect3 = np.array(feature_vect_dense[2]).reshape(-1,)
similarity_simple = cos_similarity(vect2, vect3 )
print('문장 2, 문장 3 Cosine 유사도: {0:.3f}'.format(similarity_simple))
문장 1, 문장 3 Cosine 유사도: 0.404 문장 2, 문장 3 Cosine 유사도: 0.456
# 희소 행렬, 밀집 행렬, 행렬, 배열 모두 가능
from sklearn.metrics.pairwise import cosine_similarity
similarity_simple_pair = cosine_similarity(feature_vect_simple[0] , feature_vect_simple[1:])
print(similarity_simple_pair)
[[0.40207758 0.40425045]]
similarity_simple_pair = cosine_similarity(feature_vect_simple , feature_vect_simple)
print(similarity_simple_pair)
print('shape:',similarity_simple_pair.shape)
[[1. 0.40207758 0.40425045] [0.40207758 1. 0.45647296] [0.40425045 0.45647296 1. ]] shape: (3, 3)
Opinion Review 데이터 세트를 이용한 문서 유사도 측정¶
from nltk.stem import WordNetLemmatizer
import nltk
import string
remove_punct_dict = dict((ord(punct), None) for punct in string.punctuation)
lemmar = WordNetLemmatizer()
def LemTokens(tokens):
return [lemmar.lemmatize(token) for token in tokens]
def LemNormalize(text):
return LemTokens(nltk.word_tokenize(text.lower().translate(remove_punct_dict)))
import pandas as pd
import glob ,os
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
path = r'C:\Users\admin\Desktop\머신러닝 스터디\10차시\OpinosisDataset1.0\topics'
all_files = glob.glob(os.path.join(path, "*.data"))
filename_list = []
opinion_text = []
for file_ in all_files:
df = pd.read_table(file_,index_col=None, header=0,encoding='latin1')
filename_ = file_.split('\\')[-1]
filename = filename_.split('.')[0]
filename_list.append(filename)
opinion_text.append(df.to_string())
document_df = pd.DataFrame({'filename':filename_list, 'opinion_text':opinion_text})
tfidf_vect = TfidfVectorizer(tokenizer=LemNormalize, stop_words='english' , \
ngram_range=(1,2), min_df=0.05, max_df=0.85 )
feature_vect = tfidf_vect.fit_transform(document_df['opinion_text'])
km_cluster = KMeans(n_clusters=3, max_iter=10000, random_state=0)
km_cluster.fit(feature_vect)
cluster_label = km_cluster.labels_
cluster_centers = km_cluster.cluster_centers_
document_df['cluster_label'] = cluster_label
C:\ProgramData\Anaconda3\lib\site-packages\sklearn\feature_extraction\text.py:388: UserWarning: Your stop_words may be inconsistent with your preprocessing. Tokenizing the stop words generated tokens ['ha', 'le', 'u', 'wa'] not in stop_words. warnings.warn('Your stop_words may be inconsistent with '
호텔을 주제로 군집화된 문서를 이용해 특정 문서와 다른 문서간의 유사도를 알아보자
from sklearn.metrics.pairwise import cosine_similarity
# cluster_label=2 인 데이터는 호텔로 클러스터링된 데이터임. DataFrame에서 해당 Index를 추출
hotel_indexes = document_df[document_df['cluster_label']==2].index
print('호텔로 클러스터링 된 문서들의 DataFrame Index:', hotel_indexes)
# 호텔로 클러스터링된 데이터 중 첫번째 문서를 추출하여 파일명 표시.
comparison_docname = document_df.iloc[hotel_indexes[0]]['filename']
print('##### 비교 기준 문서명 ',comparison_docname,' 와 타 문서 유사도######')
''' document_df에서 추출한 Index 객체를 feature_vect로 입력하여 호텔 클러스터링된 feature_vect 추출
이를 이용하여 호텔로 클러스터링된 문서 중 첫번째 문서와 다른 문서간의 코사인 유사도 측정.'''
similarity_pair = cosine_similarity(feature_vect[hotel_indexes[0]] , feature_vect[hotel_indexes])
print(similarity_pair)
호텔로 클러스터링 된 문서들의 DataFrame Index: Int64Index([1, 13, 14, 15, 20, 21, 24, 28, 30, 31, 32, 38, 39, 40, 45, 46], dtype='int64') ##### 비교 기준 문서명 bathroom_bestwestern_hotel_sfo 와 타 문서 유사도###### [[1. 0.0430688 0.05221059 0.06189595 0.05846178 0.06193118 0.03638665 0.11742762 0.38038865 0.32619948 0.51442299 0.11282857 0.13989623 0.1386783 0.09518068 0.07049362]]
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# argsort()를 이용하여 앞예제의 첫번째 문서와 타 문서간 유사도가 큰 순으로 정렬한 인덱스 반환하되 자기 자신은 제외.
sorted_index = similarity_pair.argsort()[:,::-1]
sorted_index = sorted_index[:, 1:]
# 유사도가 큰 순으로 hotel_indexes를 추출하여 재 정렬.
hotel_sorted_indexes = hotel_indexes[sorted_index.reshape(-1)]
# 유사도가 큰 순으로 유사도 값을 재정렬하되 자기 자신은 제외
hotel_1_sim_value = np.sort(similarity_pair.reshape(-1))[::-1]
hotel_1_sim_value = hotel_1_sim_value[1:]
# 유사도가 큰 순으로 정렬된 Index와 유사도값을 이용하여 파일명과 유사도값을 Seaborn 막대 그래프로 시각화
hotel_1_sim_df = pd.DataFrame()
hotel_1_sim_df['filename'] = document_df.iloc[hotel_sorted_indexes]['filename']
hotel_1_sim_df['similarity'] = hotel_1_sim_value
sns.barplot(x='similarity', y='filename',data=hotel_1_sim_df)
plt.title(comparison_docname)
plt.show()
위의 시각화를 통해 첫 번째 문서와 가장 비슷한 문서가 어떤 것인지를 알 수 있다.
09. 한글 텍스트 처리 - 네이버 영화 평점 감성 분석¶
한글 NLP 처리의 어려움¶
한글 NLP 처리는 대표적인 파이썬 기반의 한글 형태소 패키지인 KoNLPy를 사용한다. 일반적으로 한글 언어 처리는 영어 등의 라틴어 처리보다 어렵다. 그 이유는 띄어쓰기와 다양한 조사 때문인데 한글은 띄어쓰기를 잘못하면 의미가 왜곡되어 전달될 수 있고, 조사는 주어나 목적어를 위해 추가되는데 워낙 경우의 수가 많기 때문에 어근 추출(stemming/Lemmatization) 등의 전처리 시 제거하기가 까다롭다.
KoNLPy 소개¶
KoNLPy는 파이썬의 대표적인 한글 형태소 패키지이다. 형태소의 사전적인 의미는 '단어로서 의미를 가지는 최소 단위'이고, 형태소 분석(Morphological analysis)이란 말뭉치를 이러한 형태소 어근 단위로 쪼개고 각 형태소에 품사 태깅(POS tagging)을 부착하는 작업을 일반적으로 지칭한다. KoNLPy는 기존의 C/C++, Java로 잘 만들어진 한글 형태소 엔진을 파이썬 래퍼 기반으로 재작성한 패키지이다. 꼬꼬마(Kkma), 한나눔(Hannanum), Komoran, 은전한닢 프로젝트(Mecab), Twitter와 같이 5개의 형태소 분석 모듈을 KoNLPy에서 모두 사용할 수 있다. ( Mecab은 리눅스 환경의 KoNLPy에서만 사용이 가능하다 )
데이터 로딩¶
konlpy 다운로드
anaconda prompt에 'conda install -c conda-forge jpype1'
시스템 환경변수 설정 JAVA_HOME 'C:\Program Files\Java\jdk-17.0.2\bin\server' (예시)
<자바다운 참고자료> https://jhnyang.tistory.com/224
pip install konlpy
from konlpy.tag import Okt
okt = Okt()
print(okt.pos('아버지가 방에 들어가신다'))
[('아버지', 'Noun'), ('가', 'Josa'), ('방', 'Noun'), ('에', 'Josa'), ('들어가신다', 'Verb')]
okt.morphs('아버지가 방에 들어가신다')
['아버지', '가', '방', '에', '들어가신다']
okt.morphs('아버지가 방에 들어가신다', stem = True)
['아버지', '가', '방', '에', '들어가다']
- okt.morphs() : 텍스트를 형태소 단위로 나눔 ( norm : 문장을 정규화, stem : 어간추출 )
- okt.nouns() : 텍스트에서 명사만 추출
- okt.phrases() : 텍스트에서 어절을 추출
- okt.pos() : 텍스트를 형태소 단위로 나누고, 품사 태깅
import pandas as pd
train_df = pd.read_csv('ratings_train.txt', sep='\t')
train_df.head(3)
id | document | label | |
---|---|---|---|
0 | 9976970 | 아 더빙.. 진짜 짜증나네요 목소리 | 0 |
1 | 3819312 | 흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나 | 1 |
2 | 10265843 | 너무재밓었다그래서보는것을추천한다 | 0 |
train_df['label'].value_counts() # 1이 긍정, 0이 부정
0 75173 1 74827 Name: label, dtype: int64
<정규표현식 참고자료> https://wikidocs.net/4308
import re # 정규 표현식 모듈
train_df = train_df.fillna(' ')
# 정규 표현식을 이용하여 숫자를 공백으로 변경(정규 표현식으로 \d 는 숫자를 의미함.)
train_df['document'] = train_df['document'].apply( lambda x : re.sub(r"\d+", " ", x) )
# 테스트 데이터 셋을 로딩하고 동일하게 Null 및 숫자를 공백으로 변환
test_df = pd.read_csv('ratings_test.txt', sep='\t')
test_df = test_df.fillna(' ')
test_df['document'] = test_df['document'].apply( lambda x : re.sub(r"\d+", " ", x) )
# id 칼럼 삭제
train_df.drop('id', axis=1, inplace=True)
test_df.drop('id', axis=1, inplace=True)
TF-IDF 방식으로 단어를 벡터화, 각 문장을 한글 형태소 분석을 통해 형태소 단어로 토큰화
# KoNLPy v0.4.5. 이후 Twitter가 Okt로 changed
from konlpy.tag import Okt
Okt = Okt()
def tw_tokenizer(text):
# 입력 인자로 들어온 text 를 형태소 단어로 토큰화 하여 list 객체 반환
tokens_ko = Okt.morphs(text)
return tokens_ko
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
# Okt 객체의 morphs( ) 객체를 이용한 tokenizer를 사용. ngram_range는 (1,2)
tfidf_vect = TfidfVectorizer(tokenizer=tw_tokenizer, ngram_range=(1,2), min_df=3, max_df=0.9)
tfidf_vect.fit(train_df['document'])
tfidf_matrix_train = tfidf_vect.transform(train_df['document'])
# Logistic Regression 을 이용하여 감성 분석 Classification 수행.
lg_clf = LogisticRegression(random_state=0, max_iter = 500)
# Parameter C 최적화를 위해 GridSearchCV 를 이용.
params = { 'C': [ 1 ,3.5, 4.5, 5.5, 10 ] }
grid_cv = GridSearchCV(lg_clf , param_grid=params , cv=3 ,scoring='accuracy', verbose=1 )
grid_cv.fit(tfidf_matrix_train , train_df['label'] )
print(grid_cv.best_params_ , round(grid_cv.best_score_,4))
Fitting 3 folds for each of 5 candidates, totalling 15 fits {'C': 3.5} 0.8592
from sklearn.metrics import accuracy_score
# 학습 데이터를 적용한 TfidfVectorizer를 이용하여 테스트 데이터를 TF-IDF 값으로 Feature 변환함.
tfidf_matrix_test = tfidf_vect.transform(test_df['document'])
# classifier 는 GridSearchCV에서 최적 파라미터로 학습된 classifier를 그대로 이용
best_estimator = grid_cv.best_estimator_
preds = best_estimator.predict(tfidf_matrix_test)
print('Logistic Regression 정확도: ',accuracy_score(test_df['label'],preds))
Logistic Regression 정확도: 0.86174
10. 텍스트 분석 실습 - 캐글 Mercari Price Suggestion Challenge¶
- train_id : 데이터 id
- name : 제품명
- item_condition_id : 판매자가 제공하는 제품 상태
- category_name : 카테고리 명
- brand_name : 브랜드 이름
- price : 제품 가격, 예측을 위한 타깃 속성
- shipping : 배송비 무료 여부 ( 1 = 무료 [ 판매자 지불 ] , 0 = 유료 [ 구매자 지불 ] )
- item_description : 제품에 대한 설명
데이터 전처리¶
from sklearn.linear_model import Ridge , LogisticRegression
from sklearn.model_selection import train_test_split , cross_val_score
from sklearn.feature_extraction.text import CountVectorizer , TfidfVectorizer
import pandas as pd
mercari_df= pd.read_csv('mercari_train.tsv',sep='\t')
print(mercari_df.shape)
mercari_df.head(3)
(1482535, 8)
train_id | name | item_condition_id | category_name | brand_name | price | shipping | item_description | |
---|---|---|---|---|---|---|---|---|
0 | 0 | MLB Cincinnati Reds T Shirt Size XL | 3 | Men/Tops/T-shirts | NaN | 10.0 | 1 | No description yet |
1 | 1 | Razer BlackWidow Chroma Keyboard | 3 | Electronics/Computers & Tablets/Components & P... | Razer | 52.0 | 0 | This keyboard is in great condition and works ... |
2 | 2 | AVA-VIV Blouse | 1 | Women/Tops & Blouses/Blouse | Target | 10.0 | 1 | Adorable top with a hint of lace and a key hol... |
print(mercari_df.info())
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1482535 entries, 0 to 1482534 Data columns (total 8 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 train_id 1482535 non-null int64 1 name 1482535 non-null object 2 item_condition_id 1482535 non-null int64 3 category_name 1476208 non-null object 4 brand_name 849853 non-null object 5 price 1482535 non-null float64 6 shipping 1482535 non-null int64 7 item_description 1482531 non-null object dtypes: float64(1), int64(3), object(4) memory usage: 90.5+ MB None
brand_name 칼럼의 경우 많은 Null 값을 가지고 있는 것을 확인할 수 있다.
# target 값의 데이터 분포도 확인
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
y_train_df = mercari_df['price']
plt.figure(figsize=(6,4))
sns.distplot(y_train_df,kde=False)
plt.show()
C:\ProgramData\Anaconda3\lib\site-packages\seaborn\distributions.py:2619: FutureWarning: `distplot` is a deprecated function and will be removed in a future version. Please adapt your code to use either `displot` (a figure-level function with similar flexibility) or `histplot` (an axes-level function for histograms). warnings.warn(msg, FutureWarning)
# 로그 변환
import numpy as np
y_train_df = np.log1p(y_train_df)
sns.distplot(y_train_df, kde = False)
plt.show()
mercari_df['price'] = np.log1p(mercari_df['price'])
mercari_df['price'].head(3)
0 2.397895 1 3.970292 2 2.397895 Name: price, dtype: float64
print('Shipping 값 유형:\n',mercari_df['shipping'].value_counts())
print('item_condition_id 값 유형:\n',mercari_df['item_condition_id'].value_counts())
Shipping 값 유형: 0 819435 1 663100 Name: shipping, dtype: int64 item_condition_id 값 유형: 1 640549 3 432161 2 375479 4 31962 5 2384 Name: item_condition_id, dtype: int64
boolean_cond= mercari_df['item_description']=='No description yet'
mercari_df[boolean_cond]['item_description'].count()
82489
category_name 칼럼 : /로 분리된 카테고리를 하나의 문자열로 나타내고 있다. ( 대분류/중분류/소분류 )
# apply lambda에서 호출되는 대,중,소 분할 함수 생성, 대,중,소 값을 리스트 반환
def split_cat(category_name):
try:
return category_name.split('/')
except:
return ['Other_Null' , 'Other_Null' , 'Other_Null']
# 위의 split_cat( )을 apply lambda에서 호출하여 대,중,소 컬럼을 mercari_df에 생성.
mercari_df['cat_dae'], mercari_df['cat_jung'], mercari_df['cat_so'] = \
zip(*mercari_df['category_name'].apply(lambda x : split_cat(x)))
# 대분류만 값의 유형과 건수를 살펴보고, 중분류, 소분류는 값의 유형이 많으므로 분류 갯수만 추출
print('대분류 유형 :\n', mercari_df['cat_dae'].value_counts())
print('중분류 갯수 :', mercari_df['cat_jung'].nunique())
print('소분류 갯수 :', mercari_df['cat_so'].nunique())
대분류 유형 : Women 664385 Beauty 207828 Kids 171689 Electronics 122690 Men 93680 Home 67871 Vintage & Collectibles 46530 Other 45351 Handmade 30842 Sports & Outdoors 25342 Other_Null 6327 Name: cat_dae, dtype: int64 중분류 갯수 : 114 소분류 갯수 : 871
brand_name, category_name, item_description 칼럼의 Null값을 일괄적으로 Other Null로 변경 ( brand_name은 price 값 결정에 큰 영향을 줄 것으로 판단되기 때문에 삭제하지 않고 Other_Null로 변경한다.
mercari_df['brand_name'] = mercari_df['brand_name'].fillna(value='Other_Null')
mercari_df['category_name'] = mercari_df['category_name'].fillna(value='Other_Null')
mercari_df['item_description'] = mercari_df['item_description'].fillna(value='Other_Null')
# 각 컬럼별로 Null값 건수 확인. 모두 0가 나와야 합니다.
mercari_df.isnull().sum()
train_id 0 name 0 item_condition_id 0 category_name 0 brand_name 0 price 0 shipping 0 item_description 0 cat_dae 0 cat_jung 0 cat_so 0 dtype: int64
피처 인코딩과 피처 벡터화¶
Mercari Price Suggestion 데이터 세트는 문자열 칼럼이 많기 때문에 이 문자열 칼럼 중 레이블 또는 원-핫 인코딩을 수행하거나 피처 벡터화로 변환할 칼럼을 선별. 비교적 짧은 텍스트의 경우에는 Count 기반의 벡터화, 긴 텍스트의 경우에는 TD-IDF 기반 벡터화를 적용.
print('brand name 의 유형 건수 :', mercari_df['brand_name'].nunique())
print('brand name sample 5건 : \n', mercari_df['brand_name'].value_counts()[:5])
brand name 의 유형 건수 : 4810 brand name sample 5건 : Other_Null 632682 PINK 54088 Nike 54043 Victoria's Secret 48036 LuLaRoe 31024 Name: brand_name, dtype: int64
print('name 의 종류 갯수 :', mercari_df['name'].nunique())
print('name sample 7건 : \n', mercari_df['name'][:7])
name 의 종류 갯수 : 1225273 name sample 7건 : 0 MLB Cincinnati Reds T Shirt Size XL 1 Razer BlackWidow Chroma Keyboard 2 AVA-VIV Blouse 3 Leather Horse Statues 4 24K GOLD plated rose 5 Bundled items requested for Ruie 6 Acacia pacific tides santorini top Name: name, dtype: object
- category_name : 이전 저처리를 통해 대, 중, 소 분류별로 cat_dae, cat_jung, cat_so 칼럼으로 분리 ( 분리된 칼럼들을 원-핫 인코딩 )
- shipping ( 배송비 무료 여부 ), item_condition_id ( 상품 상태 ) : 원-핫 인코딩
- item_description : 상품에 대한 간단 설명
pd.set_option('max_colwidth', 200)
# item_description의 평균 문자열 개수
print('item_description 평균 문자열 개수:',mercari_df['item_description'].str.len().mean())
mercari_df['item_description'][:2]
item_description 평균 문자열 개수: 145.7113889385411
0 No description yet 1 This keyboard is in great condition and works like it came out of the box. All of the ports are tested and work perfectly. The lights are customizable via the Razer Synapse app on your PC. Name: item_description, dtype: object
# name 속성에 대한 feature vectorization 변환
cnt_vec = CountVectorizer()
X_name = cnt_vec.fit_transform(mercari_df.name)
# item_description 에 대한 feature vectorization 변환
tfidf_descp = TfidfVectorizer(max_features = 50000, ngram_range= (1,3) , stop_words='english')
X_descp = tfidf_descp.fit_transform(mercari_df['item_description'])
print('name vectorization shape:',X_name.shape)
print('item_description vectorization shape:',X_descp.shape)
name vectorization shape: (1482535, 105757) item_description vectorization shape: (1482535, 50000)
CountVectorizer, TfidVectorizer가 fit_transform()을 통해 반환하는 데이터는 희소 행렬 형태이다. 따라서 위의 코드를 통해 생성된 X_name, X_descp를 새로 결합해 새로운 데이터 세트로 재구성해야 한다. 이를 위해 인코딩 대상 칼럼도 밀집 행렬 형태가 아닌 희소 행렬 형태로 인코딩을 적용한 뒤 함께 결합한다. LabelBinarizer 클래스는 희소 행렬 형태의 원-핫 인코딩 변환을 지원한다. ( sparse_out = True로 설정 ) 개별 칼럼으로 만들어진 희소 행렬은 사이파이 패키지 sparse 모듈의 hstack() 함수를 이용해 결합할 수 있다.
from sklearn.preprocessing import LabelBinarizer
# brand_name, item_condition_id, shipping 각 피처들을 희소 행렬 원-핫 인코딩 변환
lb_brand_name= LabelBinarizer(sparse_output=True)
X_brand = lb_brand_name.fit_transform(mercari_df['brand_name'])
lb_item_cond_id = LabelBinarizer(sparse_output=True)
X_item_cond_id = lb_item_cond_id.fit_transform(mercari_df['item_condition_id'])
lb_shipping= LabelBinarizer(sparse_output=True)
X_shipping = lb_shipping.fit_transform(mercari_df['shipping'])
# cat_dae, cat_jung, cat_so 각 피처들을 희소 행렬 원-핫 인코딩 변환
lb_cat_dae = LabelBinarizer(sparse_output=True)
X_cat_dae= lb_cat_dae.fit_transform(mercari_df['cat_dae'])
lb_cat_jung = LabelBinarizer(sparse_output=True)
X_cat_jung = lb_cat_jung.fit_transform(mercari_df['cat_jung'])
lb_cat_so = LabelBinarizer(sparse_output=True)
X_cat_so = lb_cat_so.fit_transform(mercari_df['cat_so'])
print(type(X_brand), type(X_item_cond_id), type(X_shipping))
print('X_brand_shape:{0}, X_item_cond_id shape:{1}'.format(X_brand.shape, X_item_cond_id.shape))
print('X_shipping shape:{0}, X_cat_dae shape:{1}'.format(X_shipping.shape, X_cat_dae.shape))
print('X_cat_jung shape:{0}, X_cat_so shape:{1}'.format(X_cat_jung.shape, X_cat_so.shape))
<class 'scipy.sparse.csr.csr_matrix'> <class 'scipy.sparse.csr.csr_matrix'> <class 'scipy.sparse.csr.csr_matrix'> X_brand_shape:(1482535, 4810), X_item_cond_id shape:(1482535, 5) X_shipping shape:(1482535, 1), X_cat_dae shape:(1482535, 11) X_cat_jung shape:(1482535, 114), X_cat_so shape:(1482535, 871)
인코딩 변환된 데이터 세트가 CSR 형태로 변환된 csr_matrix 타입이다.
from scipy.sparse import hstack
import gc
sparse_matrix_list = (X_name, X_descp, X_brand, X_item_cond_id,
X_shipping, X_cat_dae, X_cat_jung, X_cat_so)
# 사이파이 sparse 모듈의 hstack 함수를 이용하여 앞에서 인코딩과 Vectorization을 수행한 데이터 셋을 모두 결합.
X_features_sparse= hstack(sparse_matrix_list).tocsr()
print(type(X_features_sparse), X_features_sparse.shape)
# 데이터 셋이 메모리를 많이 차지하므로 사용 용도가 끝났으면 바로 메모리에서 삭제.
del X_features_sparse
gc.collect()
<class 'scipy.sparse.csr.csr_matrix'> (1482535, 161569)
0
릿지 회귀 모델 구축 및 평가¶
$$ RMSLE : \epsilon = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(log(p_i+1)-log(a_i+1))^2} $$RMSLE(Root Mean Square Logarithmic Error)은 RMSE와 유사하나 오류 값에 로그를 취해 RMSE르 구하는 방식으로 낮은 가격보다 높은 가격에서 오류가 발생할 경우 오류 값이 더 커지는 것을 억제하기 위해서 이 방식을 사용한다.
def rmsle(y , y_pred):
# underflow, overflow를 막기 위해 log가 아닌 log1p로 rmsle 계산
return np.sqrt(np.mean(np.power(np.log1p(y) - np.log1p(y_pred), 2)))
def evaluate_org_price(y_test , preds):
# 원본 데이터는 log1p로 변환되었으므로 exmpm1으로 원복 필요.
preds_exmpm = np.expm1(preds)
y_test_exmpm = np.expm1(y_test)
# rmsle로 RMSLE 값 추출
rmsle_result = rmsle(y_test_exmpm, preds_exmpm)
return rmsle_result
import gc
from scipy.sparse import hstack
def model_train_predict(model,matrix_list):
# scipy.sparse 모듈의 hstack 을 이용하여 sparse matrix 결합
X= hstack(matrix_list).tocsr()
X_train, X_test, y_train, y_test=train_test_split(X, mercari_df['price'],
test_size=0.2, random_state=156)
# 모델 학습 및 예측
model.fit(X_train , y_train)
preds = model.predict(X_test)
del X , X_train , X_test , y_train
gc.collect()
return preds , y_test
Ridge를 이용해 Mercari Price의 회귀 예측을 수행
linear_model = Ridge(solver = "lsqr", fit_intercept=False)
sparse_matrix_list = (X_name, X_brand, X_item_cond_id,
X_shipping, X_cat_dae, X_cat_jung, X_cat_so)
linear_preds , y_test = model_train_predict(model=linear_model ,matrix_list=sparse_matrix_list)
print('Item Description을 제외했을 때 rmsle 값:', evaluate_org_price(y_test , linear_preds))
sparse_matrix_list = (X_descp, X_name, X_brand, X_item_cond_id,
X_shipping, X_cat_dae, X_cat_jung, X_cat_so)
linear_preds , y_test = model_train_predict(model=linear_model , matrix_list=sparse_matrix_list)
print('Item Description을 포함한 rmsle 값:', evaluate_org_price(y_test ,linear_preds))
Item Description을 제외했을 때 rmsle 값: 0.5018919352796902 Item Description을 포함한 rmsle 값: 0.4712197127333659
LightGBM 회귀 모델 구축과 앙상블을 이용한 최종 예측 평가¶
from lightgbm import LGBMRegressor
sparse_matrix_list = (X_descp, X_name, X_brand, X_item_cond_id,
X_shipping, X_cat_dae, X_cat_jung, X_cat_so)
lgbm_model = LGBMRegressor(n_estimators=200, learning_rate=0.5, num_leaves=125, random_state=156)
lgbm_preds , y_test = model_train_predict(model = lgbm_model , matrix_list=sparse_matrix_list)
print('LightGBM rmsle 값:', evaluate_org_price(y_test , lgbm_preds))
LightGBM rmsle 값: 0.45651821264983017
preds = lgbm_preds * 0.45 + linear_preds * 0.55
print('LightGBM과 Ridge를 ensemble한 최종 rmsle 값:', evaluate_org_price(y_test , preds))
LightGBM과 Ridge를 ensemble한 최종 rmsle 값: 0.4504815580394776
텍스트 분석을 위한 기반 프로세스를 알아보고, 텍스트 분류, 감성 분석, 토픽 모델링, 텍스트 군집화 및 유사도 측정을 알아보았다. 텍스트 정규화 작업은 피처 벡터화를 진행하기 이전에 수행하는 다양한 사전 작업을 의미하고 피처 벡터화는 BOW의 대표 방식인 Count 기반과 TF-IDF 기반 피처 벡터화가 있었다. 일반적으로 TF-IDF 기반의 피처 벡터화가 더 좋은 성능을 보이며 이렇게 만들어진 피처 벡터 데이터 세트는 희소 행렬 형태를 지닌다. 텍스트 분류를 할 때에는 감성 사전을 사용해 감성 분석을 진행할 수 있었고, LDA를 이용한 토픽 모델링, K-평균 군집화 기법을 이용한 텍스트 군집화, 코사인 유사도를 이용한 텍스트 유사도 측정 등이 있었다.
'머신러닝(Machine Learning)' 카테고리의 다른 글
[파이썬 머신러닝 완벽 가이드] 8장. 텍스트 분석 - 2 (0) | 2022.02.12 |
---|---|
[파이썬 머신러닝 완벽 가이드] 8장. 텍스트 분석 - 1 (0) | 2022.02.07 |
[파이썬 머신러닝 완벽 가이드] 7장. 군집화 (0) | 2022.02.03 |
댓글