【Machine Learning】加州房价预测
这个分析有点乱,感觉不是一个很好的例子,到最后不想跟了,已经算一个比较完整的流程
Housing
1. 导入数据分析包并读取数据¶
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
housing = pd.read_csv('housing.csv', sep=',')
housing.head()
housing.info()
housing['ocean_proximity'].value_counts()
果然,ocean_proximity 是分类类型,一共有五个值: <1H OCEAN, INLAND, NEAR OCEAN, NEAR BAR, ISLAND
2.2 数据基本统计值¶
housing.describe()
housing.hist(bins=50, figsize=(20,15))
plt.show()
test = np.random.permutation(5)
print(test)
def split_train_set(data, test_rate=0.2):
shuffled_indices = np.random.permutation(len(data))
test_size = int(len(data) * test_rate)
test_indices = shuffled_indices[:test_size]
train_indices = shuffled_indices[test_size:]
return data.iloc[train_indices], data.iloc[test_indices]
train_data, test_data = split_train_set(housing)
print("train set count: %d, test set count: %d" % (len(train_data), len(test_data)))
这个方法并不完美,每次运行你都会生成不一样的测试集和数据集。 我们希望每次运行这个方法生成的训练集和测试集是一样的,这样便于我们在调整算法或者使用不同的训练集时比较算法的优劣。 为了达到这个目的,
我们可以在第一次使用该方法时将训练集和测试集保存为文件,然后每次从文件中读取数据。
可以在使用 np.random.permutation() 方法之前,调用 np.random.seed() 方法
Scikit-Learn提供了一些函数,可以通过多种方式将数据集分成多个子集。最简单的函数是train_test_split,它与前面定义的函数 split_train_test几乎相同,除了几个额外特征。首先,它也有random_state参数,让你可以像之前提到过的那样设置随机生成器种子;其次,你可以把行数相同的多个数据集一次性发送给它,它会根据相同的索引将其拆分
from sklearn.model_selection import train_test_split
train_data, test_data = train_test_split(housing, test_size=0.2, random_state=42)
print("train set count: %d, test set count: %d" % (len(train_data), len(test_data)))
3.2 分层抽样划分训练集和测试集¶
当数据分布均匀的时候,随机抽样是一个不错的方法。但是当数据分布不均匀的时候,我们需要考虑使用分层抽样,比如一个国家有5个地区,人口比列是 5%,15%,30%,20%,30%,我们希望选取的样本也满足这种分布,这样抽取出来的样本能更好地代表整个数据集。
如果你咨询专家,他们会告诉你,要预测房价平均值,收入中位数是一个非常重要的属性。于是你希望确保在收入属性上,测试集能够代表整个数据集中各种不同类型的收入。由于收入中位数是一个连续的数值属性,所以你得先创建一个收入类别的属性
housing.hist(column='median_income')
housing["income_cat"] = np.ceil(housing["median_income"] / 1.5)
housing["income_cat"].where(housing["income_cat"] < 5, 5.0, inplace=True)
housing.hist(column='income_cat')
使用Scikit-Learn的StratifiedShuffleSplit进行分层抽样 class sklearn.model_selection.StratifiedShuffleSplit(n_splits=10, test_size=None, train_size=None, random_state=None)
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_data = housing.loc[train_index]
strat_test_data = housing.loc[test_index]
# 检查效果
strat_train_data.hist(column='income_cat')
strat_test_data.hist(column='income_cat')
可以看到训练集和测试集的在income_cat上的分布是一样的,跟未划分之前的数据集的分布也是一样的
# 恢复数据,删除 income_cat 列
for dataset in (strat_train_data, strat_test_data, housing):
dataset.drop(['income_cat'], axis=1, inplace=True)
4. 深入理解数据¶
4.1 将地理数据可视化¶
housing.plot(kind='scatter',x='longitude', y='latitude')
housing.plot(kind='scatter',x='longitude', y='latitude', alpha=0.1)
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()
4.2 查看属性的相关性¶
由于数据集不大,你可以使用corr()方法轻松计算出每对属性之间的标准相关系数(也称为皮尔逊相关系数):
corr_matrix = housing.corr()
print(corr_matrix)
查看每个属性与房价中位数的关系
corr_matrix["median_house_value"].sort_values(ascending=False)
相关系数的范围从-1变化到1。越接近1,表示有越强的正相关;比如,当收入中位数上升时,房价中位数也趋于上升。当系数接近于-1,则表示有强烈的负关;注意看纬度和房价中位数之间呈现出轻微的负相关(也就是说,越往北走,房价倾向于下降)。最后,系数靠近0则说明二者之间没有线性相关性。
我们也可以使用Pandas的scatter_matrix函数,它会绘制出每个数值属性相对于其他数值属性的相关性。这里我们仅关注那些与房价中位数属性最相关的,可算作是最有潜力的属性
from pandas.plotting import scatter_matrix
attrs = ["median_house_value", "median_income", "total_rooms", "housing_median_age"]
scatter_matrix(housing[attrs], figsize=(12, 8))
由图可知 相关性最强的是 median_income,放大来看, 图中有一些数据比如 median_house_value 为 500000,450000,350000 时,散点图为一条明显的直线,这是因为数据设置了房价上限,这会影响我们的预测,所以我们可以考虑将这些干扰点去除
housing.plot(kind="scatter", x="median_income", y="median_house_value", alpha=0.1)
4.3 探索属性的隐藏信息¶
在准备给机器学习算法输入数据之前,你要做的最后一件事应该是尝试各种属性的组合。比如,如果你不知道一个地区有多少个家庭,那么知道一个地区的“房间总数”也没什么用。你真正想要知道的是一个家庭的房间数量。同样地,单看“卧室总数”这个属性本身,也没什么意义,你可能是想拿它和“房间总数”来对比,或者拿来同“每个家庭的人口数”这个属性结合也似乎挺有意思。我们来试着创建这些新属性:
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)
housing = strat_train_data.drop("median_house_value", axis=1)
housing_labels = strat_train_data["median_house_value"].copy()
5.2 处理缺失值¶
处理缺失值有三个选择:
放弃对应的记录
放弃对应的属性
将缺失值设置为某个特定的值, 通常设置为 0、均值或者中位数
通过 DataFrame 的 dropna()、drop()、fillna() 可以轻松完成这些操作,这里我们使用 第三个办法
# housing.dropna(subset=["total_bedrooms"]) # option 1
# housing.drop("total_bedrooms", axis=1) # option 2
median = housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median) # option 3
Scikit-Learn提供了一个非常容易上手的教程来处理缺失值:imputer。使用方法如下,首先,你需要创建一个imputer实例,指定你要用属性的中位数值替换该属性的缺失值:
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")
# imputer 适用于数值型,先把文本类的列去掉
housing_num = housing.drop("ocean_proximity", axis=1)
imputer.fit(housing_num) # 计算数据集中位数,并保存在 statistics_ 属性中
print(imputer.statistics_)
# 对缺失值进行补充
X = imputer.transform(housing_num)
# X 是一个 Numpy 数组,将其重新转换为 DataFrame
housing_tr = pd.DataFrame(X, columns=housing_num.columns)
housing_tr.info()
可以看到已经不存在none值了
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
housing_cat = housing['ocean_proximity']
housing_cat_encoded = encoder.fit_transform(housing_cat)
print(housing_cat_encoded)
print(encoder.classes_)
这个标签转换器没有处理分类之间的相似性,Scikit-Learn 提供了一个 OneHotEncoder 编码器可以处理这种情况
5.3.2 OneHotEncoder¶
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))
print(housing_cat_1hot) # 得到的是一个Scipy稀疏矩阵,只保存元素不为 0 的 位置和值
# 使用 toarray()方法转换成一个 Numpy 密集矩阵
print(housing_cat_1hot.toarray())
5.3.3 LabelBinarizer¶
可以直接输出一个 numpy 矩阵
from sklearn.preprocessing import LabelBinarizer
encoder = LabelBinarizer()
housing_cat_1hot = encoder.fit_transform(housing_cat)
print(housing_cat_1hot)
5.3.4 自定义转换器¶
from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
def __init__(self, add_bedrooms_per_room = True): # no *args or **kargs
self.add_bedrooms_per_room = add_bedrooms_per_room
def fit(self, X, y=None):
return self # nothing else to do
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[:, 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)
print(housing_extra_attribs)
5.4 特征缩放¶
归一化:Scikit-Learn提供了一个名为MinMaxScaler的转换器。如果出于某种原因,你希望范围不是0~1,你可以通过调整超参数feature_range进行更改。 标准化:。Scikit-Learn提供了一个标准化的转换器StandadScaler。
5.5 数据处理流水线¶
Scikit-Learn提供了Pipeline来支持这样的转换。下面是一个数值属性的流水线例子:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([('imputer', SimpleImputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),])
housing_num_tr = num_pipeline.fit_transform(housing_num)
print(housing_num_tr)
此外,Scikit-Learn还提供了一个FeatureUnion类,将不同的 pipeline 组织起来. 每条子流水线从选择器转换器开始:只需要挑出所需的属性(数值或分类),删除其余的数据,然后将生成的DataFrame转换为NumPy数组,数据转换就完成了。Scikit-Learn中没有可以用来处理Pandas DataFrames的,因此我们需要为此任务编写一个简单的自定义转换器:
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
class MyLabelBinarizer(TransformerMixin):
def __init__(self, *args, **kwargs):
self.encoder = LabelBinarizer(*args, **kwargs)
def fit(self, x, y=0):
self.encoder.fit(x)
return self
def transform(self, x, y=0):
return self.encoder.transform(x)
from sklearn.pipeline import FeatureUnion
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
num_pipeline = Pipeline([('selector', DataFrameSelector(num_attribs)),
('imputer', SimpleImputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),])
cat_pipeline = Pipeline([('selector', DataFrameSelector(cat_attribs)),
('label_binarizer', MyLabelBinarizer()),])
full_pipeline = FeatureUnion(transformer_list=[("num_pipeline", num_pipeline),
("cat_pipeline", cat_pipeline),])
housing_prepared = full_pipeline.fit_transform(housing)
print(type(housing_prepared))
print(np.shape(housing_prepared))
6. 训练模型¶
6.1 LinearRegression¶
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
# 使用该模型做一些预测
some_data = housing.iloc[:5]
some_labels = housing_labels.iloc[:5]
some_data_prepared = full_pipeline.transform(some_data)
print('Predictions: ', lin_reg.predict(housing_prepared[:5]))
print('Acctual Labels: ', list(some_label))
我们可以使用Scikit-Learn的mean_squared_error函数来测量整个训练集上回归模型的RMSE(标准误差):
from sklearn.metrics import mean_squared_error
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
print(lin_rmse)
这说明我们的预测误差达到了 68628 美元
6.2 DecisionTreeRegressor¶
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared, housing_labels)