Sentiment Analysis cơ bản

amazon-reviews.jpg
Sentiment analysis – hay phân tích tâm lý của đối tượng – là một chủ đề thách thức trong Machine Learning. Mọi người thể hiện cảm nhận của mình thông qua ngôn ngữ tự nhiên có bản chất nhập nhằng, mơ hồ đã gây không ít khó khăn cho việc xử lý cho máy tính hiểu. Chưa kể, họ sử dụng các cách chơi chữ, ẩn ý hay các kí hiệu như

:), :(, =)))

để giải bày cảm xúc của họ.

Trong bài viết này, tôi sẽ sử dụng tập dữ liệu Web data: Amazon Fine Foods reviews cho việc áp dụng kĩ thuật Sentiment analysis. Đây là tutorial cơ bản dành cho các bạn mới bắt đầu nghiên cứu vấn đề này. Ở đây, ta sẽ sử dụng hướng tiếp cận Bag of Words để chuyển đổi dữ liệu văn bản thành ma trận vector từ đó có thể đưa vào các mô hình phân lớp để học.

Source code: classification_algorithms.py

Tiền xử lý dữ liệu

wordcloud.png

Khi bạn download dữ liệu về, food.txt sẽ có dạng

product/productId: B001E4KFG0
review/userId: A3SGXH7AUHU8GW
review/profileName: delmartian
review/helpfulness: 1/1
review/score: 5.0
review/time: 1303862400
review/summary: Good Quality Dog Food
review/text: I have bought several of the Vitality canned dog food products and have
found them all to be of good quality. The product looks more like a stew than a
processed meat and it smells better. My Labrador is finicky and she appreciates this
product better than most.

Ở đây, ta chỉ quan tâm đến các thông tin

  • product/productId là mã sản phẩm
  • review/score là điểm đánh giá của người dùng cụ thể
  • review/summary là đánh giá chung về sản phẩm
  • review/text là phản hồi của người dùng về sản phẩm

Kiểm tra thông tin file bằng dòng lệnh, tập dữ liệu này có hơn 500,000 dòng, hơn 56 triệu từ, và có kích thước khoảng 370 MB.

cat foods.txt | wc
 5116093 56624549 370796484

Đầu tiên, ta sẽ viết hàm convert_plain_to_csv() để chuyển đổi định dạng plain text ban đầu thành .csv

  • Đọc file food.txt
  • Mỗi lần đọc ra 9 dòng
  • Ghép các thông tin productId,score,summary,text thành một dòng ngăn cách với nhau bởi dấu “,”
  • Riêng summary và text ta sẽ làm sạch dữ liệu:
    • Loại bỏ các HTML tags
    • Loại bỏ các kí số, kí tự đặc biệt như dấu chấm, dấu phẩy,…
  • Lưu lại vào file food.csv
  • Tổng thời gian chuyển đổi khoảng 5 phút với cấu hình Macbook như bên dưới

about macbook.png

import datetime
import os
import re
import time
from itertools import islice

import numpy as np
import pandas as pd
from BeautifulSoup import BeautifulSoup
from nltk.corpus import stopwords

def time_diff_str(t1, t2):
    """
    Calculates time durations.
    """
    diff = t2 - t1
    mins = int(diff / 60)
    secs = round(diff % 60, 2)
    return str(mins) + " mins and " + str(secs) + " seconds"

def clean_sentence(sentence):
    # Remove HTML
    review_text = BeautifulSoup(sentence).text

    # Remove non-letters
    letters_only = re.sub("[^a-zA-Z]", " ", review_text)
    return letters_only

def convert_plain_to_csv(plain_name, tsv_name):
    t0 = time.time()
    with open(plain_name, "r") as f1, open(tsv_name, "w") as f2:
        i = 0
        f2.write("productId,score,summary,text\n")
        while True:
            next_n_lines = list(islice(f1, 9))
            if not next_n_lines:
                break

            # process next_n_lines: get productId,score,summary,text info
            # remove special characters from summary and text
            output_line = ""
            for line in next_n_lines:
                if "product/productId:" in line:
                    output_line += line.split(":")[1].strip() + ","
                elif "review/score:" in line:
                    output_line += line.split(":")[1].strip() + ","
                elif "review/summary:" in line:
                    summary = clean_sentence(line.split(":")[1].strip()) + ","
                    output_line += summary
                elif "review/text:" in line:
                    text = clean_sentence(line.split(":")[1].strip()) + "\n"
                    output_line += text

            f2.write(output_line)

            # print status
            i += 1
            if i % 10000 == 0:
                print "%d reviews converted..." % i

    print " %s - Converting completed %s" % (datetime.datetime.now(), time_diff_str(t0, time.time()))

Tiếp theo, ta tiến hành quan sát tập dữ liệu vừa mới chuyển đổi

train = get_reviews_data("foods.csv")
-- foods.csv found locally

print "Data dimensions:", train.shape
Data dimensions: (568454, 4)

print "List features:", train.columns.values
List features: ['productId' 'score' 'summary' 'text']

print "First review:", train["summary"][0], "|", train["text"][0]
First review: Good Quality Dog Food | I have bought several of the Vitality canned dog food products and have found them all to be of good quality  The product looks more like a stew than a processed meat and it smells better  My Labrador is finicky and she appreciates this product better than  most

Cuối cùng, ta sẽ loại bỏ các từ không liên quan bằng danh sách stopwords trong tiếng Anh. Đây là những từ hay xuất hiện trong câu nhưng không góp phần vào quá trình học của hệ thống như “a”, “and”, “is” hoặc “the”. Với mỗi dòng dữ liệu quan sát ta tiến hành loại bỏ như sau:

  • Chuyển sang chữ thường và tách thành danh sách các từ riêng biệt.
  • Sử dụng stopwords để lọc ra danh sách các từ có ý nghĩa.
  • Lưu lại vào file clean_train_reviews.csv
  • Quá trình này mất khoảng 4 phút.
def review_to_words(review):
    """
    Function to convert a raw review to a string of words
    :param review
    :return: meaningful_words
    """
    # 1. Convert to lower case, split into individual words
    words = review.lower().split()
    #
    # 2. In Python, searching a set is much faster than searching
    #   a list, so convert the stop words to a set
    stops = set(stopwords.words("english"))
    #
    # 3. Remove stop words
    meaningful_words = [w for w in words if not w in stops]
    #
    # 4. Join the words back into one string separated by space,
    # and return the result.
    return " ".join(meaningful_words)

def cleaning_data(dataset, file_name):
    t0 = time.time()

    # Get the number of reviews based on the dataframe column size
    num_reviews = dataset["text"].size

    # Initialize an empty list to hold the clean reviews
    clean_train_reviews = []

    # Loop over each review
    for i in xrange(0, num_reviews):
        # If the index is evenly divisible by 1000, print a message
        if (i + 1) % 10000 == 0:
            print "Review %d of %d\n" % (i + 1, num_reviews)

        # Call our function for each one, and add the result to the list of
        # clean reviews
        productId = str(dataset["productId"][i])
        score = str(dataset["score"][i])
        summary = str(dataset["summary"][i])
        text = review_to_words(str(dataset["text"][i]))

        clean_train_reviews.append(productId + "," + score + "," + summary + "," + text + "\n")

    print "Writing clean train reviews..."
    with open(file_name, "w") as f:
        f.write("productId,score,summary,text\n")
        for review in clean_train_reviews:
            f.write("%s\n" % review)

    print " %s - Write file completed %s" % (datetime.datetime.now(), time_diff_str(t0, time.time()))

Sử dụng Bag of words để tạo features

Bag of Words model xây dựng bộ từ vựng thông qua tập các văn bản, sau đó mô hình hóa từng văn bản (vector hóa) bằng cách đếm số lần xuất hiện của các từ xuất hiện trong văn bản đó. Ví dụ, ta có hai câu sau:

  • Câu 1: “The cat sat on the hat”
  • Câu 2: “The dog ate the cat and the hat”

Từ hai câu trên, bộ từ vựng của chúng ta sẽ là
{ the, cat, sat, on, hat, dog, ate, and }

Để có bags of words, ta sẽ đếm số lần xuất hiện của từng từ trong từng câu. Trong câu 1, “the” xuất hiện 2 lần, các từ “cat”, “sat”, “on”, và “hat” đề xuất hiện 1 lần, nên ta có feature vector cho câu 1 là

  • Câu 1: { 2, 1, 1, 1, 1, 0, 0, 0 }
  • Tương tự cho câu 2: { 3, 1, 0, 0, 1, 1, 1, 1}

Ta tiến hành vector hóa cho tập dữ liệu đã được xử lý

  • Do dữ liệu khá lớn, nên ta sẽ lấy khoảng 1000 dòng quan sát để thực nghiệm. Khi mọi thứ đã chạy ổn, ta sẽ chạy lại cho tất cả để rút ra được mô hình cuối cùng cho hệ thống.
  • Ta sẽ loại bỏ bớt các review có điểm = 3.0 để phân biệt rõ ràng hơn giữa phản hồi tích cực (positive) và tiêu cực (negative).
  • Ở đây, ta đánh giá một phản hồi là tích cực khi có điểm đánh giá >= 4.0
  • Tiếp theo, ta sẽ phân chia tập dữ liệu train và test theo tỉ lệ 80/20
  • CountVectorizer được dùng để phát sinh vector Bag of Words
  • Cuối cùng, ta sử dụng hàm fit_transform() để chuyển đổi thành ma trận term-document làm input cho các hàm phân lớp.
  • Ngoài ra, bạn có thể gọi hàm print_words_frequency() để in ra danh sách các từ kèm tần suất xuất hiện của chúng.
from sklearn.cross_validation import train_test_split
from sklearn.feature_extraction.text import CountVectorizer

def print_words_frequency(train_data_features):
    # Take a look at the words in the vocabulary
    vocab = vectorizer.get_feature_names()
    print "Words in vocabulary:", vocab

    # Sum up the counts of each vocabulary word
    dist = np.sum(train_data_features, axis=0)

    # For each, print the vocabulary word and the number of times it
    # appears in the training set
    print "Words frequency..."
    for tag, count in zip(vocab, dist):
        print count, tag

clean_train_reviews = pd.read_csv("clean_train_reviews.csv", nrows=1000)

# ignore all 3* reviews
clean_train_reviews = clean_train_reviews[clean_train_reviews["score"] != 3]
# positive sentiment = 4* or 5* reviews
clean_train_reviews["sentiment"] = clean_train_reviews["score"] >= 4

train, test = train_test_split(clean_train_reviews, test_size=0.2)

print "Creating the bag of words...\n"
vectorizer = CountVectorizer(analyzer="word",
                             tokenizer=None,
                             preprocessor=None,
                             stop_words=None,
                             max_features=10)

train_text = train["text"].values.astype('U')
test_text = test["text"].values.astype('U')

# convert data-set to term-document matrix
X_train = vectorizer.fit_transform(train_text).toarray()
y_train = train["sentiment"]

X_test = vectorizer.fit_transform(test_text).toarray()
y_test = test["sentiment"]

print_words_frequency(X_train)

Training

Đây có lẽ là bước mọi người mong chờ nhất. Ta sẽ sử dụng tập các hàm phân lớp khác nhau và để chọn ra được mô hình cho kết quả chính xác cao nhất.

from operator import itemgetter
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.gaussian_process import GaussianProcessClassifier
from sklearn.gaussian_process.kernels import RBF
from sklearn.naive_bayes import GaussianNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier

names = ["Nearest Neighbors", "Linear SVM", "RBF SVM", "Gaussian Process",
             "Decision Tree", "Random Forest", "Neural Net", "AdaBoost",
             "Naive Bayes", "QDA"]

classifiers = [
    KNeighborsClassifier(3),
    SVC(kernel="linear", C=0.025),
    SVC(gamma=2, C=1),
    GaussianProcessClassifier(1.0 * RBF(1.0), warm_start=True),
    DecisionTreeClassifier(max_depth=5),
    RandomForestClassifier(max_depth=5, n_estimators=10, max_features=1),
    MLPClassifier(alpha=1),
    AdaBoostClassifier(),
    GaussianNB(),
    QuadraticDiscriminantAnalysis()]

# iterate over classifiers
results = {}
for name, clf in zip(names, classifiers):
    print "Training " + name + " classifier..."
    clf.fit(X_train, y_train)
    score = clf.score(X_test, y_test)
    results[name] = score

print "---------------------------"
print "Evaluation results"
print "---------------------------"

# sorting results and print out
sorted(results.items(), key=itemgetter(1))
for name in results:
    print name + " accuracy: %0.3f" % results[name]

Kết thúc quá trình huấn luyện và đánh giá, ta có thể thấy hàm phân lớp Gaussian Process cho kết quả cao nhất là 79,5%

Gaussian Process accuracy: 0.795
Decision Tree accuracy: 0.784
QDA accuracy: 0.719
Naive Bayes accuracy: 0.730
Linear SVM accuracy: 0.795
Neural Net accuracy: 0.784
RBF SVM accuracy: 0.784
AdaBoost accuracy: 0.773
Random Forest accuracy: 0.795
Nearest Neighbors accuracy: 0.719

Kết

Nếu bạn đã làm theo tutorial đến được đây và chạy chương trình thành công thì xin chúc mừng, bạn đã xây dựng được cho mình một hệ thống Sentiment Analysis cơ bản. Bạn có thể thử nhiều cách khác nhau để cải thiện độ chính xác của mô hình. Bạn có thể làm sạch dữ liệu theo nhiều cách khác như giữ lại các biểu tượng cảm xúc hay các từ viết tắt phổ biến, thay đổi số lượng từ vựng của Bag of Words để xem độ chính xác có thay đổi không.

Ngoài ra bạn cũng nên áp dụng kỹ thuật này lên các tập dữ liệu khác để tạo ra nhiều ứng dụng thú vị hơn như:

  • Chọn ra nhà hàng nào được review tốt nhất trong thành phố của bạn để ghé ăn cuối tuần.
  • Thu thập dữ liệu từ một trang web ăn uống và xây dựng cho mình ứng dụng phân tích review các món ăn.
  • Thu thập dữ liệu từ các diễn đàn iPhone và Samsung thử đoán xem sản phẩm nào được đánh giá cao hơn khi áp dụng hệ thống của bạn.
  • Xây dựng ứng dụng đánh giá phim, bài hát tự động từ những phản hồi của người dùng.
  • Áp dụng cho tiếng Việt bằng cách sử dụng các thư viện do cộng đồng Xử lý ngôn ngữ tự nhiên Việt Nam đóng góp như:
    • VNLP: an open source framework for Vietnamese natural language processing
    • JVnTextPro: A Java-based Vietnamese Text Processing Tool
    • vnTokenizer: Vietnamese word segmentation

Sau bài viết này, bạn có thể nghiên cứu thêm các vấn đề liên quan như:

  • Confusion matrix.
  • False negative, False positive có ảnh hưởng thế nào đến các ứng dụng như chẩn đoán bệnh hay lọc thư rác?
  • Thế nào là bias, dữ liệu càng nhiều thì độ chính xác của mô hình có tăng lên không?
  • Các kĩ thuật đánh giá mô hình khác.

5 thoughts on “Sentiment Analysis cơ bản

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s