天天看點

Machine Learning | (1) Scikit-learn與特征工程

Machine Learning | 機器學習簡介

Machine Learning | (1) Scikit-learn與特征工程

Scikit-learn與特征工程

“資料決定了機器學習的上限,而算法隻是盡可能逼近這個上限”,這句話很好的闡述了資料在機器學習中的重要性。大部分直接拿過來的資料都是特征不明顯的、沒有經過處理的或者說是存在很多無用的資料,那麼需要進行一些特征處理,特征的縮放等等,滿足訓練資料的要求。

将初次接觸到Scikit-learn這個機器學習庫的使用

Machine Learning | (1) Scikit-learn與特征工程

Scikit-learn

Python語言的機器學習工具

所有人都适用,可在不同的上下文中重用

基于NumPy、SciPy和matplotlib建構

開源、商業可用 - BSD許可

目前穩定版本0.22

自2007年釋出以來,scikit-learn已經成為最給力的Python機器學習庫(library)了。scikit-learn支援的機器學習算法包括分類,回歸,降維和聚類。還有一些特征提取(extracting features)、資料處理(processing data)和模型評估(evaluating models)的子產品。作為Scipy庫的擴充,scikit-learn也是建立在Python的NumPy和matplotlib庫基礎之上。NumPy可以讓Python支援大量多元矩陣資料的高效操作,matplotlib提供了可視化工具,SciPy帶有許多科學計算的模型。     scikit-learn文檔完善,容易上手,豐富的API,使其在學術界頗受歡迎。開發者用scikit-learn實驗不同的算法,隻要幾行代碼就可以搞定。scikit-learn包括許多知名的機器學習算法的實作,包括LIBSVM和LIBLINEAR。還封裝了其他的Python庫,如自然語言處理的NLTK庫。另外,scikit-learn内置了大量資料集,允許開發者集中于算法設計,節省擷取和整理資料集的時間。

安裝的話參考下面步驟: 建立一個基于Python3的虛拟環境:

mkvirtualenv -p /usr/local/bin/python3.6 ml3
      

在ubuntu的虛拟環境當中運作以下指令

pip3 install Scikit-learn
      

然後通過導入指令檢視是否可以使用:

import sklearn
      

資料的特征工程

從資料中抽取出來的對預測結果有用的資訊,通過專業的技巧進行資料處理,是的特征能在機器學習算法中發揮更好的作用。優質的特征往往描述了資料的固有結構。 最初的原始特征資料集可能太大,或者資訊備援,是以在機器學習的應用中,一個初始步驟就是選擇特征的子集,或建構一套新的特征集,減少功能來促進算法的學習,提高泛化能力和可解釋性。

例如:你要檢視不同地域女性的穿衣品牌情況,預測不同地域的穿衣品牌。如果其中含有一些男性的資料,是不是要将這些資料給去除掉

特征工程的意義

更好的特征意味着更強的魯棒性

更好的特征意味着隻需用簡單模型

更好的特征意味着更好的結果

特征工程之特征處理

特征工程中最重要的一個環節就是特征處理,特征處理包含了很多具體的專業技巧

特征預處理

單個特征

歸一化

标準化

缺失值

多個特征

降維

PCA

特征工程之特征抽取與特征選擇

如果說特征處理其實就是在對已有的資料進行運算達到我們目标的資料标準。特征抽取則是将任意資料格式(例如文本和圖像)轉換為機器學習的數字特征。而特征選擇是在已有的特征中選擇更好的特征。後面會詳細介紹特征選擇主要差別于降維。

資料的來源與類型

大部分的資料都來自已有的資料庫,如果沒有的話也可以交給很多爬蟲工程師去采集,來提供。也可以來自平時的記錄,反正資料無處不在,大都是可用的。

資料的類型

按照機器學習的資料分類我們可以将資料分成:

标稱型:标稱型目标變量的結果隻在有限目标集中取值,如真與假(标稱型目标變量主要用于分類)

數值型:數值型目标變量則可以從無限的數值集合中取值,如0.100,42.001等 (數值型目标變量主要用于回歸分析)

按照資料的本身分布特性

離散型

連續型

那麼什麼是離散型和連續型資料呢?首先連續型資料是有規律的,離散型資料是沒有規律的

離散變量是指其數值隻能用自然數或整數機關計算的則為離散變量.例如,班級人數、進球個數、是否是某個類别等等

連續型資料是指在指定區間内可以是任意一個數值,例如,票房資料、花瓣大小分布資料

資料的特征抽取

現實世界中多數特征都不是連續變量,比如分類、文字、圖像等,為了對非連續變量做特征表述,需要對這些特征做數學化表述,是以就用到了特征提取. sklearn.feature_extraction提供了特征提取的很多方法

分類特征變量提取

我們将城市和環境作為字典資料,來進行特征的提取。

sklearn.feature_extraction.DictVectorizer(sparse = True)

将映射清單轉換為Numpy數組或scipy.sparse矩陣

sparse 是否轉換為scipy.sparse矩陣表示,預設開啟

方法

fit_transform(X,y)

應用并轉化映射清單X,y為目标類型

inverse_transform(X[, dict_type])

将Numpy數組或scipy.sparse矩陣轉換為映射清單

from sklearn.feature_extraction import DictVectorizer
onehot = DictVectorizer() # 如果結果不用toarray,請開啟sparse=False
instances = [{'city': '北京','temperature':100},{'city': '上海','temperature':60}, {'city': '深圳','temperature':30}]
X = onehot.fit_transform(instances).toarray()
print(onehot.inverse_transform(X))      

文本特征提取(隻限于英文)

文本的特征提取應用于很多方面,比如說文檔分類、垃圾郵件分類和新聞分類。那麼文本分類是通過詞是否存在、以及詞的機率(重要性)來表示。

(1)文檔的中詞的出現

數值為1表示詞表中的這個詞出現,為0表示未出現

sklearn.feature_extraction.text.CountVectorizer()

将文本文檔的集合轉換為計數矩陣(scipy.sparse matrices)

fit_transform(raw_documents,y)

學習詞彙詞典并傳回詞彙文檔矩陣

from sklearn.feature_extraction.text import CountVectorizer
content = ["life is short,i like python","life is too long,i dislike python"]
vectorizer = CountVectorizer()
print(vectorizer.fit_transform(content).toarray())      

需要toarray()方法轉變為numpy的數組形式

溫馨提示:每個文檔中的詞,隻是整個語料庫中所有詞,的很小的一部分,這樣造成特征向量的稀疏性(很多值為0)為了解決存儲和運算速度的問題,使用Python的scipy.sparse矩陣結構

(2)TF-IDF表示詞的重要性

TfidfVectorizer會根據指定的公式将文檔中的詞轉換為機率表示。(樸素貝葉斯介紹詳細的用法)

class sklearn.feature_extraction.text.TfidfVectorizer()

學習詞彙和idf,傳回術國文檔矩陣。

from sklearn.feature_extraction.text import TfidfVectorizer
content = ["life is short,i like python","life is too long,i dislike python"]
vectorizer = TfidfVectorizer(stop_words='english')
print(vectorizer.fit_transform(content).toarray())
print(vectorizer.vocabulary_)      

資料的特征預處理

(1)歸一化

歸一化首先在特征(次元)非常多的時候,可以防止某一維或某幾維對資料影響過大,也是為了把不同來源的資料統一到一個參考區間下,這樣比較起來才有意義,其次可以程式可以運作更快。 例如:一個人的身高和體重兩個特征,假如體重50kg,身高175cm,由于兩個機關不一樣,數值大小不一樣。如果比較兩個人的體型差距時,那麼身高的影響結果會比較大,k-臨近算法會有這個距離公式。

min-max方法

常用的方法是通過對原始資料進行線性變換把資料映射到[0,1]之間,變換的函數為:

X^{'}{=}\frac{x-min}{max-min}X′=max−minx−min

其中min是樣本中最小值,max是樣本中最大值,注意在資料流場景下最大值最小值是變化的,另外,最大值與最小值非常容易受異常點影響,是以這種方法魯棒性較差,隻适合傳統精确小資料場景。

min-max自定義處理

這裡我們使用相親約會對象資料在MatchData.txt,這個樣本時男士的資料,三個特征,玩遊戲所消耗時間的百分比、每年獲得的飛行常客裡程數、每周消費的冰淇淋公升數。然後有一個 所屬類别,被女士評價的三個類别,不喜歡、魅力一般、極具魅力。 首先導入資料進行矩陣轉換處理

import numpy as np
 
def data_matrix(file_name):
  """
  将文本轉化為matrix
  :param file_name: 檔案名
  :return: 資料矩陣
  """
  fr = open(file_name)
  array_lines = fr.readlines()
  number_lines = len(array_lines)
  return_mat = zeros((number_lines, 3))
  # classLabelVector = []
  index = 0
  for line in array_lines:
    line = line.strip()
    list_line = line.split('\t')
    return_mat[index,:] = list_line[0:3]
    # if(listFromLine[-1].isdigit()):
    #     classLabelVector.append(int(listFromLine[-1]))
    # else:
    #     classLabelVector.append(love_dictionary.get(listFromLine[-1]))
    # index += 1
  return return_mat      

輸出結果為

[[  4.09200000e+04   8.32697600e+00   9.53952000e-01]
 [  1.44880000e+04   7.15346900e+00   1.67390400e+00]
 [  2.60520000e+04   1.44187100e+00   8.05124000e-01]
 ...,
 [  2.65750000e+04   1.06501020e+01   8.66627000e-01]
 [  4.81110000e+04   9.13452800e+00   7.28045000e-01]
 [  4.37570000e+04   7.88260100e+00   1.33244600e+00]]      

我們檢視資料集會發現,有的數值大到幾萬,有的才個位數,同樣如果計算兩個樣本之間的距離時,其中一個影響會特别大。也就是說飛行裡程數對于結算結果或者說相親結果影響較大,但是統計的人覺得這三個特征同等重要,是以需要将資料進行這樣的處理。

這樣每個特征任意的範圍将變成[0,1]的區間内的值,或者也可以根據需求處理到[-1,1]之間,我們再定義一個函數,進行這樣的轉換

def feature_normal(data_set):
    """
    特征歸一化
    :param data_set:
    :return:
    """
    # 每列最小值
    min_vals = data_set.min(0)
    # 每列最大值
    max_vals = data_set.max(0)
    ranges = max_vals - min_vals
    norm_data = np.zeros(np.shape(data_set))
    # 得出行數
    m = data_set.shape[0]
    # 矩陣相減
    norm_data = data_set - np.tile(min_vals, (m,1))
    # 矩陣相除
    norm_data = norm_data/np.tile(ranges, (m, 1)))
    return norm_data      
[[ 0.44832535  0.39805139  0.56233353]
 [ 0.15873259  0.34195467  0.98724416]
 [ 0.28542943  0.06892523  0.47449629]
 ...,
 [ 0.29115949  0.50910294  0.51079493]
 [ 0.52711097  0.43665451  0.4290048 ]
 [ 0.47940793  0.3768091   0.78571804]]      

這樣得出的結果都非常相近,這樣的資料可以直接提供測試驗證了

  • min-max的scikit-learn處理

scikit-learn.preprocessing中的類MinMaxScaler,将資料矩陣縮放到[0,1]之間

>>> X_train = np.array([[ 1., -1.,  2.],
...                     [ 2.,  0.,  0.],
...                     [ 0.,  1., -1.]])
...
>>> min_max_scaler = preprocessing.MinMaxScaler()
>>> X_train_minmax = min_max_scaler.fit_transform(X_train)
>>> X_train_minmax
array([[ 0.5       ,  0.        ,  1.        ],
       [ 1.        ,  0.5       ,  0.33333333],
       [ 0.        ,  1.        ,  0.        ]])      

(3)标準化

常用的方法是z-score标準化,經過處理後的資料均值為0,标準差為1,處理方法是:

X^{'}{=}\frac{x-\mu}{\sigma}X′=σx−μ

其中\muμ是樣本的均值,\sigmaσ是樣本的标準差,它們可以通過現有的樣本進行估計,在已有的樣本足夠多的情況下比較穩定,适合嘈雜的資料場景

sklearn中提供了StandardScalar類實作列标準化:

In [2]: import numpy as np
 
In [3]: X_train = np.array([[ 1., -1.,  2.],[ 2.,  0.,  0.],[ 0.,  1., -1.]])
 
In [4]: from sklearn.preprocessing import StandardScaler
 
In [5]: std = StandardScaler()
 
In [6]: X_train_std = std.fit_transform(X_train)
 
In [7]: X_train_std
Out[7]:
array([[ 0.        , -1.22474487,  1.33630621],
       [ 1.22474487,  0.        , -0.26726124],
       [-1.22474487,  1.22474487, -1.06904497]])      

(3)缺失值

由于各種原因,許多現實世界的資料集包含缺少的值,通常編碼為空白,NaN或其他占位符。然而,這樣的資料集與scikit的分類器不相容,它們假設數組中的所有值都是數字,并且都具有和保持含義。使用不完整資料集的基本政策是丢棄包含缺失值的整個行和/或列。然而,這是以丢失可能是有價值的資料(即使不完整)的代價。更好的政策是估算缺失值,即從已知部分的資料中推斷它們。

(1)填充缺失值 使用sklearn.preprocessing中的Imputer類進行資料的填充

class Imputer(sklearn.base.BaseEstimator, sklearn.base.TransformerMixin)
    """
    用于完成缺失值的補充
    :param param missing_values: integer or "NaN", optional (default="NaN")
        丢失值的占位符,對于編碼為np.nan的缺失值,使用字元串值“NaN”
    :param strategy: string, optional (default="mean")
        插補政策
        如果是“平均值”,則使用沿軸的平均值替換缺失值
        如果為“中位數”,則使用沿軸的中位數替換缺失值
        如果“most_frequent”,則使用沿軸最頻繁的值替換缺失
    :param axis: integer, optional (default=0)
        插補的軸
        如果axis = 0,則沿列排列
        如果axis = 1,則沿行排列
    """      
>>> import numpy as np
>>> from sklearn.preprocessing import Imputer
>>> imp = Imputer(missing_values='NaN', strategy='mean', axis=0)
>>> imp.fit([[1, 2], [np.nan, 3], [7, 6]])
Imputer(axis=0, copy=True, missing_values='NaN', strategy='mean', verbose=0)
>>> X = [[np.nan, 2], [6, np.nan], [7, 6]]
>>> print(imp.transform(X))                          
[[ 4.          2.        ]
 [ 6.          3.666...]
 [ 7.          6.        ]]      

PCA(Principal component analysis),主成分分析。特點是儲存資料集中對方差影響最大的那些特征,PCA極其容易受到資料中特征範圍影響,是以在運用PCA前一定要做特征标準化,這樣才能保證每次元特征的重要性等同。

sklearn.decomposition.PCA

class PCA(sklearn.decomposition.base)
   """
   主成成分分析
   :param n_components: int, float, None or string
       這個參數可以幫我們指定希望PCA降維後的特征次元數目。最常用的做法是直接指定降維到的次元數目,此時n_components是一個大于1的整數。
       我們也可以用預設值,即不輸入n_components,此時n_components=min(樣本數,特征數)
   :param whiten: bool, optional (default False)
      判斷是否進行白化。所謂白化,就是對降維後的資料的每個特征進行歸一化。對于PCA降維本身來說一般不需要白化,如果你PCA降維後有後續的資料處理動作,可以考慮白化,預設值是False,即不進行白化
   :param svd_solver:
      選擇一個合适的SVD算法來降維,一般來說,使用預設值就夠了。
    """      

通過一個例子來看

>>> import numpy as np
>>> from sklearn.decomposition import PCA
>>> X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
>>> pca = PCA(n_components=2)
>>> pca.fit(X)
PCA(copy=True, iterated_power='auto', n_components=2, random_state=None,
  svd_solver='auto', tol=0.0, whiten=False)
>>> print(pca.explained_variance_ratio_)
[ 0.99244...  0.00755...]      

資料的特征選擇

降維本質上是從一個次元空間映射到另一個次元空間,特征的多少别沒有減少,當然在映射的過程中特征值也會相應的變化。舉個例子,現在的特征是1000維,我們想要把它降到500維。降維的過程就是找個一個從1000維映射到500維的映射關系。原始資料中的1000個特征,每一個都對應着降維後的500維空間中的一個值。假設原始特征中有個特征的值是9,那麼降維後對應的值可能是3。而對于特征選擇來說,有很多方法:

Filter(過濾式):VarianceThreshold

Embedded(嵌入式):正則化、決策樹

Wrapper(包裹式)

其中過濾式的特征選擇後,資料本身不變,而資料的次元減少。而嵌入式的特征選擇方法也會改變資料的值,次元也改變。Embedded方式是一種自動學習的特征選擇方法,後面講到具體的方法的時候就能了解了。

特征選擇主要有兩個功能:

(1)減少特征數量,降維,使模型泛化能力更強,減少過拟合

(2)增強特征和特征值之間的了解

sklearn.feature_selection

去掉取值變化小的特征(删除低方差特征)

VarianceThreshold 是特征選擇中的一項基本方法。它會移除所有方差不滿足門檻值的特征。預設設定下,它将移除所有方差為0的特征,即那些在所有樣本中數值完全相同的特征。

假設我們要移除那些超過80%的資料都為1或0的特征

from sklearn.feature_selection import VarianceThreshold
X = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]]
sel = VarianceThreshold(threshold=(.8 * (1 - .8)))
sel.fit_transform(X)
array([[0, 1],
       [1, 0],
       [0, 0],
       [1, 1],
       [1, 0],
       [1, 1]])