如何使用Sk-learn提高SVM分类器的速度

3

我正在尝试构建一个垃圾邮件分类器,已经从互联网上收集了多个数据集(例如,用于垃圾邮件/普通邮件的SpamAssassin数据库),并构建了以下内容:

import os
import numpy
from pandas import DataFrame
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.pipeline import Pipeline
from sklearn.cross_validation import KFold
from sklearn.metrics import confusion_matrix, f1_score
from sklearn import svm

NEWLINE = '\n'

HAM = 'ham'
SPAM = 'spam'

SOURCES = [
    ('C:/data/spam', SPAM),
    ('C:/data/easy_ham', HAM),
    # ('C:/data/hard_ham', HAM), Commented out, since they take too long
    # ('C:/data/beck-s', HAM),
    # ('C:/data/farmer-d', HAM),
    # ('C:/data/kaminski-v', HAM),
    # ('C:/data/kitchen-l', HAM),
    # ('C:/data/lokay-m', HAM),
    # ('C:/data/williams-w3', HAM),
    # ('C:/data/BG', SPAM),
    # ('C:/data/GP', SPAM),
    # ('C:/data/SH', SPAM)
]

SKIP_FILES = {'cmds'}


def read_files(path):
    for root, dir_names, file_names in os.walk(path):
        for path in dir_names:
            read_files(os.path.join(root, path))
        for file_name in file_names:
            if file_name not in SKIP_FILES:
                file_path = os.path.join(root, file_name)
                if os.path.isfile(file_path):
                    past_header, lines = False, []
                    f = open(file_path, encoding="latin-1")
                    for line in f:
                        if past_header:
                            lines.append(line)
                        elif line == NEWLINE:
                            past_header = True
                    f.close()
                    content = NEWLINE.join(lines)
                    yield file_path, content


def build_data_frame(path, classification):
    rows = []
    index = []
    for file_name, text in read_files(path):
        rows.append({'text': text, 'class': classification})
        index.append(file_name)

    data_frame = DataFrame(rows, index=index)
    return data_frame


data = DataFrame({'text': [], 'class': []})
for path, classification in SOURCES:
    data = data.append(build_data_frame(path, classification))

data = data.reindex(numpy.random.permutation(data.index))

pipeline = Pipeline([
    ('count_vectorizer', CountVectorizer(ngram_range=(1, 2))),
    ('classifier', svm.SVC(gamma=0.001, C=100))
])

k_fold = KFold(n=len(data), n_folds=6)
scores = []
confusion = numpy.array([[0, 0], [0, 0]])
for train_indices, test_indices in k_fold:
    train_text = data.iloc[train_indices]['text'].values
    train_y = data.iloc[train_indices]['class'].values.astype(str)

    test_text = data.iloc[test_indices]['text'].values
    test_y = data.iloc[test_indices]['class'].values.astype(str)

    pipeline.fit(train_text, train_y)
    predictions = pipeline.predict(test_text)

    confusion += confusion_matrix(test_y, predictions)
    score = f1_score(test_y, predictions, pos_label=SPAM)
    scores.append(score)

print('Total emails classified:', len(data))
print('Support Vector Machine Output : ')
print('Score:' + str((sum(scores) / len(scores))*100) + '%')
print('Confusion matrix:')
print(confusion)

我已经注释掉的那些行是邮件的集合,即使我注释掉大部分数据集并选择其中最少的邮件,它仍然运行非常缓慢(约15分钟),并且准确率约为91%。我该如何提高速度和准确性?


有多少文档?看起来文档数量对于内核来说相当高。您考虑过使用线性SVM吗? - David Maust
@DavidMaust 总共有大约3万份文档(包括被注释掉的文档)。未被注释的文档数量约为3千。我现在会尝试使用LinearSVM... - Mayur Kulkarni
是的,对于核SVM来说确实很多。线性SVM可能会做得更好。svc.SVM(kernel='linear') - David Maust
@DavidMaust 经过对3k封邮件的测试,性能有所提升,准确率从91%提高到94.5%。还有什么其他方法可以增加准确率吗?谢谢。 - Mayur Kulkarni
1
@tripleee 谢谢,我已经处理好了cmds文件,你可以在代码中找到它 SKIP_FILES = {cmds} - Mayur Kulkarni
显示剩余3条评论
1个回答

3
你正在使用核支持向量机。这会带来两个问题。
核支持向量机的运行时间复杂度:执行核支持向量机的第一步是构建相似矩阵,该矩阵成为特征集合。对于30,000个文档,相似矩阵中的元素数量就变成了90,000,000。随着语料库的增长,矩阵的大小会呈平方级别增长。这个问题可以使用scikit-learn中的RBFSampler解决,但你可能不想使用它,因为接下来有另一个原因。
维度:你正在使用术语和二元组计数作为特征集。这是一个极高维度的数据集。在高维空间中使用RBF核,即使是小差异(噪声)也会对相似性结果产生很大影响。请参见维度灾难。这可能是为什么你的RBF核产生比线性核更差的结果的原因。

随机梯度下降: SGD 可以用于替代标准的 SVM,通过良好的参数调整,它可能会产生类似甚至更好的结果。缺点是 SGD 有更多的参数需要调整,包括学习率和学习率调度。此外,在少数迭代中,SGD 并不理想。在这种情况下,其他算法如 Follow The Regularized Leader (FTRL) 将表现更好。Scikit-learn 没有实现 FTRL。使用 SGDClassifierloss="modified_huber" 通常效果很好。

现在我们已经解决了问题,有几种方法可以提高性能:

tf-idf 权重:使用 tf-idf,更常见的单词被赋予较小的权重。这使分类器能够更好地表示更具有意义的罕见单词。可以通过将 CountVectorizer 切换到 TfidfVectorizer 来实现。

参数调整:使用线性SVM时,没有gamma参数,但可以使用C参数来极大地改善结果。在SGDClassifier的情况下,也可以调整alpha和学习率参数。 集成学习:在多个子样本上运行模型并平均结果通常会产生比单次运行更强大的模型。这可以使用scikit-learn中的BaggingClassifier实现。此外,结合不同的方法可以产生显着更好的结果。如果使用了明显不同的方法,请考虑使用树模型(RandomForestClassifier或GradientBoostingClassifier)作为最后一阶段的堆叠模型。

使用tf-idf和LinearSVC提高了速度,并显示惊人的99.5%准确率 :) - Mayur Kulkarni
很好。那是交叉验证的准确率吗? - David Maust

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接