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()
idincomeageexperience_yearsis_marriedcityregioncurrent_job_yearscurrent_house_yearshouse_ownershipcar_ownershipprofessionlabel
0train_08529345442single2100210rentedno130
1train_17848654559single2292913rentedno430
2train_284914916120single11428811rentedno120
3train_386315446913married276141312rentedno270
4train_469472336210single56111012rentedno470

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

data.info()
data.describe()
<class 'pandas.core.frame.DataFrame'>
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
incomeageexperience_yearscityregioncurrent_job_yearscurrent_house_yearsprofessionlabel
count1.680000e+05168000.000000168000.000000168000.000000168000.000000168000.000000168000.000000168000.000000168000.000000
mean4.994944e+0649.96157710.088887157.93044613.8015546.33957111.99767325.2510540.123065
std2.879353e+0617.0531955.99859492.1231659.3799153.6470731.39961314.7223420.328513
min1.031000e+0421.0000000.0000000.0000000.0000000.00000010.0000000.0000000.000000
25%2.499018e+0635.0000005.00000078.0000006.0000004.00000011.00000013.0000000.000000
50%4.994848e+0650.00000010.000000157.00000014.0000006.00000012.00000025.0000000.000000
75%7.475446e+0665.00000015.000000238.00000022.0000009.00000013.00000038.0000000.000000
max9.999938e+0679.00000020.000000316.00000028.00000014.00000014.00000050.0000001.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([[<matplotlib.axes._subplots.AxesSubplot object at 0x000001DB35685A00>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x000001DB35EA1BB0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x000001DB35EC90A0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x000001DB35EFF490>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x000001DB35F2B910>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x000001DB35F58CA0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x000001DB35F58D90>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x000001DB381E1280>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x000001DB3823AA90>]],
      dtype=object)

请添加图片描述

划分测试集与训练集

这里只是最简单的用法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)
<matplotlib.axes._subplots.AxesSubplot at 0x1db33e068b0>

请添加图片描述

temp_data.corr()
incomeageexperience_yearscityregioncurrent_job_yearscurrent_house_yearsprofessionlabel
income1.0000000.0009500.004149-0.003351-0.0019290.009478-0.0026930.004420-0.001807
age0.0009501.000000-0.0034190.005144-0.0054330.000720-0.019908-0.012319-0.023407
experience_years0.004149-0.0034191.000000-0.0230260.0000070.6456490.0179780.001453-0.031337
city-0.0033510.005144-0.0230261.000000-0.035727-0.027860-0.0089310.0186460.005056
region-0.001929-0.0054330.000007-0.0357271.0000000.0084730.0041850.001800-0.004040
current_job_years0.0094780.0007200.645649-0.0278600.0084731.0000000.003652-0.004915-0.015477
current_house_years-0.002693-0.0199080.017978-0.0089310.0041850.0036521.0000000.002531-0.006133
profession0.004420-0.0123190.0014530.0186460.001800-0.0049150.0025311.000000-0.004007
label-0.001807-0.023407-0.0313370.005056-0.004040-0.015477-0.006133-0.0040071.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初学者来说,当然是算法流程调用函数类对象类型之类的东西更麻烦一些。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值