本篇为《机器学习完备流程总结+实践经验细节+代码工具书(2)》,基于上一篇数据分析中获得的结果,进行数据处理相关流程(基于python3):
什么是好的机器学习模型/如何得到好的模型+数据分析阶段:https://blog.csdn.net/weixin_44563688/article/details/86535274
前置数据处理阶段:(you are here)
非神经网络机器学习模型的搭建,训练及评价阶段: https://blog.csdn.net/weixin_44563688/article/details/86568400
神经网络机器学习模型的搭建,训练及评价阶段: https://blog.csdn.net/weixin_44563688/article/details/88884468
数据处理的目的:机器学习固然强大,但为了高效地进行模型训练以及获得理想的最终表现,我们需要在将数据feed进入模型之前,对数据进行一些处理,这些处理大多出于统计学的考虑,也在一直以来的机器学习实践中无数次证明着它们的必要性,数据在经过这一环节的处理之后,就可以直接用于模型的训练了。step1-5的处理目标为input数据中的连续非类别变量,step6中的处理为input数据中的类别变量,在进行step1-5时需要将input数据中的连续非类别变量挑选出来进行组织,因此默认这些步骤中data这个变量里的feature均为连续值。因为数据增强本质是一种避免overfitting的方法,不应该算是数据处理的常规流程,所以我将图像数据的数据增强代码展示于博客最后一节:神经网络模型的搭建与训练流程。
step1 :处理缺失值。在上一篇数据分析的环节中我们通过heatmap以及DataFrame的isnull()方法已经确定了缺失值的位置以及大体分布情况,在数据处理的第一步中,我们的任务就是解决缺失值的问题,常见情况下,解决缺失值有以下几种办法:舍弃,平均值填充,中位数填充,众数填充,插值填充,回归填充以及gruopby之后的平均值/中位数/众数/插值/回归填充。这里介绍下各种方法相应的适用情况:
舍弃:直接drop掉含有该缺失值的sample或者出现缺失值数量最多的feature,简单粗暴,一般在有缺失值的sample占数据总体比重较小,或者该sample的其他填充方式不好进行,胡或者该出现缺失值较多的feature不重要的情况(与最终结果的covariance系数较小,在上一篇数据分析中已经使用DataFrame中的corr()方法获得)可采取,缺点是造成了部分有用信息(information)的丢失:直接舍弃sample来解决null value问题的同时,也舍弃了这个sample中其他正确有用的信息(其他feature的值),直接舍弃feature的同时,即直接舍弃了这个数据维度,其他sample中的该feature的信息也被直接去除。
平均值填充:用该feature的平均值来填充对应的缺失位置,一般情况下比较robust,但是存在例外,就是outlier问题比较严重,或者数据分布非常crazy(简单来理解就是分布不够continuous,一阶导数变化不够平缓)。outlier和数据分布的大概情况同样在上一篇中我们已经利用相关方法进行了了解和分析。
中位数填充:一般情况下robust,但是和平均值一样,不适用于crazy的数据分布,想象一个数据分布,数据大多平均分布在两头,但是在中部仍然连续,那么这个中位数很可能会存在于其实并没有什么数据点在的中部,与实际的真实值相差较大。
众数填充:比较推荐的方法,简单,而且多半情况下比较robust,但是仍然受限于数据分布,若某一个数据分布比较uniform,histogram接近一个长方形,那么这个众数的价值就不大了。所以中位数,众数,以及平均值的具体选择是建立在了解数据分布的基础之上的。
插值填充:若模型本身的组织形式存在某种关联,比如相邻sample在时间或者空间上连续,比如sample 4是采样自sample 3和sample 5中间的空间位置,缺失feature为该点的温度,那么我们就可以用sample 3或者sample 5处温度的平均值来估计sample 4的温度,更加精确的可以采用双线性插值。
回归填充:即建立一个额外的回归机器学习模型对缺失值进行预测回归,一般会有较好的表现,问题是加大了工作量(即选择合适的模型以及训练等等)
*groupby之后进行以上操作:实际中,如果输入数据(X)中存在类别变量,拿经典的泰坦尼克数据举例,若我们缺失某一住于上等仓乘客的年龄数据,用全体乘客的年龄平均值来进行缺失值填充会是一个不错的选择,但是由于联想到住在上等仓的乘客大都已经经过了事业的奋斗期,是社会中比较成功的人士,因此他们的年龄会比较高,所以我们应该将所有乘客按照住的仓位等级进行划分,然后用住在上等仓的乘客的年龄平均值去填充该乘客缺失的年龄值一般会有更好的效果,这就是group by填充缺失值的思想。
#可视化null值的分布和数量,进一步决定如何处理null值
sns.heatmap(data.isnull(),yticklabels = False,cbar = False,cmap = 'viridis')
#直接舍弃,根据具体的数据类型,可用下列方法:
DataFrame:data.drop(‘xxx‘,inplace = True)
np.array:np.delete(data,xxx,xxx)
#若用groupby方法,可以将数据转换为dataframe形式直接调用
#groupby()方法:
data.groupby(['course'])['score'].mean()
#使用sklearn中的imputer来进行“mean”,“median”,“most_frequent”
#方法的缺失值填充,需要注意的是不同数据中缺失值的表示方法会有不同,有的数据缺失值用0表示,有的则用NaN等,需要
#在missing_values = 'NaN'中明确具体的缺失值表示。
from sklearn.preprocessing import imputer
imputer = imputer(missing_values = 'NaN',strategy = 'mean',axis = 0)
imputer = imputer.fit(x[:,1:3])
x[:,1:3] = imputer.transform([x[:,1:3]])
#插值预测取决于具体的数据结构,因此这里不展示代码
#回归预测问题就是建立另一个机器学习预测模型,只要使用
#后续文章中的步骤进行即可。
step2 :数据降维。在数据分析环节中我们已经对feature之间彼此的相关程度进行了分析和直观可视化,若出现了两两相关性较高的feature我们应该对其进行处理,原因是利用相关性较高的数据会使得模型更容易overfitting,同时在维度变高时我们对feature的联合概率估计会变得更加不准确,因此我们应该确保在不损失重要信息的情况下避免加入过多feature。大概方法有直接舍弃某一feature以及使用PCA,LDA,tsne等降维方法降维。同时,在高维空间中模拟数据的分布密度函数将变得十分困难,因此在数据维度过高时我们可以考虑使用降维方法对数据进行降维。再者,在面对某些高维数据时,我们需要提前对它的可分性做出判断,这种时候将数据降至2/3维可以十分方便地对数据的可分性做一个直观的定性判断。
tSNE:效果最好,但是计算复杂度高,而且涉及调参问题,在数据量庞大时基本不于考虑。
PCA:通过线性变换降维,因此降维之后的结果空间只是一个线性空间,但是计算速度很快,一般表现也不错,但是需要注意PCA是在寻找feature space中方差最大的方向,因此默认feature space中的矢量方向的重要程度=沿该矢量方向数据的方差大小,但是在实际问题中这可能是不正确的,有的时候可能会在降维中产生宝贵数据信息的损失,因此使用PCA应慎重。在处理分类问题时具体保留多少维度应该通过保留特征方向的特征值占所有特征值的比例来定性估计variance的降维后损失来计算获得。
LDA:相比于PCA,在处理分类问题时我们更应考虑LDA,LDA是一种监督学习的,专门为分类问题设计的降维技术,也就是说它的数据集的每个样本是有类别输出的。这点和PCA不同。PCA是不考虑样本类别输出的无监督降维技术。LDA的思想可以用一句话概括,就是“投影后类内方差最小,类间方差最大”。即投影后希望每一种类别数据的投影点尽可能的接近,而不同类别的数据的类别中心之间的距离尽可能的大。
#将dataset_train中的数据降维,并把fit之后得到的相应tansforemer存
在指定transformer_filename下方便对于testset的处理
n = ##the data dimension you want after dimension reduction
def dim_reduction(data,transformer_filename,n):
#下列方法选一即可
from sklearn.decomposition import PCA
#from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
#from sklearn.manifold import TSNE
from sklearn.externals import joblib
#部分关键参数介绍:
#for PCA LDA TSNE:
#n_components:结果空间的维度,
#for TSNE:
#perplexity:5-50,数据集越大,需要的参数值就越大
#early_exaggeration:默认为12.0,不是很关键
#verbose:训练过层是否可视
#method:对于大苏局使用默认值,小数据使用exact
transformer = PCA(n_components=n)
#transformer= LinearDiscriminantAnalysis(n_components=n)
#transformer = TSNE(n_components=n)
data_1 = transformer.fit_transform(data)
#if the transformer is LDA
#data_1 = transformer.fit(data, y).transform()
joblib.dump(transformer,transformer_filename)
return data_1
#这里使用的转换器需要保存,因为以后每次有新的数据进入模
#型时,都要使用保存在的转换器来进行降维。比如在实际碰到
#某一个数据data时,我们要时候的数据是data在这个低维空间的投影data_pro:
def dim_reduction_test(data,transformer_filename):
from sklearn.externals import joblib
transformer = joblib.load(transformer_filename)
data_pro = transformer.transform(data)
return data_pro
step3 :进行数据的归一化/标准化。如果feature之间的分布范围差别过大,会严重影响模型的训练速度,有时候甚至是最后的训练结果,所以这里我们需要将数据的所有feature转换至基本相同的分布空间,具体有两种方法:
归一化:将feature转换至[-1,1]的分布范围,具体方法是所有sample中的某一feature减去这个feature在所有sample中的平均值,再用这个结果除该feature的最大值于最小值的差值,如果outlier严重(最大值太大,最小值太小),效果会受到影响。
标准化:将所有feature转换至均值为0,标准差为1的分布空间中,类似于z-scoring,让所有sample中的某一feature减去这个feature在所有sample中的平均值,之后在除这个feature在所有sample中分布的标准差,同样受feature分布的影响。
一般情况下采用归一化会多一点点,但是个人更推荐标准化,因为对于outlier会更加robust一点。
若为图像数据,会比较复杂一点,具体的方法取决于之后要采用的机器学习模型,由于一般我们采用CNN处理面对图像的机器学习问题,所以这里以CNN为例,其他模型的话采用下列思想作出修改即可:
图像问题的归一化/标准化难点在于具体哪一部分pixel应被当作一类feature,这里不妨深入一点思考为什么我们会需要归一化/标准化,或者说以机器学习的角度来看什么是一类feature,机器学习视野下的feature就是经过完全相同处理的数据,比如构建一个fully connected的神经网络来进行房价预测,那么由于输入数据中特定维度的数据永远属于同一类,比如房间面积,卧室傻数量等等,因此所有同一类的数据在进入神经网络后会经过完全一样的处理流程(与完全一样的参数相作用),所以反过来,对于那么feature不易划分的任务,比如图像,我们可以认为:feature就是经过完全相同处理的数据。若为CNN,统一个channel的所有像素经过的处理是一样的,所以一个channel就是一个feature,若要进行归一化或者标准化,我们应该计算channelwise的mean,variance/range。
#将dataset_train中的数据进行归一化/标准化,并把相应参数存
#在指定目录下方便对于testset的处理和将数据返回到原来的范围
def fit_myscaler(dataset_train,scaler_filename):
from sklearn.processing import MinMaxScaler
#from sklearn.preprocessing import StandardScaler
from sklearn.externals import joblib
scaler = MinMaxScaler(feature_range = (0,1))
#scaler = StandardScaler()
dataset_train_scaled = scaler.fit_transform(dataset_train)
joblib.dump(scaler,scaler_filename)
return dataset_train_scaled
#将归一化之后的数据返回到来的空间,配合上一步的函数使用
def get_inverse_myscaler_data(dataset_train_scaled,scaler_filename):
from sklearn.externals import joblib
scaler = joblib.load(scaler_filename)
dataset_train = scaler.inverse_transform(dataset_train_scaled)
return dataset_train
######################
#CNN模型中的图像标准化函数:
to be updated.
*step4 :修正skewness。经过标准化或者均一化之后,我们希望数据的分布能够类似于一个以0为中心,方差为1的高斯分布,只有这样分布的数据,才能在机器学习模型中进行最高效地进行训练,但是实际情况中我们获得的数据分布可能是左倾或者右倾的,如果这种分布的倾斜比较严重,我们就需要修正数据的分布,让数据变成我们想要的标准高斯分布(若数据维数太高不好检查分布时时这步可以考虑跳过,倾向程度在多数情况下并不是一个不能忽视的问题)。
倾斜程度的修正通过:
http://www.econ.uiuc.edu/~econ508/Papers/boxcox64.pdf (skewness方向的经典paper)中的方法实施。简单来说此方法是设计了一个mapping function的形式,这个function根据输入变量lamda值的不同,形状和性质会有很大的变化,我们尝试不同的lamda,然后用labmda对应的各个function来map分布倾斜程度较大的feature数据,再次测量经过map之后的数据的skewness,从所有的function中选出一个最后结果分布skewness最小的function作为我们最终使用的skewness reduction map function来降低数据的skewness,加快之后机器学习模型的训练。
#首先使用seaborn方法pairplot来检查归一化/标准化/降维之后数
#据的分布(对角线位置)和相关性关系,确保数据分布类似高
#斯分布且彼此想关性不太大,定位到分布倾斜程度大的feature。
sns.pairplot(data)
#或者可以使用scipy中的skew方法查看某一feature分布的skewness,越不倾斜skewness的值应该越接近0:
import scipy
scipy.stats.skew(##your feature here###, axis=0, bias=True)
#若发现某一feature倾斜程度过大(examine visially或者
#scipy.stats.skew返回了一个较大的值,这个值究竟多少算大应
#该根据具体使用的模型来定,目前在这里我给不出具体的建
议)那么就需要对倾斜程度进行修正:
#data:the feature you want to modify,a vector.
def resolve_skewness(data):
#默认lamda的候选数值,可以在这里进行自定义调节
lamda = np.linspace(-10,10,50)
result = []
best_lamda = 0
best_skewness = 100
for each in lamda:
skewness = abs(scipy.stats.skew((data**each - 1)/each))
if skewness <= best_skewness:
best_lamda = each
best_skewness = skewness
skewness = abs(scipy.stats.skew(np.log(data)))
if skewness <= best_skewness:
best_lamda = 0
return best_lamda
#保存这里返回的best_lamda这个值,这个值确定了一个方程,
#这个方程可以将我们的feature分布倾斜程度大大降低,之后
#用这个方程来处理该feature:
if best_lamda != 0:
data = (data**best_lamda - 1)/best_lamda)
else:
data = np.log(data)
#处理之后得到的data倾斜程度就很低了。
step5 :处理不平衡数据。当不同label的训练数据数量相差较大或者数量比例不合理时,模型的表现会收到一定影响。一般情况情况模型会倾向于产生预测结果为数量比例多于合理值的那些类别,所以我们可以根据模型在各个类别数据预测准确率上的差异来判断训练数据中是否存在配比不合理的情况。具体的解决措施有:
step6 :分布转移。在处理机器学习问题时,由于在实际中要用到的数据不好获得,我们经常会遇到实际使用的数据和训练数据分布不太一样的情况,也就是实际使用的数据和训练的数据来自于不同的数据源:比如以大气数据为例,实际使用的温度的检测设备和获得的data中温度的检查装置不同,这种时候,在处理实际问题的时候模型的表现就不会非常好,因为机器学习的本质是模拟一种分布(generative model模拟输入和输出的联合分布p(x,y),而discriminative model实在模拟条件概率分布p(y | x)),若真实数据和训练集数据不同的话,这两类分布也就会产生偏差,我们真正需要的分布并不是我们尝试模拟的分布,一般来说我们能获得的实际中将来要用到的数据不会太多,所以我们才会用其他数据源获得的类似数据,在这种情况下,也不太方便构建一个复杂数学模型来进行分布的转换,所以我们使用另一种简单的办法(意味着限制较多,不能genralized):构建一个从真实数据源到训练数据源的线性变换,再构建一个从训练数据源到真实数据源的线性变换(也就是前一个线性变换的逆变换):
Y = XM
X = YM^-1
M是一个m by m的矩阵(m为sample的维度)
这个问题就成为了一个求解LS问题的线性代数问题。
值得注意的是具体的分布转移处理十分灵活,我们应该根据具体的情况来进行,比如无人车驾驶的问题中,我们实际使用的激光雷达位置高于获取的数据中的激光雷达高度,高出的部分长度为d,那么我们只要对实际获得的点阵云数据在z轴方向减去这个高度d,就可以获得和训练数据基本一样的分布。
#假设data_true为真实数据源中获得的数据,data_train为可以
#可以获得的训练数据或者将要把data_true转换过去数据。
#这里以图片为例,我们可以获得的训练数据为
#sRGB color space表示实际获得的属于来自于
#device color space,到sRGB具体的转换公式无从得知,
#因此我们需要拟合一个线性变换,将device color space
#转换至sRGBcolor space,需要注意的是,这里的有几个点
#需要注意:
#1:数据的结构仍然为行为sample,列为feature
#2:data_true和data_train对应行的sample因相互匹配
#意思就是这是相同的数据在不同的feature space中的不同的表
#示,若果以color space的转换为例,两组data中对应行的#sample应该表示同一个pixel,只是用的不同的量化方法而已
#3:数据的分布应该尽量‘广‘,这个广的意思是尽量涵盖数据分
#布中较大的范围,以color space的转换来说,这些sample中应
#该有红色,蓝色,黄色,绿色,紫色,黑色,白色...等等尽量
#多的颜色,实际在CV中采用24种颜色构成的patch。如果数据
#复杂,不好做广度的分析,那么尽量多的加入数据也可有不错
#的效果。
#定义一个函数,该函数可以计算出两个space的transformer matrix并将其保存与指定文件file_name中。
#这里默认的数据类型不再是DataFrame而是np.ndarray:
#data_input:存在于转换对象space的数据
#data_output:存在于转换目标space的数据
def find_linear_transformer(data_input,data_output,file_name):
transformer = np.linalg.inv(data_input.transpose()*data_input)*data_input.transpose()*data_output
np.savez(file_name,space_transform=transformer)
#在将来需要进行分布转换的时候只要:
transformer =np.load(file_name + '.npz')
data_train = data_true*transformer['space_transform']
step7 :将类别变量转换为onehot/dummy variable。如果在features中存在无内在相对大小关系的类别变量(比如图片分类问题中的猫,狗,车等等,有内在相对大小关系的类别变量如高,中,低,炎热,温暖,凉爽,寒冷等),那么为了数据在模型中的合理使用,我们不应该单纯地将数据类别转换为scaler,而应该表示成为one hot形式的向量。
这里需要注意,在一般的机器学习问题中,若要处理分类问题,我们同时也需要将label(y)转换成为onehot形式,但是在神经网络模型中这一部并不必要,因为往往神经网络架构比如pytorch中,常用的分类loss function–cross entropy loss会自动将scaler形式的label转换成onehot来计算loss value。若不是用cross entropy作为分类损失函数,需要确认下该loss function的默认要求label为什么结构。
1.如果data类型是np.ndarray:
#输入:
#X:包含要进行onehot处理的feature的全部数据。
#n:要处理feature所在的列序号。
#返回值:指定列(feature)被encode成为onehot形式的输入数据(X)
def label_to_onehot(X, n):
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
labelencoder_X = LabelEncoder()
X[:, n] = labelencoder_X.fit_transform(X[:, n])
onehotencoder = OneHotEncoder(categorical_features = [n])
X = onehotencoder.fit_transform(X).toarray()
return X
# Encoding the label if needed
#labelencoder_y = LabelEncoder()
#y = labelencoder_y.fit_transform(y)
2.如果data类型是Dataframe:
#将label型数据变为dummy variable(也就是onehot)
#drop_first即去掉dummies中的第一列,因为得知其他列的值
#我们也就可以知道第一列的值,所以onehot label的第一列的
#是可以去除的
X = pd.get_dummies(X,columns=['###name of feature
you want to convert to dummy variable###'],drop_first=True)
step8 :进行validation set, training set以及test set的划分,这里涉及到一个问题,如果数据体量太大,我们不能一次全部把所有数据读入内存进行训练该怎么办?(比如无人车驾驶中的模式识别问题时,或者高清图片的分类等等)数据很可能是几十G甚至上百G的大小,这个时候我们自然无法提前把所有数据读入内存中,因此我们要分情况讨论:
1:数据不大,我们可以直接把所有数据读入内存然后训练,这时我们直接进行一般的train,val,test划分就好:
#将数据(data and label)划分为train,validation,test三部分
#默认:train:0.6,val:0.2,test:0.2
#输入:
#X:input data
#y:label
#creat:由于在划分了train,val,test之后数据不应该发生变动,因此需要将第一次的划分结果
#进行保存方便之后的复用,如果是第一次划分则creat = True
#若为false,则读取第一次保存的划分结果并返回
def train_val_test(x,y,creat = False):
if creat = True:
from sklearn.model_selection import train_test_split
X_train, X_test,y_train,y_test = train_test_split(x,y,test_size = 0.2,random_state = 101)
X_train, X_val,y_train,y_val = train_test_split(X_train,y_train,test_size = 0.25,random_state = 101)
np.savez('data_and_label',X_train=X_train,X_val=X_val,X_test=X_test,y_train=y_train,\
y_val=y_val,y_test=y_test)
if creat = False:
data=np.load('data_and_label.npz')
X_train = data['X_train']
X_val = data['X_val']
X_test = data['X_test']
y_train = data['y_train']
y_val = data['y_val']
y_test = data['y_test']
return X_train,X_val,X_test,y_train,y_val,y_test
2:数据体量过大,无法读入内存。这种情况下我们只能在需要训练数据的时候才把需要的那部分数据读入模型进行训练,针对这种情况,我们实际划分对象的不再直接的数据本身,而可以是数据的序号,在需要具体数据的时候只要读取相对应划分到三个set中数据的标号,我们就可以读取数据in real time:
#将数据(data and label)划分为train,validation,test三部分
#默认:train:0.6,val:0.2,test:0.2
#输入:
#num_sample:所有sample的总数
#creat:由于在划分了train,val,test之后数据不应该发生变动,因此需要将第一次的划分结果
#进行保存方便之后的复用,如果是第一次划分则creat = True
#若为false,则读取第一次保存的划分结果并返回
def train_val_test(num_sample,creat = False):
if creat == True:
index = np.arange(num_sample)
np.random.shuffle(index)
train_family = index[0:(num_sample//10)*6]
val_family = index[(num_sample//10)*6:(num_sample//10)*8]
test_family = index[(num_sample//10)*8:]
np.savez('family_index',train_family=train_family,val_family=val_family,test_family=test_family)
if creat == False:
data=np.load('family_index.npz')
train_family = data['train_family']
val_family = data['val_family']
test_family = data['test_family']
return train_family,val_family,test_family
下一篇将详细介绍非神经网络的传统机器学习模型的搭建训练与评价的相关基本流程,经验技巧与代码~
非神经网络机器学习模型的搭建,训练及评价阶段: https://blog.csdn.net/weixin_44563688/article/details/86568400
神经网络机器学习模型的搭建,训练及评价阶段: https://blog.csdn.net/weixin_44563688/article/details/88884468
转载请告知作者,并附上原始CSDN博客链接。