有時,機器學習模型的可能配置即使沒有上千種,也有數百種,這使得手工找到最佳配置的可能性變得不可能,因此自動化是必不可少的。在處理複合特徵空間時尤其如此,在複合特徵空間中,我們希望對資料集中的不同特徵應用不同的轉換。一個很好的例子是將文字文件與數字資料相結合,然而,在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 dataframe class 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類。整個管道結構如圖所示:
管道示意圖。整個物件(稱為複合估計器)可以用作模型;所有的轉換器和估計器物件以及它們的引數,都成為我們模型的超引數。
工作流程如下
1. 一系列文件進入管道,CountWords和MeanWordLength在管道中建立兩個名為nwords和meanword_length的數字列。
1. 文字列被傳遞給CountVectorizer,而nwords和meanword_length首先透過FeatureSelector,然後是SimpleImputer。
1. 轉換後的資料集被傳遞給估計器物件。
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。注意,如果你自己執行筆記本,確切的數字可能會改變。
在超引數網格上繪製了平衡精度圖,顯示了模型效能如何在超引數空間上變化。
總結我們已經討論了很多,特別是,如何透過設定一個複合評估器來自動化整個建模過程,複合評估器是包含在單個管道中的一系列轉換和評估器。這不僅是一個很好的實踐,而且是搜尋大型超引數空間的唯一可行方法,在處理複合特徵空間時經常出現這種情況。我們看到了將文字資料與數字資料組合在一起的示例,但是對於任何資料型別都可以很容易地遵循相同的過程,從而使你能夠更快、更有效地工作。