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

다섯 번째 프로젝트

 

1. 추천 시스템에 대한 생각

2. 추천시스템(TFIDF, Word2Vec)

3. GUI로 앱처럼 사용할 수 있게 (PyQt5)

 


드디어 마무리 단계에 왔다 ㅠㅠㅠ 티스토리 블로그 글을 작성하면서 파이널 프로젝트를 하는데 아직도 모르는게 태산이다. 파이널 프로젝트는 자연어(NLP)로 진행중인데, 왜 국문학과를 찾는지 알것 같다.

 

이번 프로젝트를 할 때, 후기 개수 자체가 부족하고, 과마다 특성 있는 키워드가 영화 리뷰처럼 다양하지 않아서(ex. OOO 원장님 너무 친절하고 좋아요. 아프지 않게 잘해주시고 병원이 깔끔해요. 등등) 힘들 것이라 예상을 했다. 결과는 예상대로 워드클라우드로 돌렸을 때 불용어처리할 것이 엄청 많았다. 그래서 추가적으로 팀원들이 후기를 one sentence로 만들 때 진료과목도 집어넣기는 했는데, 이게 얼마나 효과가 있을지는 잘 모르겠다.

 

이번에는 Django 대신에 PyQt5를 이용해서 GUI 앱을 만들었다. 저번에 해보자고 했는데 드디어.. 완성!

 

 

 

 

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

PyQt5 내용은 위키독스를 통해서 잘 배울 수 있다. 모르는 기능들을 하나하나 찾아서 구현이 됐다. 물론 심각한 노가다가 필요한 작업이라고 생각한다. Django, 안드로이드스튜디오 많은 시간이 필요하다.

 

 

 

 

우선 제일 첫번째 안 (다른 팀원)

- 검색기능 / 진료과목 카테고리 / 지역을 클릭하면 => 추천 병원과 병원 정보가 나오게 하는 것이다. 

 

 

 

 

두번째 안 (나)

- 검색기능 / 진료과목을 라디오버튼으로 클릭 => 추천 병원과 나에게 맞는 병원 리스트 담기 병원 정보 나오게 하기 

 

 

 

세번째 안 (절충안)

- 병원을 모를 수도 있기 때문에, 우선 진료과목과 지역을 먼저 선택하게 했다. 그후에 나오는 병원과 유사한 병원을 추가로 검색하고 싶으면 검색하게 만들었다. 그리고 홈페이지의 경우 url을 직접 복사붙여넣기 하는 것보다 바로가기로 만드는 것이 좋다고 생각하여 따로 뺐다.

 

 

 

 

우선 터미널에서 designer를 입력하여 Qt Designer를 실행시켜 ui를 만든다. 안드로이드스튜디오처럼 자신이 원하는 버튼이나 위젯을 끌어다 쓰고, 클래스 옆에 파이참에서 호출하기 쉬운 objectName을 지정해준다. 

 

우리가 구현했던 기능은 자동완성기능, 필터링 기능(지역 진료과목), url클릭시 바로 웹으로 넘어가기, 리스트내 요소를 클릭하여 정보 받아오는 기능, 추천기능, 리셋기능,  등이다. 간단해 보여도 간단하지 않고 시간이 꽤 걸렸다.

 

 

 

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
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QStringListModel
from PyQt5 import uic
import pandas as pd
from sklearn.metrics.pairwise import linear_kernel
from gensim.models import Word2Vec
from scipy.io import mmwrite, mmread
import pickle
import webbrowser
 
form_window = uic.loadUiType('plz_Yes_button_edit_3.ui')[0]
 
class Exam(QWidget, form_window):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
 
###########################################
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = Exam()
    w.show()
    sys.exit(app.exec_())
 
cs

가장 기본적인 구성 - 본인이 만든 ui를 불러오는 코드부터 GUI를 실행시키는 코드까지

 

 

 

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
        #데이터 불러오기
        self.df_review = pd.read_csv('./datasets/model_data_Hospital_and_info2.csv',index_col=0)
        self.df_review.info()
        self.Tfidf_matrix = mmread('./models/tfidf_hospital_review_l.mtx').tocsr()
        self.embedding_model = Word2Vec.load('./models/word2VecModel_hospital_l2.model')
        with open('./models/tfidf_l.pickle''rb'as f:
            self.Tfidf = pickle.load(f)
 
        # 카테고리 목록 리스트화
        self.cmb_title_2.addItem('과를 선택하세요')
        category = list(self.df_review.category.unique()) #카테고리 중복 없이
        category = sorted(category)
 
        for c in category :
            self.cmb_title_2.addItem(c)
 
        # 지역 목록 리스트화
        self.cmb_title.addItem('지역을 선택하세요')
        add_list = []
        for i in self.df_review.addresses:
            a = i.split(' ')[0#지역이름만
            add_list.append(a)
 
        add_set = set(add_list) #중복 제거 위해 set
        address = list(add_set) #다시 list
        address = sorted(address)
        address.pop(0#지역 아닌 다른 단어가 있어서 pop
 
 
        for add in address:
            self.cmb_title.addItem(add) #지역 목록
 
        # 병원 목록과 진료과목 리스트로 만들기
        total = ''
        for c in self.df_review.clinics:
            total += c
 
        totals = total.split(', ')
        total_set = set(totals)
        total = list(total_set)  #진료 과목
        total = sorted(total)
 
        titles = list(self.df_review.names) # 병원 이름
        titles = sorted(titles) # 따로 정렬하는 이유는 병원 이름이 먼저 나오게 하기 위해서
 
        key_title = titles + total      #병원 + 진료 과목
 
cs

데이터, Tfidf, Word2Vec 모델을 불러오고 카테고리 리스트, 지역 목록 리스트, 병원 이름과 진료과목을 리스트로 만들었다. 공간을 크게 차지하지 않고 옵션을 선택할 수 있는 QComboBox에 리스트를 추가했다. 카테고리와 병원 목록은 쉽게 만들 수 있었으나, 지역의 경우 통일이 되지 않아서 따로 전처리를 하였다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#주소 통일
add_dic = {'강원도' : '강원','경기도':'경기','경상남도':'경남','경상북도':'경북','광주광역시':'광주','대구광역시':'대구','대전광역시''대전',
           '부산광역시''부산''서울시' :'서울','서울특별시':'서울','서초구':'서울시 서초구','수원시':'경기','인천광역시':'인천','전라북도':'전북','제주특별자치도':'제주','충청북도':'충북'}
add_list = list(add_dic)
 
list = []
for i in df_review.addresses :
    a = i.split(' ')
    if a[0in add_list :
        a[0= add_dic[a[0]]
    #print(a)
    a = ' '.join(a)
    list.append(a)
df_review.addresses = list
print(df_review.addresses.head(100))
df_review.to_csv('./model_data_Hospital_and_info2.csv')
cs

지역의 경우 17개 시도별로 나누려고 split으로 주소를 나눠 가장 첫번째 글자를 가져왔다. 그렇기 때문에 서울시, 서울, 서울특별시처럼 같은 지역인데 이름이 다를 경우 통일하였다. 다행히 크게 수작업을 해도 되지 않아서 안도의 한숨이...

 

 

 

1
2
3
4
5
        #자동완성
        model = QStringListModel()
        model.setStringList(list(key_title))
        completer = QCompleter()
        completer.setModel(model)
cs

자동완성 기능 생각보다 어렵지 않았다. from PyQt5.QtCore import QStringListModel 불러오고 QCompleter로 자동완성을 시켜주고 이걸 QLineEdit으로 받아줬다.

 

 

 

1
2
3
4
5
6
7
8
9
        # 버튼 함수
        self.le_title.setCompleter(completer)
        self.le_title.returnPressed.connect(self.btn_recommend_slot)
        self.btn_recommend.clicked.connect(self.btn_recommend_slot) # 엔터 또는 버튼 클릭시
        self.cmb_title_2.currentIndexChanged.connect(self.cmb_title_slot_2)
        self.cmb_title.currentIndexChanged.connect(self.cmb_title_slot)
        self.listWidget.itemClicked.connect(self.hospital_info)
        self.btn_html.clicked.connect(self.open_web)
        self.btn_recommend_5.clicked.connect(self.btn_clicked)
cs

버튼을 클릭하거나, 엔터를 누르거나, index가 변할 때 어떤 식으로 작동할지를 구현하는 준비단계?

 

 

 

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
    # 병원 지역별로 필터링
    def cmb_title_slot(self):
        print('지역 선택 클릭')
        self.le_title.clear() # 먼저 병원을 클릭 했을 때
        title = self.cmb_title_2.currentText()
        address = self.cmb_title.currentText()
 
        region = self.df_review[(self.df_review.category == title) &(self.df_review.region == address)].iloc[:101# 자체 추천 순위로 출력
        recommend = list(region)
        #print(recommend)
 
        self.listWidget.clear()
        self.listWidget.insertItems(0, recommend)
 
 
    # 카테고리 탑10 병원
    def cmb_title_slot_2(self):
        print('과 선택 클릭')
        title = self.cmb_title_2.currentText()
 
        top = self.df_review[self.df_review.category == title].iloc[:10,1]
        #recommend = '\n'.join(list(top)) # 이거는 lbl_result에
        recommend = list(top)
 
        self.listWidget.clear()
        self.listWidget.insertItems(0, recommend)
cs

지역 필터링, 병원 필터링 currentIndexChanged, index가 바뀌면 함수가 실행이 된다. 원래 cmb_title_slot1과 2를 바꿔야 하는데 처음에 설정을 그렇게 해서 지나갔다. 

 

현재 QComboBox에서 선택한 텍스트를 가져와야 필터링이 되기 때문에, currentText로 현재 텍스트를 가져왔다.  그리고 선택한  진료과목과 일치하는 탑10 병원을 listWidget에 출력하였다. QLineEdit으로 해도 되지만 나중에 병원 정보를 클릭해야 하기 때문에 안 된다. 그리고 list로 받아와서 0 번째 부터 다시 보여주는 코드를 실행했다. 마찬가지로 지역 설정을 바꿨을 경우, 우선 진료과목과 지역을 동시에 만족하는 병원을 추천하였다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    # 병원을 클릭 했을 때, 병원 정보 보여주기
    def hospital_info(self):
        print('병원 정보 클릭')
        title = self.listWidget.currentItem().text()
 
        try :
            a = self.df_review[self.df_review.names == title].iloc[03].split(',')[:10# 주요 진료 과목 10개만
            a = ','.join(a)
            b = self.df_review[self.df_review.names == title].iloc[04# 주소
            c = self.df_review[self.df_review.names == title].iloc[06# 전화번호
            #d = self.df_review[self.df_review.names == title].iloc[0, 5] # 홈페이지 url / 홈페이지 오픈 버튼으로 대체
            recommend = '[ 주요 진료 과목 ]\n{0}\n\n[ 주소 ]\n{1}\n\n[ 전화번호 ]\n{2}'.format(a, b, c)
            self.infotext.setText(recommend)
            recommend = '홈페이지 바로가기 클릭!'
            self.btn_html.setText(recommend)
        except :
            pass
 
cs

어떻게 listWidget에 있는 아이템을 가져오나 했더니, 텍스트를 가져오는 것과 비슷하게 currentItem()으로 텍스트를 불러올 수 있었다.  크롤링을 할 때 조금 멍청하게 가져와서 전처리 과정을 한 번 더 거쳤다. 진료과목을 리스트 안에 집어넣었는데 이것을 그대로 csv에 집어넣어서 '[' 리스트가 문자가 됐다. 

 

a는 진료과목, b는 주소,  c는 전화번호, d는 홈페이지 url을 보여주려고 했으나 사용자가 따로 긁어야하는 불편함이 있어서 바로 홈페이지 오픈을 시켜주기로 했다. 

 

 

 

 

1
2
3
4
5
6
    # 홈페이지 오픈
    def open_web(self):
        print('홈페이지 바로가기 클릭')
        title = self.listWidget.currentItem().text()
        html = self.df_review[self.df_review.names == title].iloc[05]
        webbrowser.open(html) # 홈페이지 연동
cs

검색을 해보니 엄청 복잡하게 url을 오픈 하는 경우가 있는데, import webbroser를 하고 html을 open 하면 바로 url로 넘어가진다코딩을 하다보면 어떻게 하면 더 코드를 간단하게 짤까 고민을 하면서, 다른 사람이 짜놓은 코드와 비교를 하다보면 가끔 감탄을 할 때가 온다. 나도 누가 코드를 보고 감탄했으면 좋겠다는 마음이.. 

 

 

 

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
    def btn_recommend_slot(self):
        print('추천 시스템 클릭')
        title = self.le_title.text()
 
        try:
            if title in list(self.df_review['names']):
                h_idx = self.df_review[
                    self.df_review['names']==title].index[0]
                cosine_sim = linear_kernel(
                    self.Tfidf_matrix[h_idx],
                    self.Tfidf_matrix)
                # recommend = '\n'.join(
                #     list(self.getRecommendation(cosine_sim))[1:])
                recommend = list(self.getRecommendation2(cosine_sim))[:-1]
 
            #elif title in total :
 
 
            else:
                print(title, '예외 키워드')
                sentence = [title] * 10
 
                sim_word = self.embedding_model.wv.most_similar(title, topn=10)
                labels = []
                for label, _ in sim_word:
                    labels.append(label)
                print(labels)
 
                for i, word in enumerate(labels):
                    sentence += [word] * (9 - i)
 
                sentence = ' '.join(sentence)
                sentence_vec = self.Tfidf.transform([sentence])
                cosine_sim = linear_kernel(sentence_vec,
                                           self.Tfidf_matrix)
                # recommend = '\n'.join(
                #     list(self.getRecommendation(cosine_sim))[:-1])
 
                recommend = list(self.getRecommendation2(cosine_sim))[:-1]
        except:
            if title :
                recommend =['검색어를 다시 확인해주세요']
                self.infotext.clear()
 
                default_text = '[ 주요 진료 과목 ]\n\n[ 주소 ]\n\n[ 전화번호 ]'
                self.infotext.setText(default_text)
 
            else:
                pass
        self.listWidget.clear()
        self.listWidget.insertItems(0, recommend)
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
    #키워드 기반 추천 시스템
    def getRecommendation2(self, cosine_sim):
        title = self.cmb_title_2.currentText()
        address = self.cmb_title.currentText()
        print(title, address)
        simScores = list(enumerate(cosine_sim[-1]))
        simScores = sorted(simScores, key=lambda x: x[1], reverse=True)
 
        if title == '과를 선택하세요' and address =='지역을 선택하세요':
            pass
        else :
            simlist = []
            for i in simScores :
                add = self.df_review.iloc[i[0],7# 지역
                tit = self.df_review.iloc[i[0],0# 카테고리
 
                if add == address and tit == title : # 지역, 카테고리 동시에 일치할 때만 추가
                    #print(add)
                    simlist.append(i)
 
            h_idx = [i[0for i in simlist[0:10]]
 
            if len(h_idx) == 0:
                RecHosptiallist = [f'{address} 지역에는 관련된 키워드가 없습니다.']
                return RecHosptiallist.names
            else :
                RecHosptiallist = self.df_review.iloc[h_idx]
                print(RecHosptiallist, '출력')
                return RecHosptiallist.names
 
        simScores = simScores[0:11]
        h_idx = [i[0for i in simScores]
        RecHosptiallist = self.df_review.iloc[h_idx]
        return RecHosptiallist.names
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
    #리셋 기능
    def btn_clicked(self):
        print('리셋 버튼 클릭')
        self.cmb_title_2.clear()
        self.cmb_title.clear()
        self.le_title.clear()
        self.infotext.clear()
 
        #병원 정보 처음에 나오는 내용
        default_text = '[ 주요 진료 과목 ]\n\n[ 주소 ]\n\n[ 전화번호 ]'
        self.infotext.setText(default_text)
 
        category = list(self.df_review.category.unique())
        category = sorted(category)
        self.cmb_title_2.addItem('과를 선택하세요')
        self.cmb_title.addItem('지역을 선택하세요')
 
        add_list = []
        for i in self.df_review.addresses:
            a = i.split(' ')[0]
            add_list.append(a)
 
        add_set = set(add_list)
        address = list(add_set)
        address = sorted(address)
        address.pop(0)
 
        for add in address:
            self.cmb_title.addItem(add)  # 지역 목록
 
        for c in category:
            self.cmb_title_2.addItem(c)  # 카테고리 목록
cs

리셋버튼을 누르면 모든 것이 리셋이 되게 하려고 했다. 찾아보니 그런 코드가 있긴 있는데, 지금 GUI의 경우 지역 목록과 카테고리 목록은 남아야 해서 리셋을 누르는 동시에 다시 지역목록과 카테고리 목록이 뜨게 했다. 여기서 특히 오류가 많이 났는데, 어디서 호출을 해주느냐에 따라서 이게 중복 노출이 되는지, 부분 노출이 되는지 갈렸다.

 

아무튼 GUI 완성 ㅠㅠ

 

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

다섯 번째 프로젝트

 

1. 추천 시스템에 대한 생각

2. 추천시스템(TFIDF, Word2Vec)

3. GUI로 앱처럼 사용할 수 있게 (PyQt5)


항상 과정이 비슷해서 앞에 부분은 코드는 생략합니다.

 

우선 로그인이 필요한 서비스라서 동적 크롤링으로 후기를 긁어왔다. 그리고 추가로 병원 이름, 클리닉명, 주소, 링크, 전화번호는 로그인 없이도 크롤링이 가능해서 BS4로 긁어왔다. 과정은 크롤링한 데이터를 전처리하고, 워드클라우드를 생성해서 불용어를 처리하고(중요), 하나의 문장으로 합쳤다.  

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from scipy.io import mmwrite, mmread #매트릭스 저장할 때 mmwrite, 읽을 땐 mmread
import pickle #변수 타입 그대로 저장
 
df_review_one_sentence = pd.read_csv('./preprocess/total_hospital_review_one_sentence.csv', index_col=0)
print(df_review_one_sentence.info())
 
Tfidf = TfidfVectorizer(sublinear_tf=True)      # sublinear_tf는 값의 스무딩 여부를 결정하는 파라미터
Tfidf_matrix = Tfidf.fit_transform(df_review_one_sentence['reviews'])       # fit_transform 된 Tfidf를 갖고 있으면 추후 데이터 추가 가능하므로 따로 저장
 
with open('./models/tfidf.pickle''wb'as f:
    pickle.dump(Tfidf, f)       # Tfidf 저장
 
mmwrite('./models/tfidf_hospital_review.mtx', Tfidf_matrix)        # 유사도 점수 매트릭스 저장
cs

TfidfVectorizer로 문서 내에서 단어 토큰을 생성하고, 각 단어의 수와 가중치를 조정하여 문자를 순서 벡터로 변환했다. 이 과정은 따로 건드리는 부분이 없다. 다만 데이터 전처리가 중요하다.

 

 

 

 

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
import pandas as pd
from sklearn.metrics.pairwise import linear_kernel
from scipy.io import mmwrite, mmread
import pickle
from gensim.models import Word2Vec
 
df_hospital_review_one_sentence = pd.read_csv('./preprocess/total_hospital_review_one_sentence.csv', index_col=0)       # 리뷰 읽어 df 생성
 
Tfidf_matrix = mmread('./models/tfidf_hospital_review.mtx').tocsr()        # matrix 불러오기
with open('./models/tfidf.pickle''rb'as f:      # tfidf 불러오기
    Tfidf = pickle.load(f)
 
def getRecommendation(cosine_sim):      # 코사인 유사도롤 활용하여 유사한 병원 추천하는 함수
    simScore = list(enumerate(cosine_sim[-1]))      # 각 코사인 유사도 값에 인덱스 붙임
    simScore = sorted(simScore, key=lambda x:x[1], reverse=True)        # simScore(코사인 유사도, x[1])가 큰 것부터 정렬. reverse=True 내림차순 정렬.
    simScore = simScore[1:11]       # 유사한 병원 10개 리스트. 0번 값은 자기 자신이므로 배제.
    hospital_idx = [i[0for i in simScore]     # 인덱스(i[0]) 뽑아서 리스트 생성
   recHospitalList = df_hospital_review_one_sentence.iloc[hospital_idx]        # df에서 해당 병원 리스트 추출
    return recHospitalList
 
 
#병원명 검색
hospital_idx = df_hospital_review_one_sentence[df_hospital_review_one_sentence['names']=='김이비인후과의원'].index[0]      # 병원 이름으로 인덱스 값 찾기
# hospital_idx = 127
# print(df_review_one_sentence.iloc[hospital, 0])
 
cosine_sim = linear_kernel(Tfidf_matrix[hospital_idx], Tfidf_matrix)      # linear_kernel은 각 Tfidf 값을 다차원 공간에 벡터(방향과 거리를 가짐)로 배치한 뒤, 코사인 유사도를 구해줌. cosine = 삼각형의 밑변 / 윗변
                                                                       # 비슷한 영화는 유사한 위치에 배치됨. 유사할수록 각이 줄어드므로 코사인 값이 1에 가까워짐. -1에 가까울수록 반대, 0에 가까울수록 무관
recommendation = getRecommendation(cosine_sim)
# print(recommendation)
print(recommendation.iloc[:, 1])
cs

pickle에 저장한 Tfidf를 불러와서 코사인 유사도를 활용하여 유사한 병원을 추천해준다. 우선 병원의 idx 값을 구한다. 그리고 추천시스템에 이와 유사한 병원들의 리스트를 반환 받아서 출력한다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
from gensim.models import Word2Vec
 
review_word = pd.read_csv('./preprocess/total_cleaned_review.csv', index_col=0)
print(review_word.info())
cleaned_token_review = list(review_word['cleaned_sentences'])
print(len(cleaned_token_review))
cleaned_tokens = []
count = 0
for sentence in cleaned_token_review:
    token = sentence.split(' ')     # 띄어쓰기 기준으로 각 단어 토큰화
    cleaned_tokens.append(token)
# print(len(cleaned_tokens))
# print(cleaned_token_review[0])
# print(cleaned_tokens[0])
embedding_model = Word2Vec(cleaned_tokens, vector_size=100, window=4,
                           min_count=20, workers=4, epochs=100, sg=1)   # vector_size는 몇차원으로 줄일지 지정, window는 CNN의 kernel_size 개념, 앞뒤로 고려하는 단어의 개수를 나타냄
                                                                        # min_count는 출현 빈도가 20번 이상인 경우만 word track에 추가하라는 의미(즉, 자주 안 나오는 단어는 제외)
                                                                        # workers는 cpu 스레드 몇개 써서 작업할 건지 지정, sg는 어떤 알고리즘 쓸건지 지정
embedding_model.save('./models/word2VecModel_hospital.model')
print(embedding_model.wv.vocab.keys())
print(len(embedding_model.wv.vocab.keys()))
cs

Word2Vec 단어의 의미를 다차원에 분산시켜 표현하여, 단어간 위치를 파악해서 유사도를 분석해준다. Skip-Gram의 경우 중심 단어를 보고 주변에 어떤 단어가 있을지 예측. CBoW는 주변에 있는 단어들로 중간에 있는 단어를 예측한다.

 

 

 

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#리뷰 기반 키워드 검색
embedding_model = Word2Vec.load('./models/word2VecModel_hospital.model')
key_word = '친절'
 
sim_word = embedding_model.wv.most_similar(key_word, topn=10)
labels = []
sentence = []
for label, _ in sim_word:
    labels.append(label)
print(labels)
for i, word in enumerate(labels):
    sentence += [word] * (9-i)
sentence = ' '.join(sentence)
#sentence = [key_word] * 10      # tf에서 높은 값을 가지도록 리스트 복사
print(sentence)
 
sentence_vec = Tfidf.transform([sentence])
cosine_sim = linear_kernel(sentence_vec, Tfidf_matrix)
recommendation = getRecommendation(cosine_sim)
print(recommendation)
cs

키워드를 기반으로 하여 병원을 추천해주는 시스템이다.

 

 

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

다섯 번째 프로젝트

 

1. 추천 시스템에 대한 생각

2. 추천시스템(TFIDF, Word2Vec)

3. GUI로 앱처럼 사용할 수 있게 (PyQt5)

 


기간 : 2021.07.02 ~ 2021.07.09

 

언어 : Python

패키지 : Scikit-learn, GENSIM(Word2Vec, TF-IDF), Selenium, Pandas, Maplotlib, WordCloud

툴 : PyCharm, Jupyter Notebook

 

 

 

영화 리뷰 기반으로 추천시스템을 같이 진행을 했다. 새로운 프로젝트를 하려고 했으나 생각보다 자료 모으기가 쉽지 않았다. 원래는 보드게임 후기를 모으려고 했으나 데이터가 너무 정제되지 않았다. 인터파크나 티켓팅하는 사이트에서 뮤지컬 후기를 긁어오려고 했으나  도배도 많았고, 알바들이 너무 많아서 포기했다. 네이버 검색을 하려고 했으나 뮤지컬의 경우 책 제목이 중간중간 있어서 일일이 또 처리를 해야 해서 병원 후기를 기반으로 한 병원 추천시스템을 진행하기로 했다.

 

데이터 크롤링 -> 데이터 전처리 -> 추천 시스템 -> GUI 순으로 프로젝트가 진행 됐으나 앞에 부분은 계속 반복되는 것 같아서 이번에 추천시스템에 대한 생각을 정리해보고자 한다.

 

 

 

출처 : 공부합시다 티스토리

협업 필터링

- 취향이 비슷한 사람끼리 그룹화해서 추천하는 방식이다. (예를 들어서 20~30대 남성, 서울 중구 지역에 사는 사람들)

- 단점은 처음 사용하는 이용자거나 새로 나온 상품을 추천하기 어렵다.

 

콘텐츠 기반 필터링

- 이용자가 소비한 콘텐츠를 기준으로 유사한 특성을 가진 콘텐츠를 추천한다.

- 단점은 다양성이 떨어진다. (이전에 진중권 교수가 얘기했던 확증편향 문제가 생긴다.)

 

 

 

분명 콘텐츠는 많은 것 같은데 또 막상 뒤적거려보면 볼게 없는 넷플릭스. 예전에 넷플릭스 추천시스템을 검색해봤을 때 수천개의 데이터를 가지고 카테고리를 나누고 각각 가중치를 곱해서 복잡하게 만들어졌다고 하는데 잘 모르겠다...

 

우선 플랫폼은 유저가 더욱 오래 사용해야 돈을 버는 구조인데, 이런 식으로 갑자기 볼 콘텐츠가 사라지면 이탈률이 커져서 플랫폼 자체적으로 손해다. 그래서 이들을 붙잡으려고 콘텐츠를 추천한다. 하지만 너무 유사한 콘텐츠를 추천하게 되면 확증편향 문제는 물론, 사람들이 질리게 되는 단점이 있는 것 같다.

 

 

 

 

유튜브의 알 수 없는 알고리즘. 내가 평상시에 보고 있던 콘텐츠도 아니고, 관심을 가지고 있던 콘텐츠도 아닌데 왜 갑자기 뜨는 것일까? 심지어 오늘 어제 영상도 아닌 몇 개월, 몇 년 전 영상이 추천이 된다?

 

나도 마케팅을 하려고 하는 사람이다 보니, 유튜브 알고리즘을 이해하려고 많은 분석과 시도를 해봤다. 조회수 100만, 200만, 300만 넘는 영상을 여러 개 보유하고 있어서 탐색기능에 대해서는 어느정도 이해를 했다. 하지만 아직도 나를 한 번도 보지 않았던 영상으로 이끄는 알고리즘에 대해서는 분석이 조금 필요하다.

 

우선 구글과 유튜브는 어느정도 연동(?)이 되어있어서, 구글에서 트래픽이 증가하면 유튜브에서도 어느정도 추천되는 모양이다. 예전에 빅맥송 오디션이 있었는데, 구글에서 검색량이 많아지자 유튜브에서도 이전 영상들이 다시 노출되는 현상이 있었다.

 

 

 

 

 

유튜브 알고리즘을 따라가려면 이용자의 개인 데이터, 노출 클릭률, 영상시청지속시간, 다음 화면의 클릭 유무 등 엄청나게 다양한 데이터가 필요하겠지만 이는 불가능하기 때문에, 간이 추천시스템을 만드려고 생각을 했다.

 

우선 우리가 했던 프로젝트는, 후기를 기반으로 TF-IDF로 각 단어의 가중치를 줘서 얼마나 자주 나오는지를 비교하여 코사인 유사도를 활용하여 영화를 추천했다. 그렇다면 코사인 유사도를 구하는 식에서 reverse = False를 할 경우에 정 반대의 영화를 추천하겠다는 생각이 들었다.

 

정반대의 영화의 경우 사용자가 관심이 없던 영화일 수도 있고, 아니면 한 번도 보지 않았던 장르의 영화일 수도 있다. 총 9개의 영화를 보여줄 때 6개는 관련성 있고, 2개는 중간 정도, 1개는 전혀 관련 없는 영화를 보여주면서 노출클릭률을 살펴본다. log.txt 파일에 데이터를 담아서 관련성 있는 영화를 클릭했을 때는 영향을 주지 않고, 총 9개의 영화를 다시 새로고침했을 때에만 점차 관련 없는 영화가 나오는 값을 줄여나간다. (-1 ~1 사이면 , -0.9 -0.8 -0.7 이런 식으로...)

 

랜덤으로 계속 영화가 나오기 때문에 이것도 정확하지는 않지만, 어떻게 하면 편향성을 줄이면서 플랫폼에 이용자를 더 오래 잡게 할 수 있을지에 대해서 계속 고민해야 할 것 같다.

 

 

 

+ Recent posts