完整代碼: https://github.com/cindycindyhi/kaggle-Titanic
特征工程系列:
為什么有的機器學習項目成功了有的卻失敗了呢?畢竟算法是有限的改進也是有限的,最主要的因素就是特征的選擇了。如果我們有一些與類別非常相關同時又相互獨立的特征,學習起來是很容易的,相反就不一定了。通常情況下,並不是直接把原始數據作為特征,而是從中構建一些特征。這是機器學習中的主要工作。在這一步驟中,通常直覺、創造性、魔法和技術一樣重要。
當然,機器學習的一個終極目標就是將特征工程過程越來越多地自動化。現在經常采用的一種方式是先自動產生大量的候選特征,然后根據它們與分類類別的信息增益等方法來選取最好的特征。但是,運行包含大量特征的學習器來尋找有用的特征組合太耗時,也容易導致過擬合。還是需要人為的介入特征的選擇中。
什么是派生屬性呢?派生屬性就是從原始數據中得到的一些屬性,比如上一節從Age屬性經過Factorize得到的Age_bin屬性就是一個派生屬性,當然這種派生只是非常簡單的派生。為什么要對這些屬性做各種各樣的統計和處理呢,這其實是特征工程的一部分,先構建足夠多可能會對結果有意義的屬性,然后再從這些候選集中選擇我們想要的特征。特征工程非常繁瑣,但是對數據挖掘非常重要,一般來說,做一個數據挖掘項目,百分之八十的努力要用在特征工程上。除了基本的轉換和interaction屬性,我們也要創造性的從原始屬性中發現新屬性。比如電話號碼,可以從中提取出來國家和區域特征。
一 研究業務邏輯提取特征
Titanic的數據集相對較為簡單,但是對於一些字符串類型的屬性,比如Name我們可以從中提取出來一些可以揭示其社會地位的稱號。
名字的長度也會代表一個人的社會地位,社會地位高的人可能會更容易得到救生船。
1 df['Names'] = df['Name'].map(lambda x: len(re.split(' ',x)))
對於名字中間的爵位,可以看到稱號有Mr Mrs Master等,經過統計可以看到有以下幾種稱號:
這些稱號有法語還有英語,需要依據當時的文化環境將其歸類,如何將其歸類可以參考這篇文章trevorstephens.com/post/73461351896/titanic-getting-started-with-r-part-4-feature
1 df['Title'] = df['Name'].map(lambda x: re.compile(",(.*?)\.").findall(x)[0]) 2 df['Title'][df.Title=='Jonkheer'] = 'Master' 3 df['Title'][df.Title.isin(['Ms','Mlle'])] = 'Miss' 4 df['Title'][df.Title == 'Mme'] = 'Mrs' 5 df['Title'][df.Title.isin(['Capt', 'Don', 'Major', 'Col', 'Sir'])] = 'Sir' 6 df['Title'][df.Title.isin(['Dona', 'Lady', 'the Countess'])] = 'Lady' 7 df['Title_id'] = pd.factorize(df.Title)[0]+1
對於Ticket屬性也需要處理,可以看到Ticket字段有的全是數字有的是字母和數字的集合,進一步對數據分析發現約25%的數據有前綴,前綴共有45種,如果把 . 和 / 去掉的話還剩29種,數字部分也有一定的規律:以1開頭的一般是一等艙2開頭的是二等艙3開頭的是三等艙,4-9開頭的大都是三等艙。以上這些數據告訴我們處理Ticket是有意義的,能夠發現其內部蘊涵的信息。
1 def processTicket(): 2 global df 3 df['TicketPrefix'] = df['Ticket'].map(lambda x: getTicketPrefix(x.upper())) 4 df['TicketPrefix'] = df['TicketPrefix'].map(lambda x: re.sub\ 5 ('[\.?\/?]','',x)) 6 df['TicketPrefix'] = df['TicketPrefix'].map(lambda x:re.sub\ 7 ('STON','SOTON',x)) 8 df['TicketPrefix'] = pd.factorize(df['TicketPrefix'])[0] 9 df['TicketNumber'] = df['Ticket'].map(lambda x: getTicketNumber(x) ) 10 df['TicketNumberLength'] = df['TicketNumber'].map(lambda x: len(x)).\ 11 astype(int) 12 df['TicketNumberStart'] = df['TicketNumber'].map(lambda x: x[0:1]).\ 13 astype(int) 14 df['TicketNumber'] = df['TicketNumber'].astype(int) 15 def getTicketPrefix(ticket): 16 match = re.compile("([a-zA-Z\.\/]+)").search(ticket) 17 if match: 18 return match.group() 19 else: 20 return 'U' 21 def getTicketNumber(ticket): 22 match = re.compile("([0-9]+$)").search(ticket) 23 if match: 24 return match.group() 25 else: 26 return '0'
二 簡單組合屬性提取特征
一些屬性可以從它本身的數據里提取一些信息,有些屬性則需要和其他屬性組合來產生信息。比如對淘寶上的一個商品來說,購買數/點擊率可以反應商品的轉化率,也是商品的一個非常重要的特征。
對於Titanic來說,我們用Age*Pclass組合產生一個屬性,雖然沒有一個名詞來解釋它,但是從結果數據上來看,我們增大了年紀大的人的權重也提高了高等艙的權重,從最后幸存的結果上看,這個組合還是有意義的。除了這兩個屬性之外,我們還可以對其他數值屬性進行數學運算,以得到更大的候選特征集。
1 numerics = df.loc[:, ['Age_scaled', 'Fare_scaled', 'Pclass_scaled', 'Parch_scaled', 'SibSp_scaled', 2 'Names_scaled', 'CabinNumber_scaled', 'Age_bin_id_scaled', 'Fare_bin_id_scaled']] 3 print "\nFeatures used for automated feature generation:\n", numerics.head(10) 4 5 new_fields_count = 0 6 for i in range(0, numerics.columns.size-1): 7 for j in range(0, numerics.columns.size-1): 8 if i <= j: 9 name = str(numerics.columns.values[i]) + "*" + str(numerics.columns.values[j]) 10 df = pd.concat([df, pd.Series(numerics.iloc[:,i] * numerics.iloc[:,j], name=name)], axis=1) 11 new_fields_count += 1 12 if i < j: 13 name = str(numerics.columns.values[i]) + "+" + str(numerics.columns.values[j]) 14 df = pd.concat([df, pd.Series(numerics.iloc[:,i] + numerics.iloc[:,j], name=name)], axis=1) 15 new_fields_count += 1 16 if not i == j: 17 name = str(numerics.columns.values[i]) + "/" + str(numerics.columns.values[j]) 18 df = pd.concat([df, pd.Series(numerics.iloc[:,i] / numerics.iloc[:,j], name=name)], axis=1) 19 name = str(numerics.columns.values[i]) + "-" + str(numerics.columns.values[j]) 20 df = pd.concat([df, pd.Series(numerics.iloc[:,i] - numerics.iloc[:,j], name=name)], axis=1) 21 new_fields_count += 2 22 23 print "\n", new_fields_count, "new features generated"
這個過程自動產生大量的特征,這里用了9個特征產生了176個特征,可能這些特征有些過於多了,但是它只是一個候選集,我們可以通過一些處理篩選掉一些特征。當然有些模型也很適合大量特征的訓練集,比如隨機森林(有論文驗證,隨機森林是分類算法中表現最好的模型)。
產生的這些特征可能高度相關於原始特征,線性模型處理這類特征時會產生multicollinearity問題,可以用計算這些特征的皮爾遜相關系數,篩選相關性特征。如果用隨機森林模型訓練的話,可以不需要這個步驟。
三 用PCA進行維歸約
通過上面三個部分的處理,我們得到了具有大量特征的維度很高的數據集,特征較多不能直接用來作為模型輸入,一是因為這些特征間具有多重共線性,可能 會導致空間的不穩定;二是因為高維空間本身具有稀疏性,一維正態分布有68%的值落於正負標准差之間,而在十維空間上只有0.02%;三是由於過多的屬性 會使挖掘需要很長時間。對於一些模型來說,比如使用L1(Lasso),當有大量屬性時效果很好,因為它可以有效忽略掉噪聲變量。而一些模型則容易過擬 合。
數據歸約技術可以用來得到數據集的規約表示,它小得多,但仍接近於保持原始數據的完整性。也就是說,在歸約后的數據集上進行數據挖掘將更加有效,仍然產生幾乎相同的數據分析結果。
PCA(主成份分析)是一種維歸約的方法,它搜索k個最能代表數據的n維正交向量,將原始數據投影到一個小的多的空間上,導致維歸約。PCA通過創建一個替換的較小的變量集組合屬性的基本要素。具體原理及python的實現過程可以參考這篇blog Implementing a Principal Component Analysis (PCA) in Python step by step
我們可以直接使用scikit-learn的PCA函數進行維規約
1 X = df.values[:, 1::] 2 y = df.values[:, 0] 3 variance_pct = .99 4 # Create PCA object 5 pca = PCA(n_components=variance_pct) 6 # Transform the initial features 7 X_transformed = pca.fit_transform(X,y) 8 # Create a data frame from the PCA'd data 9 pcaDataFrame = pd.DataFrame(X_transformed)
實驗發現,PCA對於線性模型(不使用Lasso)非常有效,但是對於隨機森林模型沒有提高。