这个系列是把《机器学习实践》书上的项目,以自己理解的形式梳理一遍,算是对自己学习过程的总结。
0.项目介绍
书中第一个项目,内容包括获取数据、数据可视化、分析数据、切分数据集、数据清理、流水线过程、选择和训练模型,走了一遍做一个完整的机器学习模型的大致流程。
1.数据查看及分析
数据加载:
下面代码中有两个函数,fetch_housing_data是自动下载网络文件并存放在本地中,load_housing_data是获取文件数据。
import os
import tarfile
from six.moves import urllib
import pandas as pd
DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml/master/"
HOUSING_PATH = "datasets/housing"
HOUSING_URL = DOWNLOAD_ROOT + HOUSING_PATH + "/housing.tgz"
def fetch_housing_data(housing_url=HOUSING_URL, housing_path=HOUSING_PATH):
if not os.path.isdir(housing_path):
os.makedirs(housing_path)
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()
def load_housing_data(housing_path=HOUSING_PATH):
csv_path = os.path.join(housing_path,"housing.csv")
return pd.read_csv(csv_path)
数据查看:
housing = load_housing_data() #housing是pandas的DataFrame数据类型
housing.head() # 查看数据前5行
housing.info() # 查看数据的整体基本信息
housing['ocean_proximity'].value_counts() # 查看单一列的统计信息
housing.describe() #查看包括std(标准差)、mean、max、min、不同比例值的信息,如下图
切分数据:
要把数据切分成train_set(训练集)和test_set(测试集),有两种方式:随机切分、按层级切分。随机切分效果不好,一般是按层级切分,尽量保证测试集适应数据的特点。
随机切分(不推荐):
import numpy as np
import hashlib
# 1、手动划分数据集-纯随机方式
# 通过每一行数据的唯一标识id,取hash值,取hash值的最后一位是在0~255的值,如果它在256*测试比例大小内,那标记为True,即为测试集中的数据
def test_set_check(identifier,test_ratio,hash):
return hash(np.int64(identifier)).digest()[-1] < 256 * test_ratio
def split_train_test_by_id(data, test_ratio, id_column, hash=hashlib.md5):
ids = data[id_column]
in_test_set = ids.apply(lambda id_: test_set_check(id_,test_ratio,hash))
return data.loc[~in_test_set], data.loc[in_test_set]
housing_with_id = housing.reset_index()
train_set,test_set = split_train_test_by_id(housing_with_id, 0.2, "index")
# 2、使用Scikit-Learn来划分数据集-纯随机方式
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
按层级切分(推荐):
# 3、将数据集进行分层抽取测试集和训练集,依据的标准是收入平均数,因为预测房价,收入中位数是个非常关键的属性。
# i:数据分析:从收入图上来看,数据集中在2~5之间,如果要按层级抽样,那每一个层级应该足够大最好。
# 书上是这样处理的,先把收入/1.5(限制类别的数量),并且取整,再把大于5的数据归到5的层级中。
housing['income_cat'] = np.ceil(housing['median_income'] / 1.5)
housing['income_cat'].where(housing['income_cat'] < 5, 5.0, inplace=True)
# 4、进行分层抽样
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']):
print(len(train_index))
print(len(test_index))
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
以某属性为标准的前提下,按层级切分可使测试集最大满足数据的分布特点。可以通过对比完整数据集和测试集的分布概率来检验。如下图:数据可视化:
- 把所有列按直方图形式查看:
%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50,figsize=(20,25))
plt.show
#把所有列以直方图形式显示
- 将地理数据可视化
#1.将地理数据可视化
housing = strat_train_set.copy()
housing.plot(kind='scatter',x='longitude',y='latitude', alpha=0.4,
s=housing['population']/100, label='population',
c='median_house_value',cmap=plt.get_cmap('jet'), colorbar=True)
plt.legend()
数据探索
- 寻找数据属性间的相关性,又称皮尔逊相关性
# 查看所有属性跟房价中位数的相关性,范围-1~1,负数代表负相关
corr_matrix = housing.corr()
corr_matrix['median_house_value'].sort_values(ascending=False)
- 尝试不同的属性组合
#尝试不同属性的组合
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)
#可以看到bedrooms_per_room比total_rooms和total_bedrooms的系数都高的多。
2、机器学习算法的数据准备
这一部分最好要用函数,甚至流水线程序来执行,方便之后的项目重用。
housing = strat_train_set.drop('median_house_value', axis=1)
housing_labels = strat_train_set['median_house_value'].copy()
处理数据缺失
数据中当出现某些值缺失时,要考虑将其填充成平均值或者直接舍弃。
# 丢弃有三种方式:
# housing.dropna(subset=['total_bedrooms'])
# housing.drop('total_bedrooms', axis=1)
# median = housing['total_bedrooms'].median()
# housing['total_bedrooms'].fillna(median)
# 填充一般是按中位数填充
from sklearn.preprocessing import Imputer
imputer = Imputer(strategy='median')
housing_num = housing.drop('ocean_proximity', axis=1)
imputer.fit(housing_num)
# imputer.statistics_
# housing_num.median().values
X = imputer.transform(housing_num)
housing_tr = pd.DataFrame(X, columns=housing_num.columns)
处理文本和分类属性
有些属性的值不是数字,这时候最好把文本值转换成数字,方便之后的处理
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder() #转换器
housing_cat = housing['ocean_proximity']
housing_cat_encoded = encoder.fit_transform(housing_cat)
housing_cat_encoded
#查看所有类型
print(encoder.classes_)
OneHot编码
如果只是把数据转换成0,1,2...这种连续的数字,机器学习算法会默认0和4的距离比2和3的距离要远,但在文本类型中不存在这种关系,所以要转成01编码,以10000,01000,00100...的这种方式,即为OneHot编码。最终为二维数组
from sklearn.preprocessing import OneHotEncoder
oneHotEncoder = OneHotEncoder()
housing_cat_1hot = oneHotEncoder.fit_transform(housing_cat_encoded.reshape(-1,1))
housing_cat_1hot.toarray()
可以一次性转换成OneHot形式:
from sklearn.preprocessing import LabelBinarizer
encoder = LabelBinarizer()
housing_cat_1hot = encoder.fit_transform(housing_cat)
特征缩放
两种方式:最小最大值缩放和标准化。
标准化受数据异常值的影响较小。
可以自定义转换器
scikit-learn中,可以创建自定义转换器,只要类中应用这三个方法:fit()、transform()、fit_transform()。如下代码:
#自定义转换器
from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedroom_ix, population_ix, household_ix = 3,4,5,6
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
def __init__(self, add_bedrooms_per_room = True):
self.add_bedrooms_per_room = add_bedrooms_per_room
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
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[:, bedroom_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) #为了将准备步骤更自动化,添加这个属性Flag
housing_extra_attribs = attr_adder.transform(housing.values)
class DataFrameSelector(BaseEstimator, TransformerMixin):
def __init__(self, attribute_names):
self.attribute_names = attribute_names
def fit(self, X, y=None):
return self
def transform(self, X):
return X[self.attribute_names].values
转换流水线
可以用流水线的方式来顺序执行数据处理步骤。
from sklearn.pipeline import FeatureUnion
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_attribs = list(housing_num)
cat_attribs = ['ocean_proximity']
num_pipeline = Pipeline([
('selector', DataFrameSelector(num_attribs)),
('imputer', Imputer(strategy='median')),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler())
])
cat_pipeline = Pipeline([
('selector', DataFrameSelector(cat_attribs)),
('label_binarizer', LabelBinarizer())
])
full_pipeline = FeatureUnion(transformer_list=[
('num_pipeline', num_pipeline),
('cat_pipeline', cat_pipeline)
])
housing_prepared = full_pipeline.fit_transform(housing)
housing_prepared.shape