SKLearn 朴素贝叶斯:在 tfidf 向量化后添加特征

Posted

技术标签:

【中文标题】SKLearn 朴素贝叶斯:在 tfidf 向量化后添加特征【英文标题】:SKLearn Naive Bayes: add feature after tfidf vectorization 【发布时间】:2017-11-10 19:15:49 【问题描述】:

所以我的任务是训练一个关于电话记录的模型。以下代码执行此操作。一点背景资料: - x 是一个字符串列表,每个第 i 个元素是一个完整的成绩单 - y 是一个布尔值列表,表示调用的结果是肯定的还是否定的。

以下代码有效,但这是我的问题。 我想将通话时长作为训练的特征。我假设在对成绩单进行矢量化的 TFIDF 转换器之后,我只需将通话时长功能连接到 TFIDF 输出,对吗?也许这比我想象的要容易,但是我在代码开头看到的 pandas 数据框中都有成绩单和持续时间。 如果我有持续时间的数据框列(numpy 数组),我需要做什么才能将该功能添加到我的模型中?

其他问题:

我是否遗漏了关于朴素贝叶斯模型的基本假设,该假设将我限制为向量化字符串? 我应该在管道的哪个步骤添加新功能? 这甚至可以在管道中完成,还是我必须将其拆分才能执行此类操作?

代码:

import numpy as np
import pandas as pd
import random
from sklearn.naive_bayes import GaussianNB
from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import SGDClassifier
from sklearn.grid_search import GridSearchCV
from sklearn.cross_validation import cross_val_score
from sklearn.feature_selection import SelectPercentile
from sklearn.metrics import roc_auc_score
from sklearn.feature_selection import chi2


def main():
filename = 'QA_training.pkl'
splitRatio = 0.67
dataframe = loadData(filename)
x, y = getTrainingData(dataframe)
print len(x), len(y)

x_train, x_test = splitDataset(x, splitRatio)
y_train, y_test = splitDataset(y, splitRatio)

#x_train = np.asarray(x_train)

percentiles = [10, 15, 20, 25, 30, 35, 40, 45, 50]

MNNB_pipe = Pipeline([('vec', CountVectorizer()),('tfidf', TfidfTransformer()),('select', SelectPercentile(score_func=chi2)),('clf', MultinomialNB())])
MNNB_param_grid = 
#'vec__max_features': (10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000),
'tfidf__use_idf': (True, False),
'tfidf__sublinear_tf': (True, False),
'vec__binary': (True, False),
'tfidf__norm': ('l1', 'l2'),
'clf__alpha': (1, 0.1, 0.01, 0.001, 0.0001, 0.00001),
'select__percentile': percentiles

MNNB_search = GridSearchCV(MNNB_pipe, param_grid=MNNB_param_grid, cv=10, scoring='roc_auc', n_jobs=-1, verbose=1)
MNNB_search = MNNB_search.fit(x_train, y_train)
MNNB_search_best_cv = cross_val_score(MNNB_search.best_estimator_, x_train, y_train, cv=10, scoring='roc_auc', n_jobs=-1, verbose=10)

SGDC_pipe = Pipeline([('vec', CountVectorizer()),('tfidf', TfidfTransformer()),('select', SelectPercentile(score_func=chi2)),('clf', SGDClassifier())])
SGDC_param_grid = 
#'vec__max_features': [10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
'tfidf__use_idf': [True, False],
'tfidf__sublinear_tf': [True, False],
'vec__binary': [True, False],
'tfidf__norm': ['l1', 'l2'],
'clf__loss': ['modified_huber','log'],
'clf__penalty': ['l1','l2'],
'clf__alpha': [1e-3],
'clf__n_iter': [5,10],
'clf__random_state': [42],
'select__percentile': percentiles

SGDC_search = GridSearchCV(SGDC_pipe, param_grid=SGDC_param_grid, cv=10, scoring='roc_auc', n_jobs=-1, verbose=1)
SGDC_search = SGDC_search.fit(x_train, y_train)
SGDC_search_best_cv = cross_val_score(SGDC_search.best_estimator_, x_train, y_train, cv=10, scoring='roc_auc', n_jobs=-1, verbose=10)

# pre_SGDC = SGDC_clf.predict(x_test)
# print (np.mean(pre_SGDC == y_test))

mydata = ['model': MNNB_search.best_estimator_.named_steps['clf'],'features': MNNB_search.best_estimator_.named_steps['select'], 'mean_cv_scores': MNNB_search_best_cv.mean(),
          #'model': GNB_search.best_estimator_.named_steps['classifier'],'features': GNB_search.best_estimator_.named_steps['select'], 'mean_cv_scores': GNB_search_best_cv.mean(),
          'model': SGDC_search.best_estimator_.named_steps['clf'],'features': SGDC_search.best_estimator_.named_steps['select'], 'mean_cv_scores': SGDC_search_best_cv.mean()]
model_results_df = pd.DataFrame(mydata)
model_results_df.to_csv("best_model_results.csv")

【问题讨论】:

FWIW,您可能希望将您的 CountVectorizer + TfidfTransformer 压缩为一个简单的 TfidfVectorizer -- 但这只是一个小的代码简化点。它根本不会改变算法。 是的,感谢您的观察! 【参考方案1】:

据我所知,sklearn 管道是 API 驱动的——管道本身并没有真正的魔法。因此,从这个角度来看,您应该能够围绕 TfidfVectorizer 创建自己的包装器,它可以满足您的需求。例如,假设您有一个如下所示的 DataFrame:

df = pd.DataFrame('text': ['foo text', 'bar text'], 'duration': [1, 2])

您可能可以按如下方式实现您的转换:

class MyVectorizer(object):
    def __init__(self, tfidf_kwargs=None):
        self._tfidf = TfidfVectorizer(**(tfidf_kwargs or None))

    def fit(self, X, y=None):
        self._tfidf.fit(X['text'], y)
        return self

    def fit_transform(self, X, y=None):
        self.fit(X)
        return self.transform(X, copy=False)

    def transform(self, X, copy=True):
        result = self._tfidf.transform(X['text'], copy=copy)
        # result is a sparse matrix.  I'm not sure of a clean way
        # to add a column to a sparse matrix.  If you have the
        # memory, you can use a dense matrix instead...
        return np.column_stack((result, X['duration']))

然后我认为您应该准备好使用它而不是原始的 tfidf 矢量化器。

【讨论】:

以上是关于SKLearn 朴素贝叶斯:在 tfidf 向量化后添加特征的主要内容,如果未能解决你的问题,请参考以下文章

我们如何使用带有多项朴素贝叶斯的 TFIDF 向量?

使用高斯朴素贝叶斯的多类分类

sklearn-朴素贝叶斯

朴素贝叶斯分类算法的sklearn实现

利用sklearn进行朴素贝叶斯分类

朴素贝叶斯