SVM model cho phân loại sắc thái bình luận (3rd place solution)

nlp
classification
demo_nlp_contest

#1

Chào mọi người, mình may mắn lọt được vào top 3 contest phân loại sắc thái bình luận. Sau đây mình xin phép được chia sẻ solution của mình, đạt 0.8914 điểm trên tập public test. Do mình mới tìm hiểu về machine learning và học code python được 1 vài tháng nên chắc chắn còn nhiều thiếu sót, hy vọng sẽ nhận được nhiều góp ý từ mọi người.

Model

clf = Pipeline([
    ('remove_spaces', RemoveConsecutiveSpaces()),
    ('features', FeatureUnion([
        ('custom_features_pipeline', Pipeline([
            ('custom_features', FeatureUnion([
                ('f01', NumWordsCharsFeature()),
                ('f02', NumCapitalLettersFeature()),
                ('f03', ExclamationMarkFeature()),
                ('f04', NumPunctsFeature()),
                ('f05', NumLowercaseLettersFeature()),
                ('f06', NumEmojiFeature())
            ], n_jobs=-1)),
            ('scaler', StandardScaler(with_mean=False))
        ])),
        ('word_char_features_pipeline', Pipeline([
            ('lowercase', Lowercase()),
            ('word_char_features', FeatureUnion([
                ('with_tone', Pipeline([
                    ('remove_punct', RemovePunct()),
                    ('tf_idf_word', TfidfVectorizer(ngram_range=(1, 4), norm='l2', min_df=2))
                ])),
                ('with_tone_char', TfidfVectorizer(ngram_range=(1, 6), norm='l2', min_df=2, analyzer='char')),
                ('with_tone_char_wb', TfidfVectorizer(ngram_range=(1, 6), norm='l2', min_df=2, analyzer='char_wb')),
                ('without_tone', Pipeline([
                    ('remove_tone', RemoveTone()),
                    ('without_tone_features', FeatureUnion([
                        ('tf_idf', Pipeline([
                            ('remove_punct', RemovePunct()),
                            ('word', TfidfVectorizer(ngram_range=(1, 4), norm='l2', min_df=2))
                        ])),
                        ('tf_idf_char', TfidfVectorizer(ngram_range=(1, 6), norm='l2', min_df=2, analyzer='char')),
                        ('tf_idf_char_wb',
                         TfidfVectorizer(ngram_range=(1, 6), norm='l2', min_df=2, analyzer='char_wb'))
                    ], n_jobs=-1))
                ]))
            ], n_jobs=-1))
        ]))
    ], n_jobs=-1)),
    ('alg', SVC(kernel='linear', C=0.2175, class_weight=None, verbose=True))
])

Mình chọn SVM bởi vì:

  1. Theo mình được biết thì SVM hoạt động khá tốt với dữ liệu có số chiều lớn (https://www.quora.com/Why-does-SVM-work-well-in-practice-even-if-the-reproduced-space-is-very-high-dimensional). Đối với các bài toán text classification, nếu mình dùng TF-IDF hay word2vec thì số chiều thường nhiều hơn cả số samples, ví dụ như trong cuộc thi này.

  2. SVM không có nhiều hyper-parameter để tune. Trên thực thế thì mình tune mỗi C. Các phương pháp khác, đặc biệt là neural nets, có thể cần tune nhiều hyper-parameter hơn.

  3. Máy tính mình dùng để train model cũng hơi cùi, không có GPU, và mình cũng chưa kịp tìm hiểu Google Colab hay các resource tương tự, nên mình thiên về các model đơn giản hơn. Model SVM mình sử dụng trong cuộc thi train mất khoảng 20 phút và 1.5GB RAM trên máy i5-4460.

Features

Thực sự mình cũng không có ý tưởng gì về feature engineer thế nào cho hợp lí. Đơn giản là mình gom hết những gì mình có thể nghĩ ra lại 1 chỗ và để SVM tự giải quyết, kiểu “throw everything on the wall and see what sticks”:

  • Word n-grams hay character n-grams?
  • “char” analyzer hay “char_wb” analyzer?
  • Bỏ dấu hay không bỏ dấu?
  • Meta features thì sao?

Kết quả là đoạn code như ở bên trên. Mình cũng hơi ngạc nhiên khi đạt được kết quả cao như vậy. Feature selection cẩn thận hơn có lẽ sẽ đem lại kết quả tốt hơn.

Code

Mình xin phép gửi mọi người source code của mình. Model của mình khá đơn giản, nên mình cũng không biết nên trình bày gì nhiều. Mình cũng chưa kịp tìm hiểu về ensemble, stacking, …, nên kết quả có thể được cải thiện hơn nữa. Cuối cùng, do mình mới học python, nên có thể có nhiều đoạn code không được đẹp lắm, mình hy vọng có thể nhận được góp ý từ mọi người.

Xin cảm ơn BTC đã cho mình cơ hội được thử sức ở một cuộc thi (lần đầu của mình), thực sự mình đã học được rất nhiều trong quá trình nghiên cứu xây dựng model, dù là cho một bài toán khá đơn giản. Mình hy vọng được cọ sát thêm trong các contest tiếp theo.


Tổng kết cuộc thi 'Phân loại sắc thái bình luận'
#2

Sao cái code của bạn mình đã chạy thử trên server tận 100Gb RAM và 40 cores CPU, ko có GPU mà rất lâu. Đã trên 20p mà mình chưa có ra được kết quả, mình ko rõ là đang bị vấn đề gì. Chỉ đo thời gian ngay bước clf.fit(train_data.data, train_data.target) mà hơn 20p vẫn chưa train xong?

Không biết bạn nào trong group đã chạy thử code này chưa và có thể cho mình biết là có quá lâu ko? :frowning:


#3

Hi bạn, mình có test trước khi up lên github và đảm bảo code chạy bình thường. Mình nghĩ có thể là do n_jobs = -1 nên sklearn sẽ cố gắng dùng tất cả các core trong máy; trong trường hợp này máy bạn có 40 cores nên khả năng overhead do parallelization khá lớn, và có thể khiến nó chạy chậm hơn bình thường. Bạn có thể thử n_jobs = 1 hay các số nhỏ hơn xem có được không?

Nếu vẫn có vấn đề buổi tối mình rảnh mình sẽ cố gắng check lại code một lần nữa.


#4

Cám ơn @iota đã rep nhé,

Mình có tìm và thay thế 4 cái n_jobs từ giá trị -1 sang giá trị 5 nhưng vẫn không thể chạy ra được kết quả bạn ơi. Nhờ bạn check lại dùm mình với nhé. Nếu được cho mình xin nick skype của bạn để dễ trao đổi dc ko? Cám ơn bạn nhé.


#5

@Minh_Phuc_Huynh mình mới chạy lại code, và thêm 1 số dòng tính giờ:

import time

start = time.time()
clf.fit(train_data.data, train_data.target)
print(time.time() - start)

test_data = load_test_data('test.crash')

start = time.time()
predicted = clf.predict(test_data.data)
print(time.time() - start)

thì kết quả nhận được là:

[LibSVM]........................*..........*.*
optimization finished, #iter = 34970
obj = -601.652307, rho = 0.741523
nSV = 6169, nBSV = 2390
Total nSV = 6169
1325.639386177063
376.8740975856781

tương đương khoảng 22 phút cho hàm fit và 6.3 phút cho hàm predict trên tập test. Mình hoàn toàn không thay đổi gì code. Bạn có thể check lại một lần nữa không? set n_jobs=1 chẳng hạn?

Skype của mình là iotadesuyo@gmail.com, mình cũng không dùng skype nhiều lắm nên có lẽ sẽ rep hơi chậm 1 chút.


#6

@iota mình set n_job = 1 thì chạy được code của bạn rồi, cám ơn bạn đã rep nhé.


#7

Chào bạn! Mình chạy thử code của bạn với python3 nhưng gặp lỗi: Traceback (most recent call last): File “./train.py”, line 183, in train_data = load_train_data(’./data/train.crash’) File “./train.py”, line 152, in load_train_data lines = file.read() File “/usr/lib/python3.4/encodings/ascii.py”, line 26, in decode return codecs.ascii_decode(input, self.errors)[0] UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xc4 in position 42: ordinal not in range(128)

Bạn có thể giúp mình fixed lỗi này đc ko? Thanks bạn!


#8

Hi bạn,

Chỗ này có vẻ như vì một lý do gì đó python3 default encoding của bạn đang là ASCII (thông thường là UTF-8). Bạn thử check bằng lệnh sau xem kết quả trả về thế nào:

import locale
locale.getpreferredencoding()

Trong trường hợp này mình nghĩ bạn có thể sửa lại hàm open() thành:

with open(filename, encoding='utf-8') as file:

Bạn thử xem thế nào nhé, mình cũng không check được trên máy mình.