336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

네 번째 프로젝트

 

1. 간단한 intro

2. 웹 크롤링 및 전처리

3. 모델 학습 및 평가


프로젝트를 하면서 느낀 보완사항은 :

-데이터의 길이가 너무 짧으면 단어를 추출하는데 한계가 있고, 과적합이 발생한다.

-유의미한 단어가 존재해야 정확도가 더 개선된다. 모델을 계속 만져봐도 정확도는 크게 개선되지 않는다.

-각 사이트마다 카테고리 분류가 다르게 되어있어서 소비자가 불편할 수도 있을 것이다.

 

 

 

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
# 모듈 임포트
import pandas as pd
import numpy as np
from tensorflow.keras.utils import to_categorical
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from tensorflow.keras.preprocessing.text import *
from tensorflow.keras.preprocessing.sequence import pad_sequences
import pickle # pickle을 이용하면 int 값을 그대로 저장하고 그대로 불러옴
from konlpy.tag import Okt # 형태소 분리기 임포트
 
pd.set_option('display.unicode.east_asian_width',True)
 
df = pd.read_csv('/content/10x10(kor2).csv', index_col=0)
 
print(df.head())
print(df.info())
 
# 중복 행 확인
col_dup = df['title'].duplicated()
print(col_dup)
 
sum_dup = df.title.duplicated().sum()
print(sum_dup)
 
 
# title 컬럼 기준으로 중복 제거 (row 통째로 제거)
df = df.drop_duplicates(subset=['title'])
 
sum_dup = df.title.duplicated().sum()
print(sum_dup)
 
 
# 인덱스 새로 고침
df.reset_index(drop=True# False로 줄 시 기존 인덱스를 colum으로 올림
               inplace=True)
 
print(df.head())
print(df.tail())
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
# 데이터 나누기
= df['title']
= df['category']
 
 
# 원핫인코딩 위해 라벨로 변경
encoder = LabelEncoder()
labeled_Y = encoder.fit_transform(Y)
label = encoder.classes_
 
# 지정한 라벨 저장
# 이미 학습한 라벨들을 이용해야 학습된 결과들과 똑같이 도출
with open('/content/10x10_category_encoder.pickle''wb'as f:
    pickle.dump(encoder, f)   
 
# 원핫인코딩 => 정답데이터 원핫인코딩(희소벡터처리)
onehot_Y = to_categorical(labeled_Y)
 
# 첫번째 데이터 형태소로 분리
okt = Okt()
okt_X = okt.morphs(X[0])
 
 
# 모든 row를 형태소로 분리
for i in range(len(X)):
    X[i] = okt.morphs(X[i])
cs

추후에 데이터들을 불러서 모델 성능이 얼마나 좋은지 평가를 하기 위해서 라벨들을 통일시켜야 한다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# stopwords 불러오기
stopwords = pd.read_csv('/content/stopwords2.csv')
 
# stopwords에 있는 단어를 제거하고 리스트에 담기
words = [] # 빈 리스트 생성
 
for word in okt_X:
    if word not in list(stopwords['stopword']):
        words.append(word)
 
#한 행씩 모든 데이터 처리
for i in range(len(X)): # X=df['title']
    result = []
    for j in range(len(X[i])):
        if len(X[i][j]) > 1:
            if X[i][j] not in list(stopwords['stopword']):
                result.append(X[i][j])
    X[i] = ' '.join(result)
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
#단어를 컴퓨터에서 인식시키기 위해 각 단어마다 숫자(라벨링) 부여 => 토큰화
# 수치적인 의미는 없음
token = Tokenizer()
token.fit_on_texts(X)
tokened_X = token.texts_to_sequences(X)
print(tokened_X[0])
 
#읽고 쓰는 과정에서 데이터의 자료형을 따로 인코딩할 필요 없이 그대로 저장
import pickle
 
with open('/content/10x10_token3.pickle''wb'as f:
    pickle.dump(token, f)
 
wordsize = len(token.word_index) + 1
 
max = 0
for i in range(len(tokened_X)):
    if max < len(tokened_X[i]):
        max = len(tokened_X[i])
 
#LSTM모델 사용을 위해 데이터 크기가 다른 것들을 0을 채워넣어 max사이즈로 통일시킴
X_pad = pad_sequences(tokened_X, max)
 
X_train, X_test, Y_train, Y_test = train_test_split(
    X_pad, onehot_Y, test_size=0.1)
print(X_train.shape)
print(X_test.shape)
print(Y_train.shape)
print(Y_test.shape)
 
xy = X_train, X_test, Y_train, Y_test
np.save('/content/10x10_data_max_{}_size_{}_2'.format(max, wordsize), xy)
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
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import *
from keras.callbacks import EarlyStopping
 
 
X_train, X_test, Y_train, Y_test = np.load(
    '/content/datasets/10x10_data_max_13_size_20320_2.npy',
    allow_pickle=True)
#max: 한 행의(리스트 글자의) 최대 길이
#size: 차원 수
 
##모델 생성
model = Sequential()
#X_pad로 사이즈 통일시킨 데이터를 embedding layer가 원핫인코딩+벡터라이징 처리해줌
#벡터라이징: 각 인덱스별 의미부여를 벡터공간이라는 개념을 도입해 처리
 
model.add(Embedding(20320300, input_length=13))
#차원수 20320를 차원수 300으로 낮추고 input_length는 max최대값인 13으로 입력
 
model.add(Conv1D(512, kernel_size=5
            padding='same', activation='relu'))#1차원 컨볼루션=> conv1d, 2차원 컨볼루션=>conv2d
model.add(MaxPool1D(pool_size=1))
#conv다음에는 maxpool함께 감
 
model.add(LSTM(128, activation='tanh',
               return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(64, activation='tanh',
               return_sequences=True))
model.add(Dropout(0.1))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dense(10, activation='softmax'))#y값개수: 10(카테고리개수)
early_stopping = EarlyStopping(monitor='val_accuracy', patience=3)  
# 5 에포크 동안 해당 값이 좋아지지 않으면 학습을 중단.
 
print(model.summary())
cs

모델 구조

 

 

 

모델 정확도는 높게 나오지만 지속적으로 val_loss 값이 증가하는 것으로 보아, 약간의 과적합이 발생함을 확인했다.

 

제목 자체가 길지 않기 때문에 okt로 형태소 분석을 끝내고, stopwords로 불용어를 제거, 기타 전처리 과정을 끝냈을 때, 제목 최대 길이가 13밖에 나오지 않아서 그렇지 않을까 하는 추측을 해본다.

 

 

 

학습한 모델을 가지고 신상품순, 판매량순으로 새로 데이터를 크롤링해서 성능을 평가해보았다. 지속적으로 학습을 시키고, 더 많은 데이터를 학습시켰다면 신상품이 들어온다고 해도 정확도가 많이 떨어지지 않을 것으로 보인다.

 

 

 

 

 

10x10으로 학습시킨 모델을 쿠팡, 지마켓에 적용하려고 하니까 성능이 확실히 떨어지는 것을 확인할 수 있다. 제목도 각 사이트마다 짓는 방법도 다를 것이며, 같은 제품이라도 다른 카테고리에 있음을 확인했다.

 

예를 들어, 비타민C 고려은단은 당연히 건강식에 있을거라고 생각했는데... 쿠팡에서 아이 유아쪽 카테고리에 있었다.

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

네 번째 프로젝트 순서

 

1. 간단한 intro

2. 웹 크롤링 및 전처리

3. 모델 학습 및 평가


팀프로젝트로 진행했기 때문에 각각 팀원들이 직접 크롤링을 해볼 수 있게, 카테고리를 정해서 각자 코드를 만들어서 공유하자고 얘기를 했다. 이전에 했던 네이버 기사랑 비슷한 느낌이라서 우선 빠르게 진행을 하고, 팀원들이 질문을 하면 피드백을 해주는 형식으로 진행했다.

 

 

 

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
import requests
import pandas as pd
from bs4 import BeautifulSoup
 
# 카테고리 설정 및 카테고리 별 url 숫자 지정
Category = ['Furniture','Life','Deco','Kitchen','Food','Baby,Kids','FasihonClothing','FasionGoods','Beauty','Jewelry']
Category_num = ['121','120','122','112','119','115','117','116','118','125']               
 
# 빈 데이터프레임 생성
df_section_title = pd.DataFrame()
 
for j in range(0,10):
    category = Category[j]
    print(category, ' 카테고리 크롤링 시작')
    category_num = Category_num[j]
    key_list = []
    
    #150페이지 크롤링
    for i in range(1,151):
        
        # 제목 추출  
        url = f'https://www.10x10.co.kr/shopping/category_main.asp?rect=&prvtxt=&rstxt=&extxt=&sflag=n&disp={category_num}&cpg={i}&chkr=False&chke=False&mkr=&sscp=Y&psz=40&srm=ne&iccd=0&styleCd=&attribCd=&icoSize=S&arrCate=&deliType=&minPrc=&maxPrc=&lstDiv=list&ab='
        headers = {'user-agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36'}
        html = requests.get(url, headers=headers)
        title = BeautifulSoup(html.text, 'html.parser')        
        keywords = title.select('p.pdtName.tPad07')        
        
        
        #6개는 행사 상품
        for k in keywords[6:78]:
            keyword = k.text
            key_list.append(keyword)        
        if i % 50 == 0:
            print(i,'페이지 완료')
cs

 

 

 

 

제목이 안 그래도 짧은데, 제목에 과적합을 야기할 수 있는 불필요한 단어들이 많이 포함되어 있어서 전처리가 필요했다. 특히 [무료배송], (1+1), ★특가할인★ 등과 같이 특정 문구[],{},()와 그 안에 들어있는 단어들을 제거해야했다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# [] / () 지우기
key_list2 = []
    
for key in key_list :
    if '[' and '(' in key :
        key = re.sub(r'\[[^)]*\]''', key)
        key_re = re.sub(r'\([^)]*\)''', key)
        key_list2.append(key_re)        
    elif '[' in key :
        key_re = re.sub(r'\[[^)]*\]''', key)
        key_list2.append(key_re)
    elif '(' in key :
        key_re = re.sub(r'\([^)]*\)''', key)
        key_list2.append(key_re)
    elif '★' in key :
        key_re = re.sub(r'\★[^)]*\★''', key)
        key_list2.append(key_re)
    else :
        key_list2.append(key)
print('(),[]삭제 완료')    
cs

이를 제거할 수 있는 코드 

 

 

 

re.sub(r'\([^)]*\)', '', key)

붉은 색 글씨 속에 있는 것들이 제거 됨을 알 수 있다.

개발자에게 쉬는 날은 없다. - 기억하자

 

 

 

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
    #형태소 분류
    titles =[]
    for key in key_list2 :
        titles.append(re.compile('[^가-힣]').sub(' ',key))
        # '가'부터 '힣'빼고 나머지 제외(한글모두)
        # ^:뒤에 붙은 것 제외하고 모두
        #정규표현식 or 점프투파이썬 정규표현식 찾아보기
 
    df_title = pd.DataFrame(titles, columns=['title'])
    df_title['category'= category
    df = pd.concat([df, df_title], axis = 0, ignore_index=True)
    
    print(f'{category} 추가')
    print('-'*30)
 
if 'Unnamed: 0' in df.columns :
    df = df.drop(['Unnamed: 0'], axis=1)   
    df = df.dropna(axis=0)
    
# ' ' 스페이스 공백 지우기 
index_list = []
for i in range(len(df)):
    if df['title'][i].isspace() == True :
        index_list.append(i)
 
df = df.drop(index= index_list, axis = 0)
    
df.to_csv('test_data.csv', encoding='utf-8-sig')    
cs

다른 팀의 경우 조금 더 장문의 글을 형태소 분석을 해야 해서 mecab을 이용했다. 하지만 우리의 경우 제목이 길지 않아서 간단하게 okt로 형태소 분류를 진행했다. 영어의 경우, 대부분 브랜드 네임이라서 따로 카테고리 분류를 진행하지 않았다.

하지만 문제가 생긴 부분은 영어가 사라진 부분이 NaN 값이 아닌 공백으로 존재하고 있었다. 그래서 공백으로만 존재하는 행을 제거했다. isspace()일 경우 따로 리스트를 생성해서 drop하는 방식을 택했다. 

 

 

 

 

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
import sys
from konlpy.tag import Okt
from collections import Counter
from wordcloud import WordCloud
 
def get_noun(category):
    
    okt=Okt()
    noun = okt.nouns(category)
    for i,v in enumerate(noun):
        if len(v) < 2:
            noun.pop(i)
            
    count = Counter(noun)
    noun_list = count.most_common(100)
    
    return noun_list
 
def visualize(noun_list):
    
    wc = WordCloud(font_path='Jalnan.ttf',\
              background_color = "white", \
              width = 1000, \
              height=1000, \
              max_words=100, \
              max_font_size=300)
    
    wc.generate_from_frequencies(dict(noun_list))
    wc.to_file('10x10_WordCloud.png')
    
if __name__=='__main__':
    filename = sys.argv[1]
    
    f= open('10x10(kor2).csv''r', encoding='utf-8')
    category = f.read()
    noun_list = get_noun(category)
    visualize(noun_list)
 
 
print('저장완료')
cs

추가로 모델 학습을 하는데 있어 불필요한 용어들이 너무 많아서 워드클라우딩을 사용하여, 불용어 리스트를 만들어서 제거하였다.  

 

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

네 번째 프로젝트 순서
 
1. 간단한 intro
2. 웹 크롤링 및 전처리
3. 모델 학습 및 평가
 


기간 : 21.06.17 ~ 21.06.24
 
언어 : Python
패키지 : Tensorflow, Scikit-learn, Okt, Beautiful Soup, Pandas, Maplotlib
툴 : Colab, PyCharm, Jupyter Notebook
 
주제 선정 이유 : 보고 있는 카테고리에서, 불필요한 상품까지 노출되는 경우 필터링. 카테고리 분류를 조금 더 깔끔하게 하기 위함.
 
 
 
 

팀원 전체가 프로젝트에 치이고 있는 상태라서 이번에는 지금까지 했던 것을 정리하는 시간을 갖기로 했다. 도서, 영화 등 다른 것도 분류를 할 수 있었으나 이미 다른 팀에서 주제를 선정해서 넘어가게 됐다. 
 
새로운 팀원들이 코드를 다루거나 이해하는 것이 쉽지 않다는 의견이 있어서, 함께 기초부터 같이 하기로 했다. 학원에서 배운 크롤링 기법은 대부분 Selenium을 사용해서 시간이 오래 걸렸다. 그래서 웬만하면 시간이 적게 드는 bs4로 해결하기 위한 코드를 새로 만들었다. 동적인 크롤링을 요하는 상황에서는 어쩔 수 없이 사용하긴 했지만.. 
 
동적 크롤링 : Selenium정적 크롤링 : Beautiful Soup
 
 
 
 
셀레니움보다 bs4가 더 확실하게 긁어오긴 하지만, 처음에 코드를 짜는데 은근히 시간이 걸린다. 셀레니움은 그냥 순서대로 가고 xpath 붙여넣기를 하면 되는데, bs4는 정적이다 보니 각 페이지별 url이나 num_id 값을 구해야하해서 고려할 요소들이 많다. 그리고 코드를 새로 만들면서 느낀 것이 홈페이지 개발자분들께서 가끔씩 특이한 이벤트를 집어넣으셔서 오류가 생긴다. try except 문을 반드시 써주도록 하자 ㅠㅠ
 
 
 

 
시간이 있는 김에 저번에 designer로 만들었던 gui로 앱 느낌의 구현을 하려고 했으나 실패하여, 다섯번째 프로젝트에 적용을 했다. 이 부분은 기대하셔도 좋다. 엄청난 발전이 있었다고 장담한다. (안드로이드 스튜디오로 앱을 만들 때처럼 노가다 느낌이 강하게 느껴진다.)
 
 
 
 
 
 

+ Recent posts