任务 根据美国人年龄,工作,教育水平等特征判定这人年收入比50000美元大还是小
问题 有的特征不是数值型,比如性别(男或女),工作类型等,而监督学习模型需要都是数值型的特征,可以通过one-hot编码解决
举例,如何用one-hot表示星期几?创七个特征分别表示周一到周末,如果是周一,则周一特征值为1,其他全为0
如何使用one-hot 1pandas(更方便) 2sklearn
调用pd.get_dummies(data)获取onehot处理的数据,然后用logistic回归模型进行预测,发现准确度只有80%,因为使用onehot后一个特征虽然分成多个0-1特征但很多0特征相同,导致结果不理想
解决方法 1在同时包含训练集和测试集的dataframe调用get_dummies 2保证分别调用tr,te的列名相同
直接用onehot的问题 1拼写有误的属性可能会变成独立的特征 2onehot虽然用01表示但模型会将01识别为连续的数,而实际01只想表示两种不同的分类,而不是当作连续数值处理 3使用get_dummies只会处理字符串的值,不会处理其他数值,比如整数
解决 1可用sklearn的OneHotEncoder指定哪些变量连续哪些变量离散 2将df的整数类别列先转化类型为str,再给get_dummies参数传入comumns=[]指定要处理的列
def test_onehot_classifier(self):
demo_df = pd.DataFrame({'integer feature': [0,1,2,1], 'categorical feature': ['sock','box','fox','box']})
display(demo_df)
demo_df['integer feature'] = demo_df['integer feature'].astype(str)
display(pd.get_dummies(demo_df, columns=['integer feature', 'categorical feature']))
数据预测准度与使用的模型有关,比如线性模型,树模型(决策树)
决策树与输入结构强相关,且无法预测,线性模型无法构造复杂模型,比如非直线模型,此时可以使用特征分箱(binning),也叫离散化(discretization),将数据分为多个特征
比如wave数据集,是一个单特征数据集,我们加载特征在-3到3之间的数据,将-3到3分成十个区间,每个点在哪个区间作为特征,总共有10个特征,10个特征用onehot编码,然后对10个特征的分箱训练(分箱训练需要用OneHotEncoder)(这样,就把单特征的wave数据集转化为了10个特征的onehot数据集)
def test_plot_binning(self):
bins = np.linspace(-3, 3, 11)
which_bin = np.digitize(self.wave[0], bins=bins)
encoder = OneHotEncoder(sparse=False).fit(which_bin)
line = np.linspace(-3, 3, 1000, endpoint=False).reshape(-1, 1)
x_bin, line_bin = encoder.transform(which_bin), encoder.transform(np.digitize(line, bins=bins))
print(f'x_bin first 5: {x_bin[:5]}')
reg = LinearRegression().fit(x_bin, self.wave[1])
plot.plot(line, reg.predict(line_bin), label='linear regression binned')
reg = DecisionTreeRegressor(min_samples_split=3).fit(x_bin, self.wave[1])
plot.plot(line, reg.predict(line_bin), label='decision tree binned')
plot.plot(self.wave[0], self.wave[1], 'o', c='k')
plot.vlines(bins, -3, 3, linewidth=1, alpha=.2)
plot.legend(loc='best')
plot.ylabel('regression output')
plot.xlabel('input feature')
plot.show()
用分箱数据对wave数据集进行线性回归和决策树回归
结果 1决策树和线性模型完全重合
原因与解释 1因为每个分箱被看作一个特征,所有模型对单个特征的的预测都是相同的
总结 1分箱后,线性模型更加灵活(可预测非线性特征),决策树模型简化了(从过拟合向欠拟合发展) 2一般来说,分箱可以增加线性模型的预测效果(更灵活,可预测非线性模型) 3如果想拟合特征非线性可以考虑分箱
如果想丰富线性模型的特征,可以给原始数据添加交互特征或多项式特征
用途 统计建模或机器学习一些基本分析
给分箱的x数据添加原始x数据,可学到偏移和斜率
def test_plot_bin_gradient(self):
bins = np.linspace(-3, 3, 11)
which_bin = np.digitize(self.wave[0], bins=bins)
# sparse默认为True,会返回降维的系数矩阵,这里False返回完整矩阵,否则np.hstack会报错
encoder = OneHotEncoder(sparse=False).fit(which_bin)
line = np.linspace(-3, 3, 1000, endpoint=False).reshape(-1, 1)
x_bin, line_bin = encoder.transform(which_bin), encoder.transform(np.digitize(line, bins=bins))
x_combined, line_combined = np.hstack([self.wave[0], x_bin]), np.hstack([line, line_bin])
reg = LinearRegression().fit(x_combined, self.wave[1])
print(f'line combine shape {line_combined.shape}')
plot.plot(line, reg.predict(line_combined), label='linear regression combined')
for bin in bins:
plot.plot([bin, bin], [-3, 3], ':', c='k')
plot.legend(loc='best')
plot.ylabel('regression output')
plot.xlabel('input feature')
plot.plot(self.wave[0], self.wave[1], 'o', c='k')
plot.show()
思考,为什么斜率是负的?
添加原始特征后,变成y=w1x1+w2。反推的话,斜率为负,即原始特征的系数w1为负,则分箱系数需比未添加原始特征时多。不算了,可能是线性代数解方程,大概算了下可能不对,算的w1小了w2就高,w2小了w1高,线性回归模型约束条件下可能w1为负最好
学习交互特征,看下效果
def test_plot_interaction_feature(self):
bins, line = np.linspace(-3, 3, 11), np.linspace(-3, 3, 1000, endpoint=False).reshape(-1, 1)
which_bin = np.digitize(self.wave[0], bins=bins)
encoder = OneHotEncoder(sparse=False).fit(which_bin)
x_bin, line_bin = encoder.transform(which_bin), encoder.transform(np.digitize(line, bins=bins))
x_prod, line_prod = np.hstack([x_bin, self.wave[0] * x_bin]), np.hstack([line_bin, line * line_bin])
print(f'x prod shape: {x_prod.shape}')
reg = LinearRegression().fit(x_prod, self.wave[1])
plot.plot(line, reg.predict(line_prod), label='linear regression product')
for bin in bins:
plot.plot([bin, bin], [-3, 3], ':', c='k')
plot.plot(self.wave[0], self.wave[1], 'o', c='k')
plot.ylabel('regression output')
plot.xlabel('input feature')
plot.show()
利用了泰勒,将拟合曲线展开泰勒级数
添加的特征为输入特征的x次幂,可传参指定
因为一次幂等于x,所以不需要再np.hstack合并特征
def test_plot_polynomial_feature(self):
line = np.linspace(-3, 3, 1000, endpoint=False).reshape(-1, 1)
poly = PolynomialFeatures(degree=10, include_bias=False).fit(self.wave[0])
x_poly = poly.transform(self.wave[0])
reg = LinearRegression().fit(x_poly, self.wave[1])
line_poly = poly.transform(line)
plot.plot(line, reg.predict(line_poly), label='polynomial regression linear')
plot.plot(self.wave[0], self.wave[1], 'o', c='k')
plot.xlabel('input feature')
plot.ylabel('regression output')
plot.legend(loc='best')
plot.show()
高次幂在边界会有极端表现
对比核svm,没有多项式这种极端表现
def test_plot_svm_wave(self):
line = np.linspace(-3, 3, 1000, endpoint=False).reshape(-1, 1)
for gamma in (1, 10):
svr = SVR(gamma=gamma).fit(*self.wave)
plot.plot(line, svr.predict(line), label=f'SVR gamma={gamma}')
plot.plot(*self.wave, 'o', c='k')
plot.xlabel('input feature')
plot.ylabel('regression feature')
plot.legend(loc='best')
plot.show()
对比发现,svm结果比polynomial好,且不需要显式特征变换
交互特征作为一种手段,可以应用于其他模型上,比如线性模型,随机森林
新增特征对不同模型来说效果不同,有的会提升准度,有的会降低准度,需取舍
除了给数据集添加交互特征核多项式特征,还可以添加非线性特征,比如log,exp, 三角函数等特征
将0-1的随机数按公式变换 y=10*exp(x),然后统计y分布情况,然后对比下原始数据
def test_plot_random_distribute(self):
r = np.random.RandomState(0)
x_org, w = r.normal(size=(1000, 3)), r.normal(size=3)
x = r.poisson(10 * np.exp(x_org))
y = np.dot(x_org, w)
bins = np.bincount(x[:, 0])
print(f'bins shape: {bins.shape}, bins [:5]: {bins[:5]}')
fig, axes = plot.subplots(2, 1, figsize=(10, 10))
axes[0].bar(range(len(bins)), bins)
axes[0].set_ylabel('number of appearances')
axes[0].set_xlabel('x')
axes[1].hist(x_org[:, 0], bins=30)
axes[1].set_ylabel('number of appearances')
axes[1].set_xlabel('y')
plot.show()
np.poisson表示泊松分布。发现数据处理后变成泊松分布了,数据处理前是正态分布
线性模型无法很好的处理非线性数据,可以试着预测下
def test_predict_poisson_linear(self):
r = np.random.RandomState(0)
x_org, w = r.normal(size=(1000, 3)), r.normal(size=3)
x, y = r.poisson(10 * np.exp(x_org)), np.dot(x_org, w)
xtr, xte, ytr, yte = train_test_split(x, y, random_state=0)
score = Ridge().fit(xtr, ytr).score(xte, yte)
print(f'ridge predict poisson distribute score: {score}')
将数据处理为线性模型,会对ridge性能有所提升,比如再用对数函数处理回来
交互特征和多项式特征一定程度可以提高线性模型性能
对基于树的模型一般没有明显提升,因为树可以自己发现重要的交互特征,不需要再处理变换数据
其他模型一定程度也可受益于新增特征,但没线性模型明显
增加特征缺点 模型复杂化,更大可能过拟合
解决方法 新增最有用的特征,不要新增无用特征
如何判断特征的用处有多大? 1单变量统计 2基于模型选择 3迭代选择 三种方法都是监督方法
概念 计算每个特征和目标值之间是否存在统计显著性,选择具有最高置信度的特征。对分类问题讲,可以叫做方差分析。
特性 单变量的,即每次单独考虑每个特征。比如如果一个特征只有和另一个特征合并时才有意义,此特征将被舍弃
操作 1选处理单变量特征对应的测试,分类问题是f_classif,回归问题是f_regression 2根据测试确定的p值选择一种舍弃特征的方法 3根据阈值舍弃p值过大的特征(阈值需要计算,有SelectKBest和SelectPercentile,前者选固定k个特征,后者选固定百分比的特征)
任务 给原始数据添加一些噪声,通过单变量统计删除无用的特征
使用SelectPercentile选择特征
def test_feature_select_base(self):
# show how to select features
r = np.random.RandomState(42)
noise = r.normal(size=(len(self.cancer.data), 50))
cancer_noise = np.hstack([self.cancer.data, noise])
xtr, xte, ytr, yte = train_test_split(cancer_noise, self.cancer.target, random_state=0, test_size=.5)
select = SelectPercentile(percentile=50).fit(xtr, ytr)
xtr_selected = select.transform(xtr)
print(f'before select shape: {xtr.shape}, after select shape: {xtr_selected.shape}')
# show which features are selected
mask = select.get_support()
print(f'feature selection mask: {mask}')
plot.matshow(mask.reshape(1, -1), cmap='gray_r')
plot.xlabel('sample index')
plot.show()
发现大部分特征都是原始特征,但仍有部分特征来源噪声
将添加噪声特征的数据和选择特征后的数据用LogisticRegression分类模型训练,发现选择特征的约为94%,未选择特征的约为93%
用处 如果比较耗资源,可以用此方法删掉一些特征,但需要注意是否删除的是无用特征
概念 通过监督学习模型选的特征,然后传入个阈值参数,将监督模型选的特征再用阈值过滤一遍
def test_feature_select_from_model(self):
noise = np.random.RandomState(42).normal(size=(len(self.cancer.data), 50))
cancer_noise = np.hstack([self.cancer.data, noise])
xtr, xte, ytr, yte = train_test_split(cancer_noise, self.cancer.target, random_state=0)
select = SelectFromModel(RandomForestClassifier(n_estimators=100, random_state=42), threshold='median').fit(xtr, ytr)
mask = select.get_support()
plot.matshow(mask.reshape(1, -1), cmap='gray_r')
plot.xlabel('sample index')
plot.show()
相比于Percentile,原始特征多选了两个,用LogisticRegression训练,准度提高到约95%
对比
单变量统计 没用监督模型选择特征
基于模型选择 用单个模型选择特征
迭代特征选择 用很多模型选择特征,每个模型使用不同数量的特征(有两种方法,一种是从0开始逐一添加特征,另一种是先用所有特征然后逐一删除特征)
特点 成本高,因为构造了一系列模型并训练
递归特征消除(Recursive Feature Elimination, RFE) 是迭代特征选择的一种方法。先从所有特征建模,然后从模型舍弃一个最不重要特征,然后用剩下特征重新建模,再舍弃一个最不重要特征,循环往复知道剩余特征数量达到预期数量
看下RFE效果
def test_feature_select_iter(self):
noise = np.random.RandomState(42).normal(size=(len(self.cancer.data), 50))
xtr, xte, ytr, yte = train_test_split(np.hstack([self.cancer.data, noise]), self.cancer.target, random_state=0, test_size=.5)
select = RFE(RandomForestClassifier(n_estimators=100, random_state=42), n_features_to_select=40).fit(xtr, ytr)
plot.matshow(select.get_support().reshape(1, -1), cmap='gray_r')
plot.xlabel('sample index')
plot.show()
发现RFE只漏了一个原始特征,但训练周期更长,因为训练了40次随机森林
当不确定用什么特征时,可以使用自动化特征选择
如果大概确定用什么特征,可以构造交互特征或多项式特征
即在建模训练时用一些已知的先验经验增加模型准确度
例子 预测某人家门口租车情况,即每3小时内租车数量,数据集为mglearn.datasets.load_citibike()
数据集为单特征,特征为租车的时间
将数据原始特征用随机森林训练,基本什么也没学到
因为决策树无法做预测
即使是未来时间租车,小时也总是在0-24范围内,假如用这个特征,那么可以使用随机森林学习
仅训练小时数的单特征准度约为60%
租车和工作日与否有关,可以添加星期几作为一个特征,和小时数作为两个特征训练随机森林模型
准确率大概达到84%
尝试用简单的线性模型训练
发现准确度大概只有13%,分析发现模型将整数编码的星期数和小时数视为连续变量,可以通过onehotEncoder编码将其转为分类变量再次训练
准确率达到62%
准确率达到85%,发现准确率和随机森林的差不多
和随机森林相比的优点是,可以看到特征对应的学习系数,而这在随机森林模型中是看不到的