투빅스 11기&12기 8주차 NLP - 12기 김주호

by 김주호 posted Sep 20, 2019
?

단축키

Prev이전 문서

Next다음 문서

ESC닫기

+ - Up Down Comment Print
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
First : Crawling
import selenium 
from selenium import webdriver
import random
from time import sleep
import pandas as pd
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from tqdm import tqdm
# Browser 켜기
browser = webdriver.Chrome()
browser.get('https://everytime.kr/timetable')
# login 함수 생성
def login(id_,password):
    browser.find_element_by_name('userid').send_keys(str(id_))
    browser.find_element_by_name('password').send_keys(str(password))
    browser.find_element_by_xpath('//*[@id="container"]/form/p[3]/input').click()
# 가끔 팝업뜨는거 닫아주기
browser.find_element_by_xpath('//*[@id="container"]/ul/li[1]').click()
# 스크롤 위치 찾기
scr1=browser.find_element_by_xpath('//*[@id="subjects"]/div[2]')
# 스크롤 계속 내리기 ( 매우 오래걸림 실행 X)
for i in range(1000):
    browser.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", scr1)
    sleep(3)
# 에타 과목 정보 저장
tables = browser.find_elements_by_tag_name("tr")
# 이 과목 평점 저장
stars = browser.find_elements_by_class_name('star')
에브리타임 과목정보 크롤링
# 칼럼 이름이 html에 있길래 뽑은건데, 2번씩 중복해서 나오길래 이렇게 뽑아봄 (사용안했음 ㅎㅎ;;ㅈㅅ;;)
cols=[]
for i in range(0,24,2):
    cols.append(table[2].text.split('\n')[i])
# 점수랑 타이틀 가져오는 코드
scores=[]
links=[]
for table in tqdm (stars) :
    scores.append(table.get_attribute('title'))
    links.append(table.get_attribute('href'))
 
# 테이블이, 3번행부터 정보가 있고, 2918행 이후로는 의미없는 행이길래 제외
# 테이블에서 세부정보 뽑는 코드
tables=tables[3:2918]
grades=[] # 추천학년
categories=[] # 수업 분류(전공,교양)
codes=[] # 과목코드
names=[] # 수업 이름
profs=[] # 교수 이름
weights=[] # 학점(1,2,3)
times=[] # 시간
competitiors=[] # 에타 시간표에 담은인원
for element in tqdm(tables):
    splited=element.text.split(' ')
    grades.append(splited[0])
    categories.append(splited[1])
    codes.append(splited[2])
    names.append(splited[3])
    profs.append(splited[4])
    weights.append(splited[5])
    try :
        times.append(splited[8]+splited[9]+splited[10]+splited[11]+splited[12])
    except :
        times.append(None)
    try :
        competitiors.append(splited[13])
    except :
        competitiors.append(None)
# 뽑은 리스트들을 df로 바꿔줬음
dfs=[names,codes,scores,categories,profs,times,grades,weights,competitiors,links]
df=pd.DataFrame()
for cols in dfs:
    df=pd.concat([df,pd.Series(cols)],axis=1)
# 칼럼 이름 만들어줌
df.columns=['class_name','class_code','score','category','professor','time',
            'recommend_year','weight','competitor','link']
# 아까 위에서 형식 제대로 안지켜진 데이터들 떨굼 ( 보완 방향 (!) )
df.dropna(inplace=True)
# 교수이름이 3글자 아닌 수업들 뽑아봄 : 왜냐면 그냥 인덱싱으로 한거라 형식 안지켜진건데 들어갔을 수 있음
error_list=[]
for i in df['professor'].unique():
    if len(i) !=3 :
        error_list.append(i)
# 직접 보고 이름 2,4글자인 교수님들 이름 빼줌
non_error=['박진','이화','박민','호티롱안','나강','문영','전긍','고댕','김예솔란','정철',
                 '최웅','안준','김강','이건','사공석진','허림','오정','이민','이찬',
                 '김명','허균','김영','이웅','임강','최영']
# 제거
for i in non_error :
    error_list.remove(i)
# 그냥 이름3자 아니거나, non error 아닌 거 다 없애버렸는데 이거 보완해야함 ( 1 )
#df2=df.query('professor not in @error_list') 
# Save
#df2.to_csv('df2.csv',index=False)
# Load
df2=pd.read_csv('df2.csv')
# 링크 추출함, 리뷰 하나도 없는 수업은 java스크립트로 뽑혀오길래 그것도 제외
link_list=list(df2['link'].unique())
link_list.remove('javascript: alert("%EC%95%84%EC%A7%81 %EB%93%B1%EB%A1%9D%EB%90%9C %EA%B0%95%EC%9D%98%ED%8F%89%EC%9D%B4 %EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4.");')
에브리타임 리뷰크롤링
from random import randint
from bs4 import BeautifulSoup as bs
import requests
import lxml.html
import re
def review_crawl(link_text):
    ## Selenium
    browser.get(link_text)
    sleep(0.5# 로딩 기다리기
    # 리뷰 내용만 추출
    reg=re.compile('[0-9]')
    reviews={}
    sleep(2# 로딩 기다리기 
    tb=browser.find_element_by_class_name('articles')
    art_table=tb.find_elements_by_tag_name('article')
    for element in art_table :
        reviews[element.find_element_by_class_name('text').text]=(float(''.join(reg.findall(element.find_element_by_class_name('on').get_attribute('style'))))*0.05)
    # 소스 뺴와서 파싱
    table=bs(browser.page_source,'xml')
    heads=table.find('div',class_='side head')
    
    # 제목, 교수 내용 추출
    class_name=(heads.find('h2').text)
    prof_name=(heads.find('span').text)
    # 강의평 테이블 추출
    articles=table.find('div',class_='side article')
    # 강의평의 평균 평점 내용 추출
    mean_score=float(articles.find('span',class_='value').text)
    # 강의평 중 세부 테이블 추출
    details=articles.find('div',class_='details')
    # 강의평 중 세부 내용 추출
    labels=details.find_all('label')
    detail=details.find_all('span')
    detail_type={}
    for l,d in zip(labels,detail):
        detail_type[str(l.text)]=d.text
    review_t=pd.DataFrame.from_dict(reviews, orient='index').reset_index()
    review_t.columns=['review','score']
    review_t['name']=class_name
    review_t['prof']=prof_name
    
    review_detail=pd.DataFrame.from_dict(detail_type,orient='index').T
    review_detail['name']=class_name
    review_detail['prof']=prof_name
    
    return review_t,review_detail
review=pd.DataFrame() # 강의평 추출 테이블
detail=pd.DataFrame() # 과목 세부정보 추출 테이블
# ↓ 가끔 안 켜고 시작하면 오류뜰 때가 있어서 그때 대비 
browser = webdriver.Chrome()
browser.get(link_list[0]) 
login('###','####')
# ↑ 가끔 안 켜고 시작하면 오류뜰 때가 있어서 그때 대비 
# 크롤링 과정 (오래걸림 실행 ㄴ)
for link in tqdm(link_list) :
    try:
        r_t,r_d=review_crawl(link)
        review=pd.concat([review,r_t])
        detail=pd.concat([detail,r_d])
        sleep(1)
    except:
        print(link , '강의평 추출 X ')
#Save
#review.to_csv('review.csv',index=False)
#detail.to_csv('detail.csv',index=False)
#Load
review=pd.read_csv('review.csv')
review=review.reset_index().drop('index',axis=1)
현재 가지고 있는 테이블
테이블 1: 에브리타임 과목정보
테이블 2: 에브리타임 리뷰
테이블 3 :에브리타임 리뷰 요약
 
review.head()
 
def cleanText(readData):
    text = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》]'' ', readData) # 특문제거
    text = re.sub('[^가-힣]',' ',text) # 한글 제외, + 단일 자음모음 제거 
    return text
clean=[]
for data in review['review']:
    clean.append(cleanText(data))
review['review']=pd.Series(clean)
Second : Text-Mining
Tokenize
from konlpy.tag import *
kkma = Kkma()
https://m.blog.naver.com/PostView.nhnblogId=wideeyed&logNo=221337575742&proxyReferer=https%3A%2F%2Fwww.google.com%2F
 
# 토크나이징하고, 에러난 row ( 대부분 빈 텍스트를 토크나이징 한 것 ) 찾아줌
corpus=[] 
error=[]
i=0
for data in tqdm(review['review']):
    try:
        corpus.append(kkma.morphs(data))
    except:
        error.append(i)
    i += 1
error=[2568,2720,6009,6532,6589,7729,8722,10853]
# 에러난 친구들
display(review.loc[error,:])
review=review.drop(error,0# 빈 행 삭제
 
# save
with open('kkma_token_0909.pickle''wb') as f:
    pickle.dump(corpus, f, pickle.HIGHEST_PROTOCOL)
#load
import pickle
with open('kkma_token_0909.pickle''rb') as f:
    corpus = pickle.load(f)
Embedding
from gensim.models import Word2Vec, FastText
embd_SG= Word2Vec(corpus, size=100, window=2, min_count=10,
                           workers=-1, iter=1000, sg=1)
#CBOW보단 Skip - Gram의 성능이 전반적으로 낫다고들 함
#리뷰 여러개니까, 차원 적으면 표현이 안될거라고 생각, 100차원정도 임시로 줘봤음
#리뷰다보니까 별로 안 길어서 윈도우는 작게주고, min_count는 의미없는 단어 나오지 말라고 10정도로 줌
# -> 이거 나중에 보완 ( 2 )
# 리뷰 단위별 벡터구하기 (받은 코드가 옛날버전인듯 word2vec 모델에 그런인자가 없던데..그래서 그냥 짬)
def get_embedding_vector(model,corpus):
    embeding_dict={}
    embeding_df=pd.DataFrame()
    for w,v in zip(model.wv.index2word , model.wv.vectors):
        embeding_dict[w]=v
        embeding_table=(pd.DataFrame(embeding_dict).T.reset_index())
    for words in (corpus):
        embeding_df=pd.concat([embeding_df,
                               embeding_table.query('@words in index').iloc[:,1:].mean()]
                             ,axis=1 )
    return embeding_df
%%time
#임베딩 벡터 추출
embedding_vect=get_embedding_vector(embd_SG,corpus)
 
 
  # This is added back by InteractiveShellApp.init_path()
Wall time: 7min 26s
vect=(embedding_vect.T.reset_index()).iloc[:,1:] # 데이터 프레임으로 바꿔주고
vect.columns=['Review_Embeded'+str(x) for x in range(100)] # 칼럼이름도 달아줌
# 리뷰랑 병합(Group화 해서 강의별 계산을 위함)
review.index=range(len(review))
df3=pd.concat([review.iloc[:,1:],vect],axis=1)
# 여기다 담아놓은다음에 Merge
feature=[]
for col_name in (['Review_Embeded'+str(x) for x in range(100)]):
    feature.append(df3.groupby(['name','prof'])[col_name].mean().reset_index())
#Merge의 과정
df=df2.drop(['class_code','time','link','competitor'],axis=1)
df.rename({'class_name':'name','professor':'prof'},axis=1,inplace=True)
for f in feature :
    df =pd.merge(df, f, on=['name','prof'],how='left')
df.drop_duplicates(inplace=True)
df.dropna(inplace=True)
# Skip-Gram을 이용한 인베딩 테이블 완성
sim_df=pd.concat([df[['name','prof',]],df.iloc[:,6:]],axis=1)
sim_df.index=[x for x in range(892)]
# 나중에 사용할 테이블
detail_table=df2[['class_name','professor','category','recommend_year','weight','score']].drop_duplicates()
detail_table.rename({'class_name':'name','professor':'prof'},axis=1,inplace=True)
"""
df3=pd.concat([df3,pd.get_dummies(df3['category'])],axis=1)
df3=pd.concat([df3,pd.get_dummies(df3['recommend_year'])],axis=1)
df3.drop(['category','recommend_year'],axis=1,inplace=True)
df3=pd.concat([df3[['name','prof','score']],df3.iloc[:,3:]],axis=1)
df3['weight']=df3['weight'].astype(int)
"""
Similarlity
from numpy import dot
from numpy.linalg import norm
import numpy as np
#코사인 유사도 계산 함수
def cos_sim(A, B): 
       return dot(A, B)/(norm(A)*norm(B))
#다른 모든 행과 유사도 계산 함수
def find_similar(input_name,input_prof,df):
    input_lecture=df.query('name == @input_name & prof == @input_prof')
    rest_lecture=df.query('name != @input_name | prof != @input_prof')
    output=rest_lecture.iloc[:,:2]
    output['similar']=''
    for i in range(len(rest_lecture)):
        output.iloc[i,2]= cos_sim(input_lecture.iloc[:,2:],rest_lecture.iloc[i,2:])
    output = pd.merge(output, detail_table, on=['name','prof'],how='left')
    output = output.sort_values('similar',ascending=False)
    return output
# 내가 들었던 수업중에 교수님이 (사람)좋았던 수업
find_similar('문명과세계화의도전','안현상',sim_df)[:5# 경제학개론 확인
 
# 강의는 썩 재미없었지만 교수님이 열정 넘치고 엄청 좋은 분이었음, 유은나 교수님의 경제학 개론도 대체적으로 비슷한 평가
display(review.query('name=="문명과세계화의도전" & prof =="안현상"')[['review']][:5])
display(review.query('name=="경제학개론" & prof =="유은나"')[['review']][:5]) 
 
# 외우기만 해서 맘에 안들었던 수업, 영화시리즈 제외하고 한문과 문화 확인
display(find_similar('영화작문','이지현',sim_df)[:5]) 
 
# 암기라는 키워드 자주등장, 차이점은 교수님 칭찬이 많음 ㅎ; 평점차인듯
display(review.query('name=="영화작문" & prof =="이지현"')[['review']][:5])
display(review.query('name=="한문과문화" & prof =="이규일"')[['review']][:5]) 
Pre-Trained Embedding
from gensim.models import FastText
%%time
model = FastText.load_fasttext_format('D:/fasttext/wiki.ko.bin')
# Pre-triainde vector를 사용해보고 싶은데, 커뮤니티 리뷰다 보니까 별 이상한 단어랑 줄임말,오타들이 다있음
# -> 그래서 일반적 word2vec모델에선 사용이 안되것 같음
#   -> 근데 FastText는 corpus에 없는 단어들도 사용 가능 + Noise에 강한모델
#      -> 따라서 FastText를 이용한 Pre_Trained Vector 사용 시도
 
fatx_vec=[] # 문장별 임베딩
for words in corpus:
    fatx_vec.append(model[words].mean(axis=0))
C:\Users\kcg99\Anaconda3\lib\site-packages\ipykernel_launcher.py:3: DeprecationWarning: Call to deprecated `__getitem__` (Method will be removed in 4.0.0, use self.wv.__getitem__() instead).
  This is separate from the ipykernel package so we can avoid doing imports until
fatx_emd=pd.DataFrame() # 임베딩한 벡터를 데이터프레임으로 만들어줌
for vecs in (fatx_vec):
    fatx_emd=pd.concat([fatx_emd,pd.DataFrame(vecs)],axis=1)
#벡터 데이터프레임 전처리    
fatx_emd=fatx_emd.T
fatx_emd.index=range(len(fatx_emd))
fatx_emd.columns=['Review_ft_Embeded'+str(x) for x in range(300)]
#그룹바이 하기위해 달아줌
fatx_df=pd.concat([review[['name','prof']],fatx_emd],axis=1)
# 강의별 임베딩 평균값 생성
feature=[]
for col_name in (['Review_ft_Embeded'+str(x) for x in range(300)]):
    feature.append(fatx_df.groupby(['name','prof'])[col_name].mean().reset_index())
# 중복값 제거
fatx_df=fatx_df.iloc[:,:2].drop_duplicates()
# 평균값과 강의명 병합
for f in feature :
    fatx_df =pd.merge(fatx_df, f, on=['name','prof'],how='left')
# 임베딩 테이블 완성
fatx_df.dropna(inplace=True)
display(find_similar('문명과세계화의도전','안현상',fatx_df)[:5])
display(find_similar('문명과세계화의도전','안현상',sim_df)[:5])
display(find_similar('영화작문','이지현',fatx_df)[:5])
display(find_similar('영화작문','이지현',sim_df)[:5])
# 결과가 다름 : 수업 분야가 비슷한것들끼리 조금더 잘 뽑은 느낌 (내 주관 + 내가 알고있는 평가)
# 가중치를 줘서 앙상블하는 것도 괜찮지 않을까?
 
 
개선방안
1) 강의평은 학생이 주관적으로 쓰는것, 게다가 온라인커뮤니티상이라 대충쓰는 경우가 많으며, 에타는 리뷰가 적어서 그게 잘 걸러지지도 않음
 
수업계획서를 크롤링해서, 내용이 비슷한것과, 교수님 성향이 비슷한 걸 나눠줄 수 있지 않을까?
 
2) 머신러닝 모델링을 진행 안했음
 
이를 이용해 평점에 어떤게 가장 영향을 많이 미치는 지 알 수 있었을텐데 ( 교수 피드백용으로 사용 할 수 있지 않을까?)
 
3) 디테일 테이블이랑 병합을 할 수 있음
 
이를 통해 유사도 말고도, 다양한 방법으로 필터링 할 수 있을텐데, 구현해야할 듯
 
관련 링크:
1) https://ratsgo.github.io/natural%20language%20processing/2017/03/08/word2vec/
2) https://fasttext.cc/docs/en/pretrained-vectors.html
3) https://byeongkijeong.github.io/fastText/
4) https://ratsgo.github.io/from%20frequency%20to%20semantics/2017/03/11/embedding/
 
1) word2vec의 전반적 사용법
2) fasttext pre-trained-vectors 구한곳
3) 그거 사용법 찾은곳
4) 임베딩의 전반적 정보 얻은곳
 
"""### 전처리 해보려고 했으나 과정이 너무 번거로워서 나중에 Develop 
error_index=df.query('professor in @error_list').index #
error_index.astype(int)
error_table=[]
for i in error_index:
    error_table.append(tables[i].text)
#### 에러의 유형 :
1) 팀팀class의 제목명이 길어서 오류 ( 팀팀class에 대한 분석은 우리task에 별로 맞지 않는듯 ) -> 제거    
2) 강의이름이 영어라 강의 제목에 띄어쓰기가 들어감  
3) 외국인 교수라 교수 이름란에 띄어쓰기가 들어감
import re
error_team= re.compile('팀팀Class')
error_english = re.compile('[a-zA-Z]')
#### 팀팀삭제
error_table2=[]
for table in error_table :
    if len(error_team.findall(table)) == 0 :
        error_table2.append(table)
#### 영어제목 수정
error_eng=[]
error_table3=[]
for table in error_table2:
    if len(error_english.findall(table)) < 5:
        error_table3.append(table)
    else :
        error_eng.append(table)
error_eng[8]
reg_kor=re.compile('[가-힣]')
eng_title=[]
for element in error_eng:
    valid_chr=''.join(element.split(' ')[3:])
    point=(reg_kor.search(valid_chr)).start()
    if len(valid_chr[:point]) >= 1:
        eng_title.append(valid_chr[:point])
    else 
eng_title
### 전처리 해보려고 했으나 과정이 너무 번거로워서 나중에 Develop 
"""
 
cs

Articles

2 3 4 5 6 7 8 9 10 11

나눔글꼴 설치 안내


이 PC에는 나눔글꼴이 설치되어 있지 않습니다.

이 사이트를 나눔글꼴로 보기 위해서는
나눔글꼴을 설치해야 합니다.

설치 취소

Designed by sketchbooks.co.kr / sketchbook5 board skin

Sketchbook5, 스케치북5

Sketchbook5, 스케치북5

Sketchbook5, 스케치북5

Sketchbook5, 스케치북5