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.

In [1]:
!cat data/foods.txt | wc
 5116093 56624549 370796484
In [2]:
!du -h data/foods.txt
368M	data/foods.txt

Đầ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,...
    </li>
  • 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
  • </ul> about macbook.png

In [ ]:
"""
CLASSIFICATION
Case study: Analyzing sentiment
Models:
    Linear classifiers (logistic regression, SVMs, perceptron)
    Kernels
    Decision trees
Algorithms:
    Stochastic gradient descent
    Boosting
Concepts:
    Decision boundaries, MLE, ensemble methods, random forests, CART, online learning
"""
import datetime
import os
import re
import time
from itertools import islice
from operator import itemgetter

import numpy as np
import pandas as pd
from BeautifulSoup import BeautifulSoup
from sklearn.cross_validation import train_test_split
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

import nltk
nltk.download('stopwords')
from nltk.corpus import stopwords
In [4]:
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 % 20000 == 0:
                print "%d reviews converted..." % i

    print " %s - Converting completed %s" % (datetime.datetime.now(), time_diff_str(t0, time.time()))
In [5]:
"""
Pre-processing
"""
# converting plain text for next processing
in_file = "data/foods.txt"
out_file = "data/foods.csv"
clean_file = "data/clean_train_reviews.csv"
convert_plain_to_csv(in_file, out_file)
20000 reviews converted...
40000 reviews converted...
60000 reviews converted...
80000 reviews converted...
100000 reviews converted...
120000 reviews converted...
140000 reviews converted...
160000 reviews converted...
180000 reviews converted...
200000 reviews converted...
220000 reviews converted...
240000 reviews converted...
260000 reviews converted...
280000 reviews converted...
300000 reviews converted...
320000 reviews converted...
340000 reviews converted...
360000 reviews converted...
380000 reviews converted...
400000 reviews converted...
420000 reviews converted...
440000 reviews converted...
460000 reviews converted...
480000 reviews converted...
500000 reviews converted...
520000 reviews converted...
540000 reviews converted...
560000 reviews converted...
 2018-05-04 12:33:44.530843 - Converting completed 2 mins and 53.04 seconds

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

In [7]:
def get_reviews_data(file_name):
    """Get reviews data, from local csv."""
    if os.path.exists(file_name):
        print("-- " + file_name + " found locally")
        df = pd.read_csv(file_name)

    return df

# Reading the Data
train = get_reviews_data(out_file)
print "Data dimensions:", train.shape
print "List features:", train.columns.values
print "First review:", train["summary"][0], "|", train["text"][0]
-- data/foods.csv found locally
Data dimensions: (568454, 4)
List features: ['productId' 'score' 'summary' 'text']
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.
In [8]:
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) % 20000 == 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()))
In [9]:
cleaning_data(train, clean_file)
Review 20000 of 568454

Review 40000 of 568454

Review 60000 of 568454

Review 80000 of 568454

Review 100000 of 568454

Review 120000 of 568454

Review 140000 of 568454

Review 160000 of 568454

Review 180000 of 568454

Review 200000 of 568454

Review 220000 of 568454

Review 240000 of 568454

Review 260000 of 568454

Review 280000 of 568454

Review 300000 of 568454

Review 320000 of 568454

Review 340000 of 568454

Review 360000 of 568454

Review 380000 of 568454

Review 400000 of 568454

Review 420000 of 568454

Review 440000 of 568454

Review 460000 of 568454

Review 480000 of 568454

Review 500000 of 568454

Review 520000 of 568454

Review 540000 of 568454

Review 560000 of 568454

Writing clean train reviews...
 2018-05-04 12:36:50.457401 - Write file completed 2 mins and 20.03 seconds

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.
In [10]:
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_file, 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)
Creating the bag of words...

Words in vocabulary: [u'bag', u'chips', u'flavor', u'food', u'good', u'great', u'kettle', u'like', u'one', u'taste']
Words frequency...
140 bag
365 chips
194 flavor
158 food
228 good
207 great
284 kettle
167 like
190 one
200 taste

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.

In [11]:
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]
Training Nearest Neighbors classifier...
Training Linear SVM classifier...
Training RBF SVM classifier...
Training Gaussian Process classifier...
Training Decision Tree classifier...
Training Random Forest classifier...
Training Neural Net classifier...
Training AdaBoost classifier...
Training Naive Bayes classifier...
Training QDA classifier...
---------------------------
Evaluation results
---------------------------
Gaussian Process accuracy: 0.816
Decision Tree accuracy: 0.822
QDA accuracy: 0.768
Naive Bayes accuracy: 0.768
Linear SVM accuracy: 0.816
Neural Net accuracy: 0.816
RBF SVM accuracy: 0.805
AdaBoost accuracy: 0.816
Random Forest accuracy: 0.816
Nearest Neighbors accuracy: 0.805

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.