環(huán)境:anaconda+jupyter notebook
數(shù)據(jù)處理前導(dǎo):
首先要明白一點:
數(shù)據(jù)決定模型的上限!數(shù)據(jù)決定模型的上限!數(shù)據(jù)決定模型的上限!(重要的事情說三遍。)對于數(shù)據(jù)的處理在一個完整案例中花費精力的比重應(yīng)該占到一半以上。
以下分為:數(shù)據(jù)分析、數(shù)據(jù)清洗兩部分。
數(shù)據(jù)分析主要包括:查看數(shù)據(jù)內(nèi)容(行列意義)、屬性(數(shù)值型、類別型)、非空值數(shù)量、屬性自身分布、屬性之間的關(guān)聯(lián)度、屬性與預(yù)測目標(biāo)值之間的關(guān)聯(lián)度高低等。
數(shù)據(jù)清洗包括:1)數(shù)值型的缺省值處理辦法、2)文本/類別型數(shù)據(jù)處理辦法3)自定義處理數(shù)據(jù)方法、4)特征縮放處理、5)對于以上幾種方法的封裝
(一)、數(shù)據(jù)分析
這一部分你可以看成是操作數(shù)據(jù)的一些常用工具羅列,一些是必要的,一些是不必要的,但都可以幫助你更好的了解你的數(shù)據(jù)。
1、收集數(shù)據(jù)(必要)->2、查看數(shù)據(jù)(5個常用函數(shù))->3、分割訓(xùn)練、測試數(shù)據(jù)(必要)->4、數(shù)據(jù)相關(guān)性及可視化
1、收集數(shù)據(jù)
創(chuàng)建一個小函數(shù)來實現(xiàn)實時下載數(shù)據(jù)包,不僅可以獲取實時數(shù)據(jù),而且很方便查看數(shù)據(jù)。
import os
import tarfile
import urllib
DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/"
HOUSING_URL = DOWNLOAD_ROOT + "datasets/housing/housing.tgz"#網(wǎng)址位置
HOUSING_PATH = os.path.join("datasets", "housing")#存儲位置
def fetch_housing_data(housing_url = HOUSING_URL, housing_path = HOUSING_PATH):
os.makedirs(housing_path, exist_ok = True)
tgz_path = os.path.join(housing_path, "housing.tgz")
urllib.request.urlretrieve(housing_url, tgz_path)
housing_tgz = tarfile.open(tgz_path)
housing_tgz.extractall(path = housing_path)#解壓
housing_tgz.close()
#調(diào)用
fetch_housing_data()
至此,已經(jīng)下載好了數(shù)據(jù)集。
接下來,需要加載你下載好的數(shù)據(jù),將數(shù)據(jù)的pandas DataFrame對象作為返回。
import pandas as pd
def load_housing_data(housing_path = HOUSING_PATH):
csv_path = os.path.join(housing_path, "housing.csv")
return pd.read_csv(csv_path)#返回 包含所有數(shù)據(jù)的pandas DataFrame對象
housing = load_housing_data()
至此,已經(jīng)將數(shù)據(jù)加載到housing中,可以對housing進行一系列數(shù)據(jù)操作了。
2、查看數(shù)據(jù)結(jié)構(gòu)
共介紹了5個常用函數(shù),注釋包含了每個函數(shù)實現(xiàn)的功能。
housing.head()#函數(shù)1:無參數(shù)時,默認(rèn)查看前5行
#前面的列都是數(shù)值屬性,最后一列是文字/類別屬性
housing.info()#函數(shù)2:查看數(shù)據(jù)集屬性描述(必要)
#如圖所示:20640行*10列,列出每列的屬性,非空值個數(shù)(total_bedrooms有空值),類型
housing.describe()#函數(shù)3:查看數(shù)值屬性列的均值、最小最大值等信息
%matplotlib inline
# 設(shè)置matplotlib使用jupyter自己的后端,隨后在notebook 上呈現(xiàn)圖形
import matplotlib.pyplot as plt
housing.hist(bins = 50, figsize = (20,15))#函數(shù)4:各屬性各自的分布:即處于橫軸區(qū)間(x軸)的樣本個數(shù)為多少(y軸)
plt.show()
housing["ocean_proximity"].value_counts()#函數(shù)5:查看這個唯一的文本/類別屬性ocean_proximity共有多少種分類
至此,我們用一些常用函數(shù)查看了數(shù)據(jù)的一些有用信息。
接下來,我們要劃分?jǐn)?shù)據(jù)集,分為訓(xùn)練集和測試集兩份。測試集比例設(shè)為20%。
3、劃分?jǐn)?shù)據(jù)集
劃分方式是靈活的,可以自定義劃分?jǐn)?shù)據(jù)集,或者調(diào)用sklearn提供的train_test_spllit()方法都是可以的。但這種隨機劃分?jǐn)?shù)據(jù)集的方式容易導(dǎo)致數(shù)據(jù)是有偏的,即存在驗本偏差。(舉個栗子:某地區(qū)一家公司要給1000個人打電話調(diào)研一些問題,已知該地區(qū)男女比例6:4,那么調(diào)查的樣本中也應(yīng)該包含6成男性、4成女性,否則將會存在抽樣偏差。)
所以這里介紹分層抽樣,一種常用的抽樣方法:
分層抽樣的前提是要直到對于樣本分布的一個重要指標(biāo)是什么(就像上面的例子中直到==知道該地區(qū)的男女比例很重要一樣),那么從領(lǐng)域?qū)<夷抢锪私獾绞杖胫形粩?shù)對于房價中位數(shù)的預(yù)測有重要作用。然而,收入中位數(shù)是一個連續(xù)值屬性,需要先把它進行區(qū)間劃分,轉(zhuǎn)為類別屬性,再從每個類中進行抽樣。
#分層之前先摸底,查看完整數(shù)據(jù)集的分布
#專家說收入中位數(shù)是很重要的,按收入范圍分成category
import numpy as np
housing["income_cat"] = pd.cut(housing["median_income"], bins = [0., 1.5, 3., 4.5, 6., np.inf], labels = [1, 2, 3, 4, 5])#創(chuàng)建5個收入分類,標(biāo)簽1-5
housing["income_cat"].hist()#顯示收入類別直方圖
至此,已經(jīng)將income_cat屬性從數(shù)值型轉(zhuǎn)化為類別型。
接下來,需要進行分層抽樣:
#開始抽樣,顯示抽樣比例分布
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits = 1, test_size = 0.2, random_state = 42)
for train_index, test_index in split.split(housing, housing["income_cat"]):
strat_train_set = housing.loc[train_index]#訓(xùn)練集
strat_test_set = housing.loc[test_index]#測試集
#檢驗各個類別的比例分布
strat_test_set["income_cat"].value_counts()/len(strat_test_set)
至此,已經(jīng)完成了分層抽樣,將數(shù)據(jù)集劃分為了strat_train_set、strat_test_set。
接下來,這個類別屬性就用不到了,刪掉即可:
#數(shù)據(jù)恢復(fù)原樣,去掉income_cat屬性
for set_ in (strat_train_set, strat_test_set):
set_.drop("income_cat", axis = 1, inplace = True)
4、數(shù)據(jù)相關(guān)性可視化
首先拷貝一份訓(xùn)練數(shù)據(jù)集的副本,以免傷害原始數(shù)據(jù)。
#創(chuàng)建數(shù)據(jù)副本,防止傷害訓(xùn)練集
housing = strat_train_set.copy()
接下來,查看房價和地理位置(x,y)和人口密度s的關(guān)系:(圖中橫軸表示經(jīng)度,縱軸表示維度,圓的半徑大小代表了人口數(shù)量,顏色從藍到紅表示房價由低到高。)
#房價分布, s-藍色-人口數(shù)量,c-顏色-價格-(藍-紅)
housing.plot(kind = "scatter", x = "longitude", y = "latitude", alpha = 0.4,
s = housing["population"]/100, label = "population", figsize = (10,7),
c = "median_house_value", cmap = plt.get_cmap("jet"), colorbar = True,
)
plt.legend()
從圖中可以初步判斷:沿海地區(qū)的房價要偏高一些,當(dāng)然也會受到人口等其他因素的影響。
接下來,尋找數(shù)據(jù)之間的相關(guān)性,這里主要查看房價與所有屬性之間的相關(guān)性:
#計算每對屬性的相關(guān)系數(shù)
corr_matrix = housing.corr()
#每個屬性與房價中位數(shù)的相關(guān)系數(shù)
corr_matrix["median_house_value"].sort_values(ascending = False)
可以看出前四行:median_income、total_rooms、housing_median_age與median_house_value的相關(guān)度還是挺高的。
接下來,看一下他們幾個之間的相關(guān)性如何:
from pandas.plotting import scatter_matrix
attributes = ["median_house_value", "median_income", "total_rooms", "housing_median_age"]
scatter_matrix(housing[attributes], figsize = (12,8))
從圖中可以看出median_house_value和median_income呈現(xiàn)較為明顯的線性相關(guān)性。(也印證了:前文所說的從領(lǐng)域?qū)<夷抢锪私獾椒績r和收入中位數(shù)有較強的相關(guān)性。)
接下來,我們從已有屬性中創(chuàng)造一些新的屬性,或許新的屬性可以和房價之間有較好的相關(guān)性,這個相關(guān)系數(shù)或許會高于已知屬性和房價的相關(guān)系數(shù)。
#幾棟房/每家
#幾個臥室/每棟房子
#幾個人/每家
housing["rooms_per_household"] = housing["total_rooms"]/housing["households"]
housing["bedrooms_per_room"] = housing["total_bedrooms"]/housing["total_rooms"]
housing["population_per_household"] = housing["population"]/housing["households"]
corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending = False)
從結(jié)果上可以看出rooms_per_household比一些已有屬性具有和房價更高的相關(guān)性。所以這個屬性或許對我們之后的房價預(yù)測起到一定幫助。
(二)、數(shù)據(jù)清洗
前文介紹了很多關(guān)于數(shù)據(jù)的一些常規(guī)操作,這些操作一些是可選的,但可以幫助更好的理解數(shù)據(jù)。
接下來,是比較重要的數(shù)據(jù)清洗。
主要介紹幾種方法適用于不同情況,最后介紹如何將這些清洗操作封裝起來。(封裝起來的目的是,訓(xùn)練集、測試集都需要執(zhí)行清洗一系列操作,封裝好可以直接調(diào)用,更加簡潔)
1、缺失值
對于含有空值的列(如前文交代的total_bedroom列),通??梢圆扇?種方法:1)放棄缺失值相應(yīng)的區(qū)域、2)放棄含缺失值的列、3)用中位數(shù)/0/均值來代替缺失值。
然而,前兩種方法對于數(shù)據(jù)來說無疑是一種浪費,這里介紹如何用第三種方法,完成缺失值填補。
首先,還是將訓(xùn)練集拷貝下來,注意是訓(xùn)練集。
用drop()和copy()將數(shù)據(jù)集的標(biāo)簽分離出來,即分理出x,y。
#干凈的訓(xùn)練集:預(yù)測器和標(biāo)簽分開,因為兩者進行數(shù)據(jù)轉(zhuǎn)換的方式不同。
housing = strat_train_set.drop("median_house_value", axis = 1)#housing數(shù)據(jù)副本 只包含預(yù)測器(9列),不含標(biāo)簽
housing_labels = strat_train_set["median_house_value"].copy()
housing.info()
從結(jié)果中可以看到:訓(xùn)練集的x為16512*9的,其中total_bedrooms有空值。
接下來,開始用中位數(shù)填補空值:
根據(jù)sklearn提供的SimpleImputer類,很方便的用中位數(shù)填補空值。(助記:0.創(chuàng)建類1.fit() 2.transform())
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy = "median")#0.類
housing_num = housing.drop("ocean_proximity", axis = 1)#housing_num不含標(biāo)簽和ocean_proximity屬性
imputer.fit(housing_num)#1.fit
X = imputer.transform(housing_num)#iii 缺失值替換成中位數(shù),從而完成訓(xùn)練集轉(zhuǎn)換結(jié)果numpy
housing_tr = pd.DataFrame(X, columns = housing_num.columns, index = housing_num.index)
# housing_num.info()#補好的數(shù)據(jù)存入了X, X再變成DataFrame存入housing_tr
housing_tr.info()
至此,已經(jīng)填補好了數(shù)值類型的缺失值。
2、文本/分類屬性
由于計算機沒辦法處理文本類型的屬性,所以需要對其進行編碼,轉(zhuǎn)化為數(shù)學(xué)表達。常用的編碼方式有:轉(zhuǎn)化為數(shù)字類別,一種是轉(zhuǎn)化為獨熱碼。
如果轉(zhuǎn)化為數(shù)字編碼:從(一)數(shù)據(jù)分析->2、查看數(shù)據(jù)結(jié)構(gòu)->函數(shù)5可知:ocean_proximity屬性共有5種類別,即為0~4,但是這里引入了一個不友好的因素,就是數(shù)字相近的類別具有較高的相似度(通俗來說就是,一個樣本如果在類別1或2之間徘徊,如果最后評分的時候把它歸為了1類,但是它其實含有和類別2相似的因素。)
所以,這里介紹采用獨熱碼的形式進行編碼:
根據(jù)sklearn提供的OneHotEncoder類,很方便的用中位數(shù)填補空值。(助記:0.創(chuàng)建類1.fit() 2.transform(),1.2可以合并)
# 編碼器,獨熱編碼:生成獨熱向量
from sklearn.preprocessing import OneHotEncoder
housing_cat = housing[["ocean_proximity"]]##
cat_encoder = OneHotEncoder()#相當(dāng)于類對象:含有方法:fit_transform,含有變量:categories_
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot
# print(housing_cat_1hot[:10])
#轉(zhuǎn)化為密集的numpy
housing_cat_1hot.toarray()
至此,完成了對文本/分類屬性的編碼。
3、自定義轉(zhuǎn)換器
可以根據(jù)需要自定義一些轉(zhuǎn)換器,來處理數(shù)據(jù),把不論是任何形式的數(shù)據(jù),最終都變成數(shù)學(xué)表達。
在此案例中主要用于處理我們后來新創(chuàng)建的幾個聯(lián)合屬性。
from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, household_ix = 3,4,5,6
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):#自定義轉(zhuǎn)換器:CombinedAttributesAdder
def __init__(self, add_bedrooms_per_room = True): #沒有 *args, **kargs
self.add_bedrooms_per_room = add_bedrooms_per_room
def fit(self, X, y = None):
return self
def transform(self, X):
rooms_per_household = X[:, rooms_ix]/X[:, household_ix]
population_per_household = X[:, population_ix]/ X[:, household_ix]
if self.add_bedrooms_per_room :
bedrooms_per_room = X[:, bedrooms_ix]/ X[:, rooms_ix]
return np.c_[X, rooms_per_household, population_per_household, bedrooms_per_room]
else:
return np.c_[X, rooms_per_household, population_per_household]
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room = False)
housing_extra_attribs = attr_adder.transform(housing.values)
4、特征縮放
如果輸入的數(shù)值屬性具有較大的比例差異,那么非常必要將其進行縮放。
常用的縮放方法有:01縮放、標(biāo)準(zhǔn)化。
0-1縮放:(x-Min)/(Max-Min)
非常簡單,但是容易受到異常值的影響。(舉個栗子:本來屬性值大部分處于0~ 15之間,突然有一個異常值為100,那么縮放之后大部分的屬性值被縮放到0~ 0.15之間,而不是0~ 1之間。)
標(biāo)準(zhǔn)化:(x-均值)/方差
不容易受到異常值的影響。
所以本案例采用標(biāo)準(zhǔn)化方式:根據(jù)sklearn提供的standardScaler類,很方便的進行屬性縮放。(在后文應(yīng)用)
5、封裝成流水線
將以上幾個步驟封裝起來,便于對任意數(shù)據(jù)集處理。
先把數(shù)值型數(shù)據(jù)的操作封裝起來,在把數(shù)值型和文本/分類型屬性一起封裝。
首先,根據(jù)sklearn提供的Pipeline類,進行初步封裝。
#安排數(shù)據(jù)轉(zhuǎn)換的正確步驟
from sklearn.pipeline import Pipeline#通過一系列轉(zhuǎn)換器/估算器定義步驟序列
from sklearn.preprocessing import StandardScaler
#定義轉(zhuǎn)換器:
num_pipeline = Pipeline([
('imputer', SimpleImputer(strategy = "median")), #轉(zhuǎn)換器:缺失值替換成中位數(shù)
('attribs_adder', CombinedAttributesAdder()), #自定義轉(zhuǎn)換器:處理數(shù)據(jù)
('std_scaler', StandardScaler()), #縮放器
])
housing_num_tr = num_pipeline.fit_transform(housing_num)#對所有數(shù)值列
housing_num_tr = pd.DataFrame(housing_num_tr)
print(housing_num_tr.info(0))
至此封裝好了數(shù)值型數(shù)據(jù)的處理。
接下來,根據(jù)sklearn提供的ColumnTransformer類進行進一步的封裝。
from sklearn.compose import ColumnTransformer
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
#定義轉(zhuǎn)換器:
full_pipeline = ColumnTransformer([ #創(chuàng)建個ColumnTransformer類對象
("num", num_pipeline, num_attribs),
("cat", OneHotEncoder(), cat_attribs),
])
housing_prepared = full_pipeline.fit_transform(housing)
至此,將所有的數(shù)據(jù)清洗操作全部封裝在了full_pipeline中。
以上(一)、(二)完成了所有對訓(xùn)練數(shù)據(jù)集的操作。
(三)、模型選擇
1、線性模型
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions) #計算rmse,[y, f(x)]
lin_rmse = np.sqrt(lin_mse)
print(lin_rmse)
結(jié)果壞掉了,欠擬合:1.選擇更大的模型2.選擇更好的特征3.減少對模型的限制,如去掉正則化。
線性模型-交叉驗證:
from sklearn.model_selection import cross_val_score
#線性模型的評分:
lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels,scoring = "neg_mean_squared_error", cv = 10)
lin_rmse_scores = np.sqrt(-lin_scores)
print("LinearRegression")
display_scores(lin_rmse_scores)
#線性模型評分6萬9
2、決策樹
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared, housing_labels)#1.fit
housing_predictions = tree_reg.predict(housing_prepared)#2.predict
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
print(tree_rmse)
訓(xùn)練集上結(jié)果太好,猜測嚴(yán)重過擬合:1.選擇簡單模型2.收集更多訓(xùn)練數(shù)據(jù)3.減少訓(xùn)練噪聲。
決策樹-交叉驗證:
from sklearn.model_selection import cross_val_score
#決策樹先來交叉驗證,評分
scores = cross_val_score(tree_reg, housing_prepared, housing_labels, scoring = "neg_mean_squared_error", cv = 10)
tree_rmse_scores = np.sqrt(-scores)#效用函數(shù),越大越好
def display_scores(scores):#結(jié)果每次運行均會在某區(qū)間變動
print("Scores:", scores)
print("Mean:", scores.mean())
print("Standard deviation:", scores.std())
print("DecisionTreeRegressor:")
display_scores(tree_rmse_scores)
發(fā)現(xiàn)決策樹如果采用交叉驗證的話,評分7萬,說明在整個測試集上確實過擬合。
3、隨機森林
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor()#0.類
forest_reg.fit(housing_prepared, housing_labels)#1.fit
housing_predictions = forest_reg.predict(housing_prepared)#2.predict
forest_mse = mean_squared_error(housing_labels, housing_predictions)#3.rmse
forest_rmse = np.sqrt(forest_mse)
print(forest_rmse)
隨機森林-交叉驗證:
from sklearn.model_selection import cross_val_score
#隨機森林的評分
forest_scores = cross_val_score(forest_reg, housing_prepared, housing_labels, scoring = "neg_mean_squared_error", cv =10)
forest_rmse_scores = np.sqrt(-forest_scores)
print("RandomForestRegressor:")
display_scores(forest_rmse_scores)
隨機森林單看rmse的值,似乎很有戲,但依舊比交叉驗證的評分低很多,意味著模型也是過擬合。
不論如何,相比于前兩種模型,表現(xiàn)稍好,所以接下來將隨機森林應(yīng)用于案例。
(四)、模型微調(diào)
常用的模型微調(diào)方法就是網(wǎng)格搜索。
根據(jù)sklearn提供的GridSearchCV類,告知其超參數(shù)有哪些、需要嘗試的參數(shù)值,他就會根據(jù)交叉驗證來評估所有超參數(shù)的可能組合。
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestRegressor
param_grid = [
{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
{'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
]
forest_reg = RandomForestRegressor()
grid_search = GridSearchCV(forest_reg, param_grid, cv = 5, scoring = 'neg_mean_squared_error', return_train_score = True)
grid_search.fit(housing_prepared, housing_labels)
grid_search.best_params_
得到最終的最優(yōu)的一組超參數(shù),使用的數(shù)據(jù)不同,該結(jié)果也會有不同。
(五)、預(yù)測結(jié)果
將上面選出的最好的超參數(shù)對應(yīng)的模型應(yīng)用于測試數(shù)據(jù)。
首先還是現(xiàn)將測試數(shù)據(jù)及進行標(biāo)簽分離,然后應(yīng)用前面封裝好的數(shù)據(jù)清洗流水線full_pipeline進行處理,接下來便可用于房價預(yù)測:
final_model = grid_search.best_estimator_
X_test = strat_test_set.drop("median_house_value", axis = 1)
y_test = strat_test_set["median_house_value"].copy()
X_test_prepared = full_pipeline.transform(X_test)
final_predictions = final_model.predict(X_test_prepared)
final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse)
print(final_rmse)
得到的測試集上的評分往往會略遜于之前交叉驗證的表現(xiàn)結(jié)果。雖然這里的結(jié)果并非如此,但也不要再調(diào)整超參數(shù)了,因為即使改變在泛化到新數(shù)據(jù)時也沒有什么用了。文章來源:http://www.zghlxwxcb.cn/news/detail-446057.html
至此,整個案例的流程就走完了。文章來源地址http://www.zghlxwxcb.cn/news/detail-446057.html
到了這里,關(guān)于【機器學(xué)習(xí)】最經(jīng)典案例:房價預(yù)測(完整流程:數(shù)據(jù)分析及處理、模型選擇及微調(diào))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!