관련 Raw Data를 내려받습니다. 약 100메가 정도됩니다. [링크]
데이타 전처리
관련 Library를 import하고 피처 타입과 Null여부를 확인하기 위해 info()를 실행합니다.
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(r'C:\Users\HANA\PycharmProjects\HANATOUR\NLP\TEXT_Example\mercari.tsv', sep='\t')
print(mercari_df.shape)
print(mercari_df.head(3))
print(mercari_df.info())
null 데이타가 많이 보이네요. 이 null데이터는 이후에 적절한 문자열로 치환하겠습니다.
RangeIndex: 693359 entries, 0 to 693358
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 test_id 693359 non-null int64
1 name 693359 non-null object
2 item_condition_id 693359 non-null int64
3 category_name 690301 non-null object
4 brand_name 397834 non-null object
5 shipping 693359 non-null int64
6 item_description 693359 non-null object
dtypes: int64(3), object(4)
memory usage: 37.0+ MB
Target값인 Price칼럼의 데이터 분포도를 살펴보겠습니다. 회귀에서 Target값의 정규 분포도는 매우 중요합니다. 왜곡돼 있을 경우 보통 로그를 씌워서 변환하면 대부분 정규 분포의 형태를 가지게 됩니다.
import matplotlib.pyplot as plt
import seaborn as sns
y_train_df = mercari_df['price']
plt.figure(figsize=(6,4))
sns.distplot(y_train_df, kde=False)
plt.show()
Price값이 비교적 적은 가격을 가진 데이터 값에 왜곡돼 있어, 로그 값으로 분포도를 다시 그려봅니다.
로그값으로 변환하면 price값이 비교적 정규 분포에 가까운 데이터를 이루게 됩니다. 그러므로 price값을 log변환값으로 변환합니다.
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'])
print(mercari_df['price'].head(3))
그 외 다른 피처의 값도 분포도 알아봅니다.
print(mercari_df['shipping'].value_counts())
print(mercari_df['item_condition_id'].value_counts())
boolean_cond = mercari_df['item_description']=='No description yet'
print(mercari_df[boolean_cond]['item_description'].count())
item_description의 값이 없을 경우 No description yet의 경우가 많아 해당 값은 적절한 값으로 변환하겠습니다.
Name: price, dtype: float64
0 819435
1 663100
Name: shipping, dtype: int64
1 640549
3 432161
2 375479
4 31962
5 2384
Name: item_condition_id, dtype: int64
82489
/으로 분리된 카테고리를 하나의 문자열로 나타내기 위해 대/중/소로 나누어 카테고리를 분리합니다. 중요한것은 zip과 apply lambda식에 적용하여 한번에 분리합니다.
def split_cat(category_name):
try:
return category_name.split('/')
except:
return ['Other_Null','Other_Null', 'Other_Null']
mercari_df['cat_dae'], mercari_df['cat_jung'], mercari_df['cat_so'] = \
zip(*mercari_df['category_name'].apply(lambda x : split_cat(x)))
print(mercari_df['cat_dae'].value_counts())
print(mercari_df['cat_jung'].value_counts())
print(mercari_df['cat_so'].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
Athletic Apparel 134383
Makeup 124624
Tops & Blouses 106960
Shoes 100452
Jewelry 61763
...
Candles 64
Ceramics and Pottery 57
Dolls and Miniatures 49
Books and Zines 46
Quilts 31
Name: cat_jung, Length: 114, dtype: int64
Pants, Tights, Leggings 60177
Other 50224
Face 50171
T-Shirts 46380
Shoes 32168
...
Seasonal 1
Towel 1
Ephemera 1
Doll Clothes 1
Tiles 1
Name: cat_so, Length: 871, dtype: int64
114
871
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='item_description')
print(mercari_df.isnull().sum())
Null값은 Other_Null로 하고 Null이 있는지 확인합니다.
train_id 0
name 0
item_condition_id 0
category_name 0
brand_name 0
price 0
shipping 0
item_description 0
dtype: int64
피처 인코딩과 피처 벡터화
해당 데이터 세트는 문자열 칼럼이 많습니다. 이 문자열 칼럼 중 레이블 또는 원-핫 인코딩을 수행하거나 피처 벡터화로 변환할 칼럼을 선별해 보겠습니다. 먼저 이 피처를 어떤 방식으로 변환할지 검토한 후에 추후에 일괄적으로 전체 속성의 변환 작업을 적용하겠습니다.
Price를 통해 상품 가격을 예측해야 하므로 회귀 모델을 기반으로 합니다. 선형 회귀 모델과 회귀 트리 모델을 모두 적용해 보겠습니다. 특히 선형 회귀의 경우 원-핫 인코딩 적용이 훨씬 선호되므로 인코딩할 피처는 모두 원-핫 인코딩을 적용하겠습니다.
피처 벡터화의 경우는 비교적 짧은 텍스트의 경우는 Count 기반의 벡터화를, 긴 텍스트는 TD-IDF기반의 벡터화를 적용하겠습니다.
먼저 brand_name 컬럼에 대해 검토해 보겠습니다. brand_name 칼럼은 상품의 브랜드명입니다. 어떤 유형으로 되어 있는지와 유형 건수, 대표적인 브랜드명을 5개 정도 확인해 보겠습니다. 동일한 방법으로 name도 확인합니다.
print(mercari_df['brand_name'].nunique())
print(mercari_df['brand_name'].value_counts()[:5])
print(mercari_df['name'].nunique())
print(mercari_df['name'].value_counts()[:10])
brand_name은 4,810개 / name은 1,225,273개 입니다. brand_name의 경우 대부분 명료한 문자열로 되어 있어 원-핫 인코딩으로 변환하겠습니다. Name의 경우 적은 단어 위주의 텍스트 형태로 되어 있어 Count기반으로 피처 벡터화 합니다.
이런식으로 확인하면서 category_name컬럼은 원-핫 인코딩을 shipping컬럼은 item_condition_id컬럼 또한 원-핫 인코딩을 사용합니다.
4810
Other_Null 632682
PINK 54088
Nike 54043
Victoria's Secret 48036
LuLaRoe 31024
Name: brand_name, dtype: int64
1225273
Bundle 2232
Reserved 453
Converse 445
BUNDLE 418
Dress 410
Coach purse 404
Lularoe TC leggings 396
Romper 353
Nike 340
Vans 334
item_description의 경우 평균 길이가 145자로 비교적 크므로 해당 칼럼은 TF-IDF로 변환하겠습니다.
print(mercari_df['item_description'].str.len().mean())
print(mercari_df['item_description'][:3])
145.71140512702905
0 No description yet
1 This keyboard is in great condition and works ...
2 Adorable top with a hint of lace and a key hol...
Name: item_description, dtype: object
name과 item_description칼럼을 피처 팩터화합니다. name칼럼의 경우는 CountVectorizer로, item_description의 경우는 TfidfVectorizer로 변환하겠습니다.
cnt_vec = CountVectorizer()
X_name = cnt_vec.fit_transform(mercari_df.name)
tfidf_descp = TfidfVectorizer(max_features=5000, ngram_range=(1,3), stop_words='english')
X_descp = tfidf_descp.fit_transform(mercari_df['item_description'])
print(X_name.shape)
print(X_descp.shape)
(1482535, 105757)
(1482535, 5000)
CountVectorizer, TfidfVectorizer가 fit_transform()을 통해 반환하는데이터는 희소 행렬입니다. 희소 행렬 객체 변수인 X_name과 X_decp를 새로 결합해 새로운 데이터 세트로 구성해야 하고 , 앞으로 인코딩 될 모든 변수도 X_name, X_descp와 결합돼 ML모델을 실행하는 기반 데이터 세트로 재구성돼야 합니다.
이를 위해서 이 인코딩 대상 칼럼도 밀집 행렬 형태가 아닌 희소 행렬 형태로 인코딩을 적용한 뒤, 함께 결합합니다.
사이킷런은 원-핫 인코딩을 위해 OnehohtEncoder와 LabelBinarizer클래스를 제공합니다. 이 중 LabelBinarizer클래스는 희소 행렬 형태의 원-핫 인코딩 변환을 지원합니다. 생성 시 sparse_out=True로 파라미터를 설정해주기만 하면 됩니다.
from sklearn.preprocessing import LabelBinarizer
#각 피처를 희소 행렬 원-핫 인코딩 변환
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['lb_item_cond_id'])
lb_shipping = LabelBinarizer(sparse_output=True)
X_shipping = lb_shipping.fit_transform(mercari_df['shipping'])
lb_cat_dae = LabelBinarizer(sparse_output=True)
X_cat_dae = lb_cat_dae.fit_transform(mercari_df['cat_dae'])
print(type(X_brand), type(X_item_cond_id))
print(X_brand.shape, X_item_cond_id.shape)
인코딩 변환된 데이터 세트가 CSR형태로 변환된 csr_matrix타입입니다.
<class 'scipy.sparse.csr.csr_matrix'> <class 'scipy.sparse.csr.csr_matrix'>
(1482535, 4810) (1482535, 5)
앞에서 피처 벡터화 변환한 데이터 세트와 희소 인코딩 변환된 데이터 세트를 hstack()을 이용해 모두 결합해 보겠습니다. 만들어진 결합 데이터가 비교적 많은 메모리를 잡아먹기 때문에 개인용 PC에서 메모리 오류가 발생할 수 있기에 메모리를 삭제합니다.
이렇게 만들어진 데이터 세트에 회귀를 적용해 price값을 예측할 수 있도록 하겠습니다.
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)
#hstack 함수를 이용해 인코딩과 벡터화를 수행한데이터 세트를 모두 결합
X_features_sparse = hstack(sparse_matrix_list).tocsr()
print(type(X_features_sparse), X_features_sparse)
#데이터 세트가 메모리를 많이 차지하므로 사용 목적이 끝났으면 바로 메모리에서 삭제
del X_features_sparse
gc.collect()
릿지 회귀 모델 구축 및 평가
적용할 평가 지표는 RMSLE(Root Mean Square Logarithmic Error)방식으로 합니다. RMSLE는 RMSE와 유사하나 오류 값에 로그를 취해 RMSE를 구하는 방식입니다.
주의할 사항은 원본 데이터의 price칼럼의 값은 왜곡된 데이터 분포를 가지고 있기 때문에 이를 정규 분포 형태로 유도하기 위해 로그 값을 취해 변환했습니다.
Ridge를 이용해 회귀 예측을 수행합니다.
def rmsle(y, y_pred):
#underflow, overflow를 막기 위해 log가 아닌 log1p로 rmsle계산
return np.sqrt(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_ist):
#scipy.sparse 모듈의 hstack을 이용해 희소 행렬 결합
X = hstack(matrix_ist).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_test
gc.collect()
return preds, y_test
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_ist=sparse_matrix_list)
print(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_ist=sparse_matrix_list)
print(evaluate_org_price(y_test, linear_preds))
LightGBM 회귀 모델 구축과 앙상블을 이용한 최종 예측 평가
LightGBM을 이용해 회귀를 수행한 뒤, 위에서 구한 릿지 모델 예측값과 LightGBM모델 예측값을 간단한 앙상블(Ensemble)방식으로 섞어서 최종 회귀 예측값을 평가합니다.
n_estimator를 1000이상 증가시키면 예측 성능은 조금 좋아지는데, 수행 시간이 PC에서 1시간 이상 걸립니다. n_estimator를 200으로 작게 설정하고 예측 성능을 측정해 보겠습니다.
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_ist=sparse_matrix_list)
print(evaluate_org_price(y_test, lgbm_preds))
preds = lgbm_preds*0.45+linear_preds*0.55
print(evaluate_org_price(preds))
이 후 2개의 모델을 합쳐 수행합니다.
preds = lgbm_preds*0.45+linear_preds*0.55
print(evaluate_org_price(preds))
'NPL with ML' 카테고리의 다른 글
Python 머신러닝, 한글 감정분석을 위한 리뷰 분석 : 프로그램부터 실전적용까지 (rhinoMorph이용 (0) | 2020.12.12 |
---|---|
Python에서 SpaCy를 사용한 텍스트 분류를 위한 기계 학습 (0) | 2020.12.01 |
토픽 모델링 [Topic Modeling] - LDA기법 소스포함 (0) | 2020.11.17 |
감성 분석 (Sentiment Analysis) - 비지도학습 기반, VADER (0) | 2020.11.13 |
감성 분석 (Sentiment Analysis) - 지도학습 기반 (0) | 2020.11.12 |
문서 유사도 측정 - 코사인 유사도(Cosine Similarity)와 실전 연습 코드 (0) | 2020.11.08 |
한글 텍스트 처리 위한 KoNLPy를 이용한 네이버 영화 평점 감정 분석 (0) | 2020.11.01 |
문서 군집화 실전 예제 - Opinion Review Data Set (0) | 2020.11.01 |