본인이 만든 함수들은 각 ipynb로 분류해 놓았다. 그러므로 이를 따라하는 것을 추천하지는 않는다..

import import_ipynb
import send_email
import Main_1
import call_Graph
import pandas as pd


importing Jupyter notebook from send_email.ipynb
importing Jupyter notebook from Main_1.ipynb
importing Jupyter notebook from call_url.ipynb
importing Jupyter notebook from call_all_company.ipynb
importing Jupyter notebook from call_GRU_predict.ipynb
Epoch 1/15
375/375 [==============================] - ETA: 0s - loss: 0.4081 - acc: 0.8263
Epoch 00001: val_acc improved from -inf to 0.84589, saving model to best_model_GRU.h5
375/375 [==============================] - 55s 145ms/step - loss: 0.4081 - acc: 0.8263 - val_loss: 0.3822 - val_acc: 0.8459
Epoch 2/15
375/375 [==============================] - ETA: 0s - loss: 0.3341 - acc: 0.8630
Epoch 00002: val_acc improved from 0.84589 to 0.85187, saving model to best_model_GRU.h5
375/375 [==============================] - 55s 145ms/step - loss: 0.3341 - acc: 0.8630 - val_loss: 0.3605 - val_acc: 0.8519
Epoch 3/15
375/375 [==============================] - ETA: 0s - loss: 0.3128 - acc: 0.8725
Epoch 00003: val_acc did not improve from 0.85187
375/375 [==============================] - 55s 146ms/step - loss: 0.3128 - acc: 0.8725 - val_loss: 0.3565 - val_acc: 0.8499
Epoch 4/15
375/375 [==============================] - ETA: 0s - loss: 0.2961 - acc: 0.8803
Epoch 00004: val_acc did not improve from 0.85187
375/375 [==============================] - 55s 146ms/step - loss: 0.2961 - acc: 0.8803 - val_loss: 0.3612 - val_acc: 0.8492
Epoch 5/15
375/375 [==============================] - ETA: 0s - loss: 0.2810 - acc: 0.8872
Epoch 00005: val_acc did not improve from 0.85187
375/375 [==============================] - 53s 142ms/step - loss: 0.2810 - acc: 0.8872 - val_loss: 0.3755 - val_acc: 0.8447
Epoch 6/15
375/375 [==============================] - ETA: 0s - loss: 0.2659 - acc: 0.8948
Epoch 00006: val_acc did not improve from 0.85187
375/375 [==============================] - 54s 143ms/step - loss: 0.2659 - acc: 0.8948 - val_loss: 0.3756 - val_acc: 0.8431
Epoch 7/15
375/375 [==============================] - ETA: 0s - loss: 0.2504 - acc: 0.9023
Epoch 00007: val_acc did not improve from 0.85187
375/375 [==============================] - 58s 155ms/step - loss: 0.2504 - acc: 0.9023 - val_loss: 0.3964 - val_acc: 0.8342
Epoch 00007: early stopping
492/492 [==============================] - 10s 20ms/step - loss: 0.3621 - acc: 0.8509

 테스트 정확도: 0.8509
importing Jupyter notebook from call_Graph.ipynb

 

 

def kwandoll():
    check1=int(input("어떤 작업을 수행하시겠습니까?\n"+"[1]:경쟁회사 전체 데이터\n"+"[2]:경쟁회사 및 자사 데이터"))
    check2=int(input("분류된 데이터를 확인하시겠습니까?\n"+"[1]:예"+"[2]:아니오"))
    check3=int(input("이메일로 받으시겠습니까?\n"+"[1]:예"+"[2]:아니오"))
    check4=int(input("그래프로 확인해보시겠습니까?\n"+"[1]:예"+"[2]:아니오"))
    if(check1==1):
        
        if(check2==1 and check4==1):
            
            Main_1.classified_all_data()
            df=pd.read_csv("C:\\Data\\thisweek_classified.csv")
            call_Graph.wordcloud(df)
            if(check3==1):
                send_email.sending_classified_all_data()
            else:
                print("지정된 디렉토리에 저장되었습니다.")
        elif(check2==1 and check4==2):
            if(check3==1):
                send_email.sending_classified_all_data()
            else:
                print("지정된 디렉토리에 저장되었습니다.")
        elif(check2==2 and check4==1):
            Main_1.none_classified_all_data()
            df=pd.read_csv("C:\\Data\\thisweek_unclassified.csv")
            call_Graph.wordcloud(df)
            if(check3==1):
                send_email.sending_unclassified_all_data()
            else:
                print("지정된 디렉터리에 저장되었습니다.")
        elif(check2==2 and check4==2):
            Main_1.none_classified_all_data()
            if(check3==1):
                send_email.sending_unclassified_all_data()
            else:
                print("지정된 디렉토리에 저장되었습니다.")

    elif(check1==2):
        if(check2==1 and check4==1):
            Main_1.classified_selected_data()
            df=pd.read_csv("C:\\Data\\thisweek_selected_classified.csv")
            call_Graph.wordcloud(df)
            if(check3==1):
                send_email.sending_classified_selected_data()
            else:
                print("지정된 디렉토리에 저장되었습니다.")
        elif(check2==1 and check4==2):
            if(check3==1):
                send_email.sending_classified_selected_data()
            else:
                print("지정된 디렉토리에 저장되었습니다.")
        elif(check2==2 and check4==1):
            Main_1.none_classified_selected_data()
            df=pd.read_csv("C:\\Data\\thisweek_selected_unclassified.csv")
            call_Graph.wordcloud(df)
            if(check3==1):
                send_email.sending_unclassified_selected_data()
            else:
                print("지정된 디렉토리에 저장되었습니다.")
        elif(check2==2 and check4==2):
            Main_1.none_classified_selected_data()
            if(check3==1):
                send_email.sending_unclassified_selected_data()
            else:
                print("지정된 디렉토리에 저장되었습니다.")

 

 

이후, kwandoll() 함수를 호출하면

어떤 작업을 수행하시겠습니까?
[1]:경쟁회사 전체 데이터
[2]:경쟁회사 및 자사 데이터1
분류된 데이터를 확인하시겠습니까?
[1]:예[2]:아니오1
이메일로 받으시겠습니까?
[1]:예[2]:아니오1
그래프로 확인해보시겠습니까?
[1]:예[2]:아니오1
시작 년도를 적으시오: 2021
시작 월을 적으시오: 01
시작 일을 적으시오: 01
종료 년도를 적으시오: 2021
종료 월을 적으시오: 01
종료 일을 적으시오: 13
퀄리티 벨류의 최댓값을 지정하세요: 90
퀄리티 벨류의 최솟값을 지정하세요: 10

 

이름을 적으세요이관형
보낼 이메일 주소를 적으세요: asd95101@naver.com
Successfully sent the mail!!!

이로써 내가 원하는 데이터는 지정 이메일로 전송이되고, 워드클라우드를 통해 그래프로 보여진다. 

이제 시작이라 말주변도없고 이 데이터도 전처리를 더 해야하지만, 인턴을 통해 이 정도까지 왔다는 것도 만족한다. 계속 공부하며 글을 올려 실력을 올려보자.

1. 감성분석엔진을 통해 분류된 전체 회사 데이터.

def weekly_all_classified_data(dict):
    start_date,end_date=start_to_date(),end_to_date()
    maximum,minimum=score_input()
    for key,value in dict.items():
        driver = webdriver.Chrome('C:\\Users\\LeeKwanHyeong\\chromedriver_win32\\chromedriver.exe',chrome_options=options)
        driver.implicitly_wait(2)
        driver.get(value)
        # sort 선택창
        elem = driver.find_element_by_xpath("//span[@class='DPvwYc']")
        elem.click()
        time.sleep(3)
        pyautogui.press('up')
        time.sleep(0.7)
        pyautogui.press('up')
        time.sleep(0.7)
        pyautogui.press('enter')
        
        while(True):
            driver.execute_script("window.scrollTo([0],document.body.scrollHeight);")
            time.sleep(0.5)
            try:
                element=driver.find_element_by_xpath('//div[@class="U26fgb O0WRkf oG5Srb C0oVfc n9lfJ"]')
                if(element is not None):
                    element.click()
                    break
            except Exception:
                continue

        html=driver.page_source
        driver.quit()
        bsObj=BeautifulSoup(html,'lxml')
        div_reviews=bsObj.find_all("div",{"class":"d15Mdf bAhLNe"})
        
        
        company_list,grade_list,date_list,content_list=[],[],[],[]
        for div in div_reviews:
            
            

            date_=div.find('span',{"class":"p2TkOb"}).get_text()
            t=re.findall(r"\d*\.\d+|\d+",date_)
            date='{0}-{1}-{2}'.format(t[0],t[1],t[2])
            year, month, day=int(t[0]), int(t[1]), int(t[2])
            dd=datetime(year,month,day)
            if((dd-start_date>=timedelta(days=0)) and (end_date-dd>=timedelta(days=0))):
                content=div.find('span',{'jsname':'bN97Pc'}).get_text()
                content=content.replace("전체 리뷰",'')
                content=re.sub('[^가-힣0-9a-zA-Z_!?@#%^&-=:;,\"\'<>\\s]','',content)
                content.encode('utf-8')
                grade=len(div.find_all('div',{'class':'vQHuPe bUWb7c'}))
                percentage,word=call_GRU_predict.GRU_predict(content)
                if(((percentage<maximum)and (percentage>minimum))) and (len(word)>6):
                    date_list.append(dd)
                    content_list.append(content)
                    grade_list.append(grade)
                    company_list.append(key)
                else:
                    continue
        grade_Series=pd.Series(grade_list)
        date_Series=pd.Series(date_list)
        content_Series=pd.Series(content_list)
                #
        company_Series=pd.Series(company_list)
        data_frame=pd.DataFrame()
        data_frame['company']=company_Series
        data_frame['date']=date_Series
        data_frame['grade']=grade_Series
        data_frame['content']=content_Series
        
        good_data=data_frame[data_frame['grade']>2]
        bad_data=data_frame[data_frame['grade']<3]
        
        writer=pd.ExcelWriter("C:/data/thisweek_classified.xlsx")
        if not os.path.exists(writer):
            good_data.to_excel(writer, sheet_name='good',header=True)
            bad_data.to_excel(writer,sheet_name='bad',header=True)
        else:
            bad_data.to_excel(writer, sheet_name='bad',header=False)
            good_data.to_excel(writer,sheet_name='good',header=False)
        writer.save()
        if not os.path.exists('C:\\Data\\thisweek_classified.csv'):
            data_frame.to_csv('C:\\Data\\thisweek_classified.csv',index=False, mode='w',encoding='utf_8_sig')
        else:
            data_frame.to_csv('C:\\Data\\thisweek_classified.csv',index=False,mode='a',encoding='utf_8_sig',header=False)
    return data_frame
        
    #location='C:\\Data\\thisweek_classified.csv'
   
            
        
def classified_all_data():
    url_dict=call_url.call_url() #url리턴 함수
      #시작일 종료일 지정
    load=call_all_company.weekly_all_classified_data(url_dict)
    return load

 

 

2. 감성분석 엔진을 통해 분류되지 않은 전체 데이터

 

def weekly_all_unclassified_data(dict):
    start_date,end_date=start_to_date(),end_to_date()
    for key,value in dict.items():
        driver = webdriver.Chrome('C:\\Users\\LeeKwanHyeong\\chromedriver_win32\\chromedriver.exe',chrome_options=options)
        driver.implicitly_wait(2)
        driver.get(value)
        # sort 선택창
        elem = driver.find_element_by_xpath("//span[@class='DPvwYc']")
        elem.click()
        time.sleep(3)
        pyautogui.press('up')
        time.sleep(0.7)
        pyautogui.press('up')
        time.sleep(0.7)
        pyautogui.press('enter')
        
        while(True):
            driver.execute_script("window.scrollTo([0],document.body.scrollHeight);")
            time.sleep(0.5)
            try:
                element=driver.find_element_by_xpath('//div[@class="U26fgb O0WRkf oG5Srb C0oVfc n9lfJ"]')
                if(element is not None):
                    element.click()
                    break
            except Exception:
                continue

        html=driver.page_source
        driver.quit()
        bsObj=BeautifulSoup(html,'lxml')
        div_reviews=bsObj.find_all("div",{"class":"d15Mdf bAhLNe"})
        
        
        company_list,grade_list,date_list,content_list=[],[],[],[]
        for div in div_reviews:
            
            

            date_=div.find('span',{"class":"p2TkOb"}).get_text()
            t=re.findall(r"\d*\.\d+|\d+",date_)
            date='{0}-{1}-{2}'.format(t[0],t[1],t[2])
            year, month, day=int(t[0]), int(t[1]), int(t[2])
            dd=datetime(year,month,day)
            if((dd-start_date>=timedelta(days=0)) and (end_date-dd>=timedelta(days=0))):
                content=div.find('span',{'jsname':'bN97Pc'}).get_text()
                content=content.replace("전체 리뷰",'')
                content=re.sub('[^가-힣0-9a-zA-Z_!?@#%^&-=:;,\"\'<>\\s]','',content)
                content.encode('utf-8')
                grade=len(div.find_all('div',{'class':'vQHuPe bUWb7c'}))
                percentage,word=call_GRU_predict.GRU_predict(content)
               
                date_list.append(dd)
                content_list.append(content)
                grade_list.append(grade)
                company_list.append(key)
                
        grade_Series=pd.Series(grade_list)
        date_Series=pd.Series(date_list)
        content_Series=pd.Series(content_list)
                #
        company_Series=pd.Series(company_list)
        data_frame=pd.DataFrame()
        data_frame['company']=company_Series
        data_frame['date']=date_Series
        data_frame['grade']=grade_Series
        data_frame['content']=content_Series
    good_data=data_frame[data_frame['grade']>2]
    bad_data=data_frame[data_frame['grade']<3]
        
    writer=pd.ExcelWriter("C:/data/thisweek_unclassified.xlsx")
    if not os.path.exists(writer):
        good_data.to_excel(writer,sheet_name='good',header=True)
        bad_data.to_excel(writer,sheet_name='bad',header=True)
    else:
        bad_data.to_excel(writer,sheet_name='bad',header=False)
        good_data.to_excel(writer,sheet_name='good',header=False)
    writer.save()
    if not os.path.exists('C:\\Data\\thisweek_unclassified.csv'):
            data_frame.to_csv('C:\\Data\\thisweek_unclassified.csv',index=False, mode='w',encoding='utf_8_sig')
    else:
        data_frame.to_csv('C:\\Data\\thisweek_unclassified.csv',index=False,mode='a',encoding='utf_8_sig',header=False)
    #all_data=pd.read_csv("C:\\Data\\thisweek_unclassified.csv")
    return data_frame
def none_classified_all_data():
    url_dict=call_url.call_url()
    load=call_all_company.weekly_all_unclassified_data(url_dict)
    return load

 

3. 감성분류를 통해 분류된 선택된 회사 데이터

def weekly_selected_classified_data(url,company):
    
    start_date,end_date=start_to_date(),end_to_date()
    maximum,minimum=score_input()
    
    driver = webdriver.Chrome('C:\\Users\\LeeKwanHyeong\\chromedriver_win32\\chromedriver.exe',chrome_options=options)
    driver.implicitly_wait(2)
    driver.get(url)
     # sort 선택창
    elem = driver.find_element_by_xpath("//span[@class='DPvwYc']")
    elem.click()
    time.sleep(3)
    pyautogui.press('up')
    time.sleep(0.7)
    pyautogui.press('up')
    time.sleep(0.7)
    pyautogui.press('enter')
    
        
    while(True):
        driver.execute_script("window.scrollTo([0],document.body.scrollHeight);")
        time.sleep(0.5)
        try:
            element=driver.find_element_by_xpath('//div[@class="U26fgb O0WRkf oG5Srb C0oVfc n9lfJ"]')
            if(element is not None):
                element.click()
                break
        except Exception:
            continue
    html=driver.page_source
    driver.quit()
    bsObj=BeautifulSoup(html,'lxml')
    div_reviews=bsObj.find_all("div",{"class":"d15Mdf bAhLNe"})
    
    
    #
    company_list,grade_list,date_list,content_list=[],[],[],[]
    
    for div in div_reviews:
        date_=div.find('span',{"class":"p2TkOb"}).get_text()
        t=re.findall(r"\d*\.\d+|\d+",date_)
        date='{0}-{1}-{2}'.format(t[0],t[1],t[2])
        year, month, day=int(t[0]), int(t[1]), int(t[2])
        dd=datetime(year,month,day)
        if((dd-start_date>=timedelta(days=0)) and (end_date-dd>=timedelta(days=0))):
                content=div.find('span',{'jsname':'bN97Pc'}).get_text()
                content=content.replace("전체 리뷰",'')
                content=re.sub('[^가-힣0-9a-zA-Z_!?@#%^&-=:;,\"\'<>\\s]','',content)
                content.encode('utf-8')
                grade=len(div.find_all('div',{'class':'vQHuPe bUWb7c'}))
                percentage,word=call_GRU_predict.GRU_predict(content)
                if(((percentage<maximum)and (percentage>minimum))) and (len(word)>6):
                    date_list.append(dd)
                    content_list.append(content)
                    grade_list.append(grade)
                    company_list.append(company)
                else:
                    continue
    grade_Series=pd.Series(grade_list)
    date_Series=pd.Series(date_list)
    content_Series=pd.Series(content_list)
    #
    company_Series=pd.Series(company_list)
    data_frame=pd.DataFrame()
    data_frame['company']=company_Series
    data_frame['date']=date_Series
    data_frame['grade']=grade_Series
    data_frame['content']=content_Series
    #
    good_data=data_frame[data_frame['grade']>2]
    bad_data=data_frame[data_frame['grade']<3]
    if not os.path.exists('C:\\Data\\thisweek_classified.csv'):
        data_frame.to_csv('C:\\Data\\thisweek_selected_classified.csv',index=False, mode='w',encoding='utf_8_sig')
    else:
        data_frame.to_csv('C:\\Data\\thisweek_selected_classified.csv',index=False,mode='a',encoding='utf_8_sig',header=False)
    
    writer=pd.ExcelWriter('C:/data/thisweek_selected_classified.xlsx')
    if not os.path.exists(writer):
        good_data.to_excel(writer,sheet_name='good',header=True)
        bad_data.to_excel(writer,sheet_name='bad',header=True)
    else:
        good_data.to_excel(writer,sheet_name='good',header=False)
        bad_data.to_excel(writer,sheet_name='bad',header=False)
    writer.save()
    return data_frame
    
    
def classified_selected_data():
    url,company=call_url.select_url()
    load=call_all_company.weekly_selected_classified_data(url,company)
    return load

 

 

4. 감성분류를 통해 분류되지 않은 선택된 회사 데이터

def weekly_selected_unclassified_data(url,company):
    start_date,end_date=start_to_date(),end_to_date()
    driver = webdriver.Chrome('C:\\Users\\LeeKwanHyeong\\chromedriver_win32\\chromedriver.exe',chrome_options=options)
    driver.implicitly_wait(2)
    driver.get(url)
     # sort 선택창
    elem = driver.find_element_by_xpath("//span[@class='DPvwYc']")
    elem.click()
    time.sleep(3)
    pyautogui.press('up')
    time.sleep(0.7)
    pyautogui.press('up')
    time.sleep(0.7)
    pyautogui.press('enter')
    
        
    while(True):
        driver.execute_script("window.scrollTo([0],document.body.scrollHeight);")
        time.sleep(0.5)
        try:
            element=driver.find_element_by_xpath('//div[@class="U26fgb O0WRkf oG5Srb C0oVfc n9lfJ"]')
            if(element is not None):
                element.click()
                break
        except Exception:
            continue
    html=driver.page_source
    driver.quit()
    bsObj=BeautifulSoup(html,'lxml')
    div_reviews=bsObj.find_all("div",{"class":"d15Mdf bAhLNe"})
    
    
    
    #
    company_list,grade_list,date_list,content_list=[],[],[],[]
    
    for div in div_reviews:
        date_=div.find('span',{"class":"p2TkOb"}).get_text()
        t=re.findall(r"\d*\.\d+|\d+",date_)
        date='{0}-{1}-{2}'.format(t[0],t[1],t[2])
        year, month, day=int(t[0]), int(t[1]), int(t[2])
        dd=datetime(year,month,day)
        if((dd-start_date>=timedelta(days=0)) and (end_date-dd>=timedelta(days=0))):
                content=div.find('span',{'jsname':'bN97Pc'}).get_text()
                content=content.replace("전체 리뷰",'')
                content=re.sub('[^가-힣0-9a-zA-Z_!?@#%^&-=:;,\"\'<>\\s]','',content)
                content.encode('utf-8')
                grade=len(div.find_all('div',{'class':'vQHuPe bUWb7c'}))
                percentage,word=call_GRU_predict.GRU_predict(content)
               
                date_list.append(dd)
                content_list.append(content)
                grade_list.append(grade)
                company_list.append(company)
    grade_Series=pd.Series(grade_list)
    date_Series=pd.Series(date_list)
    content_Series=pd.Series(content_list)
    #
    company_Series=pd.Series(company_list)
    data_frame=pd.DataFrame()
    data_frame['company']=company_Series
    data_frame['date']=date_Series
    data_frame['grade']=grade_Series
    data_frame['content']=content_Series
    #
    good_data=data_frame[data_frame['grade']>2]
    bad_data=data_frame[data_frame['grade']>3]
    
    writer=pd.ExcelWriter("C:/data/thisweek_selected_unclassified.xlsx")
    if not os.path.exists(writer):
        good_data.to_excel(writer,sheet_name='good',header=True)
        bad_data.to_excel(writer,sheet_name='bad',header=True)
    else:
        good_data.to_excel(writer,sheet_name='good',header=False)
        bad_data.to_excel(writer,sheet_name='bad',header=False)
    writer.save()
    if not os.path.exists('C:\\Data\\thisweek_selected_unclassified.csv'):
        data_frame.to_csv('C:\\Data\\thisweek_selected_unclassified.csv',index=False, mode='w',encoding='utf_8_sig')
    else:
        data_frame.to_csv('C:\\Data\\thisweek_selected_unclassified.csv',index=False,mode='a',encoding='utf_8_sig',header=False)
    
    
    return data_frame
    
    
    
    
def none_classified_selected_data():
    url,company=call_url.select_url()
    load=call_all_campany.weekly_selected_classified_data(url,company)
    return load

 

 

 

사실 준비하는데 가장 많이 공을 들인 부분이다. 훈련된 단어들을 통해 어떻게 관련지어 워드클라우드로 표현할까이다.

본인이 행한 기법은 only 긍정 단어, only 부정 단어, 긍정&부정 단어 들로 구분시켜 전체 단어의 갯수, 각 부분별 전체 빈도수, 각 단어의 빈도수를 구하여 조합을 시킨 스코어값을 대입했다.

그렇게 되면 새로운 데이터와 비교하였을때 훈련시킨 데이터의 가중치를 통해 중요도를 구분할 수 있기 때문이다. 위 계산과정은 생략하겠다. 

 

def call_dict():
    read_ne_df=pd.read_pickle("C:\\data\\real_ne.pkl")
    read_po_df=pd.read_pickle("C:\\data\\real_po.pkl")
    read_with_df=pd.read_pickle("C:\\data\\real_with.pkl")
    po_word=list(np.array(read_po_df['word']))
    po_weight=list(np.array(read_po_df['weight']))
    ne_word=list(np.array(read_ne_df['word']))
    ne_weight=list(np.array(read_ne_df['weight']))
    with_word=list(np.array(read_with_df['word']))
    with_weight=list(np.array(read_with_df['weight']))
    all_word=po_word+ne_word+with_word
    all_weight=po_weight+ne_weight+with_weight
    all_list=[all_word,all_weight]
    all_dic=dict(zip(*all_list))
    return all_dic

#들어오는 리뷰들을 지정된 점수로 분류하여 라인별로 묶기.
#들어오는 리뷰들을 지정된 점수로 분류하여 라인별로 묶기.
def scoring(sentence):
    score,word,weight=[],[],[]
    a,b=[],[]
    dictionary=call_dict()
    summation=0
    stopwords =['도', '는', '다', '의', '가', '이', '은',  '한', '에', '하', '고', '을', '를', '인', '듯', '과', '와', '네', '들', '듯', '지', '임', '게','요','거','로','으로',
            '것','수','할','하는','제','에서','그','데','번','해도','죠','된','건','바','구','세','최신','.']
    word_tokens=okt.morphs(sentence)
    word_tokens=[x for x in word_tokens if x not in stopwords]
    
    for x in word_tokens:
        if dictionary.get(x):
            
            
            if(len(x)==1):
                continue
            elif(len(x)>1):
                word.append(x)
                score.append(dictionary[x])
        else:
            continue
    for sc in score:
        summation+=sc
    for sc in score:
        weight.append(sc/summation)
    all_list=[word,weight]
    dict_n=dict(zip(*all_list))
    s_dic=sorted(dict_n.items(),key=lambda x:x[1],reverse=True)
    best_dic=s_dic[:10]
    temp_word,temp_weight=[],[]
    for split in range(len(best_dic)):
        list_=list(best_dic[split])
        temp_word.append(list_[0])
        temp_weight.append(list_[1])
    final_list=[temp_word,temp_weight]
    final=dict(zip(*final_list))
        
    return final
## scoring function을 이용해 csv로 받아온 리뷰를 정렬 
##location 매개변수는 메인함수에서 돌릴때 찾아서 변경
def merge_all(data):
    #데이터프레임으로 된것 저장
    merged={}
    temp_list=[]
    for line in data['content']:
        x=scoring(line)
        temp_list.append(x)
    for mer in range(len(temp_list)):
        merged={**temp_list[mer],**merged}
    return merged

def wordcloud(df):
    tokens=merge_all(df)
    wordcloud=WordCloud(font_path='C:/Windows/Fonts/malgun.ttf',background_color='white',colormap='Accent_r',
                       width=1500,height=1000).generate_from_frequencies(tokens)
    plt.imshow(wordcloud)
    plt.axis('off')

    plt.show()

 

이메일 기능도 요구하셨으니 이메일 기능도 만들어보자

 

#sendmail.py
import os
import smtplib
 
from email import encoders
from email.utils import formataddr
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

 
class undeadkwandoll(object):
    ENCODING = 'UTF-8'
 
    host = None
    port = None
    from_addr = None
    to_addrs = None
    message = None
    
    debug = False
    _isLogin = False
    _existAttachments = False
 
    def __init__(self, address:tuple, from_addr:str, to_addrs:list, debug=False, **kwargs):
        self.host, self.port = address
        self.from_addr = from_addr
        self.to_addrs = to_addrs
        
        # 인증 계정
        if kwargs.get("username") is not None:
            self.setAuth(
                kwargs.get("username")
                , kwargs.get("password")
            )
        
        # 디버그 모드
        self.debug = debug is not False
    
        # 첨부파일로 전송 불가능한 확장자
        self.blocked_extensions = (
            ".ade", ".adp", ".apk", ".appx", ".appxbundle", ".bat"
            , ".cab", ".chm", ".cmd", ".com", ".cpl", ".dll", ".dmg"
            , ".exe", ".hta", ".ins", ".isp", ".iso", ".jar", ".js"
            , ".jse", ".lib", ".lnk", ".mde", ".msc", ".msi", ".msix"
            , ".msixbundle", ".msp", ".mst", ".nsh", ".pif", ".ps1"
            , ".scr", ".sct", ".shb", ".sys", ".vb", ".vbe", ".vbs"
            , ".vxd", ".wsc", ".wsf", ".wsh"
        )
 
    def setAuth(self, username, password):
        self._isLogin = True
        self.username = username
        self.password = password
 
    def setMail(self, subject, body, body_type='plain', attachments=None):
        '''
        Content-Type:
            - multipart/alternative: 평문 텍스트와 HTML과 같이 다른 포맷을 함께 보낸 메시지 
            - multipart/mixed: 첨부파일이 포함된 메시지
 
        REF:
            - https://ko.wikipedia.org/wiki/MIME#Content-Type
        '''
        # 첨부파일 여부
        self._existAttachments = attachments is not None
        self.content_sub_type = "mixed" if self._existAttachments else "alternative"
 
        # 메일 콘텐츠 설정
        self.message = MIMEMultipart( self.content_sub_type )
 
        # 받는 사람 주소 설정. [( 이름 <이메일> )]
        self.FROM_ADDR_FMT = formataddr( self.from_addr )
        self.TO_ADDRS_FMT = ",".join([ formataddr( addr ) for addr in self.to_addrs ])
        
        # 메일 헤더 설정
        self.message.set_charset( self.ENCODING )
        self.message['From'] = self.FROM_ADDR_FMT
        self.message['To'] = self.TO_ADDRS_FMT
        self.message['Subject'] = subject
        
        # 메일 콘텐츠 - 내용
        self.message.attach(MIMEText( body, body_type, self.ENCODING ))
 
        # 메일 콘텐츠 - 첨부파일
        if self._existAttachments:
            self._attach_files( attachments )
 
        return self
    
    def _attach_files(self, attachments):
        '''
        Content-disposition:
            - 파일명 지정
        MIME type:
            - application/octect-stream: Binary Data
        REF:
            - https://www.freeformatter.com/mime-types-list.html
        '''
 
        for attachment in attachments:
            attach_binary = MIMEBase("application", "octect-stream")
            try:
                binary = open(attachment, "rb").read() # read file to bytes
 
                attach_binary.set_payload( binary )
                encoders.encode_base64( attach_binary ) # Content-Transfer-Encoding: base64
                
                filename = os.path.basename( attachment )
                attach_binary.add_header("Content-Disposition", 'attachment', filename=(self.ENCODING, '', filename))
                
                self.message.attach( attach_binary )
            except Exception as e:
                print( e )
 
    def send(self):
        session = None
        try:
            # 메일 세션 생성
            session = smtplib.SMTP( self.host, self.port )
            session.set_debuglevel( self.debug )
            session.ehlo()
 
            # SMTP 서버 계정 인증
            if self._isLogin:
                session.starttls()
                session.login(self.username, self.password)
            
            # 메일 발송
            session.sendmail(self.FROM_ADDR_FMT, self.TO_ADDRS_FMT, self.message.as_string())
            print( 'Successfully sent the mail!!!' )
            
        except Exception as e:
            print( e )
        finally:
            if session is not None:
                session.quit()
 
 
 

 

위 클래스는 오픈소스를 활용하였다.

 

각 부분별로 함수를 만들어보자.

def sending_classified_all_data():
    # SMTP 서버 정보
    smtp_address = ( 'smtp.gmail.com', 587 )        # SMTP 서버 호스트, 포트


    # SMTP 계정 정보
    username = "#";                 # Gmail 계정
    password = "#";                   # Gmail 앱 비밀번호
    from_addr = ('#', '#')
    
    "이름을 적으세요"
    to_addrs=[(input("이름을 적으세요"),input("보낼 이메일 주소를 적으세요: "))]
    
    subject="분류된 리뷰 이메일입니다."
    body='''  '''
    attachments = [os.path.join('C:\data','thisweek_classified.xlsx')]
    
    mail=undeadkwandoll(smtp_address,from_addr,to_addrs,debug=False)
    
    mail.setAuth(username,password)
    mail.setMail(subject,body,'html',attachments=attachments)
    mail.send()

#으로 처리한 부분은 이미 적혀있는 것이다. 

def sending_unclassified_all_data():
    # SMTP 서버 정보
    smtp_address = ( 'smtp.gmail.com', 587 )        # SMTP 서버 호스트, 포트


    # SMTP 계정 정보
    username = "#";                 # Gmail 계정
    password = "#";                   # Gmail 앱 비밀번호
    from_addr = ('Google', '#')
    
    "이름을 적으세요"
    to_addrs=[(input("이름을 적으세요"),input("보낼 이메일 주소를 적으세요: "))]
    
    subject="분류된 리뷰 이메일입니다."
    body='''  '''
    attachments = [os.path.join('C:\data','thisweek_unclassified.xlsx')]
    
    mail=undeadkwandoll(smtp_address,from_addr,to_addrs,debug=False)
    
    mail.setAuth(username,password)
    mail.setMail(subject,body,'html',attachments=attachments)
    mail.send()
    
def sending_classified_selected_data():
    # SMTP 서버 정보
    smtp_address = ( 'smtp.gmail.com', 587 )        # SMTP 서버 호스트, 포트


    # SMTP 계정 정보
    username = "#";                 # Gmail 계정
    password = "#";                   # Gmail 앱 비밀번호
    from_addr = ('Google', '#')
    
    "이름을 적으세요"
    to_addrs=[(input("이름을 적으세요"),input("보낼 이메일 주소를 적으세요: "))]
    
    subject="분류된 리뷰 이메일입니다."
    body='''  '''
    attachments = [os.path.join('C:\data','thisweek_selected_classified.xlsx')]
    
    mail=undeadkwandoll(smtp_address,from_addr,to_addrs,debug=False)
    
    mail.setAuth(username,password)
    mail.setMail(subject,body,'html',attachments=attachments)
    mail.send()
    
def sending_unclassified_all_data():
    # SMTP 서버 정보
    smtp_address = ( 'smtp.gmail.com', 587 )        # SMTP 서버 호스트, 포트


    # SMTP 계정 정보
    username = "#";                 # Gmail 계정
    password = "#";                   # Gmail 앱 비밀번호
    from_addr = ('Google', '#')
    
    "이름을 적으세요"
    to_addrs=[(input("이름을 적으세요"),input("보낼 이메일 주소를 적으세요: "))]
    
    subject="분류된 리뷰 이메일입니다."
    body='''  '''
    attachments = [os.path.join('C:\data','thisweek_unclassified.xlsx')]
    
    mail=undeadkwandoll(smtp_address,from_addr,to_addrs,debug=False)
    
    mail.setAuth(username,password)
    mail.setMail(subject,body,'html',attachments=attachments)
    mail.send()
    
def sending_unclassified_selected_data():
    # SMTP 서버 정보
    smtp_address = ( 'smtp.gmail.com', 587 )        # SMTP 서버 호스트, 포트


    # SMTP 계정 정보
    username = "#";                 # Gmail 계정
    password = "#";                   # Gmail 앱 비밀번호
    from_addr = ('Google', '#')
    
    "이름을 적으세요"
    to_addrs=[(input("이름을 적으세요"),input("보낼 이메일 주소를 적으세요: "))]
    
    subject="분류된 리뷰 이메일입니다."
    body='''  '''
    attachments = [os.path.join('C:\data','thisweek_selected_unclassified.xlsx')]
    
    mail=undeadkwandoll(smtp_address,from_addr,to_addrs,debug=False)
    
    mail.setAuth(username,password)
    mail.setMail(subject,body,'html',attachments=attachments)
    mail.send()

 

이메일에 대한 함수도 정리가 끝났다. 마지막 사전준비인 그래프 처리도 시행하자.

이 메인 엔진을 갖고 어떤걸 만들어 볼까 고민을 많이했다. 그래서 사수분께 여쭤봤고 기능들을 받았다.

1. 분류 및 미분류, 회사 전체 및 선택 회사 리뷰 기능

2. 이메일 전송기능

3. 자동 저장기능

4. 퀄리티value의 최대 최소 지정

 

대략적인 작업 흐름도를 그려보았다.

 

 

우선 날짜 입력 함수를 만들어보자.

<날짜 입력 함수>

def start_to_date():
    start_date_list=[]
    st=input("시작 년도를 적으시오: ")
    start_date_list.append(st)
    st=input("시작 월을 적으시오: ")
    start_date_list.append(st)
    st=input("시작 일을 적으시오: ")
    start_date_list.append(st)
    s_year,s_month,s_day=int(start_date_list[0]), int(start_date_list[1]), int(start_date_list[2])
    start_date=datetime(s_year,s_month,s_day)
    return start_date

def end_to_date():
    
    end_date_list=[]
    et=input("종료 년도를 적으시오: ")
    end_date_list.append(et)
    et=input("종료 월을 적으시오: ")
    end_date_list.append(et)
    et=input("종료 일을 적으시오: ")
    end_date_list.append(et)
    e_year,e_month,e_day=int(end_date_list[0]), int(end_date_list[1]), int(end_date_list[2])    
    end_date=datetime(e_year,e_month,e_day)
    
    return end_date

이 함수를 통해 우리가 반환 받는 값은 start_date(시작일) end_date(종료일) 이다. 이 사이의 리뷰들을 가져오기 위한 함수이다.

 

 

def call_url():
    
    url_dict={'CameraFi':'https://play.google.com/store/apps/details?id=com.vaultmicro.camerafi.live&hl=ko&gl=US&showAllReviews=true',
              'V_app':'https://play.google.com/store/apps/details?id=com.naver.vapp&hl=ko&gl=US&showAllReviews=true',
              'Mobizen':'https://play.google.com/store/apps/details?id=com.rsupport.mobizen.live&hl=ko&gl=US&showAllReviews=true',
              'Bigolive':'https://play.google.com/store/apps/details?id=sg.bigo.live&hl=ko&gl=US&showAllReviews=true',
              'Omlet':'https://play.google.com/store/apps/details?id=mobisocial.arcade&hl=ko&gl=US&showAllReviews=true',
              'Sgether':'https://play.google.com/store/apps/details?id=com.sgrsoft.streetgamer&hl=ko&gl=US&showAllReviews=true'}
    return url_dict    

call_url함수는 전체 회사 데이터를 가져오기 위한 사전 url 세팅 함수이다.

혹여, 나중에 더 많은 url이 필요하다면 txt에 저장해 한줄씩 읽어오는 방법도 물론 있다.

 

def select_url():
    url_list=['https://play.google.com/store/apps/details?id=com.vaultmicro.camerafi.live&hl=ko&gl=US&showAllReviews=true',
              'https://play.google.com/store/apps/details?id=com.naver.vapp&hl=ko&gl=US&showAllReviews=true',
              'https://play.google.com/store/apps/details?id=com.rsupport.mobizen.live&hl=ko&gl=US&showAllReviews=true',
              'https://play.google.com/store/apps/details?id=sg.bigo.live&hl=ko&gl=US&showAllReviews=true',
              'https://play.google.com/store/apps/details?id=mobisocial.arcade&hl=ko&gl=US&showAllReviews=true',
              'https://play.google.com/store/apps/details?id=com.sgrsoft.streetgamer&hl=ko&gl=US&showAllReviews=true']
    
    check_num=int(input("원하는 회사를 고르시오: \n"+"[0]:CameraFi\n"+"[1]:V_App\n"+"[2]:BigoLive\n"+"[3]:Mobizen\n"+"[4]:Omlet\n"+"[5]:Sgether\n"))
    
    name=check_num
    if(name==0):
        CameraFi_live=url_list[0]
        checking_url=[CameraFi_live,'CameraFi']
        checked=checking_url[0],checking_url[1]
            
    elif(name==1):
        V_App=url_list[1]
        checking_url=[V_App,'V_App']
        checked=checking_url[0],checking_url[1]
            
    elif(name==2):
        Mobizen=url_list[2]
        checking_url=[Mobizen,'Mobizen']
        checked=checking_url[0],checking_url[1]
            
    elif(name==3):
        Bigolive=url_list[3]
        checking_url=[Bigolive,'Bigolive']
        checked=checking_url[0],checking_url[1]
            
    elif(name==4):
        Omlet=url_list[4]
        checking_url=[Omlet,'Omlet']
        checked=checking_url[0],checking_url[1]
    elif(name==5):
        Sgether=url_list[5]
        checking_url=[Sgether,'Sgether']
        checked=checking_url[0],checking_url[1]
    return checked

select_url함수는 선택적 회사 데이터를 가져오기 위한 사전 url 세팅 함수이다. 

def GRU_predict(new_sentence):
    max_len=90
    new_sentence=okt.morphs(new_sentence)
    new_words=[word for word in new_sentence if not word in stopwords]
    encoded=tokenizer.texts_to_sequences([new_sentence])
    pad_new=pad_sequences(encoded,maxlen=max_len)
    score=float(GRU_model.predict(pad_new))
    percentage=score*100
    return percentage,new_words

앞서 만들어본 GRU모델은 이미 h5 파일로 저장되어있다. 그러므로 이걸 불러서 사용하기만 하면 된다.

 

 

이메일 전송기능도 준비하자.

작성하기에 앞서 준비한 데이터가 어떤 것이며, 어떤 방식으로 활용했는지 작성하겠다.

자료 데이터: 웹크롤링을 활용한 플레이스토어 리뷰

*카메라파이라이브, 비고라이브,프리즘라이브,오믈렛,하쿠나,V앱,유튜브,아프리카,트위치,페이스북,인스타그램,판도라

 

*언어는 어느 분야에서 어떤 방식으로 사용하느냐의 따라 다양한 의미로 전달된다. 이에 카메파라이 라이브와 가장 유사성 높은 어플 회사를 선택했고, 본인이 인턴하는 카메라파이라이브에서 연동시키는 어플회사 및 경쟁회사 리뷰이다.

 

*크롤링에 대한 매크로 및 처리는 따로 링크를 걸어두겠다.

 

시작하기에 앞서 사용해야 할 라이브러리를 작성한다.

 

from bs4 import BeautifulSoup
from selenium import webdriver
from pykospacing import spacing
import time
import csv
import os
import pandas as pd
from pandas import DataFrame
from pandas import Series
import re
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from konlpy.tag import Okt
from collections import Counter
import nltk
import matplotlib.pyplot as plt
import urllib.request
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from sklearn.model_selection import train_test_split
from tensorflow import keras
import pickle

우선 크롤링한 데이터를 불러와 어떤 형태로 되어있는지 확인해보자.

data_filepath=('C:\\data\\total_data.csv')
data=pd.read_csv(data_filepath,encoding='utf-8')
data=data.dropna(how='any')
data['label']=np.select([data.평점>3],[1],default=0)
data[:10]

*데이터는 구분해 놓았고, 감성분석을 시행하기 위해 어떤 방식으로 긍정과 부정의 기준을 나눌까요? 이는 평점을 기준으로 나눕니다. 가장 보편적인 기준은 5점만점에서 3점을 기준으로 나눈다.

*취향마다 다르겠지만 좀 더 strict한 기준으로 리뷰를 판단하고 싶다면 3점까지는 label 0 4점부터는 lable 1

 

len(data)
64746

 

전체 데이터는 64746개의 데이터가 있다.

 

훈련데이터와 학습데이터를 3:1로 구분한다.

train_data,test_data=train_test_split(data,test_size=.25,random_state=42)

이제 테스트 데이터에 대하여 전처리를 시작해보자.

#Test용 리뷰 처리

test_number_list=[]
for num in range(len(test_data)):
    test_number_list.append(num)
    
test_data['index']=test_number_list
test_data=test_data.set_index(['index'])

test_data['내용'].nunique(),
test_data['평점'].nunique()
test_data['label'].nunique()

test_data.drop_duplicates(subset=['내용'],inplace=True)


#Test용 리뷰 문장 전처리

test_data['내용']=test_data['내용'].str.replace('[^ㄱ-ㅎㅏ-ㅣ|가-힣]','')
test_data['내용']=test_data['내용'].str.replace(' ','')
test_data['내용']=test_data['내용'].apply(spacing)

print("총 샘플의 개수: ",len(test_data))
총 샘플의 개수:  15330

 

 

 

훈련데이터도 같은 방식으로 진행하겠다.

#Train용 리뷰 처리

train_number_list=[]
for num in range(len(train_data)):
    train_number_list.append(num)
    

train_data['index']=train_number_list
train_data=train_data.set_index(['index'])

train_data['내용'].nunique(),
train_data['평점'].nunique(),
train_data['label'].nunique()

train_data.drop_duplicates(subset=['내용'],inplace=True)


#Train용 리뷰 전처리
train_data['내용']=train_data['내용'].str.replace('[^ㄱ-ㅎㅏ-ㅣ|가-힣]','')
train_data['내용']=train_data['내용'].str.replace(' ','')
train_data['내용']=train_data['내용'].apply(spacing)

print("총 샘플의 개수: ",len(train_data))
총 샘플의 개수:  45774

*spacing 전희원님이 개발한 PyKoSpacing은 한국어 띄어쓰기 패키지로 띄어쓰기가 되어있지 않은 문장을 띄어쓰기를 한 문장으로 변환해주는 패키지이다. PyKoSpacing은 대용량 코퍼스를 학습하여 만들어진 띄어쓰기 딥 러닝 모델로 준수한 성능을 가지고 있다.

 

*from pykospacing import spacing 

 

pip install git+https://github.com/haven-jeon/PyKoSpacing.git

여기서 잠깐, 한 가지 집고 넘어가자면 본인 또한 그렇고 파이썬을 접한지 얼마 되지 않았을때 대부분 주피터노트북 저장만 해놓고 꺼버렸다. 하지만 파이썬은 휘발성 메모리? 이기 때문에 작업한 변수와 그의 맞는 데이터들은 전부 사라지게 된다. 데이터가 5만개가 아닌 10만개 100만개 된다면 처음부터 다 돌리기에는 상당한 시간이 걸릴겁니다. 이렇기에 데이터를 저장하는 습관을 들이셔야 합니다. 저는 데이터프레임을 가장 편하게 저장할 수 있는 pickle 라이브러리를 사용한다.

 

#전처리 내용 저장
test_data.to_pickle("C:\\data\\Test_data.pkl")
train_data.to_pickle("C:\\data\\Train_data.pkl")
# 다시 읽어오기
test_data=pd.read_pickle("C:\\data\\Test_data.pkl")
train_data=pd.read_pickle("C:\\data\\Train_data.pkl")

이 코드만 컴파일 시켜도, 원하는 장소에 저장이 되어있다.

 

다시 본론으로 넘어가서, 전처리를 하였으니 각 리뷰 데이터 즉 문장들을 단어(토큰)화 시켜서 불용어 처리까지 해보자.

#Train, Test, 토큰화 
okt=Okt()
test_data=pd.read_pickle("C:\\data\\Test_data.pkl")
train_data=pd.read_pickle("C:\\data\\Train_data.pkl")
stopwords = ['도', '는', '다', '의', '가', '이', '은', '한', '에', '하', '고', '을', '를', '인', '듯', '과', '와', '네', '들', '듯', '지', '임', '게','요','거','로','으로',
            '것','수','할','하는','제','에서','그','데','번','해도','죠','된','건','바','구','세']
test_data['tokenized']=test_data['내용'].apply(okt.morphs)
test_data['tokenized']=test_data['tokenized'].apply(lambda x: [item for item in x if item not in stopwords])
train_data['tokenized']=train_data['내용'].apply(okt.morphs)
train_data['tokenized']=train_data['tokenized'].apply(lambda x: [item for item in x if item not in stopwords])

*여기서 한가지 팁을 알려드리자면, 텍스트 마이닝에서 중요한 한 가지는 나만의 사전을 만드는 것이다. stopwords라는 변수안에 불용어처리 목록을 저장하였지만, 실제 더 하려면 직접 데이터를 읽어보고 빈도수를 분석하여 불용어처리를 하는 것이다.

 

훈련 데이터를 통해 긍정과 부정을 나누고, 빈도수를 분석해 보자.

negative_words=np.hstack(train_data[train_data.label==0]['tokenized'].values)
positive_words=np.hstack(train_data[train_data.label==1]['tokenized'].values)

negative_word_count=Counter(negative_words)
positive_word_count=Counter(positive_words)


print(negative_word_count.most_common(20))
print(positive_word_count.most_common(20))

[('안', 17094), ('왜', 6339), ('좀', 5388), ('업데이트', 4569), ('하고', 4483), ('만', 4470), ('영상', 4389), ('다시', 4233), ('계속', 4173), ('광고', 4122), ('너무', 3947), ('앱', 3480), ('계정', 3264), ('오류', 3243), ('때', 3243), ('화면', 3204), ('못', 3002), ('진짜', 2880), ('잘', 2680), ('재생', 2484)]
[('안', 3120), ('너무', 2086), ('좋아요', 1928), ('잘', 1686), ('앱', 1685), ('하고', 1335), ('좀', 1323), ('방송', 1321), ('때', 1294), ('만', 1272), ('볼', 1162), ('유튜브', 1113), ('영상', 1079), ('좋은데', 1066), ('좋은', 1035), ('있어서', 913), ('정말', 897), ('사람', 866), ('사용', 864), ('기능', 830)]

각각의 단어 길이 분포를 확인해보자

 

fig,(ax1,ax2) = plt.subplots(1,2,figsize=(10,5))
text_len = train_data[train_data['label']==1]['tokenized'].map(lambda x: len(x))
ax1.hist(text_len, color='red')
ax1.set_title('Positive Reviews')
ax1.set_xlabel('length of samples')
ax1.set_ylabel('number of samples')
print('긍정 리뷰의 평균 길이 :', np.mean(text_len))

text_len = train_data[train_data['label']==0]['tokenized'].map(lambda x: len(x))
ax2.hist(text_len, color='blue')
ax2.set_title('Negative Reviews')
fig.suptitle('Words in texts')
ax2.set_xlabel('length of samples')
ax2.set_ylabel('number of samples')
print('부정 리뷰의 평균 길이 :', np.mean(text_len))
plt.show()

이를 통해 긍정리뷰보다는 부정리뷰가 좀 더 길게 작성되는 경향이 있다. 예상을 해본다면 불만이 있거나 요구사항이 있을 경우 말을 길게 하는 경향도 있기 때문이다.

 

 

기계는 글자를 인식하지 못한다. 기계에게 글자를 입력하기 위해서는 글자에 넘버링을 해야하는데 그 기법은 원핫인코딩, TFIDF, 분산 점수화, 임의 정수 인코딩이 있다. 

 

본인은 정수 인코딩 기법을 사용하겠다.

 

X_Train=train_data['tokenized'].values
Y_Train=train_data['label'].values
X_Test=test_data['tokenized'].values
Y_Test=test_data['label'].values

tokenizer=Tokenizer()
tokenizer.fit_on_texts(X_Train)

threshold=2
total_cnt=len(tokenizer.word_index)
rare_cnt=0
total_freq=0
rare_freq=0

for key,value in tokenizer.word_counts.items():
    total_freq=total_freq+value
    if(value<threshold):
        rare_cnt=rare_cnt+1
        rare_freq=rare_freq+value
        
print('단어 집합(vocabulary)의 크기: ',total_cnt)
print('등장 빈도가 %s번 이하인 회귀 단어의 수: %s'%(threshold-1,rare_cnt))
print('단어 집합에서 회귀 단어의 비율:',(rare_cnt/total_cnt)*100)
print('전체 등장 빈도에서 회귀 단어 등장 빈도 비율:',(rare_freq/total_freq)*100)

단어 집합(vocabulary)의 크기:  41748
등장 빈도가 1번 이하인 회귀 단어의 수: 22682
단어 집합에서 회귀 단어의 비율: 54.330746383060266
전체 등장 빈도에서 회귀 단어 등장 빈도 비율: 2.809497444053445

단어는 약 41000개가 존재한다. 등장 빈도가 1번 이하인 단어의 수는 전체에서 약 54퍼센트를 차지하고 있다. 실제 훈련데이터에서 등장 빈도로 차지하는 비율은 2.8퍼센트 밖에 안된다. 즉, 크게 중요한 의미를 지닌 단어가 될 수 없다고 예상할 수 있다.

 

그러므로 등장 빈도가 1인 단어들의 수를 제외한 나머지 단어의 개수를 최대 크기로 제한해보자.

 

vocab_size=total_cnt-rare_cnt+2
print('단어 집합의 크기:',vocab_size)

단어 집합의 크기: 19249

 

이제 단어 집합의 크기는 19000여개이다. 이를 토크나이저 인자로 넘기면, 텍스트 시퀀스를 숫자 시퀀스로 변환해준다. 이 과정에서 이보다 큰 숫자가 부여된 단어들은 OOV로 변환시켜주자.

 

tokenizer=Tokenizer(vocab_size,oov_token='OOV')
tokenizer.fit_on_texts(X_Train)
X_Train=tokenizer.texts_to_sequences(X_Train)
X_Test=tokenizer.texts_to_sequences(X_Test)

 

변환이 잘 되었는지 상위 2개만을 출력하여 확인해보자.

print(X_Test[:2])
print(X_Train[:2])

[[431, 1958], [12, 5, 655, 996, 213, 31, 166, 1187, 8566, 744, 24, 40, 86, 99, 3, 179, 1]]
[[5, 423, 1, 383, 107, 662, 306, 7, 5440, 4157, 1067, 257, 13813], [1166, 22, 47, 192, 11116, 330, 1, 3523, 114, 209]]

 

이후, 서로 다른 길이의 샘플들의 길이를 동일하게 맞춰주는 작업을 패딩이라 한다. 전체 데이터에서 가장 길이가 긴 리뷰와 길이 분포를 확인 해보자.

 

print('리뷰의 최대 길이: ',max(len(l) for l in X_Train))
print('리뷰의 평균 길이: ',sum(map(len,X_Train))/len(X_Train))
plt.hist([len(s) for s in X_Train], bins=50)
plt.xlabel('length of samples')
plt.ylabel('number of samples')
plt.show()

리뷰의 최대 길이:  249
리뷰의 평균 길이:  17.391602271271825

리뷰의 최대 길이는 249, 평균 길이는 17이다. 그래프를 확인해보면 대체적으로 50정도 되는 것으로 예상된다.

 

만약  100으로 패딩할 경우 샘플은 몇개가 온전할 지 한번 확인해 보자.

def below_threshold_len(max_len,nested_list):
    cnt=0
    for s in nested_list:
        if(len(s)<=max_len):
            cnt=cnt+1
    print("전체 샘플 중 길이가 %s 이하인 샘플의 비율: %s"%(max_len,(cnt/len(nested_list))*100))
    
max_len=100
below_threshold_len(max_len,X_Train)

전체 샘플 중 길이가 100 이하인 샘플의 비율: 99.97865346027409

훈련 리뷰의 99.97%가 100이하의 길이를 갖는다. 그러므로 훈련 리뷰의 길이 100으로 패딩하겠다.

 

X_Train=pad_sequences(X_Train,maxlen=max_len)
X_Test=pad_sequences(X_Test,maxlen=max_len)

준비는 끝났다. 

이제 GRU 기법을 활용해 리뷰 감성분석기를 만들어 보자.

 

#GRU 기법 활요한 머신러닝 
from tensorflow.keras.layers import Embedding, Dense, GRU
from tensorflow.keras.models import Sequential
from tensorflow.keras.models import load_model
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
model=Sequential()
model.add(Embedding(vocab_size,100))
model.add(GRU(128))
model.add(Dense(1,activation='sigmoid'))


es=EarlyStopping(monitor='val_loss',mode='min',verbose=1,patience=4)
mc=ModelCheckpoint('best_model_GRU.h5',monitor='val_acc',mode='max',verbose=1,save_best_only=True)


model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])
history=model.fit(X_Train,Y_Train, epochs=15, callbacks=[es,mc], batch_size=100,validation_split=0.2)

GRU_model=load_model('best_model_GRU.h5')
print("\n 테스트 정확도: %.4f"%(GRU_model.evaluate(X_Test,Y_Test)[1]))
Epoch 1/15
367/367 [==============================] - ETA: 0s - loss: 0.4100 - acc: 0.8239
Epoch 00001: val_acc improved from -inf to 0.83233, saving model to best_model_GRU.h5
367/367 [==============================] - 47s 128ms/step - loss: 0.4100 - acc: 0.8239 - val_loss: 0.3943 - val_acc: 0.8323
Epoch 2/15
367/367 [==============================] - ETA: 0s - loss: 0.3335 - acc: 0.8609
Epoch 00002: val_acc improved from 0.83233 to 0.84937, saving model to best_model_GRU.h5
367/367 [==============================] - 49s 132ms/step - loss: 0.3335 - acc: 0.8609 - val_loss: 0.3701 - val_acc: 0.8494
Epoch 3/15
367/367 [==============================] - ETA: 0s - loss: 0.3130 - acc: 0.8730
Epoch 00003: val_acc improved from 0.84937 to 0.85167, saving model to best_model_GRU.h5
367/367 [==============================] - 50s 136ms/step - loss: 0.3130 - acc: 0.8730 - val_loss: 0.3597 - val_acc: 0.8517
Epoch 4/15
367/367 [==============================] - ETA: 0s - loss: 0.2953 - acc: 0.8810
Epoch 00004: val_acc improved from 0.85167 to 0.85352, saving model to best_model_GRU.h5
367/367 [==============================] - 48s 132ms/step - loss: 0.2953 - acc: 0.8810 - val_loss: 0.3617 - val_acc: 0.8535
Epoch 5/15
367/367 [==============================] - ETA: 0s - loss: 0.2794 - acc: 0.8882
Epoch 00005: val_acc did not improve from 0.85352
367/367 [==============================] - 50s 136ms/step - loss: 0.2794 - acc: 0.8882 - val_loss: 0.3805 - val_acc: 0.8501
Epoch 6/15
367/367 [==============================] - ETA: 0s - loss: 0.2629 - acc: 0.8963
Epoch 00006: val_acc did not improve from 0.85352
367/367 [==============================] - 49s 135ms/step - loss: 0.2629 - acc: 0.8963 - val_loss: 0.3791 - val_acc: 0.8429
Epoch 7/15
367/367 [==============================] - ETA: 0s - loss: 0.2465 - acc: 0.9028
Epoch 00007: val_acc did not improve from 0.85352
367/367 [==============================] - 48s 131ms/step - loss: 0.2465 - acc: 0.9028 - val_loss: 0.3949 - val_acc: 0.8422
Epoch 00007: early stopping
480/480 [==============================] - 8s 16ms/step - loss: 0.3584 - acc: 0.8561

 테스트 정확도: 0.8561

예측을 해보면 몇퍼센트인지를 보여준다.

 

GRU_predict("좀 어이가없네요 이어플")
2.6986032724380493

GRU_predict("이따위로 만들어서 어디에 쓰게요")
8.203727006912231

 

실제 여기서 역수를 취하여 긍정과 부정으로 프린트할 수 있게 한다면 기능을 더 추가하기가 어렵다. 그러므로, 본인은 퍼센티지 출력에만 관심을 두었다.

 

또한, 이 공부를 할 수 있게 큰 도움을 준 사이트를 포스트하고 마무리 짓겠다. 

wikidocs.net/book/2155

+ Recent posts