1. 程式人生 > >【機器學習】模型訓練前夜—資料集預處理(概念+圖+實戰)

【機器學習】模型訓練前夜—資料集預處理(概念+圖+實戰)

本文程式碼推薦使用Jupyter notebook跑,這樣得到的結果更為直觀。

缺失資料處理:


# 顯示資料的缺失值
import pandas as pd
from io import StringIO

csv_data = '''A,B,C,D
1.0,2.0,3.0,4.0
5.0,6.0,,8.0
10.0,11.0,12.0,''':
# csv_data = unicode(csv_data)
df = pd.read_csv(StringIO(csv_data))
print(df)
# 顯示每列的缺失值數量
df.isnull().sum()


刪除存在缺失值的特徵或者樣本:

# 刪除資料集中包含缺失值的行
df.dropna()


# 刪除資料集中至少包含一個NAN的列,axis=1。
df.dropna(axis=1)


# 只在所有列都為NaN的地方刪除行。
df.dropna(how='all')  


# 刪除沒有至少4個非nan值的行。
df.dropna(thresh=4)


# 只有當NaN出現在特定列(這裡:“C”)時,才會刪除行。
df.dropna(subset=['C'])


插值技術:處理資料缺失

最常用的插值技術:均值插補,使用相應的特徵均值來替換缺失值

from sklearn.preprocessing import Imputer

imr = Imputer(missing_values='NaN', strategy='mean', axis=0)
imr = imr.fit(df)
imputed_data = imr.transform(df.values)
print(df.values)


print(imputed_data)

Imputer類的fit方法:對資料集中的引數進行識別並構建相應的資料補齊模型

Imputer類的transform方法:使用剛構建的資料補齊模型對資料集中相應引數的缺失值進行補齊。

資料補齊需要保持維度相同



處理類別資料:

類別資料分為:標稱特徵、有序特徵

標稱特徵:不具備排序的特性

有序特徵:特徵為有序的或可排序的


例子:


import pandas as pd
df = pd.DataFrame([
            ['green', 'M', 10.1, 'class1'], 
            ['red', 'L', 13.5, 'class2'], 
            ['blue', 'XL', 15.3, 'class1']])

df.columns = ['color', 'size', 'price', 'classlabel']
print(df)


有序特徵的對映:

類別字串轉整數:


size_mapping = {
           'XL': 3,
           'L': 2,
           'M': 1}

df['size'] = df['size'].map(size_mapping)
print(df)


類標編碼:

類標不是有序的

特定的字串類標,賦予的具體整數值不重要,一般以列舉的方式從0開始設定類標


import numpy as np
class_mapping = {label:idx for idx,label in enumerate(np.unique(df['classlabel']))}
class_mapping

# 使用對映字典將類標轉為整數
df['classlabel'] = df['classlabel'].map(class_mapping)
print(df)

# 將字典鍵值對倒置,還原為原始資料
inv_class_mapping = {v: k for k, v in class_mapping.items()}
df['classlabel'] = df['classlabel'].map(inv_class_mapping)
print(df)

# 使用SKlearn的LabelEncoder類可以快捷的操作整數編碼
from sklearn.preprocessing import LabelEncoder

class_le = LabelEncoder()
y = class_le.fit_transform(df['classlabel'].values)
print (y)
class_le.inverse_transform(y)

標稱特徵上的獨熱編碼

獨熱編碼技術:建立一個新的虛擬特徵,虛擬特徵的每一列各代表標稱標稱資料的一個值。

# 使用OneHotEncoder類實現
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(categorical_features=[0])
ohe.fit_transform(X).toarray()

# 使用pandas的get_dummies實現
pd.get_dummies(df[['price', 'color', 'size']])



將資料集劃分為訓練集和測試資料集:

使用pandas,線上從UCI機器學習樣本資料庫讀取開源葡萄酒資料集。


df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)

df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash', 
'Alcalinity of ash', 'Magnesium', 'Total phenols', 
'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins', 
'Color intensity', 'Hue', 'OD280/OD315 of diluted wines', 'Proline']

print('Class labels', np.unique(df_wine['Class label']))
df_wine.head()


# 使用SKlearn下的model_selection模組中的train_test_split函式
from sklearmodel_selection import train_test_split
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values

X_train, X_test, y_train, y_test = \
        train_test_split(X, y, test_size=0.3, random_state=0)

將陣列中1-13個特徵賦值給X,第一列的類標賦值給變數y。

隨機將X和y各自劃分為訓練集合測試集,

test_size=0.3表示將30%的樣本劃分到X_test 和y_test,剩餘的劃分給X_train和y_train。


劃分訓練集和測試集要儘量保留有價值的資訊

一般的資料量的資料集劃分法為:60/40,70/30,80/20

大資料量的資料集劃分為:90/10,99/1



將特徵值縮放到相同的區間:

特徵縮放:資料預處理過程中至關重要的一步,為其保證模型的效能最佳。

將不同的特徵縮放到相同的區間歸一化標準化

歸一化:特徵值縮放到[0,1]區間最小到最大縮放的特例


x為特定樣本,x min和x max分別為某特徵列的最小值和最大值

案例:

from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
X_train_norm = mms.fit_transform(X_train)
X_test_norm = mms.transform(X_test)

標準化:特徵列的均值設為0,方差為1,使得特徵列的值呈標準正態分佈易於權重更新保持了異常值所蘊含的有用資訊,使得演算法受到這些值影響最小


σ和μ分別為某個特徵列的均值和標準差。

案例:

from sklearn.preprocessing import StandardScaler
stdsc = StandardScaler()
X_train_std = stdsc.fit_transform(X_train)
X_test_std = stdsc.transform(X_test)



選擇有意義的特徵值

過擬合(高方差):

模型在訓練集是的表現比測試集上好很多,過擬合於訓練資料。

       模型引數對於訓練集的特定觀測值擬合得非常接近

產生的原因:建立給定訓練集上的模型過於複雜

常用降低泛化誤差的方案:

1、       收集更多的訓練資料

2、       正則化引入罰項

3、       選擇相對較少的簡單模型

4、       降低資料的維度


使用L1正則化滿足資料稀疏化

L1降低模型複雜度



將權重的平方和用絕對值和來代替

L1正則化可以生成稀疏的特徵向量,且大多數權值為0

通過正則化引數來增加正則化的強度,使得權值向0收縮,降低模型對訓練集的依賴程度

L2的罰項是二次的





SKlearn實現L1正則化程式碼:

from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(penalty='l1', C=0.1)
lr.fit(X_train_std, y_train)
print('Training accuracy:', lr.score(X_train_std, y_train))
print('Test accuracy:', lr.score(X_test_std, y_test))

# 訓練和測試的精確度表示未出現過擬合

# 獲得截距項

lr.intercept_
lr.coef_




# 繪製正則化效果圖
import matplotlib.pyplot as plt
%matplotlib inline

fig = plt.figure()
ax = plt.subplot(111)
    
colors = ['blue', 'green', 'red', 'cyan', 
         'magenta', 'yellow', 'black', 
          'pink', 'lightgreen', 'lightblue', 
          'gray', 'indigo', 'orange']

weights, params = [], []
for c in np.arange(-4, 6):
    lr = LogisticRegression(penalty='l1', C=10**c, random_state=0)
    lr.fit(X_train_std, y_train)
    weights.append(lr.coef_[1])
    params.append(10**c)

weights = np.array(weights)

for column, color in zip(range(weights.shape[1]), colors):
    plt.plot(params, weights[:, column],
             label=df_wine.columns[column+1],
             color=color)
plt.axhline(0, color='black', linestyle='--', linewidth=3)
plt.xlim([10**(-5), 10**5])
plt.ylabel('weight coefficient')
plt.xlabel('C')
plt.xscale('log')
plt.legend(loc='upper left')
ax.legend(loc='upper center', 
          bbox_to_anchor=(1.38, 1.03),
          ncol=1, fancybox=True)
# plt.savefig('./figures/l1_path.png', dpi=300)
plt.show()


C是正則化引數的倒數。



序列特徵選擇演算法

降低模型複雜度從而解決過擬合的方法是通過特徵選擇進行降維

對未經正則化處理的模型特別有效

降維技術主要分為:特徵降維特徵提取

序列特徵選擇演算法是一種貪婪演算法將原始的d維特徵空間壓縮到一個k維的特徵子空間

經典的序列特徵選擇演算法:序列後向選擇演算法(SBS)

目的:在分類效能衰減最小的約束下,降低原始特徵空間上的資料維度,提高計算效率。

SBS可以在模型面臨過擬合問題時提高模型的預測能力

SBS演算法理念:SBS依次從特徵集合中刪除某些特徵,直到新的子特徵包含指定數量的特徵

為了確定每一步所需刪除的特徵,需要定義一個最小化的標準衡量函式。

函式準則:比較判定分類器的效能在刪除某個特定特徵前後的差異

由此可知,每一步待刪除的特徵,就是那些能夠使得函式儘可能大的特徵。

演算法步驟:

1、       設k=d進行演算法初始化,d是特徵空間Xd的維度

2、       定義x為滿足標準x=argmax(Xk-x)最大化特徵

3、       將特徵x從特徵集中刪除:X(k-1)=Xk-x,k=k-1

4、       如果k等於目標特徵數量,演算法終止,否則跳到2步。


python實現SBS演算法:

from sklearn.base import clone
from itertools import combinations
import numpy as np
from sklearn.cross_validation import train_test_split
from sklearn.metrics import accuracy_score

class SBS():
    def __init__(self, estimator, k_features, scoring=accuracy_score,
                 test_size=0.25, random_state=1):
        self.scoring = scoring
        self.estimator = clone(estimator)
        self.k_features = k_features
        self.test_size = test_size
        self.random_state = random_state

    def fit(self, X, y):
        
        X_train, X_test, y_train, y_test = \
                train_test_split(X, y, test_size=self.test_size, 
                                 random_state=self.random_state)

        dim = X_train.shape[1]
        self.indices_ = tuple(range(dim))
        self.subsets_ = [self.indices_]
        score = self._calc_score(X_train, y_train, 
                                 X_test, y_test, self.indices_)
        self.scores_ = [score]

        while dim > self.k_features:
            scores = []
            subsets = []

            for p in combinations(self.indices_, r=dim-1):
                score = self._calc_score(X_train, y_train, 
                                         X_test, y_test, p)
                scores.append(score)
                subsets.append(p)

            best = np.argmax(scores)
            self.indices_ = subsets[best]
            self.subsets_.append(self.indices_)
            dim -= 1

            self.scores_.append(scores[best])
        self.k_score_ = self.scores_[-1]

        return self

    def transform(self, X):
        return X[:, self.indices_]

    def _calc_score(self, X_train, y_train, X_test, y_test, indices):
        self.estimator.fit(X_train[:, indices], y_train)
        y_pred = self.estimator.predict(X_test[:, indices])
        score = self.scoring(y_test, y_pred)
        return score

實現SBS應用於SKlearn中KNN分類器:

%matplotlib inline
from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt

knn = KNeighborsClassifier(n_neighbors=2)

# selecting features
sbs = SBS(knn, k_features=1)
sbs.fit(X_train_std, y_train)

# plotting performance of feature subsets
k_feat = [len(k) for k in sbs.subsets_]

plt.plot(k_feat, sbs.scores_, marker='o')
plt.ylim([0.7, 1.1])
plt.ylabel('Accuracy')
plt.xlabel('Number of features')
plt.grid()
plt.tight_layout()
# plt.savefig('./sbs.png', dpi=300)
plt.show()

檢視演算法正確率達到100%的特徵:

k5 = list(sbs.subsets_[8])
print(df_wine.columns[1:][k5])


驗證KNN分類器在原始測試集上的表現:

knn.fit(X_train_std, y_train)
print('Training accuracy:', knn.score(X_train_std, y_train))
print('Test accuracy:', knn.score(X_test_std, y_test))


在選定的5個特徵集看KNN效能:

knn.fit(X_train_std[:, k5], y_train)
print('Training accuracy:', knn.score(X_train_std[:, k5], y_train))
print('Test accuracy:', knn.score(X_test_std[:, k5], y_test))


當特徵數量不及葡萄酒資料集原始資料特徵數量一半時,測試集上的預測準確率提高。

SKlearn裡有許多特徵選擇演算法:基於特徵權重的遞迴後向消除演算法基於特徵重要性的特徵選擇樹方法單變數統計方法




通過隨機森林判定特徵的重要性:

from sklearn.ensemble import RandomForestClassifier

feat_labels = df_wine.columns[1:]

forest = RandomForestClassifier(n_estimators=10000,
                                random_state=0,
                                n_jobs=-1)

forest.fit(X_train, y_train)
importances = forest.feature_importances_

indices = np.argsort(importances)[::-1]

for f in range(X_train.shape[1]):
    print("%2d) %-*s %f" % (f + 1, 30, 
                            feat_labels[f], 
                            importances[indices[f]]))

plt.title('Feature Importances')
plt.bar(range(X_train.shape[1]), 
        importances[indices],
        color='lightblue', 
        align='center')

plt.xticks(range(X_train.shape[1]), 
           feat_labels, rotation=90)
plt.xlim([-1, X_train.shape[1]])
plt.tight_layout()
# plt.savefig('./figures/random_forest.png', dpi=300)
plt.show()