尽管在机器学习并不推荐使用专家设计的规则,但是在默写特定的应用中,不得不说专家知识能够帮助我们更好的处理数据。
通常来说,领域专家可以帮助找出有用的特征,其信息量比数据的原始表示要大得多。比如,预测机票价格,假定你有价格、日期、航空公司等记录。机器学习可能从这些记录中构建一个不错的模型,但是可能无法学到机票价格中的某些因素,如节假日的高峰出行等。
下面我们利用Citi Bike数据集(自行车出租)为例来说明:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
citibike = mglearn.datasets.load_citibike()
print("citi bike:{}".format(citibike.head()))
#将月租车数量可视化
plt.figure(figsize=(10, 3))
xticks = pd.date_range(start=citibike.index.min(), end=citibike.index.max(),freq='D')
plt.xticks(xticks, xticks.strftime("%a %m-%d"), rotation=90, ha="left")
plt.plot(citibike, linewidth=1)
plt.xlabel("Date")
plt.ylabel("Rentals")
运行上述代码,其结果如下图:
对于选定的citi bike站点,自行车出租数量随时间的变化
通过运行结果可以看出,该点的自行车出租在白天与黑夜,工作日与休息日之间存在较大差异。
对于机器学习,我们都希望其能够很好的根据当前的数据去预测未来数据,我们接着来看如何进行预测:
#提取目标值
y = citibike.values
#利用“%s“将时间转换为POSX时间
x = citibike.index.astype("int").values.reshape(-1, 1)
#我们首先定义一个函数,将数据划分为训练集和测试集,构建模型并将结果转化
#使用 184个数据点作为训练集,其余的用于测试
n_train = 184
#对给定特征上的回归进行评估和作图的函数
def eval_on_features(features, target, regressor):
#将给定特征划分为训练集和测试集
x_train, x_test = features[:n_train], features[n_train:]
#同时划分目标数组
y_train, y_test = target[:n_train], target[n_train:]
regressor.fit(x_train, y_train)
print("Test-set R^R: {:.2f}".format(regressor.score(x_test, y_test)))
y_pred = regressor.predict(x_test)
y_pred_train = regressor.predict(x_train)
plt.figure(figsize=(10, 3))
plt.xticks(range(0, len(x), 8), xticks.strftime("%a %m-%d"), rotation=90,ha="left")
plt.plot(range(n_train), y_train, label="train")
plt.plot(range(n_train, len(y_test) + n_train), y_test, '-', label="test")
plt.plot(range(n_train), y_pred_train, '--', label="prediction train")
plt.plot(range(n_train, len(y_test) + n_train), y_pred, '--', label="prediction test")
plt.legend(loc=(1.01, 0))
plt.xlabel("Date")
plt.ylabel("Rentals")
#我们之前看到,随机森林需要很少的数据预处理,因此它似乎很适合作为第一个模型,我们
#使用POSIX时间特征X,并将随机森林回归传入我们的eval_on_features函数
from sklearn.ensemble import RandomForestRegressor
regressor = RandomForestRegressor(n_estimators=100, random_state=0)
plt.figure()
eval_on_features(x, y, regressor)
运行上述代码后期结果为:
Test-set R^R: -0.04
随机森林仅使用POSIX时间做出预测
从运行结果来看,此时的数据集上的预测结果相当好,这符合随机森林通常的表现。但是对于测试集来说,预测结果是一条常数直线。R*R为-0.04,这说明什么也没学到。
而出现该问题的原因是特征和随机森林的组合,测试集中POSIX时间特征的值超出了训练集训练值的范围(测试集中数据点的时间戳要晚于训练集中所有的数据点。树以及随机森林无法外推到训练集之外的特征范围。结果就是模型只能预测训练集中最近的目标值,即最后一次观测到数据的时间。而要解决该问题,就需要用到本节介绍的“专家知识”了。
通过观察训练数据中的租车图像,我们发现影响租车数量的两个重要因素:那就是租车时间点以及这天是星期几。所以,我们来添加这两个特征。由于我们从POSIX时间中学习不到任何东西,所以讲这个特征删除掉。
#以小时作为预测条件
x_hour = citibike.index.hour.values.reshape(-1, 1)
eval_on_features(x_hour, y, regressor)
运行后其结果为:
Test-set R^R: 0.60
随机森林仅使用每天的时刻做出预测
由此可见,以每天的时刻做出预测已经比使用POSIX来预测的结果好了许多,下面我们在添加星期几作为另一个特征:
x_hour_week = np.hstack([citibike.index.dayofweek.values.reshape(-1, 1),
citibike.index.hour.values.reshape(-1,1)])
eval_on_features(x_hour_week, y, regressor)
运行后其结果为:
Test-set R^R: 0.84
随机森林使用一周的星期几和时刻这两个特征的预测结果
可以看出当添加了星期几这个特征后,效果有更加好了。
但是其实我们并不需要像随机森林这样复杂的数据,下面我们看一下将其修改为线性回归模型:
from sklearn.linear_model import LinearRegression
eval_on_features(x_hour_week, y, LinearRegression())
此时运行结果为:
Test-set R^R: 0.13
线性模型同时使用星期和时刻两个特征时的预测变化
从运行结果来看,使用线性回归模型来预测时效果要差很多,其原因在于我们用整数编码一周的星期几和一天内的时间,他们被解释为连续变量。因此,线性模型只学到每天时间的线性函数——时间越晚,租车数量越多,但是实际模型要比这复杂得多。
我们可以使用分类变量(OneHotEncoder进行变换)来获取这种模式:
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
x_hour_week_onehot = encoder.fit_transform(x_hour_week).toarray()
eval_on_features(x_hour_week_onehot, y, LinearRegression())
运行后其结果为:
Test-set R^R: 0.61
线性模型使用one-hot编码过的一周的星期几和每天时刻两个特征做出的预测
通过one-hot编码,发现其性能比直接使用线性回归好了很多,当然,我们也可以使用原始的特征多项式来对数据集进行处理,情况和one-hot类似,这里不再将其展示了。