模型评估与模型优化

第6章 模型评估与模型优化

6.1模型评估一般方法

对模型的性能或优劣进行评估,通过评价指标来衡量。模型评估是机器学习任务中非常重要的一环。不同的机器学习任务有着不同的评价指标,同时同一种机器学习任务也有着不同的评价指标,每个指标的着重点不一样。如分类(classification)、回归(regression)、聚类(clustering)、SVM、推荐(recommendation)等。并且很多指标可以对多种不同的机器学习模型进行评价,如精确率-召回率(precision-recall),可以用在分类、推荐等。像分类、回归、SVM都是监督式机器学习,本节的重点便是监督式机器学习的一些评价指标。
在实际情况中,我们会用不同的度量去评估我们的模型,而度量的选择,完全取决于模型的类型和模型以后要做的事。下面我们就会学习到一些用于评价模型的常用度量和图表以及它们各自的使用场景。

6.1.1分类评价指标
分类是指对给定的数据记录预测该记录所属的类别。并且类别空间已知。它包括二分类与多分类,二分类便是指只有两种类别,其输出不是0就是1;或是与否等,也可以称为“负”(negative)与正(positive)两种类别,一般在实际计算中,将其映射到“0”-“1” class中;而多分类则指类别数超过两种。下面主要根据二分类的评价指标进行讲解,不过同时它们也可扩展到多分类任务中。下面对分类中一些常用的评价指标进行介绍。
1)混淆矩阵(Confusion Matrix)
混淆矩阵显示了分类模型相对数据的真实输出(目标值)的正确预测和不正确预测数目。矩阵为NxN,其中N为目标值(类)数目。这类模型的性能通常使用矩阵中的数据评估。下表为两个类别(阳性和阴性)的2x2混淆矩阵。

常用术语:
阳性 (P, positive)
阴性 (N, Negative)
真阳性 (TP, true positive):正确的肯定。又称:命中 (hit)
真阴性 (TN, true negative):正确的否定。又称:正确拒绝 (correct rejection)
伪阳性 (FP, false positive):错误的肯定,又称:假警报 (false alarm)、第二型错误
伪阴性 (FN, false negative):错误的否定,又称:未命中(miss)、第一型错误
灵敏度(sensitivity)或真阳性率(TPR, true positive rate):sensitivity = TP/(TP + FN);
灵敏度又称为召回率(Recall)=TP/(TP+FN) = 1 - FN/P
特异性(真阴性率)specificity = TN /(FP + TN)
准确度 (ACC, accuracy):ACC = (TP + TN) / (P + N)
预测正确的数占样本数的比例。
F1评分:
精度和灵敏度的调和平均数。
F1 = 2 precision * recall / (precision+recall) = 2TP/(2TP+FP+FN)

示例:

混淆矩阵 目标
阳性 阴性
模型 阳性 70 20 阳性预测值 0.78
阴性 30 80 阴性预测值 0.73
灵敏度 特异度 准确度 = 0.75
0.7 0.8

2)ROC曲线
判定方法:ROC曲线应尽量偏离参考线。
原理:ROC全称为Receiver Operation Characteristic Curve,ROC曲线其实就是从混淆矩阵衍生出来的图形,其横坐标为1-Specificity,纵坐标为Sensitivity。

上面那条曲线就是ROC曲线,随着阈值的减小,更多的值归于正类,敏感度和1-特异度也相应增加,所以ROC曲线呈递增趋势。而那条45度线是一条参照线,也就是说ROC曲线要与这条曲线比较。
简单的说,如果我们不用模型,直接随机把客户分类,我们得到的曲线就是那条参照线,然而我们使用了模型进行预测,就应该比随机的要好,所以ROC曲线要尽量远离参照线,越远,我们的模型预测效果越好。
3)AUC(ROC曲线下面积)
判定方法:AUC应该大于0.5.
原理:ROC曲线是根据与那条参照线进行比较来判断模型的好坏,但这只是一种直觉上的定性分析,如果我们需要精确一些,就要用到AUC,也就是ROC曲线下面积。

看上图,参考线的面积是0.5,ROC曲线与它偏离越大,ROC曲线就越往左上方靠拢,它下面的面积(AUC)也就越大,这里面积是0.869。我们可以根据AUC的值与0.5相比,来评估一个分类模型的预测效果。
6.1.2回归评估指标
均方差(MSE,Mean Squared Error):
(∑(prec-act)**2)/n (prec为预测值,act为实际值,n为总样本数)
均方根差(RMSE,Root Mean Squared Error):
就是MSE开根号
平均绝对值误差(MAE,Mean Absolute Error):
(∑|prec-act|)/n
6.1.3评估指标在Sciktlearn中对应的函数
评估分类模型:

评估回归模型:

6.2模型评估与优化

对模型评估和优化时,我们常常使用训练集-验证集二划分验证(Hold-out validation)、K折交叉验证(k-Cross-validation)、超参数调优(hyperparameter tuning)等方法。三者从不同的层次对机器学习模型进行校验。Hold-out validation是评估机器学习模型泛化性能一个经典且常用的方法,通过这种方法,我们将把数据集划分为训练集和测试集,前者用于模型训练,后者用于性能的评估。
6.2.1Hold-out validation方法
使用Hold-out validation还可以采用更好的一种划分方法,把数据划分为三个部分,训练集、验证集和测试集,训练集用于模型的拟合,模型在验证集上的性能表现作为模型选择的标准,模型评估在测试集上。Hold-out validation的一般流程图如下:

6.2.2 K-fold交叉验证
一种更好,但是计算量更大的交叉验证方法是K-fold交叉验证。如同Holdout方法,K-fold交叉验证也依赖于训练数据的若干个相互独立子集。主要的区别在于K-fold交叉验证一开始就随机把数据分割成K个不相连的子集,成为folds(一般称作K折交叉验证,K的取值有5、10或者20)。每次留一份数据作为测试集,其余数据用于训练模型。
当每一份数据都轮转一遍之后,将预测的结果整合,并与目标变量的真实值比较来计算准确率。K-fold交叉验证的图形展示如下:

以下我们通过实例来进一步说明K-折交叉验证的使用方法及其优越性。这里我们使用UCI数据集中一个有关威斯康星乳腺癌数据集。
1)读取数据

import pandas as pd

df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data', header=None)
df.head()

部分数据:

2)对标签数据转换为整数,目前是字符M(恶性)或B(良性)

from sklearn.preprocessing import LabelEncoder
X = df.loc[:, 2:].values
y = df.loc[:, 1].values
le = LabelEncoder()
y = le.fit_transform(y)
le.transform(['M', 'B'])

转换结果如下:
array([1, 0])
3)把数据划分为训练数据和测试数据,划分比例为7:3

from sklearn.cross_validation import train_test_split

X_train, X_test, y_train, y_test = \
train_test_split(X, y, test_size=0.20, random_state=1)

4)使用PCA对原数据集进行降维
这里我们采用管道或流水线方式,把对数据的标准化等组装在一起。

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline

pipe_lr = Pipeline([('scl', StandardScaler()),
('pca', PCA(n_components=2)),
('clf', LogisticRegression(random_state=1))])

5)在训练集上进行k-折交叉验证
这里我们取k=10,采用sklearn中StratifiedKFold函数。

import numpy as np
from sklearn.cross_validation import StratifiedKFold

kfold = StratifiedKFold(y=y_train,
n_folds=10,
random_state=1)

scores = []
for k, (train, test) in enumerate(kfold):
pipe_lr.fit(X_train[train], y_train[train])
score = pipe_lr.score(X_train[test], y_train[test])
scores.append(score)
print('Fold: %s, Class dist.: %s, Acc: %.3f' % (k+1, np.bincount(y_train[train]), score))

print('\nCV accuracy: %.3f +/- %.3f' % (np.mean(scores), np.std(scores)))

运行结果如下:
Fold: 1, Class dist.: [256 153], Acc: 0.891
Fold: 2, Class dist.: [256 153], Acc: 0.978
Fold: 3, Class dist.: [256 153], Acc: 0.978
Fold: 4, Class dist.: [256 153], Acc: 0.913
Fold: 5, Class dist.: [256 153], Acc: 0.935
Fold: 6, Class dist.: [257 153], Acc: 0.978
Fold: 7, Class dist.: [257 153], Acc: 0.933
Fold: 8, Class dist.: [257 153], Acc: 0.956
Fold: 9, Class dist.: [257 153], Acc: 0.978
Fold: 10, Class dist.: [257 153], Acc: 0.956

CV accuracy: 0.950 +/- 0.029
准确率达到95%,说明结果还不错。
6)改进的k-折交叉验证
k-折交叉验证比较耗资源,而且抽取数据是随机的,这就可能导致有些分发中的类别比例不很恰当,是否有更好方法?以下我们采用分层k-折交叉验证法,使用scikit-learn中cross_val_score。

from sklearn.cross_validation import cross_val_score

scores = cross_val_score(estimator=pipe_lr,
X=X_train,
y=y_train,
cv=10,
n_jobs=1)
print('CV accuracy scores: %s' % scores)
print('CV accuracy: %.3f +/- %.3f' % (np.mean(scores), np.std(scores)))

运算结果如下:
CV accuracy scores: [ 0.89130435 0.97826087 0.97826087 0.91304348 0.93478261 0.97777778 0.93333333 0.95555556 0.97777778 0.95555556]
CV accuracy: 0.950 +/- 0.029

n_jobs参数很特别,使用这个参数,我们可以把K轮的交叉验证分布到几个CPU块上,如果n_jobs=2就是2块,如果n_jobs=-1,则系统将利用所有CPU进行计算。

6.3 利用验证曲线查看是否过拟合或欠拟合

验证曲线反应参数与性能指标之间的关系图,通过验证曲线图有利于定位过拟合或欠拟合等问题,是提高模型性能的有效工具。以下我们以建立在上个数据集的逻辑斯谛模型中的正则化参数C与准确率间的关系为例,使用validation_curve函数,该函数对分类算法,默认使用分层K折交叉验证来评估模型性能,使用scikit-learn来绘制验证曲线。

import matplotlib.font_manager as fm
myfont = fm.FontProperties(fname='/home/hadoop/anaconda3/lib/python3.6/site-packages/matplotlib/mpl-data/fonts/ttf/simhei.ttf')

from sklearn.learning_curve import validation_curve

param_range = [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]
train_scores, test_scores = validation_curve(
estimator=pipe_lr,
X=X_train,
y=y_train,
param_name='clf__C',
param_range=param_range,
cv=10)

train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)

plt.plot(param_range, train_mean,
color='blue', marker='o',
markersize=5, label='训练准确率')

plt.fill_between(param_range, train_mean + train_std,
train_mean - train_std, alpha=0.15,
color='blue')

plt.plot(param_range, test_mean,
color='green', linestyle='--',
marker='s', markersize=5,
label='验证准确率')

plt.fill_between(param_range,
test_mean + test_std,
test_mean - test_std,
alpha=0.15, color='green')

plt.grid()
plt.xscale('log')
plt.legend(loc='lower right',prop=myfont)
plt.xlabel('参数C',fontproperties=myfont,size=12)
plt.ylabel('准确率',fontproperties=myfont,size=12)
plt.ylim([0.8, 1.0])
plt.tight_layout()
plt.show()


从以上图形不难看出,参数C的最优点是0.1附近,如果加大正则化强度(C较小),会导致一定程度的欠拟合;如果降低正则化强度,可能导致模型的过拟合。

6.4 利用参数网格搜索调优模型

在机器学习中,有两类参数:一种是通过训练数据得到的参数,如回归模型、神经网络中系统;一种是需要单独进行优化的参数,如正则化系统、迭代次数、树的深度等,这类参数称为调优参数,又称为超参。
这里我们将介绍一种功能强大的超参数优化方法,即网格搜索(grid search)方法,通过这种方法自动选择最优参数组合,并选择最优模型。以下利用Scikit learn中GridSearchCV函数来实现网格搜索。

from sklearn.grid_search import GridSearchCV
from sklearn.svm import SVC

pipe_svc = Pipeline([('scl', StandardScaler()),
('clf', SVC(random_state=1))])

param_range = [0.0001, 0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0]

param_grid = [{'clf__C': param_range,'clf__gamma': param_range, 'clf__kernel': ['linear','rbf']}]
gs = GridSearchCV(estimator=pipe_svc,
param_grid=param_grid,
scoring='accuracy',
cv=10,
n_jobs=-1)
gs = gs.fit(X_train, y_train)
print(gs.best_score_)
print(gs.best_params_)

运行结果:
0.978021978021978
{'clf__C': 0.1, 'clf__gamma': 0.0001, 'clf__kernel': 'linear'}

由此可知,最优参数组合是:clf__C=0.1 , clf__kernel='linear', clf__gamma=0.0001
这个组合对应模型性能达到近98%。
此外,还有随机搜索(randomized search)、嵌套交叉验证选择模型等方法。大家可作为练习去练习。这里就不展开来说了。

数据探索和预处理

1、整体思路:

理解问题:查看每个变量并且根据他们的意义和对问题的重要性进行哲学分析。
单因素研究:只关注因变量(‘SalePrice’),并且进行更深入的了解。
多因素研究:分析因变量和自变量之间的关系。、
基础清洗:清洗数据集并且对缺失数据,异常值和分类数据进行一些处理。
检验假设:检查数据是否和多元分析方法的假设达到一致。
开始之前,先导入需要的库及文件:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy.stats import norm
from sklearn.preprocessing import StandardScaler
from scipy import stats
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline

#bring in the six packs
df_train = pd.read_csv('./data/house_train.csv')

查看特征

df_train.columns
Index(['Id', 'MSSubClass', 'MSZoning', 'LotFrontage', 'LotArea', 'Street', 'Alley', 'LotShape', 'LandContour', 'Utilities', 'LotConfig','LandSlope', 'Neighborhood', 'Condition1', 'Condition2', 'BldgType','HouseStyle', 'OverallQual', 'OverallCond', 'YearBuilt', 'YearRemodAdd','RoofStyle', 'RoofMatl', 'Exterior1st', 'Exterior2nd', 'MasVnrType','MasVnrArea', 'ExterQual', 'ExterCond', 'Foundation', 'BsmtQual','BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinSF1','BsmtFinType2', 'BsmtFinSF2', 'BsmtUnfSF', 'TotalBsmtSF', 'Heating','HeatingQC', 'CentralAir', 'Electrical', '1stFlrSF', '2ndFlrSF','LowQualFinSF', 'GrLivArea', 'BsmtFullBath', 'BsmtHalfBath', 'FullBath','HalfBath', 'BedroomAbvGr', 'KitchenAbvGr', 'KitchenQual','TotRmsAbvGrd', 'Functional', 'Fireplaces', 'FireplaceQu', 'GarageType','GarageYrBlt', 'GarageFinish', 'GarageCars', 'GarageArea', 'GarageQual','GarageCond', 'PavedDrive', 'WoodDeckSF', 'OpenPorchSF','EnclosedPorch', '3SsnPorch', 'ScreenPorch', 'PoolArea', 'PoolQC','Fence', 'MiscFeature', 'MiscVal', 'MoSold', 'YrSold', 'SaleType','SaleCondition', 'SalePrice'], dtype='object')

2、准备工作

为了了解我们的数据,我们可以分析每个变量并且尝试理解他们的意义和与该问题的相关程度。
首先建立一个 Excel 电子表格,有如下目录:

变量 – 变量名。
类型 – 该变量的类型。这一栏只有两个可能值,“数据” 或 “类别”。 “数据” 是指该变量的值是数字,“类别” 指该变量的值是类别标签。
划分 – 指示变量划分. 我们定义了三种划分:建筑,空间,位置。
期望 – 我们希望该变量对房价的影响程度。我们使用类别标签 “高”,“中” 和 “低” 作为可能值。
结论 – 我们得出的该变量的重要性的结论。在大概浏览数据之后,我们认为这一栏和 “期望” 的值基本一致。
评论 – 我们看到的所有一般性评论。
我们首先阅读了每一个变量的描述文件,同时思考这三个问题:
我们买房子的时候会考虑这个因素吗?
如果考虑的话,这个因素的重要程度如何?
这个因素带来的信息在其他因素中出现过吗?
我们根据以上内容填好了电子表格,并且仔细观察了 “高期望” 的变量。然后绘制了这些变量和房价之间的散点图,填在了 “结论” 那一栏,也正巧就是对我们的期望值的校正。

我们总结出了四个对该问题起到至关重要的作用的变量:
OverallQual
YearBuilt.
TotalBsmtSF.
GrLivArea.
最重要的事情——分析 “房价”

df_train['SalePrice'].describe()
count 1460.000000
mean 180921.195890
std 79442.502883
min 34900.000000
25% 129975.000000
50% 163000.000000
75% 214000.000000
max 755000.000000
Name: SalePrice, dtype: float64

绘制直方图

sns.distplot(df_train['SalePrice']);


用seaborn绘图,得出结论:
-偏离正态分布
-数据正偏
-有峰值

数据偏度和峰度度量:

print("Skewness: %f" % df_train['SalePrice'].skew())
print("Kurtosis: %f" % df_train['SalePrice'].kurt())
Skewness: 1.882876
Kurtosis: 6.536282

偏度(Skewness)是描述某变量取值分布对称性的统计量。如果偏度=0,和正态分布的偏度相同;Skewness>0,长尾巴拖在右边;Skewness<0,长尾巴拖在左边,Skewness越大,分布形态偏移程度越大。 峰度(Kurtosis)是描述某变量所有取值分布形态陡缓程度的统计量。它是和正态分布相比较的:Kurtosis=0,与正态分布的陡缓程度相同;Kurtosis>0,比正态分布的高峰更加陡峭;反之亦然。

3、与数值类型的关系

(1)grlivarea/saleprice的散点图绘制
首先观察一下saleprice和数值型变量grlivarea之间的关系。我们还是可以观察到整体的趋势的,随着grlivarea的增大,saleprice有变高的趋势。存在线性关系!

var = 'GrLivArea'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));


可以看出’’SalePrice’和’'GrLivArea' 关系很密切,并且基本呈线性关系。

var = 'TotalBsmtSF'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));

TotalBsmtSF' 和 'SalePrice'关系也很密切,从图中可以看出基本呈指数分布,但从最左侧的点可以看出特定情况下’TotalBsmtSF' 对'SalePrice' 没有产生影响。

4、与类别类型的关系

(1)overallqual/saleprice的箱形图
这里有一个问题,为什么我们不用散点图了呢?那么先画一个散点图看下效果。如下图:

#box plot overallqual/saleprice
var = 'OverallQual'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
f, ax = plt.subplots(figsize=(8, 6))
fig = sns.boxplot(x=var, y="SalePrice", data=data)
fig.axis(ymin=0, ymax=800000);


可见也是有相互关系的,如果用箱形图表示的话,会更加清晰:
(2)YearBuilt/saleprice的箱形图
两个变量之间的关系没有很强的趋势性,但是可以看出建筑时间较短的房屋价格更高。

var = 'YearBuilt'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
f, ax = plt.subplots(figsize=(16, 8))
fig = sns.boxplot(x=var, y="SalePrice", data=data)
fig.axis(ymin=0, ymax=800000);
plt.xticks(rotation=90);

两个变量之间的关系没有很强的趋势性,但是可以看出建筑时间较短的房屋价格更高。

总结:

'GrLivArea' 和 'TotalBsmtSF' 与 'SalePrice'似乎线性相关,并且都是正相关。 对于 'TotalBsmtSF',线性关系的斜率十分的高。
'OverallQual' 和 'YearBuilt' 与 'SalePrice'也有关系。'OverallQual'的相关性更强, 箱型图显示了随着整体质量的增长,房价的增长趋势。

我们只分析了四个变量,但是还有许多其他变量我们也应该分析,这里的技巧在于选择正确的特征(特征选择)而不是定义他们之间的复杂关系(特征工程)。它们之间的复杂关系可以通过模型学习。

5、客观分析

上面的分析过于主观,我们应该更客观的去分析。
主要有三方面的分析:
-相关矩阵热图
-SalePrice相关矩阵热图
-最相关变量之间的散点图

(1)相关矩阵热图

#correlation matrix
corrmat = df_train.corr()
f, ax = plt.subplots(figsize=(12, 9))
sns.heatmap(corrmat, vmax=.8, square=True);


热图是观察特征和特征、特征和label之间关系的一种快速的方式。首先,最吸引注意的是两个红色方块。第一处指向TotalBsmtSF和1stFlrSF这两个变量,第二处指向Garage相关的变量。这两块显示了TotalBsmtSF和1stFlrSF的相关性、GarageCar和GarageArea的相关性很大。事实上,这种关联性很强,暗示它们存在多重共线性。我们能够推断当变量间具有多重共线性,那么它们给出的信息也基本上是一样的。Heatmaps恰恰可以检测多重共线性的情况并决定选择哪些特征,所以是一个非常重要的工具。

另外观察特征和SalePrice之间的关系,可以看到GrLivArea、TotalBsmtSF、OverallQual,也看到了一些其他的变量应该纳入我们考虑。

(2)SalePrice相关矩阵热图
筛选与SalePrice相关性强的特征,重点观察。

#saleprice correlation matrix
k = 10 #number of variables for heatmap
cols = corrmat.nlargest(k, 'SalePrice')['SalePrice'].index
cm = np.corrcoef(df_train[cols].values.T)
sns.set(font_scale=1.25)
hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 10}, yticklabels=cols.values, xticklabels=cols.values)
plt.show()

从图中可以看出:
'OverallQual', 'GrLivArea' 以及 'TotalBsmtSF' 与 'SalePrice'有很强的相关性。
'GarageCars' 和 'GarageArea' 也是相关性比较强的变量. 车库中存储的车的数量是由车库的面积决定的,它们就像双胞胎,所以不需要专门区分'GarageCars' 和 'GarageArea' ,所以我们只需要其中的一个变量。这里我们选择了'GarageCars'因为它与'SalePrice' 的相关性更高一些。
'TotalBsmtSF' 和 '1stFloor' 与上述情况相同,我们选择 'TotalBsmtSF' 。
'FullBath'几乎不需要考虑。
'TotRmsAbvGrd' 和 'GrLivArea'也是变量中的双胞胎。
'YearBuilt' 和 'SalePrice'相关性似乎不强。
(3).'SalePrice' 和相关变量之间的散点图

#scatterplot
sns.set()
cols = ['SalePrice', 'OverallQual', 'GrLivArea', 'GarageCars', 'TotalBsmtSF', 'FullBath', 'YearBuilt']
sns.pairplot(df_train[cols], size = 2.5)
plt.show();


尽管我们已经知道了一些主要特征,这一丰富的散点图给了我们一个关于变量关系的合理想法。其中,'TotalBsmtSF' 和 'GrLiveArea'之间的散点图是很有意思的。我们可以看出这幅图中,一些点组成了线,就像边界一样。大部分点都分布在那条线下面,这也是可以解释的。地下室面积和地上居住面积可以相等,但是一般情况下不会希望有一个比地上居住面积还大的地下室。
'SalePrice' 和'YearBuilt' 之间的散点图也值得我们思考。在“点云”的底部,我们可以观察到一个几乎呈指数函数的分布。我们也可以看到“点云”的上端也基本呈同样的分布趋势。并且可以注意到,近几年的点有超过这个上端的趋势。

6、缺失数据

关于缺失数据需要思考的重要问题:
这一缺失数据的普遍性如何?
缺失数据是随机的还是有律可循?
这些问题的答案是很重要的,因为缺失数据意味着样本大小的缩减,这会阻止我们的分析进程。除此之外,以实质性的角度来说,我们需要保证对缺失数据的处理不会出现偏离或隐藏任何难以忽视的真相。

#missing data
total = df_train.isnull().sum().sort_values(ascending=False)
percent = (df_train.isnull().sum()/df_train.isnull().count()).sort_values(ascending=False)
missing_data = pd.concat([total, percent], axis=1, keys=['Total', 'Percent'])
missing_data.head(20)


(1)当超过15%的数据都缺失的时候,我们应该删掉相关变量且假设该变量并不存在。根据这一条,一系列变量都应该删掉,例如'PoolQC', 'MiscFeature', 'Alley'等等,这些变量都不是很重要,因为他们基本都不是我们买房子时会考虑的因素。
(2)'GarageX' 变量群的缺失数据量都相同,由于关于车库的最重要的信息都可以由'GarageCars' 表达,并且这些数据只占缺失数据的5%,我们也会删除上述的'GarageX' 变量群。同样的逻辑也适用于 'BsmtX' 变量群。
(3)对于 'MasVnrArea' 和 'MasVnrType',我们可以认为这些因素并不重要。除此之外,他们和'YearBuilt' 以及 'OverallQual'都有很强的关联性,而这两个变量我们已经考虑过了。所以删除 'MasVnrArea'和 'MasVnrType'并不会丢失信息。

(4)最后,由于'Electrical'中只有一个损失的观察值,所以我们删除这个观察值,但是保留这一变量。

#dealing with missing data
df_train = df_train.drop((missing_data[missing_data['Total'] > 1]).index,1)
df_train = df_train.drop(df_train.loc[df_train['Electrical'].isnull()].index)
df_train.isnull().sum().max() #just checking that there's no missing data missing...

总结:处理空值,我们删掉所有有较多空值的特征,除了特征Electrical,因为只有一个缺失值,所以删除含该空值的样本即可。

7、异常值处理

异常值也是我们应该注意的东西。因为异常值能明显的影响我们的模型,并且是一个有价值的信息来源,帮助我们对特定行为有更多的见解。异常值是一个复杂的主题,并且值得研究。在这里,我们将用SalePrice的标准差和一系列的散点来进行快速分析。

7.1单因素分析
这里的关键在于如何建立阈值,定义一个观察值为异常值。
我们对数据进行正态化,意味着把数据值转换成均值为0,方差为1的数据。

#standardizing data
saleprice_scaled = StandardScaler().fit_transform(df_train['SalePrice'][:,np.newaxis]);
low_range = saleprice_scaled[saleprice_scaled[:,0].argsort()][:10]
high_range= saleprice_scaled[saleprice_scaled[:,0].argsort()][-10:]
print('outer range (low) of the distribution:')
print(low_range)
print('\nouter range (high) of the distribution:')
print(high_range)
outer range (low) of the distribution:
[[-1.83820775]
[-1.83303414]
[-1.80044422]
[-1.78282123]
[-1.77400974]
[-1.62295562]
[-1.6166617 ]
[-1.58519209]
[-1.58519209]
[-1.57269236]]

outer range (high) of the distribution:
[[ 3.82758058]
[ 4.0395221 ]
[ 4.49473628]
[ 4.70872962]
[ 4.728631 ]
[ 5.06034585]
[ 5.42191907]
[ 5.58987866]
[ 7.10041987]
[ 7.22629831]]

进行正态化后,可以看出:
低范围的值都比较相似并且在0附近分布。
高范围的值离0很远,并且7点几的值远在正常范围之外。
现在看,我们不把任何值作为异常值,但是我们应该注意这两个大于7的值。
7.2双变量分析

#bivariate analysis saleprice/grlivarea
var = 'GrLivArea'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));


这是之前GrLivArea和SalePrice关系散点图:
(1)有两个离群的'GrLivArea' 值很高的数据,我们可以推测出现这种情况的原因。或许他们代表了农业地区,也就解释了低价。 这两个点很明显不能代表典型样例,所以我们将它们定义为异常值并删除。

(2)图中顶部的两个点是七点几的观测值,他们虽然看起来像特殊情况,但是他们依然符合整体趋势,所以我们将其保留下来。

删除异常点:

df_train.sort_values(by = 'GrLivArea', ascending = False)[:2]
df_train = df_train.drop(df_train[df_train['Id'] == 1299].index)
df_train = df_train.drop(df_train[df_train['Id'] == 524].index)

(3). 'TotalBsmtSF'和'SalePrice'双变量分析

#bivariate analysis saleprice/grlivarea
var = 'TotalBsmtSF'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));


我们可以感觉到试图消除一些观察(例如,TotalBsmtSF> 3000),但是我们认为这是不值得的。 我们可以保留它们,所以我们不会做任何事情。

8、核心部分

核心部分
“房价”到底是谁?
这个问题的答案,需要我们验证根据数据基础进行多元分析的假设。
我们已经进行了数据清洗,并且发现了“SalePrice”的很多信息,现在我们要更进一步理解‘SalePrice’如何遵循统计假设,可以让我们应用多元技术。

应该测量4个假设量:
(1)正态性:它的重要在于很多统计检验是基于正态分布的,在房价预测的问题中,我们只检查了单变量的正态性。但是单变量正态性不能确保多变量的正态性,但是会其帮助作用。
(2)同方差性:假设在预测变量的范围因变量表现出同等水平的方差
(3)线性:通过观察散点图,看是否为线性关系,如果不是,需要数据转换,但是大多数情况下都是满足线性关系的。
(4)相关错误缺失
1). ‘SalePrice’
正态性:

应主要关注以下两点:
直方图 - 峰度和偏度。
正态概率图 - 数据分布应紧密跟随代表正态分布的对角线。

#histogram and normal probability plot
sns.distplot(df_train['SalePrice'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['SalePrice'], plot=plt)

可以看出,房价分布不是正态的,显示了峰值,正偏度。但是,信息没有丢失,简单的数据变换就可以解决这个问题,为了防止正偏,log变换作用很好。

#applying log transformation
df_train['SalePrice'] = np.log(df_train['SalePrice'])

#transformed histogram and normal probability plot
sns.distplot(df_train['SalePrice'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['SalePrice'], plot=plt)

从图中可以看出:
显示出了偏度
大量为0的观察值(没有地下室的房屋)
含0的数据无法进行对数变换,可用log(x+1)

2). GrLivArea
绘制直方图和正态概率曲线图:

#histogram and normal probability plot
sns.distplot(df_train['GrLivArea'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['GrLivArea'], plot=plt)

进行对数变换:

#data transformation
df_train['GrLivArea'] = np.log(df_train['GrLivArea'])

#transformed histogram and normal probability plot
sns.distplot(df_train['GrLivArea'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['GrLivArea'], plot=plt)

3). 'TotalBsmtSF'

绘制直方图和正态概率曲线图:

#histogram and normal probability plot
sns.distplot(df_train['TotalBsmtSF'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train['TotalBsmtSF'], plot=plt)

从图中可以看出:
 显示出了偏度
 大量为 0 的观察值(没有地下室的房屋)
 含 0 的数据无法进行对数变换

我们建立了一个变量,可以得到有没有地下室的影响值(二值变量),我们选择忽略零值,只对非零值进行对数变换。这样我们既可以变换数据,也不会损失有没有地下室的影响。

#create column for new variable (one is enough because it's a binary categorical feature)
#if area>0 it gets 1, for area==0 it gets 0
df_train['HasBsmt'] = pd.Series(len(df_train['TotalBsmtSF']), index=df_train.index)
df_train['HasBsmt'] = 0
df_train.loc[df_train['TotalBsmtSF']>0,'HasBsmt'] = 1

对该变量进行转换

#transform data
df_train.loc[df_train['HasBsmt']==1,'TotalBsmtSF'] = np.log(df_train['TotalBsmtSF'])

#histogram and normal probability plot
sns.distplot(df_train[df_train['TotalBsmtSF']>0]['TotalBsmtSF'], fit=norm);
fig = plt.figure()
res = stats.probplot(df_train[df_train['TotalBsmtSF']>0]['TotalBsmtSF'], plot=plt)

9、同方差性

最好的测量两个变量的同方差性的方法就是图像。
1). SalePrice 和 GrLivArea 同方差性
绘制散点图:

#scatter plot
plt.scatter(df_train['GrLivArea'], df_train['SalePrice']);

2).'SalePrice' with 'TotalBsmtSF'同方差性

绘制散点图:

#scatter plot
plt.scatter(df_train[df_train['TotalBsmtSF']>0]['TotalBsmtSF'], df_train[df_train['TotalBsmtSF']>0]['SalePrice']);


可以看出'SalePrice'在整个 'TotalBsmtSF'变量范围内显示出了同等级别的变化。
这就是正态化的作用,我们仅仅需要保证特征的正态化,就能解决同方差的问题。

10、虚拟变量

对类别变量进行虚拟编码:

df_train['SaleCondition'].unique()
array(['Normal', 'Abnorml', 'Partial', 'AdjLand', 'Alloca', 'Family'], dtype=object)

#convert categorical variable into dummy
df_train = pd.get_dummies(df_train)

df_train

1457 rows × 222 columns
[/cceN_python]

结论

整个方案中,我们使用了很多《多元数据分析》中提出的方法。我们对变量进行了哲学分析,不仅对'SalePrice'进行了单独分析,还结合了相关程度最高的变量进行分析。我们处理了缺失数据和异常值,我们验证了一些基础统计假设,并且将类别变量转换为虚拟变量。

但问题还没有结束,我们还需要预测房价的变化趋势,房价预测是否适合线性回归正则化的方法?是否适合组合方法?或者一些其他的方法?这篇文章解决了数据预处理的问题,预处理后,我们选择正确的模型算法进行拟合。

参考以下网站:
https://www.kaggle.com/pmarcelino/comprehensive-data-exploration-with-python
http://www.jianshu.com/p/defcbbfdb324

Scikit-learn简介

第4章 Scikit-learn简介

自2007年发布以来,Scikit-learn已经成为Python重要的机器学习库了。Scikit-Learn简称Sklearn,支持包括分类、回归、降维和聚类四大机器学习算法。还包含了特征提取、数据处理和模型评估三大模块。
Sklearn是Scipy的扩展,建立在NumPy和matplotlib库的基础上。利用这几大模块的优势,可以大大提高机器学习的效率。
Sklearn拥有着完善的文档,上手容易,具有着丰富的API,在学术界颇受欢迎。Sklearn已经封装了大量的机器学习算法,同时Sklearn内置了大量数据集,节省了获取和整理数据集的时间
1、 Sklearn安装
推荐使用Anaconda科学计算环境,里面已经内置了NumPy、SciPy、sklearn等模块,直接可用。或者使用conda进行包管理。

conda install scikit-learn

当然也可使用pip安装,在安装前,需要先安装如下依赖库:
Python(>=2.7 or >=3.5)
NumPy
SciPy
安装以后,我们可以参考安装的版本。

import sklearn
sklearn.__version__
'0.18.1'

2、 Sklearn基础
2.1 估计器(Estimator)
估计器,很多时候可以直接理解成分类器,主要包含两个函数:
fit():训练算法,设置内部参数。接收训练集和类别两个参数。
predict():预测测试集类别,参数为测试集。
大多数scikit-learn估计器接收和输出的数据格式均为numpy数组或类似格式。
2.2 转换器(Transformer)
转换器用于数据预处理和数据转换,主要是三个方法:
fit():训练算法,设置内部参数。
transform():数据转换。
fit_transform():合并fit和transform两个方法。
2.3 流水线(Pipeline)
流水线的功能:
跟踪记录各步骤的操作(以方便地重现实验结果),对各步骤进行一个封装,确保代码的复杂程度不至于超出掌控范围。
基本使用方法:
流水线的输入为一连串的数据挖掘步骤,其中最后一步必须是估计器,前几步是转换器。输入的数据集经过转换器的处理后,输出的结果作为下一步的输入。最后,用位于流水线最后一步的估计器对数据进行分类。
每一步都用元组( ‘名称’,步骤)来表示。现在来创建流水线。
使用示例:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import MinMaxScaler
from sklearn.neighbors import KNeighborsClassifier

scaling_pipeline = Pipeline([('scale', MinMaxScaler()),('predict', KNeighborsClassifier())])

2.4 预处理
要在from sklearn.preprocessing包下。
规范化:
MinMaxScaler :最大最小值规范化
Normalizer :使每条数据各特征值的和为1
StandardScaler :为使各特征的均值为0,方差为1
编码:
LabelEncoder :把字符串类型的数据转化为整型
OneHotEncoder :特征用一个二进制数字来表示
Binarizer :为将数值型特征的二值化
MultiLabelBinarizer:多标签二值化
pandas.get_dummies: 对离散型特征进行one-hot编码,以下是对get_dummies使用示例:
离散特征的编码分为两种情况:
1、离散特征的取值之间没有大小的意义,比如color:[red,blue],那么就使用one-hot编码
2、离散特征的取值有大小的意义,比如size:[X,XL,XXL],那么就使用数值的映射{X:1,XL:2,XXL:3}
使用pandas可以很方便的对离散型特征进行one-hot编码
(1)生成一个矩阵

import pandas as pd
df = pd.DataFrame([
['green', 'M', 10.1, 'class1'],
['red', 'L', 13.5, 'class2'],
['blue', 'XL', 15.3, 'class1']])

df.columns = ['color', 'size', 'prize', 'class label']
df

(2)把size列用数值化,对应关系为:

size_mapping = { 'XL': 3,'L': 2, 'M': 1}
size_mapping = {
'XL': 3,
'L': 2,
'M': 1}
df['size'] = df['size'].map(size_mapping)
df


(3)对列class label数值化

class_mapping = {label:idx for idx,label in enumerate(set(df['class label']))}
df['class label'] = df['class label'].map(class_mapping)
df


(4)使用get_dummies进行one-hot编码

pd.get_dummies(df)


2.5 特征
sklearn常见的转换功能:


归一化使不同规格的数据转换到同一规格。无量刚好与标准化(Normalizer)的区别:
简单来说,无量纲化是依照特征矩阵的列处理数据,标准化是依照特征矩阵的行处理数据。标准化的前提是样本各特征值服从正态分布,标准化后将其转换成标准正态分布。标准化的公式类似于标准化,不同的是样本均值和样本标准差改为特征值均值和特征值标准差。
2.5.1 特征抽取
包:from sklearn.feature_extraction
特征抽取是机器学习任务最为重要的一个环节,一般而言,它对最终结果的影响要高过机器学习算法本身。只有先把现实用特征表示出来,才能借助机器学习的力量找到问题的答案。特征选择的另一个优点在于:降低真实世界的复杂度,模型比现实更容易操纵。
一般最常使用的特征抽取技术都是高度针对具体领域的,对于特定的领域,如图像处理,在过去一段时间已经开发了各种特征抽取的技术,但这些技术在其他领域的应用却非常有限。
DictVectorizer: 将dict类型的list数据,转换成numpy array
FeatureHasher : 特征哈希,相当于一种降维技巧
image:图像相关的特征抽取
text: 文本相关的特征抽取
text.CountVectorizer:将文本转换为每个词出现的个数的向量
text.TfidfVectorizer:将文本转换为tfidf值的向量
text.HashingVectorizer:文本的特征哈希
示例

CountVectorize只数出现个数:

TfidfVectorizer:个数+归一化(不包括idf:

HashingVectorizer:

2.5.2 特征选择
包:sklearn.feature_selection
特征选择的原因如下:
(1)降低复杂度
(2)降低噪音
(3)增加模型可读性
VarianceThreshold: 删除特征值的方差达不到最低标准的特征
SelectKBest: 返回k个最佳特征
SelectPercentile: 返回表现最佳的前r%个特征
单个特征和某一类别之间相关性的计算方法有很多。最常用的有卡方检验(χ2)。其他方法还有互信息和信息熵。
2.6 降维
包:sklearn.decomposition
主成分分析算法(Principal Component Analysis, PCA)的目的是找到能用较少信息描述数据集的特征组合。它意在发现彼此之间没有相关性、能够描述数据集的特征,确切说这些特征的方差跟整体方差没有多大差距,这样的特征也被称为主成分。这也就意味着,借助这种方法,就能通过更少的特征捕获到数据集的大部分信息。
2.7 组合
包:sklearn.ensemble
组合技术即通过聚集多个分类器的预测来提高分类准确率。
常用的组合分类器方法:
(1)通过处理训练数据集。即通过某种抽样分布,对原始数据进行再抽样,得到多个训练集。常用的方法有装袋(bagging)和提升(boosting)。
(2)通过处理输入特征。即通过选择输入特征的子集形成每个训练集。适用于有大量冗余特征的数据集。随机森林(Random forest)就是一种处理输入特征的组合方法。
(3)通过处理类标号。适用于多分类的情况,将类标号随机划分成两个不相交的子集,再把问题变为二分类问题,重复构建多次模型,进行分类投票。

BaggingClassifier: Bagging分类器组合
BaggingRegressor: Bagging回归器组合
AdaBoostClassifier: AdaBoost分类器组合
AdaBoostRegressor: AdaBoost回归器组合
GradientBoostingClassifier:GradientBoosting分类器组合
GradientBoostingRegressor: GradientBoosting回归器组合
ExtraTreeClassifier:ExtraTree分类器组合
ExtraTreeRegressor: ExtraTree回归器组合
RandomTreeClassifier:随机森林分类器组合
RandomTreeRegressor: 随机森林回归器组合
使用举例

from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
AdaBoostClassifier(DecisionTreeClassifier(max_depth=1),
algorithm="SAMME",
n_estimators=200)

解释
装袋(bagging):根据均匀概率分布从数据集中重复抽样(有放回),每个自助样本集和原数据集一样大,每个自助样本集含有原数据集大约60%的数据。训练k个分类器,测试样本被指派到得票最高的类。
提升(boosting):通过给样本设置不同的权值,每轮迭代调整权值。不同提升算法之间的差别,一般是(1)如何更新样本的权值,(2)如何组合每个分类器的预测。其中Adaboost中,样本权值是增加那些被错误分类的样本的权值,分类器C_i的重要性依赖于它的错误率。
Boosting主要关注降低偏差,因此Boosting能基于泛化性能相当弱的学习器构建出很强的集成;Bagging主要关注降低方差,因此它在不剪枝的决策树、神经网络等学习器上效用更为明显。偏差指的是算法的期望预测与真实预测之间的偏差程度,反应了模型本身的拟合能力;方差度量了同等大小的训练集的变动导致学习性能的变化,刻画了数据扰动所导致的影响。

2.8 模型评估(度量)
包:sklearn.metrics
sklearn.metrics包含评分方法、性能度量、成对度量和距离计算。
分类结果度量
参数大多是y_true和y_pred。
accuracy_score:分类准确度
condusion_matrix :分类混淆矩阵
classification_report:分类报告
precision_recall_fscore_support:计算精确度、召回率、f、支持率
2.9 交叉验证
包:sklearn.cross_validation

KFold:K-Fold交叉验证迭代器。接收元素个数、fold数、是否清洗
LeaveOneOut:LeaveOneOut交叉验证迭代器
LeavePOut:LeavePOut交叉验证迭代器
LeaveOneLableOut:LeaveOneLableOut交叉验证迭代器
LeavePLabelOut:LeavePLabelOut交叉验证迭代器
LeaveOneOut(n) 相当于 KFold(n, n_folds=n) 相当于LeavePOut(n, p=1)。
LeaveP和LeaveOne差别在于leave的个数,也就是测试集的尺寸。LeavePLabel和LeaveOneLabel差别在于leave的Label的种类的个数。
LeavePLabel这种设计是针对可能存在第三方的Label,比如我们的数据是一些季度的数据。那么很自然的一个想法就是把1,2,3个季度的数据当做训练集,第4个季度的数据当做测试集。这个时候只要输入每个样本对应的季度Label,就可以实现这样的功能。
以下是实验代码:

import numpy as np
import sklearn
import sklearn.cross_validation as cross_validation
X = np.array([[1, 2], [3, 4], [5, 6], [7, 8],[9, 10]])
y = np.array([1, 2, 1, 2, 3])
def show_cross_val(method):
if method == "lolo":
labels = np.array(["summer", "winter", "summer", "winter", "spring"])
cv = cross_validation.LeaveOneLabelOut(labels)
elif method == 'lplo':
labels = np.array(["summer", "winter", "summer", "winter", "spring"])
cv = cross_validation.LeavePLabelOut(labels,p=2)
elif method == 'loo':
cv = cross_validation.LeaveOneOut(n=len(y))
elif method == 'lpo':
cv = cross_validation.LeavePOut(n=len(y),p=3)
for train_index, test_index in cv:
print("TRAIN:", train_index, "TEST:", test_index)
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
print ("X_train: ",X_train )
print ("y_train: ", y_train )
print ("X_test: ",X_test )
print ("y_test: ",y_test )
if __name__ == '__main__':
show_cross_val("lpo")

常用方法
• train_test_split:分离训练集和测试集(不是K-Fold)
• cross_val_score:交叉验证评分,可以指认cv为上面的类的实例
• cross_val_predict:交叉验证的预测
2.10 网格搜索
包:sklearn.grid_search
网格搜索最佳参数

GridSearchCV:搜索指定参数网格中的最佳参数
ParameterGrid:参数网格
ParameterSampler:用给定分布生成参数的生成器
RandomizedSearchCV:超参的随机搜索
通过best_estimator_.get_params()方法,获取最佳参数。
2.11 多分类、多标签分类
包:sklearn.multiclass
OneVsRestClassifier:1-rest多分类(多标签)策略
OneVsOneClassifier:1-1多分类策略
OutputCodeClassifier:1个类用一个二进制码表示
示例代码

from sklearn import metrics
from sklearn import cross_validation
from sklearn.svm import SVC
from sklearn.multiclass import OneVsRestClassifier
from sklearn.preprocessing import MultiLabelBinarizer
import numpy as np
from numpy import random
X=np.arange(15).reshape(5,3)
y=np.arange(5)
Y_1 = np.arange(5)
random.shuffle(Y_1)
Y_2 = np.arange(5)
random.shuffle(Y_2)
Y = np.c_[Y_1,Y_2]
def multiclassSVM():
X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, test_size=0.2,random_state=0)
model = OneVsRestClassifier(SVC())
model.fit(X_train, y_train)
predicted = model.predict(X_test)
print (predicted)
def multilabelSVM():
Y_enc = MultiLabelBinarizer().fit_transform(Y)
X_train, X_test, Y_train, Y_test = cross_validation.train_test_split(X, Y_enc, test_size=0.2, random_state=0)
model = OneVsRestClassifier(SVC())
model.fit(X_train, Y_train)
predicted = model.predict(X_test)
print (predicted)
if __name__ == '__main__':
multiclassSVM()

上面的代码测试了svm在OneVsRestClassifier的包装下,分别处理多分类和多标签的情况。特别注意,在多标签的情况下,输入必须是二值化的。所以需要MultiLabelBinarizer()先处理。
3、 创建自己的转换器
在特征抽取的时候,经常会发现自己的一些数据预处理的方法,sklearn里可能没有实现,但若直接在数据上改,又容易将代码弄得混乱,难以重现实验。这个时候最好自己创建一个转换器,在后面将这个转换器放到pipeline里,统一管理。
如果我们想接收一个numpy数组,根据其均值将其离散化,任何高于均值的特征值替换为1,小于或等于均值的替换为0。
代码实现:

from sklearn.base import TransformerMixin
from sklearn.utils import as_float_array

class MeanDiscrete(TransformerMixin):

#计算出数据集的均值,用内部变量保存该值。
def fit(self, X, y=None):
X = as_float_array(X)
self.mean = np.mean(X, axis=0)
#返回self,确保在转换器中能够进行链式调用(例如调用transformer.fit(X).transform(X))
return self

def transform(self, X):
X = as_float_array(X)
assert X.shape[1] == self.mean.shape[0]
return X > self.mean

参考博客:
http://www.jianshu.com/p/516f009c0875

机器学习简单示例

第3章 机器学习简单示例

为更好理解机器学习中的有关概念和流程,我们以一个实际案例来说明,这个实例需求就是根据(输入数据为房子面积,目标数据为房子价格),建立一个模型对新房价进行预测。

  • 输入数据(只有一维):房子面积
  • 目标数据(只有一维):房子价格
  • 需要做的:就是根据已知的房子面积和价格进行机器学习,建立一个模型,找到它们之间的内在关系。

下面我们就一步步地进行操作。

1、获取与预处理数据

查看源数据:

system head -5 ./data/python_and_ml/prices.txt
['2104,399900', '1600,329900', '2400,369000', '1416,232000', '3000,539900']

显示前5行数据,每行数据由‘房子面积,房子价格’组成,这些数据比较大,相差也比较大,故需要对源数据进行标准化处理,使用如下公式:

其中  表示房子面积的均值、std(X)表示X的标准差,具体实现代码如下:

%matplotlib inline
#导入需要的库
import numpy as np
import matplotlib.pyplot as plt
# 定义存储输入数据、目标数据的数组
x, y = [], []
#遍历数据集,变量sample对应每个样本
for sample in open("./data/python_and_ml/prices.txt", "r"):
x1, y1 = sample.split(",")
x.append(float(x1))
y.append(float(y1))
#把读取后的数据转换为numpy数组
x, y = np.array(x), np.array(y)
# 对x数据进行标准化处理
x = (x - x.mean()) / x.std()
# 可视化原数据
plt.figure()
plt.scatter(x, y, c="g", s=20)
plt.show()

这里横轴是标准化的房子面积,纵轴为房子价格,通过数据可视化,我们能很直观了解到原数据的分布情况,接下来我们开始训练模型。

2、 选择和训练模型

从数据可视化后的结果看来,我们可以通过线性回归(Linear Regression)中的多项式拟合来得到一个模型,该模型的数学表达式为:

构造模型的损失函数

其中f(x|p;n)就是我们的模型,p,n是模型参数,p是多项式f的各项系统,n是多项式的次数。L(p;n)是模型的损失函数,这里我们采用了常用的平方损失函数。x、y分别是输入向量和目标向量,它们都是47维的向量。

在确定模型后,接下来就开始编写代码训练模型。训练一般是最小化损失函数,这里就是使L(p;n)最小,从而求出相关参数。

当然,这里我们采用数理统计中的正规方程方法。具体实现代码如下:

#在(-2,4)这个区间上取100个点作为画图的基础
x0 = np.linspace(-2, 4, 100)

#利用numpy的函数定义训练并返回多项式回归模型函数
# deg参数表示模型参数中的n
def get_model(deg):
return lambda input_x=x0: np.polyval(np.polyfit(x, y, deg), input_x)

其中polyval和polyfit都是numpy中的函数。

polyfit(x,y,deg):该函数返回使得上述损失函数  最小的参数p;

polyval:根据多项式的各项系统p和多项式中的x,返回多项式的y值。

3、 评估与可视化结果

模型训练好以后,接下来就要判断各种参数下模型的性能了,为简单起见,我们这里采用deg=1,4,10(即多项式最大次数分别为1,4,10),采用损失函数来衡量模型的性能。

# 定义损失函数
def get_cost(deg, input_x, input_y):
return 0.5 * ((get_model(deg)(input_x) - input_y) ** 2).sum()

# 设置n的几个参数值
test_set = (1, 4, 10)
for d in test_set:
print(get_cost(d, x, y))

# Visualize results
plt.scatter(x, y, c="g", s=20)
for d in test_set:
plt.plot(x0, get_model(d)(), label="degree = {}".format(d))
plt.xlim(-2, 4)
plt.ylim(1e5, 8e5)
plt.legend()
plt.show()

损失值分别为:

96732238800.4

94112406641.7

75874846680.1

结果可视化图像:

从损失值来看,n=10时,损失值最小,当从图形来看,当n=10,显然存在过拟合。

n=4的损失值小于n=1,但从图形来看,n=4也是过拟合,所以综合来看,取n=1为一个不错选择。

通过这个简单实例,我们对机器学习的大致流程有个大致了解,对机器学习相关概念也有更具体认识,对后续更复杂的机器学习应该有了一个很好的基础。

 

 

 

 

机器学习一般流程

第2章 机器学习一般流程

机器学习一般流程大致分为收集数据、探索数据、预处理数据,对数据处理后,接下来开始训练模型、评估模型,然后优化模型等步骤,具体可参考下图:

 

通过这个图形我们可以比较直观了解机器学习的一般步骤或整体框架,接下来我们就各部分分别加以说明。

1 数据探索

拿到数据以后,一般不会急于创建模型、训练模型,在这之前,需要对数据、对需求或机器学习的目标进行分析,尤其对数据进行一些必要的探索,如了解数据的大致结构、数据量、各特征的统计信息、整个数据质量情况、数据的分布情况等。为了更好体现数据分布情况,数据可视化是一个不错方法。

2 数据预处理

通过对数据探索后,可能发现不少问题:如存在缺失数据、数据不规范、数据分布不均衡、存在奇异数据、有很多非数值数据、存在很多无关或不重要的数据等等。这些问题的存在直接影响数据质量,为此,数据预处理工作应该就是接下来的重点工作,数据预处理是机器学习过程中必不可少的重要步骤,特别是在生产环境中的机器学习,数据往往是原始、为加工和处理过,数据预处理常常占据整个机器学习过程的大部分时间。

数据预处理过程中,一般包括数据清理、数据转换、规范数据、特征选择等等工作。

3 选择模型并进行训练

在模型选择时,一般不存在某种对任何情况都表现很好的算法(这种现象又称为没有免费的午餐)。因此在实际选择时,一般会选用几种不同方法来训练模型,然后比较它们的性能,从中选择最优的这个。当然,在比较不同模型之前,我们需要先确认衡量性能的指标,对分类问题常用的是准确率或ROC曲线,对回归连续性目标值问题一般采用误差来评估。

训练模型前,一般会把数据集分为训练集和测试集,或对训练集再细分为训练集和验证集,从而对模型的泛化能力进行评估。

4 模型验证和使用未知数据进行预测

使用训练数据构建模型后,通常使用测试数据对模型进行测试,测试模型对新数据的

测试。如果我们对模型的测试结果满意,就可以用此模型对以后的进行预测;如果我们测试结果不满意,我们可以优化模型,优化的方法很多,其中网格搜索参数是一种有效方法,当然我们也可以采用手工调节参数等方法。如果出现过拟合,尤其是回归类问题,我们可以考虑正则化方法来降低模型的泛化误差。

第9章Tensorflow基础

第9章Tensorflow基础

Tensorflow的系统架构和数据流程图的介绍能够方便读者先对Tensorflow运行的基础有所了解,然后引出Tensorflow程序设计中的基本概念,包括张量(Tensor),算子(Operation),变量(Variables),占位符(Place Holder),图(Graph)等,最后介绍如何在Tensorflow可视化数据流程图和Tensorflow的分布式运行。

9.1 Tensorflow系统结构

https://www.tensorflow.org/extend/architecture

TensorFlow的系统结构如下图所示,中间部分是C的API接口,上面是可编程的客户端,下面是后端执行系统。客户端系统提供多语言支持的编程模型,负责构造计算图。后端执行系统是TensorFlow的运行时系统,主要负责计算图的执行过程,包括计算图的剪枝,设备分配,子图计算等。

图9.1 Tensorflow系统结构

下面分别介绍Tensorflow系统结构的核心组件:

Client是前端系统的主要组成部分,它是一个支持多语言的编程环境。它提供基于计算图的编程模型,方便用户构造各种复杂的计算图,实现各种形式的模型设计。

Distributed Master从计算图中反向遍历,找到所依赖的最小子图,再把最小子图分割成子图片段派发给Worker Service。随后Worker Service启动子图片段的执行过程。

Worker Service 可以管理多个设备。Worker Service将按照从Distributed Master接收的子图在设备上调用的Kernel实现完成运算,并发送结果给其他Work Service,以及接受其他Worker Service的运算结果。

Kernel是Operation在不同硬件设备的运行和实现,它负责执行具体的运算。

图9.2 Tensorflow运行机制

如上图,客户端启动Session并把定义号的数据流程图传给执行层,Distributed Master进程拆解最小子图分发给Worker Service,Worker Service调用跨设备的Kernel的Operation,利用各个的资源完成运算。

9.2数据流程图

Tensorflow的特点之一就是用数据流程图定义程序,本节会介绍数据流程图的基本知识。数据流程图用来定义数据在链接在一起的函数中运算,由节点(Node),边(Edge)来确定数据运算的路径图,每个函数会把输出传递给后续函数。在数据流程图的语境中,节点通常以圆圈、椭圆和方框表示,代表了对数据所做的运算或某种算子(Operation)。边对应于向算子传入和从算子传出的实际数值,通常以箭头表示。可从概念上将边视为不同算子之间的连接,因为它们将控制信息流动走向,让信息从一个节点传输到另一个节点。

图9.3 数据流程图

首先上图Data Flow1由节点a, b, c, d, e 和相应的边组成,有两个输入和一个输出,定义了以下计算:

当时,Data Flow 1就可以完成计算 .

如果我们把 Data Flow 1整体视为一个构件,那么其他的数据流程图也可以成为它的输入或输出,在可视化的时候可以把每个数据流程图内部的运算隐藏起来,从而能更容易展示运算的结构链路。

9.3 Tensorflow基本概念

Abadi M, Agarwal A, Barham P, et al. Tensorflow: Large-scale machine learning on heterogeneous distributed systems[J]. arXiv preprint arXiv:1603.04467, 2016.

TensorFlow就是采用数据流图程(Data Flow Graphs)对张量来进行计算。让我们先Tensorflow的几个基本概念做简单介绍,包括张量(Tensor),算子(Operation),会话(Session),变量(Variables),占位符(Place Holder)和图(Graph),图的部分将在下一小节介绍。

名副其实,TensorFlow,就是张量的流动。张量(Tensor),即任意维度的数据,张量有多种. 零阶张量为纯量或标量 (scalar) 也就是一个数值,比如 [1];一阶张量为向量 (Vector), 比如 一维的 [1, 2, 3];二阶张量为矩阵 (Matrix), 比如二维的 [[1, 2, 3],[4, 5, 6],[7, 8, 9]]; 以此类推。另外张量对象用形状(Shape)属性来定义结构, Python中的元组和列表都可以定义张量的形状。张量每一维可以是固定长度,也可以用None表示为可变长度。

###指定0阶张量的形状,可以是任意整数,如1, 3, 5, 7等t_list=[]t_tuple=()###指定一个长度为2的向量,例如[2,3]t_1=[2]###指定一个2*3矩阵的形状##例如 [[1,2,3],[4,5,6]]t_2=(2, 3)###示任意长度的向量t_3=[None]###表示行数任意列数为2的矩阵的形状t_4=(None, 2)###表示第一维长度为3,第二维长度为2,第三维长度任意的3阶张量t_5=[3, 2, None]
[/cc]
Tensorflow和Numpy有很好的兼容,Tensorflow的数据类型是基于Numpy的数据类型的。Tensorflow支持的数据类型如下表:

数据类型 描述
tf.float32 32 位浮点数
tf.float64 64 位浮点数
tf.int64 64 位有符号整型
tf.int32 32 位有符号整型
tf.int16 16 位有符号整型
tf.int8 8 位有符号整型
tf.uint8 8 位无符号整型
tf.string 可变长度的字节数组.每一个张量元素都是一个字节数组
tf.bool 布尔型
tf.complex64 由两个32位浮点数组成的复数:实数和虚数
tf.qint32 用于量化Ops的32位有符号整型
tf.qint8 用于量化Ops的8位有符号整型
tf.quint8 用于量化Ops的8位无符号整型

表9.1 Tensorflow数据类型

对张量进行计算的函数叫算子,算子是不变的,但流动的张量会经函数运算而发生改变。TensorFlow的运行时包括数值计算,多维数组操作,控制流,状态管理等。

(Tensorflow支持的算子类型总结)

客户端使用会话来和执行系统交互,主要包括建立会话和执行数据流图。Tf.session()用来启动会话,Session.run()用来执行会话或初始化变量,返回的张量为Numpy数组。

###引入tensorflow和 numpyimport tensorflow as tfimport numpy as np###定义张量t_1为标量,常数 2t_1=tf.constant(2)###定义t_2 张量为2*1阶矩阵t_2=np.array([[2],[4]],dtype=np.int32)###定义 1*2 阶矩阵,数据类型为int32t_3=np.array([[6,8]],dtype=np.int32)###定义2*1阶矩阵,数据类型为int32t_4=np.array([[1],[3]],dtype=np.int32)###定义对张量 t_2 和t_3做矩阵乘法算子的运算t_5=tf.multiply(t_2,t_3)###定义对张量 t_2和t_4做矩阵加法算子的运算t_6=tf.add(t_2,t_4)###建立会话sess=tf.Session()###用Session.run()执行运算并显示结果print("t_5 :",sess.run(t_5))print ("t_6 :",sess.run(t_6))

以上实例运行结果为:

('t_5 :', array([[12, 16],       [24, 32]], dtype=int32))('t_6 :', array([[3],       [7]], dtype=int32))

Tensorflow中的变量用来保持运算中Tensor的句柄,但与Python不同的是必须要明确定义为变量,并在完成初始化后才能正常运算。Variable对象初值为全0,全1或随机数填的张量。Tensorflow提供了一些Operaiton来方便变量初值的定义,如tf.zeros()、 tf.ones()、 tf.random_normal ()和 tf.random_uniform ()tf.truncated_normal ()

###引入Tensorflowimport tensorflow as tf###定义变量counter,指定初始值为0counter = tf.Variable(0, name="counter")###定义算子add_one完成加1操作    add_one = tf.add(counter, 1)###加1的结果再赋值给变量counterupdate = tf.assign(counter, add_one)###定义会话with tf.Session() as sess:###初始化变量,此时才完成对counter赋值为0的操作sess.run(tf.global_variables_initializer())###循环执行3次counter加1并再赋值给变量counter的运算并每次都打印结果for _ in range(3):     sess.run(update)     print(sess.run(counter))###关闭会话sess.close()

以上代码的运行结果为:

123

在运行时指定张量对象的变量叫占位符(Place holder)。在调用Session.run()时,需要由feed_dict来给占位符传入Tensor对象。

###引入Tensorflow和Numpyimport tensorflow as tfimport numpy as np###定义张量x为3*1阶矩阵x = tf.placeholder(dtype=np.float32, shape=[3, 1])###定义张量y为1*3阶矩阵y = tf.placeholder(dtype=np.float32, shape=[1, 3])###定义张量z为矩阵x乘以矩阵yz = tf.matmul(x, y)###定义会话with tf.Session() as sess:###在运算时给x和y传入值,完成运算z_output = sess.run(z, feed_dict={x: [[1],[2],[3]], y:[[1,2,3]]})###打印运算结果print(z_output)###关闭会话sess.close()

以上代码的运行结果为:

[ [1.  2.  3.]  [ 2.  4.  6.]  [ 3.  6.  9.]]

9.4 TensorFlow实现数据流程图

如数据流图的部分介绍,如果把边当作张量把结点当作算子,那么数据流图就Tensorflow中把张量,算子,变量等元素定义成链接在一起的函数运算。以下代码就是用Tensorflow定义了图9.3的数据流程图,完成了对应的运算。算子的name属性可以帮助标识

###引入Tensorflowimport tensorflow as tf###定义算子a=tf.constant(2)b=tf.constant(4)c=tf.multiply(a,b)d=tf.add(a,b)e=tf.add(c,d)###定义会话sess=tf.Session()#会话调用运算output=sess.run(e)#打印运算结果print(output)###关闭会话sess.close()

以上代码运行结果为:

14

9.5可视化数据流程图

会话会默认初始化一个图对象,可以通过tf.get_default_graph()引用,用户也可以自定义新的Graph (tf.Graph())。如果给算子定义名称,并把生成的数据流程图对象写入log文件,那么最终Tensorboard Sever可以将数据流程图以可视化的形式展现出来。还是以上一小节的实例为基础,代码稍作修改就可以实现数据流程图的可视化。

###引入Tensorflowimport tensorflow as tf###定义算子及算子名称a=tf.constant(2,name="input_a")b=tf.constant(4,name="input_b")c=tf.multiply(a,b,name="mul_c")d=tf.add(a,b,name="add_d")e=tf.add(c,d,name="add_e")sess=tf.Session()output=sess.run(e)print(output)###将数据流程图写入log文件writer=tf.summary.FileWriter('home/feigu/tmp',sess.graph)writer.close()sess.close()

在执行完以上代码,还需要启动 Tensorboard Server来查看数据流图。启动命令为 Tensorflow –logdir=”folder of log”,以下是实例:

然后在浏览器中进入Torsenboard( http://localhost:6006),点击进入GRAPHS就可以看到以上程序实例的可视化的数据流程图,如下图所示:

9.4 数据流图实例

现实中的模型运算会更复杂,需要对图中的运算进行封装来获得更好的可视化, Tensorflow采用作用域(name space) 来组织运算的封装。

import tensorflow as tfgraph=tf.Graph()with graph.as_default():    in_1=tf.placeholder(tf.float32, shape=[], name="input_a")    in_2=tf.placeholder(tf.float32, shape=[], name="input_b")    const=tf.constant(3, dtype=tf.float32, name="static_value")    with tf.name_scope("Transformation"):        with tf.name_scope("A"):            A_mul=tf.multiply(in_1, const)            A_out=tf.subtract(A_mul, in_1)        with tf.name_scope("B"):            B_mul=tf.multiply(in_2, const)            B_out=tf.subtract(B_mul, in_2)        with tf.name_scope("C"):            C_div=tf.div(A_out, B_out)            C_out=tf.add(C_div, const)        with tf.name_scope("D"):            D_div=tf.div(B_out, A_out)            D_out=tf.add(D_div, const)            out=tf.maximum(C_out, D_out)writer=tf.summary.FileWriter('home/feigu/tmp', graph=graph)writer.close()

执行完以上代码,在Tensorboard Server启动的情况下,在浏览器中进入 http://localhost:6006,点击进入GRAPHS可以看到如下定义的数据流图:

图9.5 数据流图name space实例

9.6 分布式TensorFlow

### 加载图像

### 图像格式

### 把图像转换为TFRecord文件

### 读取文件

### 图像处理实例

机器学习简介

第1章 机器学习简介

大数据、人工智能是目前大家谈论比较多的话题,它们的应用也越来越广泛、与我们的生活关系也越来越密切,影响也越来越深远,其中很多已进入寻常百姓家,如无人机、网约车、自动导航、智能家电、电商推荐、人机对话机器人等等。

大数据是人工智能的基础,而使大数据转变为知识或生产力,离不开机器学习(Machine Learning),可以说机器学习是人工智能的核心,是使机器具有类似人的智能的根本途径。

本章主要介绍机器有关概念、与大数据、人工智能间的关系、机器学习常用架构及算法等,具体如下:

  • 机器学习的定义
  • 大数据与机器学习
  • 机器学习与、人工智能及深度学习
  • 机器学习的基本任务
  • 如何选择合适算法
  • Spark在机器学习方面的优势

1.1机器学习的定义

机器学习是什么?是否有统一或标准定义?目前好像没有,即使在机器学习的专业人士,也好像没有一个被广泛认可的定义。在维基百科上对机器学习有以下几种定义:

“机器学习是一门人工智能的科学,该领域的主要研究对象是人工智能,特别是如何在经验学习中改善具体算法的性能”。

“机器学习是对能通过经验自动改进的计算机算法的研究”。

“机器学习是用数据或以往的经验,以此优化计算机程序的性能标准。”

一种经常引用的英文定义是:A computer program is said to learn from experience (E) with respect to some class of tasks( T) and performance(P) measure , if its performance at tasks in T, as measured by P, improves with experience E。

可以看出机器学习强调三个关键词:算法、经验、性能,其处理过程如下图所示。                

图1.1 机器学习处理流程

图1.1表明机器学习是使数据通过算法构建出模型,然后对模型性能进行评估,评估后的指标,如果达到要求就用这个模型测试新数据,如果达不到要求就要调整算法重新建立模型,再次进行评估,如此循环往复,最终获得满意结果。

1.2大数据与机器学习

我们已进入大数据时代,产生数据的能力空前高涨,如互联网、移动网、物联网、成千上万的传感器、穿戴设备、GPS等等,存储数据、处理数据等能力也得到了几何级数的提升,如Hadoop、Spark技术为我们存储、处理大数据提供有效方法。
数据就是信息、就是依据,其背后隐含了大量不易被我们感官识别的信息、知识、规律等等,如何揭示这些信息、规则、趋势,正成为当下给企业带来高回报的热点。其中数据是重要考量,在某个方面来说,数据比算法重要,数据犹如经验。

1.3 机器学习、人工智能及深度学习

人工智能和机器学习这两个科技术语如今已经广为流传,已成为当下的热词,
然而,他们间有何区别?又有哪些相同或相似的地方?虽然人工智能和机器学习高度相关,但却并不尽相同。
人工智能是计算机科学的一个分支,目的是开发一种拥有智能行为的机器,目前
很多大公司都在努力开发这种机器学习技术。他们都在努力让电脑学会人类的行为模式,
以便推动很多人眼中的下一场技术革命——让机器像人类一样“思考”。
过去10年,机器学习已经为我们带来了无人驾驶汽车、实用的语音识别、有效的网络搜索等等。接下来人工智能将如何改变我们的生活?在哪些领域最先发力?我们拭目以待。
对很多机器学习来说,特征提取不是一件简单的事情。在一些复杂问题上,
要想通过人工的方式设计有效的特征集合,往往要花费很多的时间和精力。

 

图1.2 机器学习与深度学习流程对比

深度学习解决的核心问题之一就是自动地将简单的特征组合成更加复杂的特征,并利用这些组合特征解决问题。深度学习是机器学习的一个分支,它除了可以学习特征和任务之间的关联以外,还能自动从简单特征中提取更加复杂的特征。图1.2 中展示了深度学习和传统机器学习在流程上的差异。如图1.2 所示,深度学习算法可以从数据中学习更加复杂的特征表达,使得最后一步权重学习变得更加简单且有效。

前面我们分别介绍了机器学习、人工智能及深度学习,它们间的关系如何?

图1.3 人工智能、机器学习与深度学习间的关系

人工智能、机器学习和深度学习是非常相关的几个领域。图1.3说明了它们之间大致关系。人工智能是一类非常广泛的问题,机器学习是解决这类问题的一个重要手段,深度学习则是机器学习的一个分支。在很多人工智能问题上,深度学习的方法突破了传统机器学习方法的瓶颈,推动了人工智能领域的快速发展。

1.4 机器学习的基本任务

机器学习基于数据,并以此获取新知识、新技能。它的任务有很多,分类是其基本任务之一,分类就是将新数据划分到合适的类别中。分类一般用于目标特征为类别型,如果目标特征为连续型,我们往往采用回归方法,回归对新进行预测,回归是机器学习中使用非常广泛的方法之一。

分类和回归,都是先根据标签值或目标值建立模型或规则,然后利用这些带有目标值的数据形成的模型或规则,对新数据进行识别或预测。这两种方法都属于监督学习。与监督学习相对是无监督学习,无监督学习不指定目标值或预先无法知道目标值,它可以将把相似或相近的数据划分到相同的组里,聚类就是解决这一类问题的方法之一。

除了监督学习、无监督学习这两种最常见的方法外,还有半监督学习、强化学习等方法,这里我们就不展开了,下图形为这些基本任务间的关系。

 

图1.4  机器学习基本任务

1.5 如何选择合适算法

在讲如何选择机器学习算法之前,我们先简单介绍一下机器常用方法,重点介绍算法核心思想、优缺点及模式图示等方面的内容,具体可参考下图:

当我们接到一个数据分析或挖掘的任务或需求时,如果希望用机器学习来处理,首要任务是根据任务或需求选择合适算法,选择哪种算法较合适?分析的一般步骤为:

图1.5 选择算法的一般步骤

充分了解数据及其特性,有助于我们更有效地选择机器学习算法。采用以上步骤在一定程度上可以缩小算法的选择范围,使我们少走些弯路,但在具体选择哪种算法方面,一般并不存在最好的算法或者可以给出最好结果的算法,在实际做项目的过程中,这个过程往往需要多次尝试,有时还要尝试不同算法。不过先用一种简单熟悉的方法,然后,在这个基础上不断优化,时常能收获意想不到的效果。

1.6机器学习常用术语

为更好说明机器学习中一些常用术语,我们以一个预测天气的示例来说明,通过具体数据来介绍一些概念,可能比单纯定义来得更具体一些。

假如我们有一组天气数据,是来自全世界不同国家和地区的每日天气,内容包括最高温度、最低温度、平均湿度、风速之类的相关数据,例如数据的一部分是这样的:

在这组数据中,我们将称A市、B市、C市、D市等以及温度、湿度等情况的总和称为数据集(data set)。表格中的每一行,也就是某城市和它的对应的情况被称为一个样本(sample/instance)。表格中的每一列,例如最高温度、最低温度,被称为特征(feature/attribute),而每一列中的具体数值,例如36℃ 、28℃,被称为属性值(attribute value)。数据中也可能会有缺失数据(missing data),例如B市的某时刻风速,我们会将它视作缺失数据。

如果我们想预测城市的天气,例如是晴朗还是阴雨天,这些数据是不够的,除了特征以外,我们还需要每个城市的具体天气情况,也就是通常语境下的结果。在机器学习中,它会被称为标签(label),用于标记数据。值得注意的是,数据集中不一定包含标签信息,而这种区别会引起方法上的差别。我们可以给上述示例加上一组标签:


在机器学习中,数据集又可以分为三类:训练集(Training Set )、测试集(Testing Set)和交叉验证集(Cross-Validation Set)。 顾名思义,训练集在机器学习的过程中,用来训练我们模型的部分;而测试集用评估、测试模型泛化能力的部分;交叉验证集它是用来调整模型具体参数的数据集。
根据数据有没有标签,我们可以把机器学习分类为监督学习(Supervised Learning)、无监督学习(Unsupervised Learning)和强化学习(Reinforcement  Learning)。

监督学习是学习给定标签的数据集,比如说有一组气候数据,给出他们的详细资料,将它们作为判断或预测天气是晴天还是阴雨等标签的依据,然后预测明天是否会下雨,就是一种典型的监督学习。监督学习中也有不同的分类,如果我们训练的结果是阴雨、晴天之类离散的类型,则称为分类(Classification),如果只有两种类型的话可以进一步称为二分类(Binary Classification);如果我们训练的结果是下雨的概率为0.87之类连续的数字,则称为回归(Regression)。

无监督学习是学习没有标签的数据集,比如在分析大量语句之后,训练出一个模型将较为接近的词分为一类,而后可以根据一个新的词在句子中的用法(和其他信息)将这个词分入某一类中。其中比较微妙的地方在于,这种问题下使用聚类(Clustering)(方法)所获得的簇(Cluster)(结果),有时候是无法人为地观察出其特征的,但是在得到聚类后,可能会对数据集有新的启发。

强化学习是构建一个系统,在与环境交互的过程中提高系统的性能。环境的当前状态信息中通常包含一个反馈信号,这个反馈值不是一个确定的类标或连续类型的值,而是通过反馈函数产生的对当前系统行为的评价。通过探索性的试错或者借助精心设计的激励系统使得正向反馈最大化。象棋或围棋对弈就是一个常见的强化学习例子。下图为强化学习系统原理示意图:

 

对模型性能的评估,或对其泛化能力的评估,往往是一项重要内容。对分类问题我们通常利用准确率、ROC曲线等指标进行评估;对回归类模型,我们通常均方误差(MSE)、均方根误差(RMSE)等指标来确定模型的精确性。

但值得注意的是,模型并不是误差越小就一定越好,因为如果仅仅基于误差,我们可能会得到一个过拟合(Overfitting)的模型;但是如果不考虑误差,我们可能会得到一个欠拟合(Underfitting)的模型,用图像来说的话大致可以这样理解:

如果模型十分简单,往往会欠拟合,对于训练数据和测试数据的误差都会很大;但如果模型太过于复杂,往往会过拟合,那么训练数据的误差可能相当小,但是测试数据的误差会增大。所以需要“驰张有度”,找到最好的那个平衡点。

如果出现过拟合或欠拟合,有哪些解决方法呢?

1、对于欠拟合,一般可考虑提高数据质量、规范特征、增加新特征或训练数据量等方法;采用交叉验证及网格搜索参数等方法调优超参数(不是通过算法本身学习出来的参数,如迭代步数、树高度、步长等);采用其它算法如集成算法等等。

2、对于过拟合问题,可以考虑引入正则化,正则化指修改算法,使其降低泛化误差(而非降低训练误差);对于维数较大的情况,采用PCA降维也是选项之一。

在模型训练过程中,泛化误差不会随着模型复杂度趋于0,相反它一般呈现U型曲线,但训练误差一般训练复杂度增强而变小,直至趋于0。具体关系我们可以参考下图:

聚类问题的标准一般基于距离:簇内距离(Intra-cluster Distance)和簇间距离(Inter-cluster Distance)。根据常识而言,簇内距离是越小越好,也就是簇内的元素越相似越好;而簇间距离越大越好,也就是说簇间(不同簇)元素越不相同越好。

Theano基础

Theano是Python的一个库,为开源项目,在2008年,由Yoshua Bengio领导的加拿大蒙特利尔理工学院LISA实验室开发。对于解决大量数据的问题,使用Theano可能获得与手工用C实现差不多的性能。另外通过利用GPU,它能获得比CPU上的快很多数量级的性能。
Theano把计算机代数系统(CAS)和优化的编译器结合在一起。 它也可以对许多数学操作生成自定义的C代码。这种CAS和优化编译的组合对于有复杂数学表达式重复的被求值并且求值速度很关键的问题是非常有用的。对于许多不同的表达式只求值一次的场景,Theano也能最小化编译/分析的次数,但是仍然可以提供诸如自动差分这样的符号计算的特性。
Theano主要特点:
第一个特点:
使用“符号计算图”来描述模型表达式的开源架构,当前很多优秀的开源工具库或深度学习框架,如TensorFlow、Keras等,都借鉴了Theano的设计风格及其底层设计,因此,了解Theano的特点,尤其是符合图的机制对我们学好其它开源工具非常有帮助。
第二个特点:Theano针对多维数组(或张量),能够高效实现、编译和评估数学表达式,它同时还可以使得代码在GPU上执行。它没有专门提供深度学习相关的API,因此,用户构建模型时需要从最基本的网络层开始构建。
1、符号变量
Theano的变量类型称为符号变量,用TensorVariable表示,又称为张量,它是Theano表达式和运算操作的基本单位。创建符号变量的方式有如下几种:
 使用内置的变量类型创建
目前Theano支持7种内置的变量类型,分别是标量(scalar)、向量(vector)、行(row)、列(col)、矩阵(matrix)、tensor3、tensor4等。其中标量是0阶张量,向量为1阶张量,矩阵为二阶张量等,以下为创建内置变量的实例:

import theano
from theano import tensor as T
data=T.vector(name='data',dtype='float64')

其中,
name指定变量名字
dtype指变量的数据类型。
以下我们通过Theano的tensor模块中scalar来计算一维数据样本点x的输入z,权重假设为w,偏移量为b,即它们间的关系为:
z=w*x+b
实现这个表达式的代码如下:

import theano
from theano import tensor as T
#初始化
x=T.scalar()
w=T.scalar()
b=T.scalar()

z=w*x+b
#编译
net_input=theano.function(inputs=[w,x,b],outputs=z)
#执行
print('net input: %.2f' %net_input(3.0,2.0,0.1))

运算结果为:net input: 6.10
 自定义变量类型
内置的变量类型只能处理4维及以下的变量,如果需要处理更高维的数据时,我们可以使用Theano的自定义变量类型,具体通过TensorType方法来实现:

import theano
from theano import tensor as T

mytype=T.TensorType('float64',broadcastable=(),name=None,sparse_grad=False)

其中broadcastable是True或False的布尔类型元组,元组的大小等于变量的维度,如果为True,表示变量在对应维度上的数据可以进行广播,否则数据不能广播。
广播机制(broadcast)是一种重要机制,有了这种机制,就可以方便对不同维的张量进行运算,否则,就要手工把低维数据变成高维,利用广播机制系统自动利用复制等方法把低维数据补齐,numpy也有这种机制。以下我们通过一个实例来说明广播机制原理:

上图矩阵与向量相加的具体代码如下:

import theano
import numpy as np
import theano.tensor as T
r = T.row()
r.broadcastable
# (True, False)

mtr = T.matrix()
mtr.broadcastable
# (False, False)

f_row = theano.function([r, mtr], [r + mtr])
R = np.arange(1,3).reshape(1,2)
R
#array([[1, 2]])

M = numpy.arange(1,7).reshape(3, 2)
M
#array([[1, 2],
# [3, 4],
# [5, 6]])

f_row(R, M)
#[array([[ 2., 4.],
# [ 4., 6.],
# [ 6., 8.]])]

 将Python类型变量或者Numpy类型变量转化为Theano共享变量
共享变量是Theano实现变量更新的重要机制,后面我们会详细讲解。要创建一个共享变量,只要把一个Python对象或Numpy对象传递给shared函数即可。

import theano
import numpy as np
import theano.tensor as T

data=np.array([[1,2],[3,4]])
shared_data=theano.shared(data)
type(shared_data)

2.符号计算图模型
要定义一个符号表达式,首先创建表达式所需的变量,然后通过操作符(op)把这些变量结合在一起。
Theano处理符号表达式时通过把符号表达式转换为一个计算图(graph)来处理(TensorFlow也使用了这种方法,等到我们介绍TensorFlow时,大家可对比一下),符号计算图的节点有:variable、type、apply和op
variable节点:即符号的变量节点,符号变量是符号表达式存放信息的数据结构,可以分为输入符号和输出符号。
type节点:当定义了一种具体的变量类型以及变量的数据类型时,Theano为其指定数据存储的限制条件。
apply节点:把某一种类型的符号操作符应用到具体的符号变量中,与variable不同,apply节点无须由用户指定,一个apply节点包括3个字段:op、inputs、outputs。
op节点:即操作符节点,定义了一种符号变量间的运算,如+、-、sum()、tanh()等。
Theano是将符号表达式的计算表示成graphs。这些graphs是由将Apply 和 Variable节点内连接而组成的,它们是分别与函数的应用和数据相连接的。 操作是由 Op 实例表示,而数据类型是由 Type 实例表示。下面有一段代码和一个图表,该图表用来说明由这些代码所构建的结构。借助这个图或许有助于您理解如何将这些片拟合到一起:

import theano
import numpy as np
import theano.tensor as T

x = T.dmatrix('x')
y = T.dmatrix('y')
z = x + y

图中箭头表示指向python对象的引用。这里的蓝色盒子是一个 Apply 节点。红色盒子是 Variable 节点。绿色圆圈是Ops。紫色盒子是 Types。
在创建 Variables 之后,对它们应用 Apply Ops 从而得到更多的变量,并得到一个二分、有向、无环图。变量指向 Apply 节点的过程是用来表示函数通过它们的owner 域来生成它们 。这些Apply节点是通过它们的inputs和outputs域来得到它们的输入和输出变量的。
x 和 y 的owner 域的指向都是None是因为它们不是另一个计算的结果。如果它们中的一个是另一个计算的结果,那么owner域将会指向另一个的蓝色盒。
3.函数
函数是Theano的一个核心设计模块,我们了解了Theano如何把一个符号表达式转化为符号计算图,函数的功能则是提供一个接口,把函数计算图编译为可调用的函数对象。
3.1 Theano的编程风格
在theano中,我们一般是先声明自变量x(不需要赋值),然后编写函数方程结束后;最后在为自变量赋值,计算出函数的输出值y。如下示例:

import theano

#声明一个int类型的变量x
x=theano.tensor.iscalar('x')
#定义y=x^3
y=theano.tensor.pow(x,3)
#定义函数的自变量为x(输入),因变量为y(输出)
f=theano.function([x],y)
#计算当x=2的时候,函数f(x)的值
print (f(2))
#计算当x=4时,函数f(x)=x^3的值
print (f(4))
8
64

一旦我们定义了f(x)=x^3,这个时候,我们就可以输入我们想要的x值,然后计算出x的三次方。

3.2 函数的定义
Theano函数定义语法为:
theano.function(inputs, outputs, mode=None, updates=None, givens=None, no_default_updates=False, accept_inplace=False, name=None,rebuild_strict=True, allow_input_downcast=None, profile=None, on_unused_input='raise')。
参数看起来很多,但常用的一般只用到三个,inputs表示自变量、outputs表示函数的因变量(也就是函数的返回值),还有另外一个比较常用的是updates这个参数,这个一般用于神经网络共享变量参数更新,通常以字典或元组列表的形式指定;givens是一个字典或元组列表,记为[(var1,var2)],表示在每一次函数调用时,在符号计算图中,把符号变量var1节点替换为var2节点,该参数常用来指定训练数据集的batch大小。
我们看一个有多个自变量、同时又有多个因变量的函数定义例子:

import theano
x, y =theano.tensor.fscalars('x', 'y')
z1= x + y
z2=x*y
f =theano.function([x,y],[z1,z2])#定义x、y为自变量,z1、z2为函数返回值(因变量)
print(f(2,3))#返回当x=2,y=3的时候,函数f的因变量z1,z2的值
[array(5.0, dtype=float32), array(6.0, dtype=float32)]

3.3 重要函数
Theano有个很好用的函数,就是求函数的偏导数theano.grad(),比如下面S函数:

我们要求当x=3的时候,s函数的导数,代码如下:

import theano
x =theano.tensor.fscalar('x')#定义一个float类型的变量x
y= 1 / (1 + theano.tensor.exp(-x))#定义变量y
dx=theano.grad(y,x)#偏导数函数
f= theano.function([x],dx)#定义函数f,输入为x,输出为s函数的偏导数
print(f(3))#计算当x=3的时候,函数y的偏导数
0.045176658779382706

3.4 更新共享变量参数
在theano.function函数中,有个非常重要的参数updates,updates是一个包含两个元素的列表或tuple,updates=[old_w,new_w],当函数被调用的时候,这个会用new_w替换old_w,具体看下面这个例子

import theano
w= theano.shared(1)#定义一个共享变量w,其初始值为1
x=theano.tensor.iscalar('x')
f=theano.function([x], w, updates=[[w, w+x]])#定义函数自变量为x,因变量为w,当函数执行完毕后,更新参数w=w+x
print(f(3))#函数输出为w
print(w.get_value())#这个时候可以看到w=w+x为4
1
4

在求梯度下降的时候,经常用到updates这个参数。比如updates=[w,w-α*(dT/dw)],其中dT/dw就是我们梯度下降的时候,损失函数对参数w的偏导数,α是学习率。为便于大家的更全面的了解Theano函数一些使用,下面我们通过一个逻辑回归的完整实例来说明:

import numpy as np
import theano
import theano.tensor as T
rng = np.random

# 我们为了测试,自己生成10个样本,每个样本是3维的向量,然后用于训练
N = 10
feats = 3
D = (rng.randn(N, feats).astype(np.float32), rng.randint(size=N, low=0, high=2).astype(np.float32))

# 声明自变量x、以及每个样本对应的标签y(训练标签)
x = T.matrix("x")
y = T.vector("y")

#随机初始化参数w、b=0,为共享变量
w = theano.shared(rng.randn(feats), name="w")
b = theano.shared(0., name="b")

#构造损失函数
p_1 = 1 / (1 + T.exp(-T.dot(x, w) - b)) # s激活函数
xent = -y * T.log(p_1) - (1-y) * T.log(1-p_1) # 交叉商损失函数
cost = xent.mean() + 0.01 * (w ** 2).sum()# 损失函数的平均值+L2正则项以防过拟合,其中权重衰减系数为0.01
gw, gb = T.grad(cost, [w, b]) #对总损失函数求参数的偏导数

prediction = p_1 > 0.5 # 大于0.5预测值为1,否则为0.

train = theano.function(inputs=[x,y],outputs=[prediction, xent],updates=((w, w - 0.1 * gw), (b, b - 0.1 * gb)))#训练所需函数
predict = theano.function(inputs=[x], outputs=prediction)#测试阶段函数

#训练
training_steps = 1000
for i in range(training_steps):
pred, err = train(D[0], D[1])
print (err.mean())#查看损失函数下降变化过程

4、共享变量
共享变量是实现机器学习算法参数更新的重要机制。对于一般的符号变量,没有赋予初始值。在编写深度学习程序时,需要对权重参数进行初始化,此时需要带有初始值的符号变量,这种带有初始值的符号变量称为共享变量,共享变量的初始值由Numpy数据指定。
创建共享变量有两种模型:深拷贝和浅拷贝。深拷贝复制Numpy数组,浅拷贝只是复制Numpy的指针,后续对Numpy的修改将影响浅拷贝的共享变量值。这两种模式通过borrow参数来设置,默认borrow=False(即深拷贝),下面我们通过一个实例来说明:

import numpy as np
import theano
np_array=np.ones(2,dtype='float32')
s_default=theano.shared(np_array)
s_f=theano.shared(np_array,borrow=False)
s_t=theano.shared(np_array,borrow=True)
np_array的值为:array([ 1., 1.], dtype=float32)

下面我们修改np_array的值,查看对不同模式的共享变量影响。

np_array+=2
np_array
# array([ 3., 3.], dtype=float32)
s_default.get_value()
# array([ 1., 1.], dtype=float32)
s_f. get_value()
# array([ 1., 1.], dtype=float32)
s_t. get_value()
# array([ 3., 3.], dtype=float32)

通过以上代码运行结果可以看到,修改numpy的np_array值,对深拷贝的共享变量s_default,s_f变量没有影响,对s_t共享变量有影响。
5、配置Theano
现在maeOS、Linux或windows,大都使用64位内存寻址方式。Numpy和Theano默认情况下,也都使用双精度浮点格式(float64),但是,如果想要使用GPU加速计算,一般依赖32位的内存寻址方式,这是目前Theano唯一支持的计算框架。CPU在32位和64位都可以。
修改Theano的配置,可以通过以下两种方法,优先级从高到底:
(1)通过设置THEANO_FLAGS环境变量;
(2)通过.theanorc文件来设置。
5.1通过THEANO_FLAGS配置
通过设置THEANO_FLAGS环境变量来修改Theano的配置,这种方式可以是全局的,也可以是针对某个脚本文件。THEANO_FLAGS以字符串的形式显示,字符串以逗号分隔,如下所示:
THEANO_FLAGS='floatX=float32,device=gpu0'
具体修改可以直接修改环境变量或在命令行下修改或在脚本中修改。在脚本中修改时,需要在导入theano之前修改,否则可能导致修改无效。如下示例:

import os
os.environ["THEANO_FLAGS"]= 'floatX=float32,device=cpu'
import theano
print(theano.config.floatX)
float64

5.2通过.theanorc文件配置
.theanorc文件一般在$HOME目录下,编辑这个文件添加如下内容即可:

[global]
device=cpu
floatX=float64

数据可视化

1、 数据可视化
无论是大数据、还是小数据、也不管通过统计还是挖掘或机器学习,人们最终想看到的数据,越直观越好,所以这个就涉及到一个数据的可视化问题,而python或pandas的数据可视化功能很强大,可画的种类多,也非常便捷,这是一般数据库软件和开发工具目前所欠缺的。以下我们通过两个实例来说明利用python的matplotlib或pandas实现数据的可视化。
下例利用matplotlib实现数据的可视化

In [1]: %paste
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif']=['SimHei'] ###显示中文
plt.rcParams['axes.unicode_minus']=False ##防止坐标轴上的-号变为方块
x = np.linspace(0, 10, 100)
y = np.sin(x)
y1 = np.cos(x)
##绘制一个图,长为10,宽为6(默认值是每个单位80像素)
plt.figure(figsize=(10,6))
###在图列中自动显示$间内容
plt.plot(x,y,label="$sin(x)$",color="red",linewidth=2)
plt.plot(x,y1,"b--",label="$cos(x^2)$") ###b(blue),--线形
plt.xlabel(u"X值") ##X坐标名称,u表示unicode编码
plt.ylabel(u"Y值")
plt.title(u"三角函数图像") ##t图名称
plt.ylim(-1.2,1.2) ##y上的max、min值
plt.legend() ##显示图例
plt.savefig('fig01.png') ##保持到当前目录
plt.show()

以下是运行结果:


(图 1-1 matplotlib数据可视化)

2、数据地图

下例通过matplotlib及mpl_toolkits.basemap实现地图数据的可视化。

# -*- coding: utf-8 -*-

from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif']=['SimHei']
#============================================# read data
names = []
pops = []
lats = []
lons = []
countries = []
for line in file("/home/hadoop/data/bigdata_map/china_city_jobs_stat.csv"):
info = line.split(',')
names.append(info[0])
pops.append(float(info[1]))
lat = float(info[2][:-1])
if info[2][-1] == 'S': lat = -lat
lats.append(lat)
lon = float(info[3][:-1])
if info[3][-1] == 'W': lon = -lon + 360.0
lons.append(lon)
country = info[4]
countries.append(country)

#============================================
#lat0 = 35;lon0 = 120;change = 25;
lat0 = 30;lon0 = 120;change = 26;
lllat=lat0-change; urlat=lat0+change;
lllon=lon0-change; urlon=lon0+change;

map = Basemap(ax=None,projection='stere',lon_0=(urlon + lllon) / 2,lat_0=(urlat + lllat) / 2,llcrnrlat=lllat, urcrnrlat=urlat,llcrnrlon=lllon,urcrnrlon=urlon,resolution='f')
# draw coastlines, country boundaries, fill continents.
map.drawcoastlines(linewidth=0.25)
map.drawcountries(linewidth=0.25)
# draw the edge of the map projection region (the projection limb)
map.drawmapboundary(fill_color='#689CD2')
# draw lat/lon grid lines every 30 degrees.
#map.drawmeridians(np.arange(0,360,30))
#map.drawparallels(np.arange(-90,90,30))
# Fill continent wit a different color
map.fillcontinents(color='green',lake_color='#689CD2',zorder=0)
# compute native map projection coordinates of lat/lon grid.
shapefilepath = '/home/hadoop/data/bigdata_map/map/map'
map.readshapefile(shapefilepath,'city') #添加街道数据
x, y = map(lons, lats)

max_pop = max(pops)
# Plot each city in a loop.
# Set some parameters
size_factor = 80.0
y_offset = 15.0
rotation = 30
for i,j,k,name in zip(x,y,pops,names):
size = size_factor*k/max_pop
cs = map.scatter(i,j,s=size,marker='o',color='yellow')
plt.text(i,j+y_offset,name,rotation=rotation,fontsize=1)

plt.title(u'中国大数据主要城市需求分布图(2017-03-17)')
plt.show()

运行结果如下图:


(图1-2中国大数据需求分别图)

【几点说明】
为了在图形中显示中文,做了如下处理:
(1)、把simhei.ttf文件上传到如下目录:
~/ anaconda2/lib/python2.7/site-packages/matplotlib/mpl-data/fonts/ttf
(2)、删除~/.cache/matplotlib下的缓存文件 ##删除matplotlib使用缺省字体
(3)、然后配置:

plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False ##防止坐标轴上负号(-)变为小正方形。

3、下例为使用pandas的plot函数画图非常简便。

df=DataFrame(np.random.randn(10,4).cumsum(0),columns=['A','B','C','D'], index=np.arange(0,100,10))
df.plot()

(图1-3使用pandas的plot函数画图)

数据分析

第1章 pandas基础

1.1 pandas简介

“Pandas经过几个版本的更新,目前已经成为数据清洗、处理和分析的不二选择。”
前面我们介绍了NumPy,它提供了数据处理功能,但需要写很多命令,是否有更加便捷、直观、有效的方法?Pandas就是为此而诞生的。Pandas提供了众多更高级、更直观的数据处理功能,尤其是它的DataFrame数据结构,将给你全新的体验,可以用处理数据库表或电子表格的方式来处理分析数据。
Pandas基于NumPy构建的,它提供的结构或工具,让以NumPy为中心的数据处理、数据分析变得更加简单和高效。
Pandas中两个最常用的对象是Series和DataFrame。使用pandas前,需导入以下内容:

In [1]: import numpy as np
In [2]: from pandas import Series,DataFrame
In [3]: import pandas as pd

1.2 pandas数据结构

Pandas主要采用Series和DataFrame两种数据结构。Series是一种类似一维数据的数据结构,由数据(values)及索引(indexs)组成,而DataFrame是一个表格型的数据结构,它有一组有序列,每列的数据可以为不同类型(NumPy数据组中数据要求为相同类型),它既有行索引,也有列索引。

a1=np.array([1,2,3,4])
a2=np.array([5,6,7,8])
a3=np.array(['a','b','c','d'])
df=pd.DataFrame({'a':a1,'b':a2,'c':a3})
print df

1.3 Series

上章节我们介绍了多维数组(ndarray),当然,它也包括一维数组,Series类似一维数组,为啥还要介绍Series呢?或Series有哪些特点?
Series一个最大特点就是可以使用标签索引,序列及ndarray也有索引,但都是位置索引或整数索引,这种索引有很多局限性,如根据某个有意义标签找对应值?切片时采用类似[2:3]的方法,只能取索引为2这个元素等等,无法精确定位。
Series的标签索引(它位置索引自然保留)使用起来就方便多了,而且定位也更精确,不会产生歧义。举例说明。

In [1]: import numpy as np
In [2]: from pandas import Series,DataFrame
In [3]: import pandas as pd

In [4]: s1=Series([1,3,6,-1,2,8])

In [5]: s1
Out[5]:
0 1
1 3
2 6
3 -1
4 2
5 8
dtype: int64

In [6]: s1.values
Out[6]: array([ 1, 3, 6, -1, 2, 8])

In [7]: s1.index
Out[7]: RangeIndex(start=0, stop=6, step=1)
###创建Series时,自定义索引或称为标签索引
In [8]: s2=Series([1,3,6,-1,2,8],index=['a','c','d','e','b','g'])

In [9]: s2
Out[9]:
a 1
c 3
d 6
e -1
b 2
g 8
dtype: int64

In [10]: s2['a'] ###根据标签索引找对应值
Out[10]: 1
In [11]: s2[['a','e']] ###根据标签索引找对应值
Out[11]:
a 1
e -1
dtype: int64

当然,Series除了标签索引外,还有其它很多优点,如运算的简洁:

In [15]: s2[s2>1]
Out[15]:
c 3
d 6
b 2
g 8
dtype: int64

In [16]: s2*10
Out[16]:
a 10
c 30
d 60
e -10
b 20
g 80
dtype: int64

1.4 DataFrame

DataFrame除了索引有位置索引也有标签索引,而且其数据组织方式与MySQL的表极为相似,除了形式相似,很多操作也类似,这就给我们操作DataFrame带来极大方便。这些是DataFrame特色的一小部分,它还有比数据库表更强大的功能,如强大统计、可视化等等。
DataFrame几要素:index、columns、values等,columns就像数据库表的列表,index是索引,当然values就是值了。

In [18]:
####自动生成一个3行4列的DataFrame,并定义其索引(如果不指定,缺省为整数索引)####及列名
d1=DataFrame(np.arange(12).reshape((3,4)),index=['a','b','c'],columns=['a1','a2','a3','a4'])

In [19]: d1
Out[19]:
a1 a2 a3 a4
a 0 1 2 3
b 4 5 6 7
c 8 9 10 11

In [20]: d1.index ##显示索引
Out[20]: Index([u'a', u'b', u'c'], dtype='object')

In [21]: d1.columns ##显示列名
Out[21]: Index([u'a1', u'a2', u'a3', u'a4'], dtype='object')

In [22]: d1.values ##显示值
Out[22]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])

1.4.1 生成DataFrame

生成DataFrame有很多,比较常用的有导入等长列表、字典、numpy数组、数据文件等。

In [33]: data={'name':['zhanghua','liuting','gaofei','hedong'],'age':[40,45,50,46],'addr':['jianxi','pudong','beijing','xian']}

In [34]: d2=DataFrame(data)

In [35]: d2
Out[35]:
addr age name
0 jianxi 40 zhanghua
1 pudong 45 liuting
2 beijing 50 gaofei
3 xian 46 hedong

In [36]: d3=DataFrame(data,columns=['name','age','addr'],index=['a','b','c','d'])

In [37]: d3
Out[37]:
name age addr
a zhanghua 40 jianxi
b liuting 45 pudong
c gaofei 50 beijing
d hedong 46 xian

1.4.2 获取数据

获取DataFrame结构中数据可以采用obj[]操作、查询、obj.ix[]等命令。

In [8]: data={'name':['zhanghua','liuting','gaofei','hedong'],'age':[40,45,50,46],'addr':['jianxi','pudong','beijing','xian']}
###把字典数据转换为DataFrame,并指定索引
In [9]: d3=DataFrame(data,columns=['name','age','addr'],index=['a','b','c','d'])

In [10]: d3
Out[10]:
name age addr
a zhanghua 40 jianxi
b liuting 45 pudong
c gaofei 50 beijing
d hedong 46 xian

In [11]: d3[['name','age']] ##选择列
Out[11]:
name age
a zhanghua 40
b liuting 45
c gaofei 50
d hedong 46

In [12]: d3['a':'c'] ##选择行
Out[12]:
name age addr
a zhanghua 40 jianxi
b liuting 45 pudong
c gaofei 50 beijing

In [13]: d3[1:3] ##选择行(利用位置索引)
Out[13]:
name age addr
b liuting 45 pudong
c gaofei 50 beijing
In [14]: d3[d3['age']>40] ###使用过滤条件
Out[14]:
name age addr
b liuting 45 pudong
c gaofei 50 beijing
d hedong 46 xian
obj.ix[indexs,[columns]]可以根据列或索引同时进行过滤,具体请看下例:
In [16]: d3.ix[['a','c'],['name','age']]
Out[16]:
name age
a zhanghua 40
c gaofei 50

In [17]: d3.ix['a':'c',['name','age']]
Out[17]:
name age
a zhanghua 40
b liuting 45
c gaofei 50

In [18]: d3.ix[0:3,['name','age']]
Out[18]:
name age
a zhanghua 40
b liuting 45
c gaofei 50

1.4.3 修改数据

我们可以像操作数据库表一样操作DataFrame,删除数据,插入数据、修改字段名、索引名、修改数据等,以下通过一些实例来说明。

In [9]: data={'name':['zhanghua','liuting','gaofei','hedong'],'age':[40,45,50,46],'addr':['jianxi','pudong','beijing','xian']}

In [10]: d3=DataFrame(data,columns=['name','age','addr'],index=['a','b','c','d'])

In [11]: d3
Out[11]:
name age addr
a zhanghua 40 jianxi
b liuting 45 pudong
c gaofei 50 beijing
d hedong 46 xian

In [12]: d3.drop('d',axis=0) ###删除行,如果欲删除列,使axis=1即可
Out[12]:
name age addr
a zhanghua 40 jianxi
b liuting 45 pudong
c gaofei 50 beijing
In [13]: d3 ###从副本中删除,原数据没有被删除
Out[13]:
name age addr
a zhanghua 40 jianxi
b liuting 45 pudong
c gaofei 50 beijing
d hedong 46 xian
###添加一行,注意需要ignore_index=True,否则会报错
In [14]: d3.append({'name':'wangkuan','age':38,'addr':'henan'},ignore_index=True)
Out[14]:
name age addr
0 zhanghua 40 jianxi
1 liuting 45 pudong
2 gaofei 50 beijing
3 hedong 46 xian
4 wangkuan 38 henan

In [15]: d3 ###原数据未变
Out[15]:
name age addr
a zhanghua 40 jianxi
b liuting 45 pudong
c gaofei 50 beijing
d hedong 46 xian
###添加一行,并创建一个新DataFrame
In [16]: d4=d3.append({'name':'wangkuan','age':38,'addr':'henan'},ignore_index=True)

In [17]: d4
Out[17]:
name age addr
0 zhanghua 40 jianxi
1 liuting 45 pudong
2 gaofei 50 beijing
3 hedong 46 xian
4 wangkuan 38 henan

In [18]: d4.index=['a','b','c','d','e'] ###修改d4的索引

In [19]: d4
Out[19]:
name age addr
a zhanghua 40 jianxi
b liuting 45 pudong
c gaofei 50 beijing
d hedong 46 xian
e wangkuan 38 henan
In [20]: d4.ix['e','age']=39 ###修改索引为e列名为age的值

In [21]: d4
Out[21]:
name age addr
a zhanghua 40 jianxi
b liuting 45 pudong
c gaofei 50 beijing
d hedong 46 xian
e wangkuan 39 henan

1.4.4 汇总统计

Pandas有一组常用的统计方法,可以根据不同轴方向进行统计,当然也可按不同的列或行进行统计,非常方便。
常用的统计方法有:


(表1-1 Pandas统计方法)
统计方法 说明
count 统计非NA的数量
describe 统计列的汇总信息
min、max 计算最小值和最大值
sum 求总和
mean 求平均数
var 样本的方差
std 样本的标准差
以下通过实例来说明这些方法的使用

from pandas import DataFrame
import numpy as np
import pandas as pd
inputfile = '/home/hadoop/data/stud_score.csv'
data = pd.read_csv(inputfile)
#其他参数,
###header=None 表示无标题,此时缺省列名为整数;如果设为0,表示第0行为标题
###names,encoding,skiprows等
#读取excel文件,可用 read_excel
In [7]: df=DataFrame(data)

In [8]: df.head(3) ###显示前3行
Out[8]:
stud_code sub_code sub_nmae sub_tech sub_score stat_date
0 2.015101e+09 10101.0 数学分析 NaN 90.0 NaN
1 2.015101e+09 10102.0 高等代数 NaN 88.0 NaN
2 2.015101e+09 10103.0 大学物理 NaN 67.0 NaN

In [9]: df.count()
Out[9]:
stud_code 121
sub_code 121
sub_nmae 121
sub_tech 0
sub_score 121
stat_date 0
dtype: int64

In [10]: df['sub_score'].describe() ##汇总学生各科成绩
Out[10]:
count 121.000000
mean 78.561983
std 12.338215
min 48.000000
25% 69.000000
50% 80.000000
75% 89.000000
max 98.000000
Name: sub_score, dtype: float64

In [11]: df['sub_score'].std() ##求学生成绩的标准差
Out[11]: 12.338214729032906

注:DataFrame数据结构的函数或方法有很多,大家可以通过df.[Tab键]方式查看,具体命令的使用方法,如df.count(),可以在Ipython命令行下输入:?df.count() 查看具体使用,退出帮助界面,按q即可。

1.4.5 应用函数及映射

我们知道数据库中有很多函数可用作用于表中元素,DataFrame也可将函数(内置或自定义)应用到各列或行上,而且非常方便和简洁,具体可用通过DataFrame的apply,使或applymap或map,也可以作用到元素级。以下通过实例说明具体使用。

In [23]: d1=DataFrame(np.arange(12).reshape((3,4)),index=['a','b','c'],columns=['a1','a2','a3','a4'])

In [24]: d1
Out[24]:
a1 a2 a3 a4
a 0 1 2 3
b 4 5 6 7
c 8 9 10 11

In [25]: d1.apply(lambda x:x.max()-x.min(),axis=0) ###列级处理
Out[25]:
a1 8
a2 8
a3 8
a4 8
dtype: int64

In [26]: d1.applymap(lambda x:x*2) ###处理每个元素
Out[26]:
a1 a2 a3 a4
a 0 2 4 6
b 8 10 12 14
c 16 18 20 22
In [27]: d1.ix[1]
Out[27]:
a1 4
a2 5
a3 6
a4 7
Name: b, dtype: int64
In [28]: d1.ix[1].map(lambda x:x*2) ###处理每行数据
Out[28]:
a1 8
a2 10
a3 12
a4 14
Name: b, dtype: int64

1.4.6 时间序列

pandas最基本的时间序列类型就是以时间戳(时间点)(通常以python字符串或datetime对象表示)为索引的Series:

dates = ['2017-06-20','2017-06-21','2017-06-22','2017-06-23','2017-06-24']
ts = pd.Series(np.random.randn(5),index = pd.to_datetime(dates))
ts
####ts结果为
2017-06-20 -1.360504
2017-06-21 -0.966608
2017-06-22 0.754748
2017-06-23 0.832451
2017-06-24 -0.307611
dtype: float64
索引为日期的DataFrame数据的索引、选取以及子集构造
ts.index
###显示结果
DatetimeIndex(['2017-06-20', '2017-06-21', '2017-06-22', '2017-06-23',
'2017-06-24'],
dtype='datetime64[ns]', freq=None)

#传入可以被解析成日期的字符串
ts['2017-06-21']
###显示结果
-0.96660788809762366
#传入年或年月
ts['2017-06']
###显示结果
2017-06-20 -1.360504
2017-06-21 -0.966608
2017-06-22 0.754748
2017-06-23 0.832451
2017-06-24 -0.307611
dtype: float64

#时间范围进行切片
ts['2017-06-20':'2017-06-22']
###显示结果
2017-06-20 -1.360504
2017-06-21 -0.966608
2017-06-22 0.754748
dtype: float64