NLTK的NaiveBayesClassifier在Python中非常慢?
我正在使用NLTK的朴素贝叶斯分类器来进行情感分析,但整个过程非常慢。我甚至尝试保存我的训练数据,这样就不用每次都重新训练,但速度和时间上没有任何变化。
保存数据的方法是:
import cPickle
f = open('my_classifier.pickle', 'wb')
pickle.dump(classifier, f)
f.close()
稍后加载数据的方法是:
import cPickle
f = open('my_classifier.pickle')
classifier = pickle.load(f)
f.close()
我还能做些什么来提高速度呢?分析一句话需要6秒钟,我希望能在1秒钟以内完成(我是在一个网站上运行这个)。
*现在我已经改用cPickle来保存和加载数据,性能提升到了3秒!
3 个回答
如果你真的要分析大约十个词,却引入了400万15000个特征,大部分特征其实是用不上的。这就说明,使用某种基于磁盘的数据库来存储特征会更好,这样你只需要提取你需要的特征。即使是处理一长句子,且数据库效率不高,4次查找乘以50个词的时间也远远少于你现在看到的情况——最糟糕的情况下也许只需要几百毫秒,而不是几秒钟。
可以先看看 anydbm,使用NDBM或GDBM作为后端,然后根据你的熟悉程度和可用性考虑其他后端。
你的后续评论似乎表明你对自己在做什么和事情应该如何运作有一些基本的误解。我们来举个简单的例子,假设词汇表里有五个词。
# training
d = { 'good': 1, 'bad': -1, 'excellent': 1, 'poor': -1, 'great': 1 }
c = classifier(d)
with open(f, "classifier.pickle", "w") as f:
pickle.dump(c, f)
sentences = ['I took a good look', 'Even his bad examples were stunning']
# classifying, stupid version
for sentence in sentences:
with open(f, "classifier.pickle", "r") as f:
c = pickle.load(f)
sentiment = c(sentence)
# basically, for word in sentence.split(): if word in d: sentiment += d[word]
print sentiment, sentence
# classifying, slightly less stupid version
with open(f, "classifier.pickle", "r") as f:
c = pickle.load(f)
# FastCGI init_end here
for sentence in sentences:
sentiment = c(sentence)
print sentiment, sentence
你现在遇到的情况就像是一个比较笨的方法。稍微聪明一点的方法是只加载一次分类器,然后对每个输入句子运行它。这就是FastCGI能为你做的:你可以在程序启动时加载一次,然后有一个服务在输入句子到来时运行它。这种方法资源利用更高效,但需要一些工作,因为把你的脚本转换成FastCGI并设置服务器基础设施有点麻烦。如果你预计会有大量使用,这绝对是个好方法。
但是要注意,模型中的五个特征中,实际上只需要两个。句子中的大部分词没有情感评分,而情感数据库中的大部分词也不需要用来计算这些输入的评分。所以,数据库的实现大致会像这样(这是DBM部分的粗略伪代码)
with opendbm("sentiments.db") as d:
for sentence in sentences:
sentiment = 0
for word in sentence.split():
try:
sentiment += d[word]
except KeyError:
pass
print sentiment, sentence
每次交易的成本更高,所以它比FastCGI版本的效率低,因为FastCGI版本只在启动时将整个模型加载到内存中;但它不需要你保持状态或设置FastCGI基础设施,而且比每次句子都加载整个模型的笨方法高效得多。
(实际上,对于没有FastCGI的网络服务,你的opendbm
应该放在for
循环里面,而不是反过来。)
NLTK是一个教学工具包,它并不是为了速度而优化的。如果你想要一个快速的朴素贝叶斯分类器,可以使用scikit-learn里的那个。NLTK里有一个封装可以用,但直接用scikit-learn会更快。
另外,如果你使用内存映射,scikit-learn的模型加载速度会很快。首先,训练模型并用
# Let "clf" be your classifier, usually a Pipeline of CountVectorizer
# and MultinomialNB
from sklearn.externals import joblib
joblib.dump(clf, SOME_PATH, compress=0) # turn off compression
保存它,然后用
clf = joblib.load(SOME_PATH, mmap_mode='r')
加载它。这样也可以便宜地在多个工作进程之间共享模型。
如果还是太慢,那就确保一次处理一批文档,而不是一个一个来。这样速度会快很多。
声明:我写了scikit-learn中很多朴素贝叶斯的代码,还有NLTK的scikit-learn封装代码。