使用scikit-learn管道自動組合文本和數字數據
有時,機器學習模型的可能配置即使沒有上千種,也有數百種,這使得手工找到最佳配置的可能性變得不可能,因此自動化是必不可少的。在處理複合特徵空間時尤其如此,在複合特徵空間中,我們希望對數據集中的不同特徵應用不同的轉換。一個很好的例子是將文本文檔與數字數據相結合,然而,在scikit-learn中,我找不到關於如何自動建模這種類型的特徵空間的信息。
使用scikit-learn管道可以更有效地工作,而不是手動將文本轉換成詞袋,然後再手動添加一些數字列。這篇文章將告訴你如何去做。
使用管道允許你將一系列轉換步驟和評估器(分類器或回歸器)視為單個模型,稱為複合評估器。這不僅使你的代碼保持整潔並防止訓練集和測試集之間的信息洩漏,而且還允許你將轉換步驟視為模型的超參數,然後通過網格搜索在超參數空間中優化模型。這意味著你可以在文本數據的同時試驗不同的數值特徵組合,以及不同的文本處理方法,等等。
在接下來的內容中,你將看到如何構建這樣一個系統:將帶標籤的文本文檔集合作為輸入;自動生成一些數值特徵;轉換不同的數據類型;將數據傳遞給分類器;然後搜索特徵和轉換的不同組合,以找到性能最佳的模型。
選型
我使用的是垃圾簡訊數據集,可以從UCI機器學習庫下載,它包含兩列:一列簡訊文本和一個相應的標籤列,包含字符串' Spam '和' ham ',這是我們必須預測的。和往常一樣,整個項目代碼都可以在GitHub上找到(https://github.com/job9931/Blog-notebooks/tree/main/automatedmodelselection)。
第一步是定義要應用於數據集的轉換。要在scikit-learn管道中包含數據轉換,我們必須把它寫成類,而不是普通的Python函數;一開始這可能聽起來令人生畏,但它很簡單。另一種方法是簡單地定義一個普通的Python函數,並將其傳遞給FunctionTransformer類,從而將其轉換為一個scikit-learn transformer對象。然而,在這裡,我將向你展示更多的手工方法,這樣你就可以看到實際發生了什麼,因為我認為它有助於理解scikit-learn是如何工作的。
你創建一個類,它繼承了scikit-learn提供的BaseEstimator和TransformerMixin類,它們提供了創建與scikit-learn管道兼容的對象所需的屬性和方法。然後,在init()方法中包含函數參數作為類屬性,並使用將用於轉換數據集的函數體覆蓋transform()方法。我在下面提供了三個例子。
from sklearn.base import BaseEstimator, TransformerMixinclass CountWords(BaseEstimator,TransformerMixin):#creates a dataframe from a series of text documents by creating a new column named n_words, #which contains the number of words in each document def __init__(self,new_col_name): self.new_col_name = new_col_name def fit(self,series,y=None): return self def transform(self,series): n_words_col = series.apply(lambda x: len(x.split(' '))).rename(self.new_col_name) return pd.concat([series, n_words_col], axis=1)class MeanWordLength(BaseEstimator,TransformerMixin): #creates a column mean length of words in message def __init__(self,text_column): self.text_column = text_column def fit(self,dataframe,y=None): return self def transform(self,dataframe): dataframe['mean_word_length'] = dataframe[self.text_column].apply(lambda x: sum(map(len,x.split(' ') ))/len(x.split(' '))) return dataframeclass FeatureSelector(BaseEstimator,TransformerMixin): #creates a new dataframe using only columns listed in attribute_names def __init__(self,attribute_names): self.attribute_names = attribute_names def fit(self, dataframe, y=None): return self def transform(self, dataframe): return dataframe[self.attribute_names].values
管道中使用的自定義轉換器對象。在每個示例中,fit()方法不執行任何操作,所有工作都體現在transform()方法中。
前兩個轉換符用於創建新的數字特徵,這裡我選擇使用文檔中的單詞數量和文檔中單詞的平均長度作為特徵。由於我們的數據集只包含兩列,文本和標籤,我們的文本在分離標籤列之後被存儲為熊貓系列,我們應該在項目的一開始就這樣做。因此,CountWords.transform()被設計為接受一個序列並返回一個數據流,因為我將使用它作為管道中的第一個轉換器。
final transformer FeatureSelector將允許我們將各種特性作為模型的超參數。它的transform()方法接受列名列表,並返回一個僅包含這些列的DataFrame;通過向它傳遞不同的列名列表,我們可以在不同的特徵空間中搜索以找到最佳的一個。這三個轉換器提供了我們構建管道所需的所有附加功能。
構建管道
最終的管道由三個組件構成:初始管道對象、ColumnTransformer對象和估計器。第二個組件ColumnTransformer是0.20版本中引入的一個方便的類,它允許你對數據集的指定列應用單獨的轉換。在這裡,我們將使用它將CountVectorizer應用到文本列,並將另一個管道num_pipeline應用到數值列,該管道包含FeatureSelector和scikit-learn的SimpleImputer類。整個管道結構如圖所示:
管道示意圖。整個對象(稱為複合估計器)可以用作模型;所有的轉換器和估計器對象以及它們的參數,都成為我們模型的超參數。
工作流程如下
一系列文檔進入管道,CountWords和MeanWordLength在管道中創建兩個名為nwords和meanword_length的數字列。文本列被傳遞給CountVectorizer,而nwords和meanword_length首先通過FeatureSelector,然後是SimpleImputer。轉換後的數據集被傳遞給估計器對象。from sklearn.pipeline import Pipelinefrom sklearn.compose import ColumnTransformerfrom sklearn.impute import SimpleImputerfrom sklearn.feature_extraction.text import CountVectorizerfrom sklearn.svm import SVC#Define the names of the text and numerical featurestext_features = 'text'numerical_features = ['n_words','mean_word_length']#Create the initial pipeline which generates numerical columnspipeline_1 = Pipeline([('n_words',CountWords('n_words')), ('mean_length',MeanWordLength('text'))])#Then use ColumnTransformer to process the numerical columns and the text column separately.#We define and apply num_pipeline to the numerical columns and CountVectorizer to the text columnnum_pipeline = Pipeline([('selector',FeatureSelector(numerical_features)), ('imp',SimpleImputer())])pipeline_2 = ColumnTransformer([ ("txt", CountVectorizer(), 'text'), ("num", num_pipeline,['n_words','mean_word_length']), ])#Build the final pipeline using pipeline_1 and pipeline_2 and an estimator, in this case SVC()pipeline = Pipeline([('add_numerical',pipeline_1), ('transform',pipeline_2), ('clf',SVC())])
最終的管道由初始化對象、ColumnTransformer對象和估計器對象組成。注意,ColumnTransformer可以將整個管道應用於某些列。
在上面的代碼示例中,我們使用CountVectorizer和SimpleImputer的默認參數,同時保留數字列,並使用支持向量分類器作為估計器。這最後一個管道是我們的複合估計器,它裡面的每個對象,以及這些對象的參數,都是一個超參數,我們可以自由地改變它。這意味著我們可以搜索不同的特徵空間、不同的向量化設置和不同的估計器對象。
通過網格搜索選擇最佳模型
使用複合估計器設置,很容易找到最佳執行模型;你所需要做的就是創建一個字典,指定想要改變的超參數和想要測試的值。然後將其傳遞給scikit-learn的GridSearchCV類,該類對每個超參數值組合使用交叉驗證來評估模型,然後返回最好的。
from sklearn.model_selection import GridSearchCV#params is a dictionary, the keys are the hyperparameter and the vaules are a list of values#to search over.params = [{'transform__txt__max_features':[None,100,10], 'transform__num__selector__attribute_names': [['n_words'], ['mean_word_length'], ['n_words','mean_word_length']]} ]#GridSearchCV by default stratifies our cross-validation#and retrains model on the best set of hyperparametersmodel = GridSearchCV(pipeline,params,scoring='balanced_accuracy',cv=5)model.fit(X_train, y_train)#display all of the results of the grid searchprint(model.cv_results_)#display the mean scores for each combination of hyperparametersprint(model.cv_results_[model.cv_results_['mean_test_score']])
參數網格被定義為一個字典,鍵是超參數,值是要搜索的值的列表。然後將其與複合估計數器一起傳遞給GridSearchCV,並將其與訓練數據相匹配。
我們的複合估計器總共有65個可調參數,但是,這裡只改變了兩個參數:使用的數字列和CountVectorizer的max_features參數,該參數設置詞彙表中使用的單詞的最大數量。在代碼中,你可以看到如何獲得所有可用超參數的列表。下面是繪製在超參數空間上的平均平衡精度的可視化圖。
當我們只使用一個數字列nwords並使用詞彙表中的所有單詞(即maxfeatures = None)時,可以獲得最佳性能。在交叉驗證期間,該模型的平衡精度為0.94,在測試集上評估時為0.93。注意,如果你自己運行筆記本,確切的數字可能會改變。
在超參數網格上繪製了平衡精度圖,顯示了模型性能如何在超參數空間上變化。
總結
我們已經討論了很多,特別是,如何通過設置一個複合評估器來自動化整個建模過程,複合評估器是包含在單個管道中的一系列轉換和評估器。這不僅是一個很好的實踐,而且是搜索大型超參數空間的唯一可行方法,在處理複合特徵空間時經常出現這種情況。我們看到了將文本數據與數字數據組合在一起的示例,但是對於任何數據類型都可以很容易地遵循相同的過程,從而使你能夠更快、更有效地工作。
作者:Oliver Batey
deephub翻譯組