python sklearn机器学习项目流程初探

本文是python初学者上手机器学习的学习记录,重点是熟悉整个操作流程。
整个流程包括数据载入,查看数据结构,划分测试集与训练集,数据探索,数据准备,选择和训练模型,交叉验证以及测试集评估算法。
对于第一次上手的新手来说,还是很烦躁的。

数据载入

csv文件用的是pd.read_csv函数。注意文件路径中的“\”应该再使用一个“\”进行转义,或者直接换成“/”。

import numpy as np
import pandas as pd
data=pd.read_csv('C:\\Users\\43480\\Downloads\\train.csv')

查看数据结构

个人理解这是整个流程中重要性最容易被忽略的一个部分。了解了数据性质,才可以在后面的各个流程中选择合适的算法和算法参数。
在这里,需要对于数据的质量做出判断。

data.head()默认是前五行。项目目标是用前面的数据预测最后一列的label。

import matplotlib.pyplot as plt
data.head()
id income age experience_years is_married city region current_job_years current_house_years house_ownership car_ownership profession label
0 train_0 8529345 44 2 single 210 0 2 10 rented no 13 0
1 train_1 7848654 55 9 single 229 2 9 13 rented no 43 0
2 train_2 8491491 61 20 single 114 28 8 11 rented no 12 0
3 train_3 8631544 69 13 married 276 14 13 12 rented no 27 0
4 train_4 6947233 62 10 single 56 11 10 12 rented no 47 0

data.info()可以看到各个属性的类型与非空值的数量,决定了是否需要独热编码将文本转成数字以及是否需要用数据清理补全空值。
data.describe()可以看到数字量的信息,重点是看各个量的分布情况,会不会是一个很偏的分布或者存在明显的离群点,如果是,有的时候需要有对应的处理。

data.info()
data.describe()

RangeIndex: 168000 entries, 0 to 167999
Data columns (total 13 columns):
 #   Column               Non-Null Count   Dtype 
---  ------               --------------   ----- 
 0   id                   168000 non-null  object
 1   income               168000 non-null  int64 
 2   age                  168000 non-null  int64 
 3   experience_years     168000 non-null  int64 
 4   is_married           168000 non-null  object
 5   city                 168000 non-null  int64 
 6   region               168000 non-null  int64 
 7   current_job_years    168000 non-null  int64 
 8   current_house_years  168000 non-null  int64 
 9   house_ownership      168000 non-null  object
 10  car_ownership        168000 non-null  object
 11  profession           168000 non-null  int64 
 12  label                168000 non-null  int64 
dtypes: int64(9), object(4)
memory usage: 16.7+ MB
income age experience_years city region current_job_years current_house_years profession label
count 1.680000e+05 168000.000000 168000.000000 168000.000000 168000.000000 168000.000000 168000.000000 168000.000000 168000.000000
mean 4.994944e+06 49.961577 10.088887 157.930446 13.801554 6.339571 11.997673 25.251054 0.123065
std 2.879353e+06 17.053195 5.998594 92.123165 9.379915 3.647073 1.399613 14.722342 0.328513
min 1.031000e+04 21.000000 0.000000 0.000000 0.000000 0.000000 10.000000 0.000000 0.000000
25% 2.499018e+06 35.000000 5.000000 78.000000 6.000000 4.000000 11.000000 13.000000 0.000000
50% 4.994848e+06 50.000000 10.000000 157.000000 14.000000 6.000000 12.000000 25.000000 0.000000
75% 7.475446e+06 65.000000 15.000000 238.000000 22.000000 9.000000 13.000000 38.000000 0.000000
max 9.999938e+06 79.000000 20.000000 316.000000 28.000000 14.000000 14.000000 50.000000 1.000000

value_counts()方法可以查看object类型对象的信息。

data['label'].value_counts()
0    147325
1     20675
Name: label, dtype: int64

data.hist(bins=100, figsize=(20,15))
.hist()查看数值类型对象的分布,仍然是,主要看数据是否存在出乎意料的分布。

data.hist(bins=100, figsize=(20,15))
array([[,
        ,
        ],
       [,
        ,
        ],
       [,
        ,
        ]],
      dtype=object)

python sklearn机器学习项目流程初探_第1张图片

划分测试集与训练集

这里只是最简单的用法train_set,test_set=train_test_split(data,test_size=0.2),并没有指定随机种子random_seed和用于处理不平衡数据集的参数satisfy

from sklearn.model_selection import train_test_split
train_set,test_set=train_test_split(data,test_size=0.2)

由于在本例分类问题中,明显看出label的分布很不平衡。因此,有必要在创建测试集时考虑这一点,即stratify=data[‘label’]。注意这里奇怪的设定X和y的方法data[:][data.columns[:-1]]以及data[‘label’](这里的data[‘label’]也可以写成data[:][data.columns[-1]])。

from sklearn.model_selection import train_test_split
train_set,test_set,y_train,y_test=train_test_split(data[:][data.columns[:-1]],data[:][data.columns[-1]],test_size=0.2,stratify=data['label'])

查看训练集和验证集的label分布比例。可以发现二者中label的01比是一样的。

y_train.value_counts()/len(train_set)
0    0.876935
1    0.123065
Name: label, dtype: float64
y_test.value_counts()/len(test_set)
0    0.876935
1    0.123065
Name: label, dtype: float64

数据探索

理论上来说,从这里开始的所有操作都应该在训练集上进行。数据探索的目的是为了进一步寻找显而易见的数据之间的关系。这里用到的函数是散点图.plot(kind=‘scatter’)和相关系数矩阵.corr()。

这里使用.join是因为需要查看的是各个变量与最后的预测结果label的关系,alpha可以认为是表示数据点颜色深浅的参数。

data_train=train_set.copy()
temp_data=data_train.join(y_train)
temp_data[1:1000].plot(kind="scatter",x="income",y="label",alpha=0.05)

python sklearn机器学习项目流程初探_第2张图片

temp_data.corr()
income age experience_years city region current_job_years current_house_years profession label
income 1.000000 0.000950 0.004149 -0.003351 -0.001929 0.009478 -0.002693 0.004420 -0.001807
age 0.000950 1.000000 -0.003419 0.005144 -0.005433 0.000720 -0.019908 -0.012319 -0.023407
experience_years 0.004149 -0.003419 1.000000 -0.023026 0.000007 0.645649 0.017978 0.001453 -0.031337
city -0.003351 0.005144 -0.023026 1.000000 -0.035727 -0.027860 -0.008931 0.018646 0.005056
region -0.001929 -0.005433 0.000007 -0.035727 1.000000 0.008473 0.004185 0.001800 -0.004040
current_job_years 0.009478 0.000720 0.645649 -0.027860 0.008473 1.000000 0.003652 -0.004915 -0.015477
current_house_years -0.002693 -0.019908 0.017978 -0.008931 0.004185 0.003652 1.000000 0.002531 -0.006133
profession 0.004420 -0.012319 0.001453 0.018646 0.001800 -0.004915 0.002531 1.000000 -0.004007
label -0.001807 -0.023407 -0.031337 0.005056 -0.004040 -0.015477 -0.006133 -0.004007 1.000000

数据准备

这个阶段的目的是把数据处理成机器学习算法能够直接使用的数据。主要包括缺失值处理、文本和分类属性处理和特征缩放。

缺失值处理

缺失值处理可以使用dropna()以及fillna()等方法,实际操作中这些方法都集成在SimpleImputer类中。注意如果使用strategy=‘median’,则这个类只能在数值属性上计算。

文本和分类属性处理

简单的做法是使用下述的OrdinalEncoder()类,这个类的问题是对于无序类别转换后会在未来的相似度度量时出现问题,因此对于无序类别,一般都采用后面用到的OneHotEncoder()类进行编码转换。

下面这个部分的代码首先划分了数值型属性和文本分类属性,然后对于数值使用SimpleImputer,对于文本分类使用OrdinalEncoder。

from sklearn.preprocessing import OrdinalEncoder
from sklearn.impute import SimpleImputer
imputer=SimpleImputer(strategy='median')
cat=['is_married','house_ownership','car_ownership']
data_cat=data_train[cat]
cat.append('id')
data_num=data_train.drop(cat,axis=1)
cat.remove('id')

ordinal_encoder=OrdinalEncoder()
data_cat_encodered=ordinal_encoder.fit_transform(data_cat)

说实话我不太清楚pipeline的优势在哪里。理论上来说可以让程序简洁易读,但是实际操作的时候,一旦数据处理的结果除了问题,还是需要把pipeline拆开一步一步看里面的问题到底出在哪里的。
只能说再学习再领会了。

特征缩放

也就是标准化,用的是StandardScaler类。

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

num_pipeline=Pipeline([
    ('imputer',SimpleImputer(strategy='median')),
    #('attribs_adder',CombinedAttributesAdder()),
    ('std_scaler',StandardScaler())
])
data_num_tr=num_pipeline.fit_transform(data_num)
from sklearn.preprocessing import OneHotEncoder
cat_encoder=OneHotEncoder()
data_cat_1hot=cat_encoder.fit_transform(data_cat)

ColumnTransformer类在这里的用法是把处理后的数值型和文本型属性连接成一张完整的数据表格。讲道理这个类的参数很奇怪。

from sklearn.compose import ColumnTransformer


num_attribs=data_train.columns.drop(cat).drop('id')#.drop()可以在不影响原对象的情况下生成一个新的对象
cat_attribs=cat.copy()

full_pipeline=ColumnTransformer([
    ('num',num_pipeline,num_attribs),
    ('cate',OneHotEncoder(),cat_attribs)
])

data_prepared=full_pipeline.fit_transform(data_train)

到这里数据预处理就做完了,后面就是使用各种算法了。

选择和训练模型

这里首先使用最简单的最小二乘法试一试。暂时无视最小二乘做分类预测合不合适的问题…

from sklearn.linear_model import LinearRegression
lin_reg=LinearRegression()
data_labels=y_train
lin_reg.fit(data_prepared,data_labels)
LinearRegression()

从训练集中拿出一些数据来看看预测结果。

some_data=data_train.iloc[:100]
some_labels=data_labels.iloc[:100]
some_data_prepared=full_pipeline.transform(some_data)
print('Predictions:', lin_reg.predict(some_data_prepared))
print('labels:',some_labels)
Predictions: [0.13324356 0.06813812 0.11545181 0.11249924 0.08952332 0.1189003
 0.105793   0.10971451 0.11217117 0.13776016 0.08611679 0.10936356
 0.09428406 0.12818909 0.1342659  0.10783768 0.11572647 0.1403389
 0.09718704 0.0999794  0.12628555 0.11398315 0.10149384 0.08406067
 0.11320114 0.12833023 0.09347153 0.11769485 0.11962891 0.10712051
 0.12628555 0.12824249 0.11003494 0.11316299 0.12756729 0.14403152
 0.12031937 0.09963608 0.13869095 0.1473732  0.1410408  0.14761734
 0.09777069 0.1433754  0.12514114 0.13670731 0.12614822 0.11314774
 0.12529373 0.1254158  0.11951828 0.12356949 0.11419296 0.14549637
 0.12572861 0.14506912 0.12781143 0.13658524 0.14636612 0.11413193
 0.11112976 0.13087845 0.14978409 0.11518478 0.14868546 0.1241951
 0.11139297 0.12134171 0.11556244 0.11551285 0.14125443 0.06076431
 0.13763809 0.0622139  0.12202835 0.14395523 0.12439346 0.15021133
 0.10580826 0.13664627 0.09584427 0.10268402 0.15408707 0.12779617
 0.09299088 0.13241959 0.13728714 0.14119339 0.10634232 0.10967636
 0.08873367 0.13383865 0.10382462 0.10637283 0.12781906 0.1294899
 0.11076736 0.12068558 0.12443161 0.10746384]
labels: 45247     0
127747    0
43295     0
25850     0
62422     0
         ..
665       0
101019    0
196       0
58710     0
102191    0
Name: label, Length: 100, dtype: int64

在整个训练集上的预测结果

from sklearn.metrics import mean_squared_error
data_predictions=lin_reg.predict(data_prepared)
lin_mse=mean_squared_error(data_labels,data_predictions)
lin_rmse=np.sqrt(lin_mse)
lin_rmse
0.3279736801232515

交叉验证

使用的是cross_val_score函数。反正书上给出了带负号的理由,那就用neg吧。从lin_rmse_score的结果看,拟合效果不错。

from sklearn.model_selection import cross_val_score
scores=cross_val_score(lin_reg,data_prepared,data_labels,scoring='neg_mean_squared_error',cv=10)
lin_rmse_score=np.sqrt(-scores)
lin_rmse_score
array([0.33027754, 0.32981625, 0.3325474 , 0.33003665, 0.3275183 ,
       0.32668944, 0.3253184 , 0.32128751, 0.32691752, 0.32952707])

线性回归是一种很简单的算法,因此就没有搜索超参数的步骤了(因为就没有超参数)。但是不代表其他算法没有…

测试集评估算法

将测试集的数据经过前面的一套预处理后,再用线性回归算法得出一个预测结果。

X_test_prepared=full_pipeline.transform(test_set)
final_prediction=lin_reg.predict(X_test_prepared)
final_mse=mean_squared_error(y_test,final_prediction)
final_rmse=np.sqrt(final_mse)
final_rmse
0.32780615241094346

通过查看lin_reg.coef_可以发现,获得的参数相当离谱。

lin_reg.coef_
array([-6.22622660e-04, -7.69702530e-03, -1.18704161e-02,  1.52550113e-03,
       -8.62294723e-04,  2.80572379e-03, -1.79606626e-03, -1.29128873e-03,
        5.58111749e+09,  5.58111749e+09, -1.38026382e+11, -1.38026382e+11,
       -1.38026382e+11,  1.11388033e+11,  1.11388033e+11])

最后将得到的预测值与测试集的给定结果y_test作比较,对于最小二乘法的结果给出评价。注意这里的y_test是Series,final_label是list,无法直接比较。

length_test=len(final_prediction)
final_label=[1 if final_prediction[i]>0.5 else 0 for i in range(length_test)]
Y_test=y_test.tolist()
final_percentage=[1 if final_label[i]==Y_test[i] else 0 for i in range(length_test)]
error_rate=1-sum(final_percentage)/length_test
error_rate
0.12306547619047614

注意到0.1230很眼熟,说明最小二乘法进行分类会得到很离谱的结果,最次也应该用个Logistic回归之类的算法。
当然,region和city区间的数据应该按照文本和分类属性做独热编码,实际没有做,对于预测结果也是有很大的影响的(个人可能会直接删掉这两个属性…)。
不过,本文的主要内容是算法运行的整个流程,采用的算法是否合适不是本文重点。对于python初学者来说,当然是算法流程调用函数类对象类型之类的东西更麻烦一些。

你可能感兴趣的:(python机器学习,list,python,机器学习)