机器学习之数据与特征工程

    学习过程中梳理一些知识点,在机器学习中,对原始数据的处理和特征提取,是最为重要的,处理好原始数据,哪怕后面使用简单的算法都可以得到比较好的准确率。

    这是大神们的理解

    

    说白了,所谓的特征工程,听起来很高大上,其实就是从数据中抽取出来的对预测结构有用的信息,使得其能在机器学习算法上发挥更好的作用。

    大部分算法精进和数据分析所做的工作都是:

    a)跑数据,    各种map-reduce,从数据空中获取数据。

    b)数据清洗,清除明显有问题的数据。(如,一个身高数据中,一个身高为3米,这种数据明显是错误的,需要清洗)。

    c)分析业务,找特征。。。

    d)应用在常见的机器学习算法上。

下面以一个经典的案例来介绍数据处理和特征提取。

泰坦尼克号问题,是Kaggle中的经典入门问题。泰坦尼克号的故事大家都很清除了,船要沉了,大家都想上救生船,但是救生船的数量有限,一个前提要求是【lady and kid first】。以这个为背景,给出了训练集和测试数据集,其中包含一些乘客的个人信息和存活情况,需要尝试根据训练集生成一个很是的模型,预测其他人的存活情况。

    关于训练集合测试数据集,大家可以在Kaggle中下载。

    这里提供一个连接,供大家下载数据集   点击打开链接

    首先,我们先看看数据长得什么样

    用pandas加载数据,并打印出数据的列

import pandas as pd #数据分析
import numpy as np #科学计算
from pandas import Series,DataFrame

data_train = pd.read_csv("G:\Train.csv")
data_train.columns
    输出:Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'],
      dtype='object')
    我们看大概有以下这些字段
    PassengerId => 乘客ID
    Pclass => 乘客等级(1/2/3等舱位)
    Name => 乘客姓名
    Sex => 性别
    Age => 年龄
    SibSp => 堂兄弟/妹个数
    Parch => 父母与小孩个数
    Ticket => 船票信息
    Fare => 票价
    Cabin => 客舱

    Embarked => 登船港口

    我们还可以通过pandas看看一些信息

data_train.info()
    
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB

上面数据告诉我们一些直观的信息,训练数据中有891名乘客,但是有些属性特征的数据不全。如乘客的年龄(Age),客舱(Cabin)。这些信息显然还不够,我们再利用pandas获取展示更多具体的数据

data_train.describe()

    其中mean字段表明,其中只有大概0.383838的人获救了,乘客的平均年龄是29.7岁等等。

    但是上述的信息还是不能给我们提供很有用的特征,,,我们还需要进一步分析数据。

    我们最终的结构是要预测是否获救,所以我们查看一个或多个数据属性与最后的Survived之前的关系

    还有就是只看数据(数字)是看不出有什么的关联的。这时候,最好是进行图像的展示。

import matplotlib.pyplot as plt
fig = plt.figure()
fig.set(alpha=0.2)  # 设定图表颜色alpha参数

plt.subplot2grid((2,3),(0,0))             # 在一张大图里分列几个小图f
data_train.Survived.value_counts().plot(kind='bar')# plots a bar graph of those who surived vs those who did not. 
plt.title(u"获救情况 (1为获救)") # puts a title on our graph
plt.ylabel(u"人数")  

plt.subplot2grid((2,3),(0,1))
data_train.Pclass.value_counts().plot(kind="bar")
plt.ylabel(u"人数")
plt.title(u"乘客等级分布")

plt.subplot2grid((2,3),(0,2))
plt.scatter(data_train.Survived, data_train.Age)
plt.ylabel(u"年龄")                         # sets the y axis lable
plt.grid(b=True, which='major', axis='y') # formats the grid line style of our graphs
plt.title(u"按年龄看获救分布 (1为获救)")


plt.subplot2grid((2,3),(1,0), colspan=2)
data_train.Age[data_train.Pclass == 1].plot(kind='kde')   # plots a kernel desnsity estimate of the subset of the 1st class passanges's age
data_train.Age[data_train.Pclass == 2].plot(kind='kde')
data_train.Age[data_train.Pclass == 3].plot(kind='kde')
plt.xlabel(u"年龄")# plots an axis lable
plt.ylabel(u"密度") 
plt.title(u"各等级的乘客年龄分布")
plt.legend((u'头等舱', u'2等舱',u'3等舱'),loc='best') # sets our legend for our graph.


plt.subplot2grid((2,3),(1,2))
data_train.Embarked.value_counts().plot(kind='bar')
plt.title(u"各登船口岸上船人数")
plt.ylabel(u"人数")  
plt.show()

可以得到下面一张图


由上图可见:

    1)被救人数300多点,半数不到。

    2)3等仓人数非常多,遇难和获救的人的年龄段跨度都很广

    3)等船港口人数中,S港远多于其他两个港口

由此可以分析:

    1)不同舱位和乘客等级在最后获救概率上回有不同。

    2)年龄对获救概率有一定影响,毕竟背景前提是【女士和小孩优先】

    3)是不是和登船港口有关系呢,不同的登船港口和乘客的身份等级有关??

有了这些想法,试试把这些属性和最后获救参数进行统计

    

#看看各乘客等级的获救情况
fig = plt.figure()
fig.set(alpha=0.2)  # 设定图表颜色alpha参数

Survived_0 = data_train.Pclass[data_train.Survived == 0].value_counts()
Survived_1 = data_train.Pclass[data_train.Survived == 1].value_counts()
df=pd.DataFrame({u'获救':Survived_1, u'未获救':Survived_0})
df.plot(kind='bar', stacked=True)
plt.title(u"各乘客等级的获救情况")
plt.xlabel(u"乘客等级") 
plt.ylabel(u"人数") 

plt.show()

从上面的图表中可以明显看出,1号舱获救的比例<2号舱获救的比例<三号舱获救比例

所以船舱等级会影响最后的获救的结果。


#看看各登录港口的获救情况
fig = plt.figure()
fig.set(alpha=0.2)  # 设定图表颜色alpha参数

Survived_0 = data_train.Embarked[data_train.Survived == 0].value_counts()
Survived_1 = data_train.Embarked[data_train.Survived == 1].value_counts()
df=pd.DataFrame({u'获救':Survived_1, u'未获救':Survived_0})
df.plot(kind='bar', stacked=True)
plt.title(u"各登录港口乘客的获救情况")
plt.xlabel(u"登录港口") 
plt.ylabel(u"人数") 

plt.show()

这个图表中,并没有看出什么特别的

继续分析

#看看各性别的获救情况
fig = plt.figure()
fig.set(alpha=0.2)  # 设定图表颜色alpha参数

Survived_m = data_train.Survived[data_train.Sex == 'male'].value_counts()
Survived_f = data_train.Survived[data_train.Sex == 'female'].value_counts()
df=pd.DataFrame({u'男性':Survived_m, u'女性':Survived_f})
df.plot(kind='bar', stacked=True)
plt.title(u"按性别看获救情况")
plt.xlabel(u"性别") 
plt.ylabel(u"人数")
plt.show()

有上图标中看出,获救人员中,女性比例高于男性,看来背景中的【女士和小孩优先】还是被执行的不错。所以,性别特征可以影响最后获救的概率。

#然后我们再来看看各种舱级别情况下各性别的获救情况
fig=plt.figure()
fig.set(alpha=0.65) # 设置图像透明度,无所谓
plt.title(u"根据舱等级和性别的获救情况")

ax1=fig.add_subplot(141)
data_train.Survived[data_train.Sex == 'female'][data_train.Pclass != 3].value_counts().plot(kind='bar', label="female highclass", color='#FA2479')
ax1.set_xticklabels([u"获救", u"未获救"], rotation=0)
ax1.legend([u"女性/高级舱"], loc='best')

ax2=fig.add_subplot(142, sharey=ax1)
data_train.Survived[data_train.Sex == 'female'][data_train.Pclass == 3].value_counts().plot(kind='bar', label='female, low class', color='pink')
ax2.set_xticklabels([u"未获救", u"获救"], rotation=0)
plt.legend([u"女性/低级舱"], loc='best')

ax3=fig.add_subplot(143, sharey=ax1)
data_train.Survived[data_train.Sex == 'male'][data_train.Pclass != 3].value_counts().plot(kind='bar', label='male, high class',color='lightblue')
ax3.set_xticklabels([u"未获救", u"获救"], rotation=0)
plt.legend([u"男性/高级舱"], loc='best')

ax4=fig.add_subplot(144, sharey=ax1)
data_train.Survived[data_train.Sex == 'male'][data_train.Pclass == 3].value_counts().plot(kind='bar', label='male low class', color='steelblue')
ax4.set_xticklabels([u"未获救", u"获救"], rotation=0)
plt.legend([u"男性/低级舱"], loc='best')

plt.show()

从上图标中可以看出,高级船舱中(船舱为1和2的)获救比例不管男女都要比低级船舱(船舱为3号的)高。由此可以考虑组合一个船舱号和性别的特征。

对于其他属性,比如堂兄弟,乘客ID,船票,父母与小孩个数,这些属性对最终获救有什么影响呢。我们来分析一下

g = data_train.groupby(['SibSp','Survived'])
df = pd.DataFrame(g.count()['PassengerId'])
df


上面为分析堂兄弟与获救情况的表格,但是从中并没有什么明显的关联。

同样分析父母与小孩个数与获救之前的关系

g = data_train.groupby(['Parch','Survived'])
df = pd.DataFrame(g.count()['PassengerId'])
df

上面中,也没有看出明显的规律,同样先放一放

船票信息Ticket只是船的标号。和最后的是否获救没有太大的关系,不纳入考虑的特征范畴。

Cabin在测试集中,只有204个,有很多缺省的值,先看看已有参数的分布情况

data_train.Cabin.value_counts()




Cabin值不集中,而且缺失很多,完全丢失又觉得可惜。我们可以在有无Cabin信息这个粗粒度上看看获救的情况

#cabin的值计数太分散了,绝大多数Cabin值只出现一次。感觉上作为类目,加入特征未必会有效
#那我们一起看看这个值的有无,对于survival的分布状况,影响如何吧
fig = plt.figure()
fig.set(alpha=0.2)  # 设定图表颜色alpha参数

Survived_cabin = data_train.Survived[pd.notnull(data_train.Cabin)].value_counts()
Survived_nocabin = data_train.Survived[pd.isnull(data_train.Cabin)].value_counts()
df=pd.DataFrame({u'有':Survived_cabin, u'无':Survived_nocabin}).transpose()
df.plot(kind='bar', stacked=True)
plt.title(u"按Cabin有无看获救情况")
plt.xlabel(u"Cabin有无") 
plt.ylabel(u"人数")
plt.show()

#似乎有cabin记录的乘客survival比例稍高,那先试试把这个值分为两类,有cabin值/无cabin值,一会儿加到类别特征好了

从上面的图表中可以看到,有Cabin信息的获救概率会高一些。


毕竟有丢失的数据的属性,对下一不工作影响很大。我们可以将Cabin这个属性按照有无信息,分为Yes和No两种属性进行处理。而测试数据集里面的Age也存在数据缺失的情况。

通常遇到数据缺失的情况,我们会有集中处理方式:

    1,如果缺失值的样本占总数的比例极高,我们可以直接舍弃,如果强行作为特征加入的话,可能会带来noise,影响最后的结果。

    2,如果缺失值的样本适中,并且该属性为非连续值,可以参照Cabin这个属性的处理方式进行处理。

    3,如果缺失值的样本适中,并且该属相为连续值,可以先对此属性就行离散化,在进行处理。

    4,有些情况下,缺失的数值并不是很多。我们可以试试根据已经有的值进行拟合,补充上。对于Age这个属性就可以采用这种拟合不全的方式。

这里采用scikit-learn中的RandomForest来拟合Age的缺省数据。

from sklearn.ensemble import RandomForestRegressor
 
### 使用 RandomForestClassifier 填补缺失的年龄属性
def set_missing_ages(df):
    
    # 把已有的数值型特征取出来丢进Random Forest Regressor中
    age_df = df[['Age','Fare', 'Parch', 'SibSp', 'Pclass']]

    # 乘客分成已知年龄和未知年龄两部分
    known_age = age_df[age_df.Age.notnull()].as_matrix()
    unknown_age = age_df[age_df.Age.isnull()].as_matrix()

    # y即目标年龄
    y = known_age[:, 0]

    # X即特征属性值
    X = known_age[:, 1:]

    # fit到RandomForestRegressor之中
    rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
    rfr.fit(X, y)
    
    # 用得到的模型进行未知年龄结果预测
    predictedAges = rfr.predict(unknown_age[:, 1::])
    
    # 用得到的预测结果填补原缺失数据
    df.loc[ (df.Age.isnull()), 'Age' ] = predictedAges 
    
    return df, rfr

def set_Cabin_type(df):
    df.loc[ (df.Cabin.notnull()), 'Cabin' ] = "Yes"
    df.loc[ (df.Cabin.isnull()), 'Cabin' ] = "No"
    return df

data_train, rfr = set_missing_ages(data_train)
data_train = set_Cabin_type(data_train)
data_train

整理后的部分数据展示如下


891 rows × 12 columns

因为逻辑回归的时候,需要输入特征都是数值型特征,需要对类目型特征进行因子化--one-hot编码

什么叫做因子化/one-hot编码?举个例子:

以Embarked为例,原本一个属性维度,因为其取值可以是[‘S’,’C’,’Q‘],而将其平展开为’Embarked_C’,’Embarked_S’, ‘Embarked_Q’三个属性

原本Embarked取值为S的,在此处的”Embarked_S”下取值为1,在’Embarked_C’, ‘Embarked_Q’下取值为0
原本Embarked取值为C的,在此处的”Embarked_C”下取值为1,在’Embarked_S’, ‘Embarked_Q’下取值为0
原本Embarked取值为Q的,在此处的”Embarked_Q”下取值为1,在’Embarked_C’, ‘Embarked_S’下取值为0

我们使用pandas的”get_dummies”来完成这个工作,并拼接在原来的”data_train”之上,如下所示。


# 因为逻辑回归建模时,需要输入的特征都是数值型特征
# 我们先对类目型的特征离散/因子化
# 以Cabin为例,原本一个属性维度,因为其取值可以是['yes','no'],而将其平展开为'Cabin_yes','Cabin_no'两个属性
# 原本Cabin取值为yes的,在此处的'Cabin_yes'下取值为1,在'Cabin_no'下取值为0
# 原本Cabin取值为no的,在此处的'Cabin_yes'下取值为0,在'Cabin_no'下取值为1
# 我们使用pandas的get_dummies来完成这个工作,并拼接在原来的data_train之上,如下所示
dummies_Cabin = pd.get_dummies(data_train['Cabin'], prefix= 'Cabin')

dummies_Embarked = pd.get_dummies(data_train['Embarked'], prefix= 'Embarked')

dummies_Sex = pd.get_dummies(data_train['Sex'], prefix= 'Sex')

dummies_Pclass = pd.get_dummies(data_train['Pclass'], prefix= 'Pclass')

df = pd.concat([data_train, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
df.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)
df

部分结果显示如下


这样一看,数据处理的差不多了,其实还有一些工作要处理的,我们看到Age和Fare两个特征浮动变化太大,这种情况加入逻辑回归和与梯度下降的话,会对收敛速度造成影响,严重的导致不收敛。所以我们先用scikit-learn里面的preprocessing模块对这俩货做一个scaling,所谓scaling,其实就是将一些变化幅度较大的特征化到[-1,1]之内。

# 接下来我们要接着做一些数据预处理的工作,比如scaling,将一些变化幅度较大的特征化到[-1,1]之内
# 这样可以加速logistic regression的收敛
import sklearn.preprocessing as preprocessing
scaler = preprocessing.StandardScaler()
age_scale_param = scaler.fit(df['Age'])
df['Age_scaled'] = scaler.fit_transform(df['Age'], age_scale_param)
fare_scale_param = scaler.fit(df['Fare'])
df['Fare_scaled'] = scaler.fit_transform(df['Fare'], fare_scale_param)
df

部分显示如下


增加了两个特征。

此致,特征提取数据优化完成,然后我们需要把之前分析的特征提取出来,并转化成算法需要的矩阵形式,进行逻辑回归建模。

# 我们把需要的feature字段取出来,转成numpy格式,使用scikit-learn中的LogisticRegression建模
from sklearn import linear_model

train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
train_np = train_df.as_matrix()

# y即Survival结果
y = train_np[:, 0]

# X即特征属性值
X = train_np[:, 1:]

# fit到RandomForestRegressor之中
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
clf.fit(X, y)
    
clf


接下来,我们要对测试集做和训练集一样的数据处理操作

data_test = pd.read_csv("G:\Test.csv")
data_test.loc[ (data_test.Fare.isnull()), 'Fare' ] = 0
# 接着我们对test_data做和train_data中一致的特征变换
# 首先用同样的RandomForestRegressor模型填上丢失的年龄
tmp_df = data_test[['Age','Fare', 'Parch', 'SibSp', 'Pclass']]
null_age = tmp_df[data_test.Age.isnull()].as_matrix()
# 根据特征属性X预测年龄并补上
X = null_age[:, 1:]
predictedAges = rfr.predict(X)
data_test.loc[ (data_test.Age.isnull()), 'Age' ] = predictedAges

data_test = set_Cabin_type(data_test)
dummies_Cabin = pd.get_dummies(data_test['Cabin'], prefix= 'Cabin')
dummies_Embarked = pd.get_dummies(data_test['Embarked'], prefix= 'Embarked')
dummies_Sex = pd.get_dummies(data_test['Sex'], prefix= 'Sex')
dummies_Pclass = pd.get_dummies(data_test['Pclass'], prefix= 'Pclass')


df_test = pd.concat([data_test, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)
df_test.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)
age_scale_param = scaler.fit(df['Age'])
df_test['Age_scaled'] = scaler.fit_transform(df_test['Age'], age_scale_param)
fare_scale_param = scaler.fit(df['Fare'])
df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'], fare_scale_param)
df_test

部分显示如下


test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
predictions = clf.predict(test)
result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
result.to_csv("logistic_regression_predictions.csv", index=False)

生成结果文件,读取结果文件

pd.read_csv("logistic_regression_predictions.csv")

这里显示部分文件信息

最后将生成的结果文件上传的Kaggle中进行正确率的比对。对于Kaggle的一些操作这里就不多说的。这个结果大概是0.76.还能接受吧,毕竟只是简单的分析,入门的级别。

等等。。。

还没有完事。

我们还要判断一下当前的模型所处的状态(欠拟合还是过拟合还是正常)

对于处理解决欠拟合和过拟合的方法这里就不再多说了,这里只介绍判断当前模型是处在什么状态

我们采用scikit-learn里面的learning_curve来分辨当前模型的状态。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.learning_curve import learning_curve

# 用sklearn的learning_curve得到training_score和cv_score,使用matplotlib画出learning curve
def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None, n_jobs=1, 
                        train_sizes=np.linspace(.05, 1., 20), verbose=0, plot=True):
    """
    画出data在某模型上的learning curve.
    参数解释
    ----------
    estimator : 你用的分类器。
    title : 表格的标题。
    X : 输入的feature,numpy类型
    y : 输入的target vector
    ylim : tuple格式的(ymin, ymax), 设定图像中纵坐标的最低点和最高点
    cv : 做cross-validation的时候,数据分成的份数,其中一份作为cv集,其余n-1份作为training(默认为3份)
    n_jobs : 并行的的任务数(默认1)
    """
    train_sizes, train_scores, test_scores = learning_curve(
        estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes, verbose=verbose)
    
    train_scores_mean = np.mean(train_scores, axis=1)
    train_scores_std = np.std(train_scores, axis=1)
    test_scores_mean = np.mean(test_scores, axis=1)
    test_scores_std = np.std(test_scores, axis=1)
    
    if plot:
        plt.figure()
        plt.title(title)
        if ylim is not None:
            plt.ylim(*ylim)
        plt.xlabel(u"训练样本数")
        plt.ylabel(u"得分")
        plt.gca().invert_yaxis()
        plt.grid()
    
        plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std, 
                         alpha=0.1, color="b")
        plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std, 
                         alpha=0.1, color="r")
        plt.plot(train_sizes, train_scores_mean, 'o-', color="b", label=u"训练集上得分")
        plt.plot(train_sizes, test_scores_mean, 'o-', color="r", label=u"交叉验证集上得分")
    
        plt.legend(loc="best")
        
        plt.draw()
        plt.gca().invert_yaxis()
        plt.show()
    
    midpoint = ((train_scores_mean[-1] + train_scores_std[-1]) + (test_scores_mean[-1] - test_scores_std[-1])) / 2
    diff = (train_scores_mean[-1] + train_scores_std[-1]) - (test_scores_mean[-1] - test_scores_std[-1])
    return midpoint, diff

plot_learning_curve(clf, u"学习曲线", X, y)

从上图可以看出,训练集和交叉验证集上的曲线走势还是符合预期的。

现在我们的模型还不属于过拟合的状态,我们可以再增加一些特征。

要优化这个模型,在原始数据上在进行有用特征的提取。

比如说我们放弃的几个特征。Name,Ticket。能不能当做有用的特征值加入到算法中呢。

还有,最重要的Age属性。作为连续值,如果在算法模型上直接给定一个固定的参数,似乎是不存在两头照顾的情况。。

如果我们把Age离散化,按区段分作类别属性会更好一些。

而在此之前,看看现在得到的模型系数,因为系数和他们最终的判定能力强弱是正相关的

pd.DataFrame({"columns":list(train_df.columns)[1:], "coef":list(clf.coef_.T)})


我们先看看那些权重绝对值非常大的feature,在我们的模型上:

  • Sex属性,如果是female会极大提高最后获救的概率,而male会很大程度拉低这个概率。
  • Pclass属性,1等舱乘客最后获救的概率会上升,而乘客等级为3会极大地拉低这个概率。
  • 有Cabin值会很大程度拉升最后获救概率(这里似乎能看到了一点端倪,事实上从最上面的有无Cabin记录的Survived分布图上看出,即使有Cabin记录的乘客也有一部分遇难了,估计这个属性上我们挖掘还不够)
  • Age是一个负相关,意味着在我们的模型里,年龄越小,越有获救的优先权(还得回原数据看看这个是否合理)
  • 有一个登船港口S会很大程度拉低获救的概率,另外俩港口压根就没啥作用(这个实际上非常奇怪,因为我们从之前的统计图上并没有看到S港口的获救率非常低,所以也许可以考虑把登船港口这个feature去掉试试)。
  • 船票Fare有小幅度的正相关(并不意味着这个feature作用不大,有可能是我们细化的程度还不够,举个例子,说不定我们得对它离散化,再分至各个乘客等级上?)

我们通过交叉验证来一一验证我们的想法,之后具体的操作就不再说明了。

最后推荐一个连接,也是对这个案例的分析处理。

点击打开链接




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值