大數據比賽(2)-特徵那點兒事

特徵工程是一個非常重要的課題,是機器學習中不可或缺的一部分,但是它幾乎很少出現於單獨的機器學習的教程或教材中,所以需要在比賽的過程中多學習和體會。

1. 什麼是特徵工程?

“特徵工程是將原始數據轉化爲特徵,更好表示預測模型處理的實際問題,提升對於未知數據的準確性。它是用目標問題所在的特定領域知識或者自動化的方法來生成、提取、刪減或者組合變化得到特徵。”

通俗的講,特徵工程包括特徵抽取特徵選擇兩部分,在我看來所謂
特徵抽取就是通過 整合、預處理和轉化 已有數據來生成特徵;
特徵選擇就是在生成的特徵中選擇部分特徵來減少特徵數量、降維,並且使模型泛化能力更強,減少過擬合,有時還可以增強對特徵和特徵值之間的理解(其實就是在不熟悉的領域問題中找到比較好的特徵)。

“數據和特徵決定了機器學習的上限,而模型和算法只是逼近這個上限而已”,從數據裏面提取出來的特徵好壞與否直接影響模型的效果,對比賽大神來說,“數據決定了結果的上限,而算法只是儘可能逼近這個上限。”給定的數據決定了他們成績的上限,對我這類菜鳥來說:“特徵決定了你在排行榜的第幾頁”

在比賽中通常提供的是表格數據,觀測數據或實例(對應表格的一行)由不同的變量或者屬性(表格的一列)構成,這裏屬性其實就可以用作特徵。但是特徵是對於分析和解決問題有用、有意義的屬性,最初的原始特徵數據集可能太大,或者信息冗餘,因此初始步驟就是選擇特徵的子集,或構建一套新的特徵集來提高算法的學習效果,提高泛化能力和可解釋性。
可以選擇一種自己最熟悉或者最方便的特徵選擇方法先進行初步的篩選,再依據實際問題進一步優化和篩選。

2. 什麼樣的特徵是有效的?

特徵是否有效的直接評價就是與結果的“相關性”。在比賽中,“準確率”是否提高是唯一要求,在實際中可能要考慮獲取難度、實際意義等問題。
先推薦幾篇文章:

【1】乾貨:結合Scikit-learn介紹幾種常用的特徵選擇方法
http://dataunion.org/14072.html?utm_source=tuicool&utm_medium=referral
【2】Discover Feature Engineering, How to Engineer Features and How to Get Good at It
http://machinelearningmastery.com/discover-feature-engineering-how-to-engineer-features-and-how-to-get-good-at-it/

直接貼他們的文章其實就可以,不過還是結合自己的一點小經驗再整理一下,代碼部分均來自文獻【1】

(1)去掉取值變化小的特徵 Removing features with low variance
其實更像數據預處理過程,就是在大部分數據都取某一值的時候,將幾個缺失值同樣填充,或者特異值變換,也可以粗暴一點直接刪除該特徵.在Titanic比賽中,Embarked直觀上與存活率關係不大,觀察發現S市644人,空白2人,以多數填充方式處理.

(2)單變量特徵選擇 Univariate feature selection
對每一個特徵進行測試,衡量該特徵和響應變量之間的關係.
常用的手段有計算皮爾遜係數和互信息係數,皮爾遜係數只能衡量線性相關性而互信息係數能夠很好地度量各種相關性,但是計算相對複雜一些,【1】中有詳細的介紹,需要scipy和minepy包的依賴,而且佔用較多的內存。

   xl = np.array(train_feature[:100]).shape[1]
   for i in range(xl):
     print pearsonr(np.array(train_feature)[:, i:i+1], np.array(train_y))

其實更加方便的是使用基於學習模型的特徵排序 (Model based ranking),即直接使用你要用的機器學習算法,針對每個單獨的特徵和響應變量建立預測模型。

scores = []
        for i in range(np.array(train_feature).shape[1]):
            score = cross_val_score(rf, np.array(train_feature)[:, i:i+1], np.array(train_y), scoring="r2",
                              cv=ShuffleSplit(len(np.array(train_feature)), 3, .3))
            scores.append((round(np.mean(score), 3),i))
        print sorted(scores, reverse=True)       

在天池的某比賽中採用這種方法,獲得的結果:

[(0.71, 25), (0.705, 27), (0.696, 28), (0.644, 14), (0.642, 16), (0.622, 15), (0.486, 12), (0.485, 9), (0.452, 11), (0.406, 26), (0.398, 7), (0.346, 6), (0.335, 3), (0.261, 13), (0.261, 8), (0.254, 2), (0.226, 22), (0.176, 10), (0.169, 18), (0.167, 4), (0.165, 23), (0.15, 19), (0.121, 0), (0.118, 1), (0.098, 5), (0.092, 21), (0.086, 17), (0.061, 20), (0.052, 24)]

隨機森林提供了兩種特徵選擇的方法:平均不純度減少 mean decrease impurity和 平均精確率減少 Mean decrease accuracy
平均不純度減少:當訓練決策樹的時候,可以計算出每個特徵減少了多少樹的不純度。對於一個決策樹森林來說,可以算出每個特徵平均減少了多少不純度,並把它平均減少的不純度作爲特徵選擇的值。
需要注意的是:只有先被選中的那個特徵重要度很高,其他的關聯特徵重要度往往較低,因爲一旦某個特徵被選擇之後,其他特徵的重要度就會急劇下降,但實際上這些特徵對響應變量的作用可能是非常接近的。

rf = RandomForestRegressor()
rf.fit(X, Y)
print "Features sorted by their score:"
print sorted(zip(map(lambda x: round(x, 4), rf.feature_importances_), names), 
             reverse=True)

在天池的某比賽中採用這種方法,獲得的結果:

[(0.3268, 18), (0.2872, 17), (0.1429, 15), (0.0236, 12), (0.0168, 16), (0.0165, 14), (0.0161, 4), (0.0143, 11), (0.0138, 10), (0.0132, 9), (0.0126, 13), (0.0125, 1), (0.0118, 8), (0.0116, 0), (0.0108, 20), (0.0103, 22), (0.0099, 21), (0.009, 2), (0.0086, 19), (0.0084, 7), (0.0082, 6), (0.0079, 5), (0.0073, 3)]

平均精確率減少:直接度量每個特徵對模型精確率的影響。主要思路是打亂每個特徵的特徵值順序,並且度量順序變動對模型的精確率的影響,需要自己手寫:

from sklearn.cross_validation import ShuffleSplit
from sklearn.metrics import r2_score
from collections import defaultdict
X = boston["data"]
Y = boston["target"]
rf = RandomForestRegressor()
scores = defaultdict(list)
#crossvalidate the scores on a number of different random splits of the data
for train_idx, test_idx in ShuffleSplit(len(X), 100, .3):
  X_train, X_test = X[train_idx], X[test_idx]
  Y_train, Y_test = Y[train_idx], Y[test_idx]
  r = rf.fit(X_train, Y_train)
  acc = r2_score(Y_test, rf.predict(X_test))
  for i in range(X.shape[1]):
    X_t = X_test.copy()
    np.random.shuffle(X_t[:, i])
    shuff_acc = r2_score(Y_test, rf.predict(X_t))
    scores[names[i]].append((acc-shuff_acc)/acc)
print "Features sorted by their score:"
print sorted([(round(np.mean(score), 4), feat) for
        feat, score in scores.items()], reverse=True)

(3)線性模型和正則化
有些機器學習方法本身就具有對特徵進行打分的機制,或者很容易將其運用到特徵選擇任務中,例如迴歸模型,SVM,決策樹,隨機森林等等
1、用迴歸模型的係數來選擇特徵
越是重要的特徵在模型中對應的係數就會越大,如果特徵之間相對來說是比較獨立的,那麼即便是運用最簡單的線性迴歸模型也一樣能取得非常好的效果.

def pretty_print_linear(coefs, names = None, sort = False):
  if names == None:
    names = ["X%s" % x for x in range(len(coefs))]
  lst = zip(coefs, names)
  if sort:
    lst = sorted(lst,  key = lambda x:-np.abs(x[0]))
  return " + ".join("%s * %s" % (round(coef, 3), name)
                   for coef, name in lst)

lr = LinearRegression()
lr.fit(X,Y)
print "Linear model:", pretty_print_linear(lr.coef_)

2、正則化模型
所謂正則化,就是把額外的約束或者懲罰項加到已有模型(損失函數)上,以防止過擬合併提高泛化能力。一般的,添加正則化項後,損失函數由原來的E(X,Y)變爲E(X,Y)+alpha||w||,其中w是模型係數組成的向量(有些地方也叫參數parameter,coefficients),||·||表示是L1或者L2範數,alpha控制着正則化的強度,alpha過大容易欠擬合,過小容易過擬合。(當用在線性模型上時,L1正則化和L2正則化也稱爲Lasso和Ridge。)

L1: 向量中各個元素的絕對值之和 =》非零元素較少 =》稀疏化
L2: 向量中各個元素的平方和的開方 =》每個元素都很小=》防止過擬合
L1正則化將係數w的L1範數作爲懲罰項加到損失函數上,由於正則項非零,這就迫使那些弱的特徵所對應的係數變成0,可以用作特徵選擇(無信息的特徵自動歸零),還可以提高可解釋性。

from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_boston

boston = load_boston()
scaler = StandardScaler()
X = scaler.fit_transform(boston["data"])
Y = boston["target"]
names = boston["feature_names"]

lasso = Lasso(alpha=.3)#通過grid search優化
lasso.fit(X, Y)

print "Lasso model: ", pretty_print_linear(lasso.coef_, names, sort = True)

L2懲罰項中係數是二次方的,會讓係數的取值變得平均,可以提高泛化能力,防止過擬合。

from sklearn.linear_model import Ridge
from sklearn.metrics import r2_score
size = 100
#We run the method 10 times with different random seeds
for i in range(10):
  print "Random seed %s" % i
  np.random.seed(seed=i)
  X_seed = np.random.normal(0, 1, size)
  X1 = X_seed + np.random.normal(0, .1, size)
  X2 = X_seed + np.random.normal(0, .1, size)
  X3 = X_seed + np.random.normal(0, .1, size)
  Y = X1 + X2 + X3 + np.random.normal(0, 1, size)
  X = np.array([X1, X2, X3]).T
  lr = LinearRegression()
  lr.fit(X,Y)
  print "Linear model:", pretty_print_linear(lr.coef_)
  ridge = Ridge(alpha=10)
  ridge.fit(X,Y)
  print "Ridge model:", pretty_print_linear(ridge.coef_)
  print

先說結論:在比賽中,特徵選擇是十分有效的,但不一定能讓你做到更好。
一般的數據比賽,如果只是簡單的將上述方法用於給定的數據表,基本上都會有一個小的提升。但想要衝擊更高的名次,還需要在特徵構造(依據比賽數據的特點構造更多的相關特徵),模型構造(不是機器學習模型的選擇,而是對【x】,【y】的構造與選擇,train、dev、test的構造與選擇)上面下工夫,畢竟特徵選擇也只是在已有的基礎上進行的優化,很難產生質變。作爲一個學弱和菜鳥對此也沒有太好的辦法,下一篇可能對機器學習模型的選擇和參數調整稍作總結。以下對比賽中的數據及特徵處理流程進行簡單梳理:

1、樣本預處理:
(1)數據可能部分噪音,可以根據對數據的瞭解,過濾掉錯誤樣本,並且根據業務等邏輯制定filter,篩選樣本。比如年齡此特徵有許多負值及幾百的異常值,若我們判斷年齡爲關鍵特徵,則可通過規則將這些異常值樣本去掉。
實現tips:
1》python pandas可以查找異常值並且容易填充和修改
2》excel本身就是很好的數據處理軟件,其篩選和統計的功能不容小覷,曾經有人只用excel就完成了數據的全部處理和可視化工作
(2)在訓練時儘量用同量級的樣本,使其儘量均勻。特別是對樣本數少的label,可以通過抽樣來提高其比重。
(3)將部分原始數據做離散化、規範化、二值化、分箱等操作,以期更加適應機器學習模型,進一步提高精度。
tips:推薦寒小陽童鞋的博客:
機器學習系列(6)_從白富美相親看特徵預處理與選擇(下)
http://blog.csdn.net/han_xiaoyang/article/details/50503115
機器學習系列(3)_邏輯迴歸應用之Kaggle泰坦尼克之災
http://blog.csdn.net/han_xiaoyang/article/details/49797143
python對離散化、規範化都有相應的實現工具,但在實際的使用中部分規範化的方法不一定適用與所有的數據,應當儘量在神經網絡等需要該類數據的前提下使用。
(4)對原始數據中存在缺失值的處理有時會對結果產生很大的影響,對於不太重要的特徵採取“捨去”的方式,但重要的特徵需要對缺失值進行替換或賦值(對缺失值進行預測)

2、特徵調整:
(1)通過特徵組合增加衍生變量,根據業務理解對不同特徵組合,產生新特徵來進行擴維。
(2)特徵篩選,依據以上提到的特徵選擇方法進一步選擇優秀的特徵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章