1. 程式人生 > >閒扯淡之機器學習——資料預處理

閒扯淡之機器學習——資料預處理

上篇文章我們針對ML閒扯了一番,並在最後又借鑑Data Mining的CRISP-DM模型分析了一個ML專案的開發過程。

今天說點什麼呢?我猶豫了,我迷茫了!先給大家講個故事吧!

有一天你的boss找到你說:XX聽說你對ML很熟悉啊,正好我們公司有很多**方面的資料,你看看能不能搞一個ML專案為我們公司提供一些決策參考。你聽到這裡是高興還是悲傷,具體因人而異(要是小弟聽到了,絕對會很高興)。我這裡假設你很高興,接到boss的聖旨以後你就屁顛屁顛的找公司的相關資料。畢竟手持boss的聖旨,所以資料獲取應該不是太難,拿到資料你就開始瘋狂的想使用什麼模型呢?邏輯迴歸,GBDT,隨機森林,SVM、貝葉斯等等模型(別急,後面都會提到),你通過“認真”思考後選擇了一個模型,迫不及待的把資料往裡面喂(親愛的張嘴)。當你信心滿滿的點選run後,你會看到下面一行,一行,一行的紅色字型,大體意思是這裡數字無效,那裡資料為空……這時候就問你崩潰嗎?

故事說完了,正事還是要乾的,今天在網上浪了一番,發現一個妹子(CSDN的賬戶名為Charlotte77)根據自己的實際情況總結了一個ML專案的流程,如下圖所示(此圖純屬盜圖,妹子不會怪罪我的吧):
這裡寫圖片描述
其實,我盜用妹子的圖是有目的的,對,你沒有聽錯,我是帶著目的來的,現在話不多說——開幹。根據妹子的圖,我的思緒回到了扯上一篇文章的時候,突然發現上一篇沒有什麼乾貨,全文都是我在扯淡。其實,我上一篇的目的性很明確就是讓大家瞭解一下什麼叫ML以及在一個ML專案開始前我們需要做或者準備的東西,這對一個ML專案來說至關重要的。這一篇我打算說一下關於資料預處理的問題,在ML中有資料、好資料才是王道。無論對於資料探勘,還是ML,還是DL,資料都佔據很重要的地位,例如(臭不要臉的又盜圖Jim Liang的圖):

這裡寫圖片描述
上圖來自美帝的“紐約時報”的一篇報道,其實就是說資料科學家在他們的時間中有50%到80%的時間花費在收集和準備不規則資料的更為平凡的任務中,然後才能探索有用的金塊。就看上圖,不用我多廢話大家就會明白資料預處理的重要性(臭不要臉的誰讓你廢話的,滾!)。說到資料預處理,我們就說一些稍微正式的,何為資料處理?(老毛病,先說定義吧)資料預處理主要是指在對資料主要處理以前對資料進行的一些處理(就問你是不是我的回答簡單易懂)。資料預處理有四個任務,資料清洗、資料整合、資料變換和資料規約。

各位大佬,各位看官,請聽說繼續扯:

從慣性思維來講,如果一件事情放到第一位就說明這件事情很重要,例如軟體開發中的需求分析,所以資料清洗在資料預處理中佔據很重要的地位(誰讓人家是老大呢)。各位大佬,各位看官,如果你已經對ML有一定的瞭解,你就會發現有幾個資料集(例如,iris資料集,房價資料,電影評分資料集)已經被資料探勘、ML等領域的人玩爛了。這些資料集畢竟是為學習的人提供練手的玩物(看到這兩個字是不是,我邪惡了),所以這些資料的質量都相對較好。然而,在我們實際專案中的資料往往是混亂的,也可以說雜亂無章的(這裡說亂主要是指資料不完整、資料重複、資料值為空等)。如果你想要你的努力獲得效果(模型獲得更好的預測),你就必須對你的資料做預處理。其實,我聽很多高機器學習的朋友告訴我,機器學習資料時王道,較好的資料經過不同的模型訓練後,其預測結果差距不是太大(這是真的你沒有聽錯)。在真實資料中,我們拿到的資料可能包含了大量的缺失值,可能包含大量的噪音,也可能因為人工錄入錯誤(比如,醫生的就醫記錄)導致有異常點存在,對我們挖據出有效資訊造成了一定的困擾,所以我們需要通過一些方法,儘量提高資料的質量。

資料清洗是檢測和去除資料集中的噪聲資料和無關資料,處理遺漏資料,去除空白資料域和知識背景下的白噪聲。資料清洗分為有監督清洗和無監督清洗兩類(參考CSDN暱稱:樹先生海浪):

  1. 有監督清洗:在對應領域專家的指導下,收集分析資料,手工去除明顯的噪聲資料和重複記錄,填補缺值資料等清洗動作
  2. 無監督清洗:根據一定的業務規則,預先定義好資料清洗演算法,由計算機自動執行演算法,對資料集進行清洗,然後產生清洗報告

在實際專案中,完全有監督的清洗估計不太可能(你讓去人工清洗數以萬計的資料你願意嗎?反正我是不幹),因此,一般是先進行無監督清洗併產生相應的清洗報告,然後再讓專家根據清晰報告對清洗的結果進行人工整理。在資料清理前,我們需要分析資料的特點並定義資料清洗規則,清洗結束後為了保證清洗的質量,我們還需要驗證清洗結果。資料的分析是根據相關的業務知識和相應的技術,如統計學,資料探勘的方法,分析出資料來源中資料的特點,為定義資料清洗規則奠定基礎。常用的清洗規則主要包括:空值的檢查和處理;非法值的檢測和處理;不一致資料的檢測和處理;相似重複記錄的檢測和處理。執行資料清洗規則時需要檢查拼寫錯誤,去掉重複的(duplicate)記錄,補上不完全的(incomplete)記錄,解決不一致的(inconsistent)記錄,用測試查詢來驗證資料,最後需要生成資料清晰報告。在清洗結果驗證中,需要對定義的清洗轉換規則的正確性和效率進行驗證和評估,當不滿足清洗要求時要對清洗規則或系統引數進行調整和改進,資料清洗過程中往往需要多次迭代的進行分析,設計和驗證。

雜亂無章的資料,問題多如麻,因此針對資料中存在的不同問題,有不同的處理方法:

空值資料的處理方法
  1. 刪除包含空值的記錄,這種方式主要是針對包含空值的資料佔總體比例較低,刪除這些資料對於資料整體影響不大。
  2. 自動補全方法,這種方法通過統計學原理,根據資料集中記錄的取值分佈情況來對一個空值進行自動填充,可以用平均值,最大值,最小值等基於統計學的客觀知識來填充欄位。
  3. 手工的補全缺失值,這種方法僅適用於資料量比較小的情況,對於大資料量只能說no way,並且對空值不正確的填充往往將新的噪聲引入資料中,使知識獲取產生錯誤的結果。
不一致資料處理的基本方法

清洗方法主要在分析不一致產生原因的基礎上,利用各種變換函式,格式化函式,彙總分解函式去實現清洗。

噪聲資料的基本處理方法
  1. 分箱:將儲存的值分佈到一些箱中,用箱中的資料值來區域性平滑儲存資料的值,包括按箱平均值平滑,按箱中值平滑和按箱邊界值平滑。
  2. 迴歸:找到恰當的迴歸函式來平滑資料。線性迴歸要找出適合兩個變數的“最佳”直線,使得一個變數能預測另一個。多線性迴歸涉及多個變數,資料要適合一個多維面。
  3. 計算機檢查和人工檢查相結合:可以通過計算機將被判定資料與已知的正常值比較,將差異程度大於某個閾值的模式輸出到一個表中,人工稽核後識別出噪聲資料。
  4. 聚類:將類似的值組成群或“聚類”,落在聚類集合之外的值被視為孤立點。孤立點可能是垃圾資料,也可能是提供資訊的重要資料。垃圾資料將清除。

嗶嗶半天了,來點實際的吧!

我們經常用來學習的資料比較完整不適合這裡的閒扯淡,所以我特意從kaggle找了一個資料。這個資料集分為訓練資料集和測試資料集,訓練資料集的大小為(1272, 161),總計161個特徵,資料儲存格式為csv格式。我們這裡選擇使用pandas(pandas教程,這個教程基本上是按照官網翻譯的,如果你想看看官方文件可以在這裡下載,一個積分哦)處理資料。下面為大家展示一下這個資料集的資料分佈情況(由於特徵比較多,我只是簡單展示了部分特徵):
這裡寫圖片描述
這裡寫圖片描述
首先,我們來看一下csv檔案裡面的資料,在使用pandas之前匯入使用到的模組:

import numpy as np
import pandas as pd

開啟資料,顯示前2條資料:

df = pd.read_csv('./learn/2016 School Explorer.csv')
df.head(2)
#df.describe()  如果資料主體為數字,這裡也可以使用df.describe()顯示更具體的情況

由於資料主要是使用字串儲存,並且裡面有6個特徵包含%,不方便處理資料。這裡我們寫一個方法,用於處理這些資料生成float資料:

def p2f(x):
    return float(x.strip('%'))/100

寫完函數了,進一步處理資料:

#  astype(type)是將資料轉換成制定的type型別
#  apply(function)將會在DataFrame中行或列中的資料應用function函式
df['Percent of Students Chronically Absent']=df['Percent of Students Chronically Absent'].astype(str).apply(p2f)
df['Rigorous Instruction %'] = df['Rigorous Instruction %'].astype(str).apply(p2f)
df['Collaborative Teachers %'] = df['Collaborative Teachers %'].astype(str).apply(p2f)
df['Supportive Environment %'] = df['Supportive Environment %'].astype(str).apply(p2f)
df['Effective School Leadership %'] = df['Effective School Leadership %'].astype(str).apply(p2f)
df['Strong Family-Community Ties %'] = df['Strong Family-Community Ties %'].astype(str).apply(p2f)
df['Trust %'] = df['Trust %'].astype(str).apply(p2f)

下面我們通過df[‘School Income Estimate’]來看一下School Income Estimate特徵的值:

0        $31,141.72 
1        $56,462.88 
2        $44,342.61 
3        $31,454.00 
4        $46,435.59 
5        $39,415.45 
6        $43,706.73 
7        $28,820.67 
8        $34,889.24 
9        $35,545.10 
10       $40,809.90 
11       $27,881.59 
12               NaN
13               NaN
14       $63,760.00 
15               NaN
16       $62,519.57 
17       $57,504.48 
18       $56,787.20 
19               NaN
20               NaN
21       $76,833.96 
22               NaN
23       $32,817.79 
24       $26,114.78 

有沒有發現這個資料裡面不僅有NaN,而且還有$$(其實是一個刀了,如果不寫成兩個,就顯不出來),不便於我們計算,下面我們先將資料中的$,英文“,”以及空格替換掉,並將資料轉換成float(NaN後面在處理):

df['School Income Estimate'] = df['School Income Estimate'].str.replace(',', '')
df['School Income Estimate'] = df['School Income Estimate'].str.replace('$', '')
df['School Income Estimate'] = df['School Income Estimate'].str.replace(' ', '')
df['School Income Estimate'] = df['School Income Estimate'].astype(float)

處理後的結果(是不是很好了):

0        31141.72
1        56462.88
2        44342.61
3        31454.00
4        46435.59
5        39415.45
6        43706.73
7        28820.67
8        34889.24
9        35545.10
10       40809.90
11       27881.59
12            NaN
13            NaN
14       63760.00
15            NaN
16       62519.57
17       57504.48
18       56787.20
19            NaN
20            NaN
21       76833.96
22            NaN
23       32817.79
24       26114.78

下面我們使用matplotlib和pandas一起顯示部分特徵分佈資訊,同樣首先匯入matplotlib模組:

import matplotlib.pyplot as plt

畫圖如下(畫圖內容就是使用不同的特徵表示圖中的不同資訊,其他的我就不廢話了):

df.plot(kind="scatter", x="Longitude", y="Latitude",
    s=df['School Income Estimate']/1210, c="Economic Need Index", cmap=plt.get_cmap("jet"),
        label='Schools', title='New York School Population Map',colorbar=True, alpha=0.4, figsize=(15,7))
plt.legend()
plt.show()

這裡寫圖片描述

data = [
    {
        'x': df["Longitude"],
        'y': df["Latitude"],
        'text': df["School Name"],
        'mode': 'markers',
        'marker': {
            'color': df["Economic Need Index"],
            'size': df["School Income Estimate"]/4500,
            'showscale': True,
            'colorscale':'Portland'
        }
    }
]

layout= go.Layout(
    title= 'New York School Population (Economic Need Index)',
    xaxis= dict(
        title= 'Longitude'
    ),
    yaxis=dict(
        title='Latitude'
    )
)
fig = go.Figure(data=data, layout=layout)
iplot(fig, filename='scatter_hover_labels')

這裡寫圖片描述
對於資料,將字元轉化成float:

df['Percent Asian'] = df['Percent Asian'].apply(p2f)
df['Percent Black'] = df['Percent Black'].apply(p2f)
df['Percent Hispanic'] = df['Percent Hispanic'].apply(p2f)
df['Percent White'] = df['Percent White'].apply(p2f)
df['Percent Black / Hispanic'] = df['Percent Black / Hispanic'].apply(p2f)

再畫一個圖:

data = [
    {
        'x': df["Longitude"],
        'y': df["Latitude"],
        'text': df["School Name"],
        'mode': 'markers',
        'marker': {
            'color': df["Percent Black"],
            'size': df["School Income Estimate"]/4500,
            'showscale': True,
            'colorscale':'Portland'
        }
    }
]

layout= go.Layout(
    title= 'New York Black Student Ratio Of School',
    xaxis= dict(
        title= 'Longitude'
    ),
    yaxis=dict(
        title='Latitude'
    )
)
fig = go.Figure(data=data, layout=layout)
iplot(fig, filename='scatter_hover_labels')

這裡寫圖片描述
再畫一個圖:

f, axes = plt.subplots(2, 2, figsize=(19, 9), sharex=True)
sns.despine(left=True)

sns.regplot(x=df["Economic Need Index"], y=df["Percent Asian"], color='purple', ax=axes[0, 0], line_kws={"color": "black"})
sns.regplot(x=df["Economic Need Index"], y=df["Percent White"], color='g', ax=axes[0, 1], line_kws={"color": "black"})
sns.regplot(x=df["Economic Need Index"], y=df["Percent Black"], color='b', ax=axes[1, 0], line_kws={"color": "black"})
sns.regplot(x=df["Economic Need Index"], y=df["Percent Hispanic"], color='r', ax=axes[1, 1], line_kws={"color": "black"})

axes[0,0].set_title('Ecnomic Need Index (Asian)')
axes[0,1].set_title('Ecnomic Need Index (White)')
axes[1,0].set_title('Ecnomic Need Index (Black)')
axes[1,1].set_title('Ecnomic Need Index (Hispanic)')

plt.subplots_adjust(hspace=0.4)