网站首页 > 技术文章 正文
文章发布于公号【数智物语】 (ID:decision_engine),关注公号不错过每一篇干货。
来源 | SAMshare
作者 | Samshare
选自 Python-Machine-Learning-Book On GitHub
作者:Sebastian Raschka
翻译&整理 By Sam
本文的主体结构前置,如下:
一. 处理数据缺失
1.1 删除有缺失的样本或特征
1.2 填充缺失值
二. 处理分类数据
2.1 映射有序特性
2.2 对类别特征进行编码
2.3 对无序特征进行“独热编码”
三. 学习划分训练及验证集
四. 统一特征取值范围
五. 选择有意义(有效)的特征
5.1 L1正则化的稀疏解
5.2 序列特征选择算法
六. 使用随机森林评估特征重要性
PS:代码已单独保存:可在公众号后台输入“预处理”进行获取ipynb文件
01
处理数据缺失
数据缺失,在现实生活中是十分常见的,原因也是非常复杂的,在我们进行建模的过程中,如果我们不对这些缺失值进行适当的处理,出来的模型恐怕也效果不太好,其重要性这里就不累赘多说,我们先来创建一个小栗子,助于大家理解数据缺失的问题:
1import pandas as pd 2from io import StringIO 3csv_data = '''A,B,C,D 41.0,2.0,3.0,4.0 55.0,6.0,,8.0 610.0,11.0,12.0,''' 7# If you are using Python 2.7, you need 8# to convert the string to unicode: 9# csv_data = unicode(csv_data) 10df = pd.read_csv(StringIO(csv_data)) 11df
output:
1# 查看各列缺失情况 2df.isnull().sum()
output:
我们创建了一个csv格式的数据并且读入DataFrame对象,如果想统计有多少缺失,我们可以使用isnull方法来返回一个值为布尔类型的DataFrame,判断每个元素是否缺失,如果元素缺失,值为True。然后使用sum方法,我们就能得到DataFrame中每一列的缺失值个数。
这里提示一下,预处理时推荐使用pandas的DataFrame格式,而不要用NumPy数组。由DataFrame对象得到NumPy数组很方便,直接通过values属性即可,然后就可以用sklearn中的算法了。
1.1
删除有缺失的样本或特征
那么,如果我们想要删除这些缺失的数据,处理的方式是怎么样的呢?下面给出了几种场景,都是使用dropna方法来实现的。
1# 删除含有缺失值的样本(即行) 2print (df.dropna()) 3print ('\n') 4# 删除含有缺失值的特征(即列) 5print (df.dropna(axis=1)) 6print ('\n') 7# 删除所有列都是缺失的样本 8print (df.dropna(how='all')) 9print ('\n') 10# 删除没有4个非空特征的样本 11print (df.dropna(thresh=4)) 12print ('\n') 13# 删除指定特征上有缺失的样本(这里‘c'为指定特征) 14print (df.dropna(subset=['C']))
output:
1.2
填充缺失值
有些特征的缺失其实是可以通过填充的方式来弥补的,所以这里也介绍一下sklearn中的Imputer类方法。
参数axis:axis=0计算每个特征取值的平均值用来填充,若axis=1则计算样本所有取值的平均值用来填充;
参数strategy:包括mean、median和most_frequent(most_frequent对于处理分类数据类型的缺失值很有用)。
1from sklearn.preprocessing import Imputer 2# 这里使用均值填充 3imr = Imputer(missing_values='NaN', strategy='mean', axis=0) 4imr = imr.fit(df) 5imputed_data = imr.transform(df.values) 6imputed_data
02
处理分类数据
分类数据也是在现实生活中很常见的,之前我们举例子的都是数值型变量,而其他也有很多分类变量,并且这些变量还分有序和无序变量。如衣服的码数、鞋子的码数,其实都是分类变量,但是它们是有序的,而像衣服的颜色、鞋子的牌子等等,就是无序的分类变量,这两类,在后续的处理中,也是有一些不同的地方的。
我们继续举栗子来讲解,先创建一个dataframe对象:
1import pandas as pd 2df = pd.DataFrame([['green', 'M', 10.1, 'new balance'], 3 ['red', 'L', 13.5, 'Nike'], 4 ['blue', 'XL', 15.3, 'new balance']]) 5df.columns = ['颜色', '尺寸', '单价', '牌子'] 6df
以上,包括了无序分类变量(颜色、牌子)、有序分类变量(尺寸)和数值型变量(单价)。
2.1
映射有序特性
有些算法其实对有序分类变量(ordinal feature)的解释还是不行的,我们需要将其转为整型数值。unfortunately,并没有能够直接调用的方法来自动得到正确顺序的有序分类变量。因此,我们要自己定义映射函数。
1size_mapping = {'XL': 3, 2 'L': 2, 3 'M': 1} 4df['尺寸'] = df['尺寸'].map(size_mapping) 5df
可以看到,尺寸从XL、L和M变成了3、2、1。
我们可以映射过去,当然也是可以反映射回去的:
1# 反映射字典 2inv_size_mapping = {v: k for k, v in size_mapping.items()} 3df['尺寸'] = df['尺寸'].map(inv_size_mapping) 4df
2.2
对类别特征进行编码
上面是对一些无序分类特征进行了映射编码,那么对于我们的标签(也叫类别、目标)也是需要进行编码的,这样子算法才可以进行识别解释。一样的,标签也是无序的,我们可以从0开始编码。
1import numpy as np 2class_mapping = {label: idx for idx, label in enumerate(np.unique(df['牌子']))} 3class_mapping
1df['牌子'] = df['牌子'].map(class_mapping) 2df
可以看到,我们把类别转为了0/1变量,一样的,我们也可以转回去。
1# 同样的,也可以转回去 2inv_class_mapping = {v: k for k, v in class_mapping.items()} 3df['牌子'] = df['牌子'].map(inv_class_mapping) 4df
上面是我们自己手动创建的映射字典,sklearn 中提供了 LabelEncoder 类来实现类似的类别转换工作。
1# 使用自带的LabelEncoder类来实现类别的转换 2from sklearn.preprocessing import LabelEncoder 3class_le = LabelEncoder() 4y = class_le.fit_transform(df['牌子'].values) 5df['牌子'] = y 6df
1# 同样的,也可以转回去 2y_inv = class_le.inverse_transform(y) 3df['牌子'] = y_inv 4df
2.3
对无序特征进行“独热编码”
上面讲了有序分类变量和类别的编码操作,但是对无序分类变量是否也可以类似地操作呢?答案是不行的。就上面的栗子,其中颜色就是无序分类变量,如果按照上面的编码方式,green、red 和 blue 将会被编码为 1、2、3,而这样子算法就会误认为 green<red<blue,然而,他们之间是没有这样子的顺序的。
所以,我们不能采取上面的操作,这里介绍一个“独热编码”。下面我们使用 LabelEncoder 来进行转换:
1df['尺寸'] = df['尺寸'].map(size_mapping) 2X = df[['颜色', '尺寸', '单价']].values 3color_le = LabelEncoder() 4X[:, 0] = color_le.fit_transform(X[:, 0]) 5X
1from sklearn.preprocessing import OneHotEncoder 2ohe = OneHotEncoder(categorical_features=[0]) 3ohe.fit_transform(X).toarray()
1pd.get_dummies(df[['单价', '颜色', '尺寸']])
可以看出,颜色被重新编码为3个新特征,如上图。其实这种编码操作在分类变量中是非常常用的,毕竟大多数的分类变量都是无序的。
03
学习划分训练及验证集
进行到实战,我们导入一个wine数据集,这个可以直接从网络上进行下载,这个数据集主要包含了酒的化学成分的,我们试着导入数据集:
1df_wine = pd.read_csv('https://archive.ics.uci.edu/' 2 'ml/machine-learning-databases/wine/wine.data', 3 header=None) 4df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash', 5 'Alcalinity of ash', 'Magnesium', 'Total phenols', 6 'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins', 7 'Color intensity', 'Hue', 'OD280/OD315 of diluted wines', 8 'Proline'] 9print('Class labels', np.unique(df_wine['Class label'])) 10df_wine.head()
其中,酒的种类有3种,分别是class label 1、2、3,我们调用下面的代码划分训练和验证集,test_size=0.3,使得训练集占Wine样本数的70%,测试集占30%。
在分割数据集时,我们一般选择60:40, 70:30或者80:20。对于大数据集,90:10甚至 99:1也是比较常见的。
1from sklearn.model_selection import train_test_split 2X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values 3X_train, X_test, y_train, y_test = \ 4 train_test_split(X, y, test_size=0.3, random_state=0)
04
统一特征取值范围
特征缩放(feature scaling)是预处理阶段的关键步骤,但常常被遗忘。虽然存在决策树和随机森林这种是少数不需要特征缩放的机器学习算法,但对于大部分机器学习算法和优化算法来说,如果特征都在同一范围内,会获得更好的结果。
你想象一下有两个特征,一个特征的取值范围是[1,10],另一个特征的取值范围是[1,100000]。很明显,如果使用kNN算法,它是用欧氏距离作为距离度量,第二维度特征也就占据了主要的话语权。
面对这些情况,还是有方法可以解决的,使得不同的特征有相同的取值范围,分别是:
> 归一化(normalization):归一化指的是将特征范围缩放到[0,1],是最小-最大缩放(min-max scaling)的特例。
> 标准化(standardization):将特征值缩放到以0为中心,标准差为1,换句话说,标准化后的特征形式服从正态分布,这样学习权重参数更容易。
4.1
归一化与标准化演示
1ex = pd.DataFrame([0, 1, 2, 3, 4, 5]) 2# 标准化 3ex[1] = (ex[0] - ex[0].mean()) / ex[0].std(ddof=0) 4# 归一化 5ex[2] = (ex[0] - ex[0].min()) / (ex[0].max() - ex[0].min()) 6ex.columns = ['input', 'standardized', 'normalized'] 7ex
4.2
归一化实现代码
1from sklearn.preprocessing import MinMaxScaler 2mms = MinMaxScaler() 3X_train_norm = mms.fit_transform(X_train) 4X_test_norm = mms.transform(X_test)
4.3
标准化实现代码
1from sklearn.preprocessing import StandardScaler 2stdsc = StandardScaler() 3X_train_std = stdsc.fit_transform(X_train) 4X_test_std = stdsc.transform(X_test)
05
选择有意义(有效)的特征
如果一个模型在训练集的表现比测试集好很多,那模型很可能过拟合了。过拟合意味着模型捕捉了训练集中的特例模式,但对未知数据的泛化能力比较差。
模型过拟合的一个原因是对于给定的训练集数据,模型过于复杂,常用的减小泛化误差的做法包括:
1. 收集更多的训练集数据
2. 正则化,即引入模型复杂度的惩罚项
3. 选择一个简单点的模型,参数少一点的
4. 降低数据的维度
下面,我们重点学习正则化和特征选择的方法来降低过拟合。
5.1
L1正则化
关于正则化,主要是L1和L2,具体的原理这里就不介绍了,大家可以去百度一下,有很多不错的文章可以看看。其实在具体应用,就是在使用模型算法的时候,作为一个参数进行设置即可(penalty = 'l1'),如下所示:
1from sklearn.linear_model import LogisticRegression 2lr = LogisticRegression(penalty='l1', C=0.1) 3lr.fit(X_train_std, y_train) 4print('Training accuracy:', lr.score(X_train_std, y_train)) 5print('Test accuracy:', lr.score(X_test_std, y_test))
我们可以看看,在不同正则下的特征数量变化情况:
1import matplotlib.pyplot as plt 2fig = plt.figure() 3ax = plt.subplot(111) 4colors = ['blue', 'green', 'red', 'cyan', 5 'magenta', 'yellow', 'black', 6 'pink', 'lightgreen', 'lightblue', 7 'gray', 'indigo', 'orange'] 8weights, params = [], [] 9for c in np.arange(-4., 6.): 10 lr = LogisticRegression(penalty='l1', C=10.**c, random_state=0) 11 lr.fit(X_train_std, y_train) 12 weights.append(lr.coef_[1]) 13 params.append(10.**c) 14weights = np.array(weights) 15for column, color in zip(range(weights.shape[1]), colors): 16 plt.plot(params, weights[:, column], 17 label=df_wine.columns[column + 1], 18 color=color) 19plt.axhline(0, color='black', linestyle='--', linewidth=3) 20plt.xlim([10**(-5), 10**5]) 21plt.ylabel('weight coefficient') 22plt.xlabel('C') 23plt.xscale('log') 24plt.legend(loc='upper left') 25ax.legend(loc='upper center', 26 bbox_to_anchor=(1.38, 1.03), 27 ncol=1, fancybox=True) 28plt.show()
我们可以发现,如果C<0.1,正则项威力很大时,所有特征权重都为0:
5.2
序列特征选择算法
另一种减小模型复杂度和避免过拟合的方法是通过特征选择进行维度降低(dimensionality reduction),这个方法尤其对非正则模型有用。维度降低有两种做法:特征选择(feature selection)和特征抽取(feature extraction)。
特征选择会从原始特征集中选择一个子集合。特征抽取是从原始特征空间抽取信息,从而构建一个新的特征子空间,我们主要学习这种特征选择算法。
特征选择算法的原理是自动选择一个特征子集,子集中的特征都是和问题最相关的特征,这样能够提高计算效率并且由于溢出了不相干特征和噪音也降低了模型的泛化误差。
一个经典的序列特征选择算法是序列后向选择(sequential backward selection, SBS),它能够降低原始特征维度提高计算效率,在某些情况下,如果模型过拟合,使用SBS后甚至能提高模型的预测能力。
SBS原理实现代码:
1from sklearn.base import clone 2from itertools import combinations 3import numpy as np 4from sklearn.metrics import accuracy_score 5from sklearn.model_selection import train_test_split 6class SBS(): 7 def __init__(self, estimator, k_features, scoring=accuracy_score, 8 test_size=0.25, random_state=1): 9 self.scoring = scoring 10 self.estimator = clone(estimator) 11 self.k_features = k_features 12 self.test_size = test_size 13 self.random_state = random_state 14 def fit(self, X, y): 15 X_train, X_test, y_train, y_test = \ 16 train_test_split(X, y, test_size=self.test_size, 17 random_state=self.random_state) 18 dim = X_train.shape[1] 19 self.indices_ = tuple(range(dim)) 20 self.subsets_ = [self.indices_] 21 score = self._calc_score(X_train, y_train, 22 X_test, y_test, self.indices_) 23 self.scores_ = [score] 24 while dim > self.k_features: 25 scores = [] 26 subsets = [] 27 for p in combinations(self.indices_, r=dim - 1): 28 score = self._calc_score(X_train, y_train, 29 X_test, y_test, p) 30 scores.append(score) 31 subsets.append(p) 32 best = np.argmax(scores) 33 self.indices_ = subsets[best] 34 self.subsets_.append(self.indices_) 35 dim -= 1 36 self.scores_.append(scores[best]) 37 self.k_score_ = self.scores_[-1] 38 return self 39 def transform(self, X): 40 return X[:, self.indices_] 41 def _calc_score(self, X_train, y_train, X_test, y_test, indices): 42 self.estimator.fit(X_train[:, indices], y_train) 43 y_pred = self.estimator.predict(X_test[:, indices]) 44 score = self.scoring(y_test, y_pred) 45 return score
现在我们用KNN作为Estimator来运行SBS算法:
1import matplotlib.pyplot as plt 2from sklearn.neighbors import KNeighborsClassifier 3knn = KNeighborsClassifier(n_neighbors=2) 4# selecting features 5sbs = SBS(knn, k_features=1) 6sbs.fit(X_train_std, y_train) 7# plotting performance of feature subsets 8k_feat = [len(k) for k in sbs.subsets_] 9plt.plot(k_feat, sbs.scores_, marker='o') 10plt.ylim([0.7, 1.1]) 11plt.ylabel('Accuracy') 12plt.xlabel('Number of features') 13plt.grid() 14plt.tight_layout() 15plt.show()
SBS的fit方法中有分割数据集的功能,我们为SBS提供了训练集,然后fit方法将其分割为子训练集和子测试集,而这个子测试集被称为验证集(validation dataset)。
SBS算法记录了每一步最优特征子集的成绩,我们画出每个最优特征子集在验证集上的分类准确率:
我们可以看到,最开始随着特征数目的减少,分类准确率一直在提高,原因可能是降低了维度诅咒。对于k={5,6,7,8,9,10,11},分类准确率是100%.
我们打印出特征选择最优的5个特征:
1# 打印出最优的5个特征 2k5 = list(sbs.subsets_[8]) 3print(df_wine.columns[1:][k5])
output: Index(['Alcohol', 'Malic acid', 'Alcalinity of ash', 'Hue', 'Proline'], dtype='object')
1# 原模型的预测效果(即全变量) 2knn.fit(X_train_std, y_train) 3print('Training accuracy:', knn.score(X_train_std, y_train)) 4print('Test accuracy:', knn.score(X_test_std, y_test))
output: Training accuracy: 0.983870967742 Test accuracy: 0.944444444444
1# 特征选择后的新模型预测效果 2knn.fit(X_train_std[:, k5], y_train) 3print('Training accuracy:', knn.score(X_train_std[:, k5], y_train)) 4print('Test accuracy:', knn.score(X_test_std[:, k5], y_test))
output: Training accuracy: 0.959677419355 Test accuracy: 0.962962962963
可以看出,原模型是有一点点过拟合的,训练的时候准确度达到98%,但验证的时候只有94%,而我们使用更少的变量,准确度也可以达到95%,而且训练和验证的效果差异不会特别大。
06
使用随机森林评估特征重要性
随机森林能够度量每个特征的重要性,我们可以依据这个重要性指标进而选择最重要的特征。sklearn中已经实现了用随机森林评估特征重要性,在训练好随机森林模型后,直接调用feature_importances属性就能得到每个特征的重要性。
下面用Wine数据集为例,我们训练一个包含10000棵决策树的随机森林来评估13个维度特征的重要性(第三章我们就说过,对于基于树的模型,不必对特征进行标准化或归一化):
1from sklearn.ensemble import RandomForestClassifier 2feat_labels = df_wine.columns[1:] 3forest = RandomForestClassifier(n_estimators=10000, 4 random_state=0, 5 n_jobs=-1) 6forest.fit(X_train, y_train) 7importances = forest.feature_importances_ 8indices = np.argsort(importances)[::-1] 9for f in range(X_train.shape[1]): 10 print("%2d) %-*s %f" % (f + 1, 30, 11 feat_labels[indices[f]], 12 importances[indices[f]])) 13plt.title('Feature Importances') 14plt.bar(range(X_train.shape[1]), 15 importances[indices], 16 color='lightblue', 17 align='center') 18plt.xticks(range(X_train.shape[1]), 19 feat_labels[indices], rotation=90) 20plt.xlim([-1, X_train.shape[1]]) 21plt.tight_layout() 22plt.show()
我们可以得出结论:‘Alcohol’是最能区分类别的特征。有趣地是,重要性排名前三的特征也在SBS的最优5特征子集中。
sklearn的随机森林实现,包括一个transform方法能够基于用户给定的阈值进行特征选择,所以如果你要用RandomFroestClassifier作为特征选择器,可以设置阈值为0.15,会选择出三个维度特征,Alcohol、Malic acid和Ash。
1from sklearn.feature_selection import SelectFromModel 2sfm = SelectFromModel(forest, threshold=0.15, prefit=True) 3X_selected = sfm.transform(X_train) 4X_selected.shape
output:(124, 3)
星标我,每天多一点智慧
猜你喜欢
- 2024-10-11 机器学习-常用回归算法归纳(全网之最)!错过损失一个亿!
- 2024-10-11 机器爱学习12——欠拟合、过拟合之判断方法、原因及解决方法
- 2024-10-11 向量范数在机器学习中的应用:你真的了解吗?
- 2024-10-11 06-人人都懂的人工智能:正则化技术防止模型过拟合
- 2024-10-11 为什么反物质如此稀少?对称性告诉你答案
- 2024-10-11 算法岗面试「完整脉络」梳理:手推公式、通用问题、常见算法
- 2024-10-11 L2正则化为什么能够使得模型更简单?是因为这
- 2024-10-11 稀疏位图算法RoaringBitmap 稀疏图是指零元素非常少的矩阵
- 2024-10-11 机器学习——线性回归 线性回归教程
- 2024-10-11 PyTorch(八)——梯度下降、优化器、偏差方差与正则化
你 发表评论:
欢迎- 最近发表
-
- 在 Spring Boot 项目中使用 activiti
- 开箱即用-activiti流程引擎(active 流程引擎)
- 在springBoot项目中整合使用activiti
- activiti中的网关是干什么的?(activiti包含网关)
- SpringBoot集成工作流Activiti(完整源码和配套文档)
- Activiti工作流介绍及使用(activiti工作流会签)
- SpringBoot集成工作流Activiti(实际项目演示)
- activiti工作流引擎(activiti工作流引擎怎么用)
- 工作流Activiti初体验及在数据库中生成的表
- Activiti工作流浅析(activiti6.0工作流引擎深度解析)
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)