ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 🤗Roberta를 이용한 리뷰 감정분석
    딥러닝 2023. 9. 7. 11:29

    <<본 포스팅은 Sentiment Analysis Python 🤗 [Youtube Tutorial] | Kaggle을 참조하였음>>

     

     

    원본 노트북에서는 Amazon Fine Food Reviews | Kaggle를 이용하여 분석했지만 나는 Wine Reviews | Kaggle를 이용했다. (데이터만 바꾸었고 분석 방식에는 차이 없다.)

     

     

     

     

    데이터, 라이브러리 불러오기

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    import seaborn as sns
    import nltk
    
    plt.style.use('ggplot')

     

    df = pd.read_csv('/kaggle/input/wine-reviews/winemag-data-130k-v2.csv')
    print(df.shape)
    df = df.head(500)
    print(df.shape)

     

    df.head()를 찍어 dataframe의 형태를 확인한다. 감정분석에 사용할 것은 'description' 열이다.

     

     

     

    Quick EDA

    ax = df['points'].value_counts().sort_index() \
        .plot(kind='bar',
              title='Count of Reviews by points',
              figsize=(10, 5))
    ax.set_xlabel('Review Points')
    plt.show()

    상위 500개 데이터에 대해서는 대체로 높은 점수를 주었다.

     

     

     

    Basic NLTK

    'NLTK'란? NLTK(Natural Language Toolkit)는 NLP를 위한 파이썬 라이브러리로 텍스트 마이닝, 정보 검색, 기계 학습, 문서 분류, 감정 분석, 형태소 분석, 구문 분석 등의 NLP 작업을 수행하기 위해 사용된다. (챗 지피티 ㄳ)

    여기서는 NLTK tokenizer, pos tag, chunk를 사용한다.

     

    example = df['description'][50]
    print(example)

     

    tokens = nltk.word_tokenize(example)
    tokens[:10]

    word tokenizer를 이용하여 위에서 본 리뷰를 word 단위로 쪼갠 결과이다.

     

     

     

     

     

     

     

    tagged = nltk.pos_tag(tokens)
    tagged[:10]

    pos_tag를 처음봐서 서치를 좀 해봤다. pos는 Part of speech로 해당 단어가 어느 품사에 속하는지 판별하고, 이를 tagging한다. 단어가 명사, 동사, 형용사 등 어떤 품사인지를 나타내는 것이다.

     

     

     

     

     

     

    entities = nltk.chunk.ne_chunk(tagged)
    entities.pprint()

    ne_chunk는 NER(Named Entity Recognition)을 수행한다. NER이란 텍스트에서 Named 개체를 찾아낸다. 해당 결과에서는 Nero를 GPE(Geo-Political Entity, 지리적 위치)로 인식했고, Syrah를 사람 이름으로 인식했다.

    입력된 텍스트에서 NER을 올바르게 수행하면 텍스트의 문맥과 의도를 파악하는 데 큰 도움을 줄 수 있다.

     

     

     

     

     

    1. VADER Sentiment Scoring

    먼저 NLTK에서 제공하는 SentimentIntensityAnalyzer를 사용하여 neg/neu/pos를 분류해본다.

     

     

    from nltk.sentiment import SentimentIntensityAnalyzer
    from tqdm.notebook import tqdm
    
    sia = SentimentIntensityAnalyzer()
    sia.polarity_scores(example)

    위 예시 리뷰는 중립일 확률이 0.912라고 판단했다. 여기서 compound는 positive, negative, neutral 세 점수를 전부 합한 후 [-1, 1] 범위의 값을 갖도록 normalization 한 것이라고 한다. 따라서 compound가 +1에 가까우면 positive, -1에 가까우면 negative에 치중을 두어 판단한 것이다.

     

     

    res = {}
    for i, row in tqdm(df.iterrows(), total=len(df)):
        text = row['description']
        myid = row['Unnamed: 0']
        res[myid] = sia.polarity_scores(text)
        
        
    vaders = pd.DataFrame(res).T
    vaders = vaders.reset_index().rename(columns={'index': 'Unnamed: 0'})
    vaders = vaders.merge(df)
    
    vaders.head()

    전체 (500개) 리뷰에 대하여 감정분석을 시행하고 그 결과를 원본 Dataframe의 왼쪽에 이어붙였다.

     

     

     

    ax = sns.barplot(data=vaders, x='points', y='compound')
    ax.set_title('Compund Score by Wine Review')
    plt.show()

    Compound 점수가 +1에 가까울수록 긍정 평가이고, -1에 가까울수록 부정 평가라는 것을 알았다. 그렇다면 리뷰의 points는 compound와 선형관계여야 하는데, 위 그래프의 결과 어느정도 선형 관계임을 알 수 있다.

     

     

    fig, axs = plt.subplots(1, 3, figsize=(12, 3))
    sns.barplot(data=vaders, x='points', y='pos', ax=axs[0])
    sns.barplot(data=vaders, x='points', y='neu', ax=axs[1])
    sns.barplot(data=vaders, x='points', y='neg', ax=axs[2])
    axs[0].set_title('Positive')
    axs[1].set_title('Neutral')
    axs[2].set_title('Negative')
    plt.tight_layout()
    plt.show()

    리뷰어가 남긴 points 값이 클수록 positive값이 크고, points 값이 작을수록 negative 값이 크다.

     

     

     

     

    2. 🤗Roberta Pretrained Model

    이번에는🤗transformer로 사용할 수 있는 Roberta 모델을 이용해본다.

     

    from transformers import AutoTokenizer
    from transformers import AutoModelForSequenceClassification
    from scipy.special import softmax
    MODEL = f"cardiffnlp/twitter-roberta-base-sentiment"
    tokenizer = AutoTokenizer.from_pretrained(MODEL)
    model = AutoModelForSequenceClassification.from_pretrained(MODEL)

     

     

    VADER와 Roberta의 결과를 비교해본다.

    # VADER
    print(example)
    sia.polarity_scores(example)

    # Roberta
    encoded_text = tokenizer(example, return_tensors='pt')
    output = model(**encoded_text)
    scores = output[0][0].detach().numpy()
    scores = softmax(scores)
    scores_dict = {
        'roberta_neg' : scores[0],
        'roberta_neu' : scores[1],
        'roberta_pos' : scores[2]
    }
    print(scores_dict)

    둘이 제법 비슷한 결과를 나타냈다.

     

     

    def polarity_scores_roberta(example):
        encoded_text = tokenizer(example, return_tensors='pt')
        output = model(**encoded_text)
        scores = output[0][0].detach().numpy()
        scores = softmax(scores)
        scores_dict = {
            'roberta_neg' : scores[0],
            'roberta_neu' : scores[1],
            'roberta_pos' : scores[2]
        }
        return scores_dict
        
        
        
    res = {}
    for i, row in tqdm(df.iterrows(), total=len(df)):
        try:
            text = row['description']
            myid = row['Unnamed: 0']
            vader_result = sia.polarity_scores(text)
            vader_result_rename = {}
            for key, value in vader_result.items():
                vader_result_rename[f"vader_{key}"] = value
            roberta_result = polarity_scores_roberta(text)
            both = {**vader_result_rename, **roberta_result}
            res[myid] = both
        except RuntimeError:
            print(f'Broke for id {myid}')
            
            
    results_df = pd.DataFrame(res).T
    results_df = results_df.reset_index().rename(columns={'index': 'Unnamed: 0'})
    results_df = results_df.merge(df, how='left')

    Roberta의 감성분석 결과를 VADER 결과를 붙인 Dataframe에 붙여준다.

     

     

     

    sns.pairplot(data=results_df,
                 vars=['vader_neg', 'vader_neu', 'vader_pos',
                      'roberta_neg', 'roberta_neu', 'roberta_pos'],
                hue='points',
                palette='tab10')
    plt.show()

    pairplot을 나타낸 결과이다. 맨 아랫 줄 세 번째 plot을 보면 roberta에서 높은 확률로 pos라고 예측했음에도 불구하고 vader에서는 pos 확률이 굉장히 낮게 측정될 수 있다는 것을 알 수 있다. 당연히 모델마다 상이한 결과를 산출하므로, 주어진 데이터와 상황에 맞추어 모델을 선택해야 한다.

     

     

     

     

    나라별 분석

    사용한 Wine Reviews 데이터에는 country 열이 존재해서 나라별 평균을 분석해보았다.

    grouped = results_df_for_vis.groupby("country").mean().reset_index()
    
    plt.figure(figsize=(12, 4))
    
    plt.subplot(131)
    plt.bar(grouped["country"], grouped["roberta_pos"], color="mediumseagreen")
    plt.title("Positive Sentiment")
    plt.xticks(rotation=90)
    
    plt.subplot(132)
    plt.bar(grouped["country"], grouped["roberta_neu"], color="darkgrey")
    plt.title("Neutral Sentiment")
    plt.xticks(rotation=90)
    
    plt.subplot(133)
    plt.bar(grouped["country"], grouped["roberta_neg"], color="coral")
    plt.title("Negative Sentiment")
    plt.xticks(rotation=90)
    
    plt.tight_layout()
    plt.show()

    비교적 Romania 사람들이 긍정적인 평가를, Spain 사람들이 부정적인 평가를 작성한 것으로 보인다. (500개 데이터에 대한 결과이므로 이를 통해 전체 데이터셋을 판단할 수는 없다.)

    '딥러닝' 카테고리의 다른 글

    2. 분류와 회귀  (0) 2023.09.13
    1. 순방향 신경망  (0) 2023.09.13
    K means, 계층 군집화를 이용한 팀원 짜기  (0) 2023.09.12
    PyTorch CNN 모델 Fast API로 배포하기  (0) 2023.09.07
    LSTM을 이용한 저수율 예측  (0) 2023.09.07
Designed by Tistory.