四、模型诊断和调整
在这一章中,我们将了解在构建机器学习(ML)系统时应该意识到和会遇到的不同陷阱。我们还将学习行业标准的高效设计实践来解决这个问题。
在本章中,我们将主要使用来自 UCI 知识库的数据集“Pima Indian diabetes”,它有 768 个记录、8 个属性、2 个类、268 个(34.9%)糖尿病测试的阳性结果和 500 个(65.1%)阴性结果。所有患者均为至少 21 岁的皮马印第安裔女性。
数据集的属性:
-
怀孕次数
-
口服葡萄糖耐量试验中 2 小时的血浆葡萄糖浓度
-
舒张压(毫米汞柱)
-
三头肌皮褶厚度(毫米)
-
2 小时血清胰岛素(微单位/毫升)
-
身体质量指数(体重,单位为千克/(身高,m)²)
-
糖尿病谱系功能
-
年龄(岁)
最佳概率截止点
预测概率是一个介于 0 和 1 之间的数字。传统上,> . 5 是用于将预测概率转换为 1(正)的分界点,否则为 0(负)。当你的训练数据集有一个正例和反例相等的例子时,这个逻辑工作得很好;然而,在真实的场景中却不是这样。
解决方法是找到最优的截断点(即真阳性率高,假阳性率低的点)。任何高于这个阈值的都可以标记为 1,否则为 0。清单 4-1 应该说明了这一点,所以让我们加载数据并检查类分布。
import pandas as pd
import pylab as plt
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn import metrics
# read the data in
df = pd.read_csv("Data/Diabetes.csv")
# target variable % distribution
print (df['class'].value_counts(normalize=True))
#----output----
0 0.651042
1 0.348958
Listing 4-1Load Data and Check the Class Distribution
让我们建立一个快速逻辑回归模型,并检查其准确性(清单 4-2 )。
X = df.ix[:,:8] # independent variables
y = df['class'] # dependent variables
# evaluate the model by splitting into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
# instantiate a logistic regression model, and fit
model = LogisticRegression()
model = model.fit(X_train, y_train)
# predict class labels for the train set. The predict fuction converts probability values > .5 to 1 else 0
y_pred = model.predict(X_train)
# generate class probabilities
# Notice that 2 elements will be returned in probs array,
# 1st element is probability for negative class,
# 2nd element gives probability for positive class
probs = model.predict_proba(X_train)
y_pred_prob = probs[:, 1]
# generate evaluation metrics
print ("Accuracy: ", metrics.accuracy_score(y_train, y_pred))
#----output----
Accuracy: 0.767225325885
Listing 4-2Build a Logistic Regression Model and Evaluate the Performance
最佳截止点是真阳性率(tpr)高而假阳性率(fpr)低,并且 tpr - (1-fpr)为零或接近零。清单 4-3 是绘制 tprvs 的接收器工作特性(ROC)图的示例代码。1-fpr。
# extract false positive, true positive rate
fpr, tpr, thresholds = metrics.roc_curve(y_train, y_pred_prob)
roc_auc = metrics.auc(fpr, tpr)
print("Area under the ROC curve : %f" % roc_auc)
i = np.arange(len(tpr)) # index for df
roc = pd.DataFrame({'fpr' : pd.Series(fpr, index=i),'tpr' : pd.Series(tpr, index = i),'1-fpr' : pd.Series(1-fpr, index = i), 'tf' : pd.Series(tpr - (1-fpr), index = i),'thresholds' : pd.Series(thresholds, index = i)})
roc.ix[(roc.tf-0).abs().argsort()[:1]]
# Plot tpr vs 1-fpr
fig, ax = plt.subplots()
plt.plot(roc['tpr'], label="tpr")
plt.plot(roc['1-fpr'], color = 'red', label='1-fpr')
plt.legend(loc='best')
plt.xlabel('1-False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic')
plt.show()
#----output----
Listing 4-3Find Optimal Cutoff Point
从图表中可以看出,tpr 与 1-fpr 的交叉点是最佳分界点。为了简化寻找最佳概率阈值和实现可重用性,我创建了一个函数来寻找最佳概率截止点(清单 4-4 )。
def Find_Optimal_Cutoff(target, predicted):
""" Find the optimal probability cutoff point for a classification model related to the event rate
Parameters
----------
target: Matrix with dependent or target data, where rows are observations
predicted: Matrix with predicted data, where rows are observations
Returns
-------
list type, with optimal cutoff value
"""
fpr, tpr, threshold = metrics.roc_curve(target, predicted)
i = np.arange(len(tpr))
roc = pd.DataFrame({'tf' : pd.Series(tpr-(1-fpr), index=i), 'threshold' : pd.Series(threshold, index=i)})
roc_t = roc.ix[(roc.tf-0).abs().argsort()[:1]]
return list(roc_t['threshold'])
# Find optimal probability threshold
# Note: probs[:, 1] will have the probability of being a positive label
threshold = Find_Optimal_Cutoff(y_train, probs[:, 1])
print ("Optimal Probability Threshold: ", threshold)
# Applying the threshold to the prediction probability
y_pred_optimal = np.where(y_pred_prob >= threshold, 1, 0)
# Let's compare the accuracy of traditional/normal approach vs optimal cutoff
print ("\nNormal - Accuracy: ", metrics.accuracy_score(y_train, y_pred))
print ("Optimal Cutoff - Accuracy: ", metrics.accuracy_score(y_train, y_pred_optimal))
print ("\nNormal - Confusion Matrix: \n", metrics.confusion_matrix(y_train, y_pred))
print ("Optimal - Cutoff Confusion Matrix: \n", metrics.confusion_matrix(y_train, y_pred_optimal))
#----output----
Optimal Probability Threshold: [0.36133240553264734]
Normal - Accuracy: 0.767225325885
Optimal Cutoff - Accuracy: 0.761638733706
Normal - Confusion Matrix:
[[303 40]
[ 85 109]]
Optimal - Cutoff Confusion Matrix:
[[260 83]
[ 47 147]]
Listing 4-4A Function for Finding Optimal Probability Cutoff
请注意,正常截止方法与最佳截止方法之间的总体准确性没有显著差异;都是 76%。但是,在最佳临界值方法中,真实阳性率增加了 36%(即,您现在能够将 36%以上的阳性病例捕获为阳性)。此外,假阳性(I 型错误)增加了一倍(即,预测个体没有糖尿病为阳性的概率增加了)。
哪个错误代价大?
嗯,这个问题没有一个答案!这取决于领域、您试图解决的问题以及业务需求(图 4-1 )。在我们的皮马糖尿病案例中,相比之下,第二类错误可能比第一类错误更具破坏性,但这是有争议的。
图 4-1
I 型与 II 型误差
罕见事件或不平衡数据集
向分类算法提供正和负实例的相等样本将产生最佳结果。事实证明,高度偏向一个或多个类的数据集是一个挑战。
重采样是解决不平衡数据集问题的常见做法。虽然重采样中有许多技术,但在这里我们将学习三种最流行的技术(图 4-2 ):
图 4-2
不平衡数据集处理技术
-
随机欠采样:减少多数类以匹配少数类计数
-
随机过采样:通过在少数类中随机选取样本来增加少数类,直到两个类的计数匹配
-
合成少数过采样技术(SMOTE) :通过使用特征空间相似性(欧几里德距离)连接所有 k 个(缺省值= 5)少数类最近邻居,通过引入合成例子来增加少数类
让我们使用 sklearn 的 make_classification 函数创建一个样本不平衡数据集(清单 4-5 )。
# Load libraries
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from imblearn.under_sampling import RandomUnderSampler
from imblearn.over_sampling import RandomOverSampler
from imblearn.over_sampling import SMOTE
# Generate the dataset with 2 features to keep it simple
X, y = make_classification(n_samples=5000, n_features=2, n_informative=2,
n_redundant=0, weights=[0.9, 0.1], random_state=2017)
print ("Positive class: ", y.tolist().count(1))
print ("Negative class: ", y.tolist().count(0))
#----output----
Positive class: 514
Negative class: 4486
Listing 4-5Rare Event or Imbalanced Data Handling
让我们将前面描述的三种采样技术应用于数据集,以平衡数据集并进行可视化,以便更好地理解。
# Apply the random under-sampling
rus = RandomUnderSampler()
X_RUS, y_RUS = rus.fit_sample(X, y)
# Apply the random over-sampling
ros = RandomOverSampler()
X_ROS, y_ROS = ros.fit_sample(X, y)
# Apply regular SMOTE
sm = SMOTE(kind='regular')
X_SMOTE, y_SMOTE = sm.fit_sample(X, y)
# Original vs resampled subplots
plt.figure(figsize=(10, 6))
plt.subplot(2,2,1)
plt.scatter(X[y==0,0], X[y==0,1], marker="o", color="blue")
plt.scatter(X[y==1,0], X[y==1,1], marker='+', color="red")
plt.xlabel('x1')
plt.ylabel('x2')
plt.title('Original: 1=%s and 0=%s' %(y.tolist().count(1), y.tolist().count(0)))
plt.subplot(2,2,2)
plt.scatter(X_RUS[y_RUS==0,0], X_RUS[y_RUS==0,1], marker="o", color="blue")
plt.scatter(X_RUS[y_RUS==1,0], X_RUS[y_RUS==1,1], marker='+', color="red")
plt.xlabel('x1')
plt.ylabel('y2')
plt.title('Random Under-sampling: 1=%s and 0=%s' %(y_RUS.tolist().count(1), y_RUS.tolist().count(0)))
plt.subplot(2,2,3)
plt.scatter(X_ROS[y_ROS==0,0], X_ROS[y_ROS==0,1], marker="o", color="blue")
plt.scatter(X_ROS[y_ROS==1,0], X_ROS[y_ROS==1,1], marker='+', color="red")
plt.xlabel('x1')
plt.ylabel('x2')
plt.title('Random over-sampling: 1=%s and 0=%s' %(y_ROS.tolist().count(1), y_ROS.tolist().count(0)))
plt.subplot(2,2,4)
plt.scatter(X_SMOTE[y_SMOTE==0,0], X_SMOTE[y_SMOTE==0,1], marker="o", color="blue")
plt.scatter(X_SMOTE[y_SMOTE==1,0], X_SMOTE[y_SMOTE==1,1], marker='+', color="red")
plt.xlabel('x1')
plt.ylabel('y2')
plt.title('SMOTE: 1=%s and 0=%s' %(y_SMOTE.tolist().count(1), y_SMOTE.tolist().count(0)))
plt.tight_layout()
plt.show()
#----output----
警告
请记住,随机欠采样增加了信息或概念丢失的机会,因为我们正在减少多数类,并且随机过采样& SMOTE 会由于多个相关实例而导致过拟合问题。
哪种重采样技术是最好的?
嗯,这个问题还是没有答案!让我们对前面三个重采样数据尝试一个快速分类模型,并比较其准确性。我们将使用 AUC 指标,因为这是模型性能的最佳表现之一(清单 4-6 )。
from sklearn import tree
from sklearn import metrics
from sklearn.cross_ model_selection import train_test_split
X_RUS_train, X_RUS_test, y_RUS_train, y_RUS_test = train_test_split(X_RUS, y_RUS, test_size=0.3, random_state=2017)
X_ROS_train, X_ROS_test, y_ROS_train, y_ROS_test = train_test_split(X_ROS, y_ROS, test_size=0.3, random_state=2017)
X_SMOTE_train, X_SMOTE_test, y_SMOTE_train, y_SMOTE_test = train_test_split(X_SMOTE, y_SMOTE, test_size=0.3, random_state=2017)
# build a decision tree classifier
clf = tree.DecisionTreeClassifier(random_state=2017)
clf_rus = clf.fit(X_RUS_train, y_RUS_train)
clf_ros = clf.fit(X_ROS_train, y_ROS_train)
clf_smote = clf.fit(X_SMOTE_train, y_SMOTE_train)
# evaluate model performance
print ("\nRUS - Train AUC : ",metrics.roc_auc_score(y_RUS_train, clf.predict(X_RUS_train)))
print ("RUS - Test AUC : ",metrics.roc_auc_score(y_RUS_test, clf.predict(X_RUS_test)))
print ("ROS - Train AUC : ",metrics.roc_auc_score(y_ROS_train, clf.predict(X_ROS_train)))
print ("ROS - Test AUC : ",metrics.roc_auc_score(y_ROS_test, clf.predict(X_ROS_test)))
print ("\nSMOTE - Train AUC : ",metrics.roc_auc_score(y_SMOTE_train, clf.predict(X_SMOTE_train)))
print ("SMOTE - Test AUC : ",metrics.roc_auc_score(y_SMOTE_test, clf.predict(X_SMOTE_test)))
#----output----
RUS - Train AUC : 0.988945248974
RUS - Test AUC : 0.983964646465
ROS - Train AUC : 0.985666951094
ROS - Test AUC : 0.986630288452
SMOTE - Train AUC : 1.0
SMOTE - Test AUC : 0.956132695918
Listing 4-6Build Models on Various Resampling Methods and Evaluate Performance
这里,随机过采样在训练集和测试集上都表现得更好。作为一种最佳实践,在现实世界的用例中,建议查看其他指标(如精确度、召回率、混淆矩阵)并应用业务上下文或领域知识来评估模型的真实性能。
偏差和方差
监督学习的一个基本问题是偏差–
方差权衡。理想情况下,模型应该具有两个关键特征:
-
它应该足够敏感,以准确地捕获训练数据集中的关键模式。
-
它应该足够一般化,以便在任何看不见的数据集上都能很好地工作。
不幸的是,在试图实现上述第一点时,很有可能过度拟合有噪声或不具有代表性的训练数据点,从而导致模型泛化失败。另一方面,试图概括一个模型可能会导致无法捕捉重要的规律性(图 4-3 )。
偏见
如果训练数据集和测试数据集上的模型精度较低,则该模型被称为拟合不足或具有较高的偏差。这意味着模型在回归中没有很好地拟合训练数据集点,或者决策边界在分类中没有很好地分离类。偏差的两个主要原因是 1)没有包括正确的特征,以及 2)没有为模型拟合选择正确的多项式次数。
要解决拟合不足的问题或减少偏差,请尝试包含更有意义的特征,并通过尝试更高阶的多项式拟合来增加模型的复杂性。
变化
如果模型在训练数据集上给出高精度,但是在测试数据集上精度急剧下降,则该模型被称为过度拟合或具有高方差。过度拟合的主要原因是使用更高阶的多项式次数(可能不是必需的),这将使决策边界工具很好地拟合所有数据点,包括训练数据集的噪声,而不是基础关系。这将导致训练数据集中的高准确度(实际与预测),并且当应用于测试数据集时,预测误差将会很高。
要解决过度拟合问题:
-
尝试减少要素的数量,即仅保留有意义的要素,或者尝试保留所有要素但减少要素参数大小的正则化方法。
-
降维可以消除噪声特征,从而降低模型方差。
-
引入更多的数据点来增大训练数据集也将减少方差。
-
例如,选择正确的模型参数有助于减少偏差和方差。
-
使用正确的正则化参数可以减少基于回归的模型中的方差。
-
For a decision tree, reducing the depth of the decision tree will reduce the variance.
图 4-3
偏差-方差权衡
-
k 倍交叉验证
K-fold 交叉验证将训练数据集分成 k 个折叠,而不进行替换-任何给定的数据点都将只是其中一个子集的一部分,其中 k-1 个折叠用于模型训练,一个折叠用于测试。该过程重复 k 次,以便我们获得 k 个模型和性能估计值(图 4-4 )。
然后,我们基于单个折叠计算模型的平均性能,以获得与维持或单个折叠方法相比对训练数据的子划分不太敏感的性能估计。
图 4-4
k 倍交叉验证
清单 4-7 显示了使用 sklearn 的 k-fold 交叉验证来构建分类模型的示例代码。
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
# read the data in
df = pd.read_csv("Data/Diabetes.csv")
X = df.ix[:,:8].values # independent variables
y = df['class'].values # dependent variables
# Normalize Data
sc = StandardScaler()
sc.fit(X)
X = sc.transform(X)
# evaluate the model by splitting into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=2017)
# build a decision tree classifier
clf = tree.DecisionTreeClassifier(random_state=2017)
# evaluate the model using 10-fold cross-validation
train_scores = cross_val_score(clf, X_train, y_train, scoring="accuracy", cv=5)
test_scores = cross_val_score(clf, X_test, y_test, scoring="accuracy", cv=5)
print ("Train Fold AUC Scores: ", train_scores)
print ("Train CV AUC Score: ", train_scores.mean())
print ("\nTest Fold AUC Scores: ", test_scores)
print ("Test CV AUC Score: ", test_scores.mean())
#---output----
Train Fold AUC Scores: [0.80555556 0.73148148 0.81308411 0.76635514 0.71028037]
Train CV AUC Score: 0.7653513326410523
Test Fold AUC Scores: [0.80851064 0.78723404 0.78723404 0.77777778 0.8 ]
Test CV AUC Score: 0.7921513002364066
Listing 4-7
K-fold Cross-Validation
分层 K 倍交叉验证
扩展交叉验证是分层的 k-fold 交叉验证,其中类别比例在每个 fold 中保持不变,从而导致更好的偏差和方差估计(列表 4-8 和 4-9 )。
from sklearn.metrics import roc_curve, auc
from itertools import cycle
from scipy import interp
kfold = model_selection.StratifiedKFold(n_splits=5, random_state=2019)
mean_tpr = 0.0
mean_fpr = np.linspace(0, 1, 100)
colors = cycle(['cyan', 'indigo', 'seagreen', 'yellow', 'blue', 'darkorange'])
lw = 2
i = 0
for (train, test), color in zip(kfold.split(X, y), colors):
probas_ = clf.fit(X[train], y[train]).predict_proba(X[test])
# Compute ROC curve and area the curve
fpr, tpr, thresholds = roc_curve(y[test], probas_[:, 1])
mean_tpr += interp(mean_fpr, fpr, tpr)
mean_tpr[0] = 0.0
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, lw=lw, color=color,
label='ROC fold %d (area = %0.2f)' % (i, roc_auc))
i += 1
plt.plot([0, 1], [0, 1], linestyle="--", lw=lw, color="k",
label='Luck')
mean_tpr /= kfold.get_n_splits(X, y)
mean_tpr[-1] = 1.0
mean_auc = auc(mean_fpr, mean_tpr)
plt.plot(mean_fpr, mean_tpr, color="g", linestyle="--",
label='Mean ROC (area = %0.2f)' % mean_auc, lw=lw)
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()
#----Output----
Listing 4-9Plotting the ROC Curve for Stratified K-fold Cross-Validation
from sklearn import model_selection
kfold = model_selection.StratifiedKFold(n_splits=5, random_state=2019)
train_scores = []
test_scores = []
k = 0
for (train, test) in kfold.split(X_train, y_train):
clf.fit(X_train[train], y_train[train])
train_score = clf.score(X_train[train], y_train[train])
train_scores.append(train_score)
# score for test set
test_score = clf.score(X_train[test], y_train[test])
test_scores.append(test_score)
k += 1
print('Fold: %s, Class dist.: %s, Train Acc: %.3f, Test Acc: %.3f'
% (k, np.bincount(y_train[train]), train_score, test_score))
print('\nTrain CV accuracy: %.3f' % (np.mean(train_scores)))
print('Test CV accuracy: %.3f' % (np.mean(test_scores)))
#----output----
Fold: 1, Class dist.: [277 152], Train Acc: 0.758, Test Acc: 0.806
Fold: 2, Class dist.: [277 152], Train Acc: 0.779, Test Acc: 0.731
Fold: 3, Class dist.: [278 152], Train Acc: 0.767, Test Acc: 0.813
Fold: 4, Class dist.: [278 152], Train Acc: 0.781, Test Acc: 0.766
Fold: 5, Class dist.: [278 152], Train Acc: 0.781, Test Acc: 0.710
Train CV accuracy: 0.773
Test CV accuracy: 0.765
Listing 4-8
Stratified K-fold Cross-Validation
集成方法
集成方法能够将多个模型分数组合成单个分数,以创建稳健的通用模型。
在高层次上,有两种类型的集成方法:
-
组合相似类型的多个模型。
-
引导聚集
-
助推
-
-
组合各种类型的多个模型。
-
投票分类
-
混合或堆叠
-
制袋材料
Bootstrap aggregation(也称为 bagging)由 Leo Breiman 于 1994 年提出;这是一种减少模型方差的模型聚合技术。训练数据被分成多个样本,替换为引导样本。引导样本大小将与原始样本大小相同,原始值的 3/4 和替换导致值的重复(图 4-5 )。
图 4-5
拔靴带
构建每个引导样本的独立模型,并使用回归预测的平均值或分类的多数投票来创建最终模型。
图 4-6 显示了装袋工艺流程。如果 N 是从原始训练集中创建的引导样本的数量,对于 i = 1 到 N,训练一个基本 ML 模型 C i 。
C 最终= y 的累计最大值)
图 4-6
制袋材料
我们来比较一下独立决策树模型和 100 棵树的 bagging 决策树模型(清单 4-10 )的性能。
# Bagged Decision Trees for Classification
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
# read the data in
df = pd.read_csv("Data/Diabetes.csv")
X = df.ix[:,:8].values # independent variables
y = df['class'].values # dependent variables
#Normalize
X = StandardScaler().fit_transform(X)
# evaluate the model by splitting into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2019)
kfold = model_selection.StratifiedKFold(n_splits=5, random_state=2019)
num_trees = 100
# Decision Tree with 5 fold cross validation
clf_DT = DecisionTreeClassifier(random_state=2019).fit(X_train,y_train)
results = model_selection.cross_val_score(clf_DT, X_train,y_train, cv=kfold)
print ("Decision Tree (stand alone) - Train : ", results.mean())
print ("Decision Tree (stand alone) - Test : ", metrics.accuracy_score(clf_DT.predict(X_test), y_test))
# Using Bagging Lets build 100 decision tree models and average/majority vote prediction
clf_DT_Bag = BaggingClassifier(base_estimator=clf_DT, n_estimators=num_trees, random_state=2019).fit(X_train,y_train)
results = model_selection.cross_val_score(clf_DT_Bag, X_train, y_train, cv=kfold)
print ("\nDecision Tree (Bagging) - Train : ", results.mean())
print ("Decision Tree (Bagging) - Test : ", metrics.accuracy_score(clf_DT_Bag.predict(X_test), y_test))
#----output----
Decision Tree (stand alone) - Train : 0.6742199894235854
Decision Tree (stand alone) - Test : 0.6428571428571429
Decision Tree (Bagging) - Train : 0.7460074034902167
Decision Tree (Bagging) - Test : 0.8051948051948052
Listing 4-10Stand-Alone Decision Tree vs. Bagging
特征重要性
决策树模型具有显示重要特征的属性,这些特征基于基尼或熵信息增益(列表 4-11 )。
feature_importance = clf_DT.feature_importances_
# make importances relative to max importance
feature_importance = 100.0 * (feature_importance / feature_importance.max())
sorted_idx = np.argsort(feature_importance)
pos = np.arange(sorted_idx.shape[0]) + .5
plt.subplot(1, 2, 2)
plt.barh(pos, feature_importance[sorted_idx], align="center")
plt.yticks(pos, df.columns[sorted_idx])
plt.xlabel('Relative Importance')
plt.title('Variable Importance')
plt.show()
#----output----
Listing 4-11Decision Tree Feature Importance Function
随机森林
随机选取一个观察子集和一个变量子集来构建多个独立的基于树的模型。这些树更不相关,因为在树的分割过程中只使用了变量的子集,而不是在树的构造中贪婪地选择最佳分割点(清单 4-12 )。
from sklearn.ensemble import RandomForestClassifier
num_trees = 100
kfold = model_selection.StratifiedKFold(n_splits=5, random_state=2019)
clf_RF = RandomForestClassifier(n_estimators=num_trees).fit(X_train, y_train)
results = model_selection.cross_val_score(clf_RF, X_train, y_train, cv=kfold)
print ("\nRandom Forest (Bagging) - Train : ", results.mean())
print ("Random Forest (Bagging) - Test : ", metrics.accuracy_score(clf_RF.predict(X_test), y_test))
#----output----
Random Forest - Train : 0.7379693283976732
Random Forest - Test : 0.8051948051948052
Listing 4-12
RandomForest Classifier
极度随机化的树(ExtraTree)
这种算法是为了给装袋过程引入更多的随机性。树分裂是从每个分裂的样本值范围中完全随机选择的,这允许进一步减少模型的方差,但代价是偏差略有增加(清单 4-13 )。
from sklearn.ensemble import ExtraTreesClassifier
num_trees = 100
kfold = model_selection.StratifiedKFold(n_splits=5, random_state=2019)
clf_ET = ExtraTreesClassifier(n_estimators=num_trees).fit(X_train, y_train)
results = cross_validation.cross_val_score(clf_ET, X_train, y_train, cv=kfold)
print ("\nExtraTree - Train : ", results.mean())
print ("ExtraTree - Test : ", metrics.accuracy_score(clf_ET.predict(X_test), y_test))
#----output----
ExtraTree - Train : 0.7410893707033315
ExtraTree - Test : 0.7987012987012987
Listing 4-13Extremely Randomized Trees (ExtraTree)
决策边界看起来如何?
让我们执行主成分分析,为了便于绘图,只考虑前两个主要成分。模型构建代码将保持不变,除了在规范化之后和分割数据以进行训练和测试之前,我们需要添加以下代码。
一旦我们成功地运行了模型,我们就可以使用下面的代码来绘制独立模型和不同 bagging 模型的决策边界。
from sklearn.decomposition import PCA
from matplotlib.colors import ListedColormap
# PCA
X = PCA(n_components=2).fit_transform(X)
def plot_decision_regions(X, y, classifier):
h = .02 # step size in the mesh
# setup marker generator and color map
markers = ('s', 'x', 'o', '^', 'v')
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
cmap = ListedColormap(colors[:len(np.unique(y))])
# plot the decision surface
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, h),
np.arange(x2_min, x2_max, h))
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
Z = Z.reshape(xx1.shape)
plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
alpha=0.8, c=colors[idx],
marker=markers[idx], label=cl)
# Plot the decision boundary
plt.figure(figsize=(10,6))
plt.subplot(221)
plot_decision_regions(X, y, clf_DT)
plt.title('Decision Tree (Stand alone)')
plt.xlabel('PCA1')
plt.ylabel('PCA2')
plt.subplot(222)
plot_decision_regions(X, y, clf_DT_Bag)
plt.title('Decision Tree (Bagging - 100 trees)')
plt.xlabel('PCA1')
plt.ylabel('PCA2')
plt.legend(loc='best')
plt.subplot(223)
plot_decision_regions(X, y, clf_RF)
plt.title('RandomForest Tree (100 trees)')
plt.xlabel('PCA1')
plt.ylabel('PCA2')
plt.legend(loc='best')
plt.subplot(224)
plot_decision_regions(X, y, clf_ET)
plt.title('Extream Random Tree (100 trees)')
plt.xlabel('PCA1')
plt.ylabel('PCA2')
plt.legend(loc='best')
plt.tight_layout()
#----output----
Decision Tree (stand alone) - Train : 0.5781332628239026
Decision Tree (stand alone) - Test : 0.6688311688311688
Decision Tree (Bagging) - Train : 0.6319936541512428
Decision Tree (Bagging) - Test : 0.7467532467532467
Random Forest - Train : 0.6418297197250132
Random Forest - Test : 0.7662337662337663
ExtraTree - Train : 0.6205446853516658
ExtraTree - Test : 0.7402597402597403
Listing 4-14Plot the Decision Boudaries
bagging—基本调谐参数
让我们看看获得更好模型结果的关键调整参数。
-
n_estimators: 这是树的数量——越大越好。注意,超过某一点,结果不会有明显改善。
-
max_features: 这是用于分割节点的随机特征子集,越低越有利于减少方差(但会增加偏差)。理想情况下,对于回归问题,它应该等于 n_features(要素总数),对于分类,它应该等于 n_features 的平方根。
-
n_jobs: 用于平行建造树的核心数量。如果设置为-1,则使用系统中所有可用的核心,或者您可以指定数量。
助推
Freud 和 Schapire 在 1995 年用著名的 AdaBoost 算法引入了 boosting 的概念(自适应 boosting)。boosting 的核心概念是,与其说是一个独立的个体假设,不如说是将假设按顺序组合起来提高了准确性。本质上,助推算法将弱学习者转化为强学习者。升压算法经过精心设计,可以解决偏差问题(图 4-7 )。
概括来说,AdaBoosting 过程可以分为三个步骤:
图 4-7
AdaBoosting
-
为所有数据点分配统一的权重 W 0 (x) = 1 / N,其中 N 为训练数据点的总数。
-
在每次迭代中,将分类器 y m (x n 拟合到训练数据,并更新权重以最小化加权误差函数。
重量计算为
)。
假设权重或损失函数由
)给出,期限利率由
)给出,其中
)
-
最终模型由
)给出
AdaBoost 的示例图
让我们考虑具有十个数据点的两个类别标签的训练数据。假设,最初,所有数据点将具有由 1/10 给出的相等权重,如图 4-8 所示。
图 4-8
有十个数据点的样本数据集
提升迭代 1
注意在图 4-9 中,正类的三个点被第一个简单分类模型错误分类,因此它们将被赋予更高的权重。误差项和损失函数(学习率)分别计算为 0.30 和 0.42。由于分类错误,数据点 P3、P4 和 P5 将获得更高的权重(0.15),而其他数据点将保留原始权重(0.1)。
图 4-9
Y m1 是第一个分类或假设
推进迭代 2
让我们拟合另一个分类模型,如图 4-10 所示,并注意到负类的三个数据点(P6、P7 和 P8)被错误分类。因此,根据计算,这些点将被赋予更高的权重 0.17,而其余数据点的权重将保持不变,因为它们被正确分类。
图 4-10
Ym2是第二个分类或假设
推进迭代 3
第三分类模型错误分类了总共三个数据点:两个阳性类,P1 和 P2;和一个消极阶层,P9。因此,根据计算,这些错误分类的数据点将被分配一个新的更高的权重 0.19,而其余的数据点将保留其先前的权重(图 4-11 )。
图 4-11
Ym3是第三种分类或假设
最终模型
现在,根据 AdaBoost 算法,让我们结合如图 4-12 所示的弱分类模型。注意,最终的组合模型将具有最小的误差项和最大的学习率,从而导致更高的精确度。
图 4-12
结合弱分类器的 AdaBoost 算法
让我们从 Pima 糖尿病数据集中挑选弱预测器,并比较独立决策树模型与 AdaBoost 在决策树模型上进行 100 轮提升的性能(清单 4-15 )。
# Bagged Decision Trees for Classification
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
# read the data in
df = pd.read_csv("Data/Diabetes.csv")
# Let's use some week features to build the tree
X = df[['age','serum_insulin']] # independent variables
y = df['class'].values # dependent variables
#Normalize
X = StandardScaler().fit_transform(X)
# evaluate the model by splitting into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2019)
kfold = model_selection.StratifiedKFold(n_splits=5, random_state=2019)
num_trees = 100
# Dection Tree with 5 fold cross validation
# lets restrict max_depth to 1 to have more impure leaves
clf_DT = DecisionTreeClassifier(max_depth=1, random_state=2019).fit(X_train,y_train)
results = model_selection.cross_val_score(clf_DT, X_train,y_train, cv=kfold.split(X_train, y_train))
print("Decision Tree (stand alone) - CV Train : %.2f" % results.mean())
print("Decision Tree (stand alone) - Test : %.2f" % metrics.accuracy_score(clf_DT.predict(X_train), y_train))
print("Decision Tree (stand alone) - Test : %.2f" % metrics.accuracy_score(clf_DT.predict(X_test), y_test))
# Using Adaptive Boosting of 100 iteration
clf_DT_Boost = AdaBoostClassifier(base_estimator=clf_DT, n_estimators=num_trees, learning_rate=0.1, random_state=2019).fit(X_train,y_train)
results = model_selection.cross_val_score(clf_DT_Boost, X_train, y_train, cv=kfold.split(X_train, y_train))
print("\nDecision Tree (AdaBoosting) - CV Train : %.2f" % results.mean())
print("Decision Tree (AdaBoosting) - Train : %.2f" % metrics.accuracy_score(clf_DT_Boost.predict(X_train), y_train))
print("Decision Tree (AdaBoosting) - Test : %.2f" % metrics.accuracy_score(clf_DT_Boost.predict(X_test), y_test))
#----output----
Decision Tree (stand alone) - CV Train : 0.64
Decision Tree (stand alone) - Test : 0.64
Decision Tree (stand alone) - Test : 0.70
Decision Tree (AdaBoosting) - CV Train : 0.68
Decision Tree (AdaBoosting) - Train : 0.71
Decision Tree (AdaBoosting) - Test : 0.79
Listing 4-15Stand-Alone Decision Tree vs. AdaBoost
请注意,在这种情况下,与独立的决策树模型相比,AdaBoost 算法在训练/测试数据集之间平均增加了 9%的错误分数。
梯度推进
由于分阶段的可加性,损失函数可以用适合于优化的形式来表示。这就产生了一类被称为广义提升机器(GBM)的广义提升算法。梯度推进是 GBM 的一个示例实现,它可以处理不同的损失函数,如回归、分类、风险建模等。顾名思义,这是一种通过梯度识别弱学习者缺点的 boosting 算法(AdaBoost 使用高权重数据点),因此得名梯度 boosting。
-
迭代地将分类器 y m (x n 拟合到训练数据。初始模型将具有恒定值
)。
-
计算每个模型拟合迭代 g m (x)的损失(即预测值对实际值),或者计算负梯度并使用它来拟合新的基本学习函数 h m (x ),并找到最佳梯度下降步长
)。
-
更新函数估计 ym(x)= ym1(x)+δhm(x)并输出 y m (x)。
清单 4-16 显示了一个梯度提升分类器的示例代码实现。
from sklearn.ensemble import GradientBoostingClassifier
# Using Gradient Boosting of 100 iterations
clf_GBT = GradientBoostingClassifier(n_estimators=num_trees, learning_rate=0.1, random_state=2019).fit(X_train, y_train)
results = model_selection.cross_val_score(clf_GBT, X_train, y_train, cv=kfold)
print ("\nGradient Boosting - CV Train : %.2f" % results.mean())
print ("Gradient Boosting - Train : %.2f" % metrics.accuracy_score(clf_GBT.predict(X_train), y_train))
print ("Gradient Boosting - Test : %.2f" % metrics.accuracy_score(clf_GBT.predict(X_test), y_test))
#----output----
Gradient Boosting - CV Train : 0.66
Gradient Boosting - Train : 0.79
Gradient Boosting - Test : 0.75
Listing 4-16Gradient Boosting Classifier
让我们看看数字分类,以说明模型性能如何随着每次迭代而提高。
from sklearn.ensemble import GradientBoostingClassifier
df= pd.read_csv('Data/digit.csv')
X = df.iloc[:,1:17].values
y = df['lettr'].values
# evaluate the model by splitting into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2019)
kfold = model_selection.StratifiedKFold(n_splits=5, random_state=2019)
num_trees = 10
clf_GBT = GradientBoostingClassifier(n_estimators=num_trees, learning_rate=0.1, random_state=2019).fit(X_train, y_train)
results = model_selection.cross_val_score(clf_GBT, X_train, y_train, cv=kfold)
print ("\nGradient Boosting - Train : %.2f" % metrics.accuracy_score(clf_GBT.predict(X_train), y_train))
print ("Gradient Boosting - Test : %.2f" % metrics.accuracy_score(clf_GBT.predict(X_test), y_test))
# Let's predict for the letter 'T' and understand how the prediction accuracy changes in each boosting iteration
X_valid= (2,8,3,5,1,8,13,0,6,6,10,8,0,8,0,8)
print ("Predicted letter: ", clf_GBT.predict([X_valid]))
# Staged prediction will give the predicted probability for each boosting iteration
stage_preds = list(clf_GBT.staged_predict_proba([X_valid]))
final_preds = clf_GBT.predict_proba([X_valid])
# Plot
x = range(1,27)
label = np.unique(df['lettr'])
plt.figure(figsize=(10,3))
plt.subplot(131)
plt.bar(x, stage_preds[0][0], align="center")
plt.xticks(x, label)
plt.xlabel('Label')
plt.ylabel('Prediction Probability')
plt.title('Round One')
plt.autoscale()
plt.subplot(132)
plt.bar(x, stage_preds[5][0],align='center')
plt.xticks(x, label)
plt.xlabel('Label')
plt.ylabel('Prediction Probability')
plt.title('Round Five')
plt.autoscale()
plt.subplot(133)
plt.bar(x, stage_preds[9][0],align='center')
plt.xticks(x, label)
plt.autoscale()
plt.xlabel('Label')
plt.ylabel('Prediction Probability')
plt.title('Round Ten')
plt.tight_layout()
plt.show()
#----output----
Gradient Boosting - Train : 0.75
Gradient Boosting - Test : 0.72
Predicted letter: 'T'
梯度提升在后续迭代中纠正错误的增强迭代的负面影响。请注意,在第一次迭代中,字母“T”的预测概率为 0.25,到第十次迭代时逐渐增加到 0.76,而其他字母的概率百分比在每一轮中都有所下降。
升压—基本调谐参数
模型复杂性和过拟合可以通过使用两类参数的正确值来控制:
-
树形结构
n_estimators :这是要建立的弱学习器的数量。
max_depth :这是单个估算器的最大深度。最佳值取决于输入变量的相互作用。
min_samples_leaf :这将有助于确保 leaf 中有足够数量的样本结果。
子样本:这是用于拟合单个模型的样本分数(默认值=1)。通常,0.8(80%)用于引入样本的随机选择,这反过来增加了对过拟合的稳健性。
-
正则化参数
learning_rate :控制估值器的变化幅度。学习率越低越好,这就需要更高的 n 估计量(这就是权衡)。
Xgboost(极限梯度提升)
2014 年 3 月,Tianqui Chen 用 C++构建了 xgboost,作为分布式(深度)ML 社区的一部分,它有一个 Python 的接口。它是梯度推进算法的一个扩展的、更加规则的版本。这是表现最出色、大规模、可扩展的 ML 算法之一,在 Kaggle(预测建模和分析竞赛论坛)数据科学竞赛中赢得解决方案方面一直发挥着主要作用。
XGBoost 目标函数 obj(θ)=)
正则项由下式给出
梯度下降技术用于优化目标函数,关于算法的更多数学知识可在 http://xgboost.readthedocs.io/en/latest/
网站找到。
xgboost 算法的一些主要优点是
-
它实现了并行处理。
-
它有一个处理缺失值的内置标准,这意味着用户可以指定一个不同于其他观察值的特定值(如-1 或-999),并将其作为参数传递。
-
它会将树分割到最大深度,这与梯度提升不同,梯度提升在分割中遇到负损失时会停止分割节点。
XGboost 有一组参数,在较高层次上,我们可以将它们分为三类。让我们看看这些类别中最重要的。
-
一般参数
-
nthread :并行线程数;如果没有给定值,将使用所有内核。
-
Booster :这是要运行的模型类型,默认为 gbtree(基于树的模型)。“gblinear”用于线性模型
-
-
增压参数
-
eta :这是防止过拟合的学习率或步长收缩;默认值为 0.3,范围在 0 和 1 之间。
-
max_depth :树的最大深度,默认为 6
-
min_child_weight :一个孩子需要的所有观察的最小权重之和。从事件率的 1/平方根开始。
-
colsample_bytree :对每棵树随机抽样的列的分数,默认值为 1。
-
子样本:每棵树随机抽样的一部分观察值,默认值为 1。降低该值会使算法变得保守,以避免过度拟合。
-
lambda :关于权重的 L2 正则化项,默认值为 1
-
alpha :权重上的 L1 正则项
-
-
任务参数
-
目标:定义要最小化的损失函数,默认值为“reg: linear”对于二进制分类,它应该是“二进制:逻辑”,对于多类,它应该是“多:softprob”以获得概率值,而“多:softmax”以获得预测类。对于多类,将指定 num_class(唯一类的数量)。
-
eval_metric :用于验证模型性能的指标
-
sklearn 有一个 xgboost (XGBClassifier)的包装器。让我们继续使用糖尿病数据集,并使用弱学习者建立一个模型(清单 4-17 )。
import xgboost as xgb
from xgboost.sklearn import XGBClassifier
# read the data in
df = pd.read_csv("Data/Diabetes.csv")
predictors = ['age','serum_insulin']
target = 'class'
# Most common preprocessing step include label encoding and missing value treatment
from sklearn import preprocessing
for f in df.columns:
if df[f].dtype=='object':
lbl = preprocessing.LabelEncoder()
lbl.fit(list(df[f].values))
df[f] = lbl.transform(list(df[f].values))
df.fillna((-999), inplace=True)
# Let's use some week features to build the tree
X = df[['age','serum_insulin']] # independent variables
y = df['class'].values # dependent variables
#Normalize
X = StandardScaler().fit_transform(X)
# evaluate the model by splitting into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2017)
num_rounds = 100
kfold = model_selection.StratifiedKFold(n_splits=5, random_state=2017)
clf_XGB = XGBClassifier(n_estimators = num_rounds,
objective= 'binary:logistic',
seed=2017)
# use early_stopping_rounds to stop the cv when there is no score imporovement
clf_XGB.fit(X_train,y_train, early_stopping_rounds=20, eval_set=[(X_test, y_test)], verbose=False)
results = model_selection.cross_val_score(clf_XGB, X_train,y_train, cv=kfold)
print ("\nxgBoost - CV Train : %.2f" % results.mean())
print ("xgBoost - Train : %.2f" % metrics.accuracy_score(clf_XGB.predict(X_train), y_train))
print ("xgBoost - Test : %.2f" % metrics.accuracy_score(clf_XGB.predict(X_test), y_test))
#----output----
xgBoost - CV Train : 0.69
xgBoost - Train : 0.73
xgBoost - Test : 0.74
Listing 4-17xgboost Classifier Using sklearn Wrapper
现在让我们看看如何使用 xgboost 原生接口构建一个模型。用于输入数据的 xgboostfor 的内部数据结构。将大型数据集转换为 DMatrix 对象以节省预处理时间是一个很好的做法(清单 4-18 )。
xgtrain = xgb.DMatrix(X_train, label=y_train, missing=-999)
xgtest = xgb.DMatrix(X_test, label=y_test, missing=-999)
# set xgboost params
param = {'max_depth': 3, # the maximum depth of each tree
'objective': 'binary:logistic'}
clf_xgb_cv = xgb.cv(param, xgtrain, num_rounds,
stratified=True,
nfold=5,
early_stopping_rounds=20,
seed=2017)
print ("Optimal number of trees/estimators is %i" % clf_xgb_cv.shape[0])
watchlist = [(xgtest,'test'), (xgtrain,'train')]
clf_xgb = xgb.train(param, xgtrain,clf_xgb_cv.shape[0], watchlist)
# predict function will produce the probability
# so we'll use 0.5 cutoff to convert probability to class label
y_train_pred = (clf_xgb.predict(xgtrain, ntree_limit=clf_xgb.best_iteration) > 0.5).astype(int)
y_test_pred = (clf_xgb.predict(xgtest, ntree_limit=clf_xgb.best_iteration) > 0.5).astype(int)
print ("XGB - Train : %.2f" % metrics.accuracy_score(y_train_pred, y_train))
print ("XGB - Test : %.2f" % metrics.accuracy_score(y_test_pred, y_test))
Listing 4-18xgboost Using It’s Native Python Package Code
-输出-
Optimal number of trees (estimators) is 6
[0] test-error:0.344156 train-error:0.299674
[1] test-error:0.324675 train-error:0.273616
[2] test-error:0.272727 train-error:0.281759
[3] test-error:0.266234 train-error:0.278502
[4] test-error:0.266234 train-error:0.273616
[5] test-error:0.311688 train-error:0.254072
XGB - Train : 0.73
XGB - Test : 0.73
集合投票——机器学习最大的英雄联盟
图 4-13
合奏:ML 最大的英雄联盟
投票分类器使我们能够通过来自不同类型的多个 ML 算法的多数投票来组合预测,不像 Bagging/Boosting,其中相似类型的多个分类器用于多数投票。
首先,您可以从训练数据集创建多个独立模型。然后,当要求对新数据进行预测时,可以使用投票分类器来包装您的模型,并对子模型的预测进行平均。子模型的预测可以被加权,但是手动地或者甚至启发式地指定分类器的权重是困难的。更高级的方法可以学习如何对子模型的预测进行最佳加权,但这被称为堆叠(stacked aggregation ),目前 Scikit-learn 中没有提供。
让我们在 Pima 糖尿病数据集上构建单独的模型,并尝试投票分类器,以结合模型结果来比较准确性的变化(清单 4-19 )。
import pandas as pd
import numpy as np
# set seed for reproducability
np.random.seed(2017)
import statsmodels.api as sm
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import GradientBoostingClassifier
# currently its available as part of mlxtend and not sklearn
from mlxtend.classifier import EnsembleVoteClassifier
from sklearn import model_selection
from sklearn import metrics
from sklearn.model_selection import train_test_split
# read the data in
df = pd.read_csv("Data/Diabetes.csv")
X = df.iloc[:,:8] # independent variables
y = df['class'] # dependent variables
# evaluate the model by splitting into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=2017)
LR = LogisticRegression(random_state=2017)
RF = RandomForestClassifier(n_estimators = 100, random_state=2017)
SVM = SVC(random_state=0, probability=True)
KNC = KNeighborsClassifier()
DTC = DecisionTreeClassifier()
ABC = AdaBoostClassifier(n_estimators = 100)
BC = BaggingClassifier(n_estimators = 100)
GBC = GradientBoostingClassifier(n_estimators = 100)
clfs = []
print('5-fold cross validation:\n')
for clf, label in zip([LR, RF, SVM, KNC, DTC, ABC, BC, GBC],
['Logistic Regression',
'Random Forest',
'Support Vector Machine',
'KNeighbors',
'Decision Tree',
'Ada Boost',
'Bagging',
'Gradient Boosting']):
scores = model_selection.cross_val_score(clf, X_train, y_train, cv=5, scoring="accuracy")
print("Train CV Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))
md = clf.fit(X, y)
clfs.append(md)
print("Test Accuracy: %0.2f " % (metrics.accuracy_score(clf.predict(X_test), y_test)))
#----output----
5-fold cross validation:
Train CV Accuracy: 0.76 (+/- 0.03) [Logistic Regression]
Test Accuracy: 0.79
Train CV Accuracy: 0.74 (+/- 0.03) [Random Forest]
Test Accuracy: 1.00
Train CV Accuracy: 0.65 (+/- 0.00) [Support Vector Machine]
Test Accuracy: 1.00
Train CV Accuracy: 0.70 (+/- 0.05) [KNeighbors]
Test Accuracy: 0.84
Train CV Accuracy: 0.69 (+/- 0.02) [Decision Tree]
Test Accuracy: 1.00
Train CV Accuracy: 0.73 (+/- 0.04) [Ada Boost]
Test Accuracy: 0.83
Train CV Accuracy: 0.75 (+/- 0.04) [Bagging]
Test Accuracy: 1.00
Train CV Accuracy: 0.75 (+/- 0.03) [Gradient Boosting]
Test Accuracy: 0.92
Listing 4-19
Ensemble Model
从之前的基准测试中我们可以看出,与其他模型相比,“逻辑回归”、“随机森林”、“Bagging”和 Ada/梯度提升算法具有更好的准确性。让我们结合非相似模型,如逻辑回归(基础模型)、随机森林(bagging 模型)和梯度推进(boosting 模型)来创建一个健壮的通用模型。
硬投票与软投票
多数投票也被称为硬投票。预测概率之和的 argmax 被称为软投票。参数“权重”可用于为分类器分配特定权重。每个分类器的预测分类概率乘以分类器权重并进行平均。然后,从最高平均概率类别标签中导出最终类别标签。
假设我们给所有的分类器分配一个相等的权重 1(表 4-1 )。基于软投票,预测的类别标签是 1,因为它具有最高的平均概率。集合投票模型的示例代码实现参见清单 4-20 。
表 4-1
软投票
注意
Scikit-learn 的一些分类器不支持 predict_proba 方法。
# ### Ensemble Voting
clfs = []
print('5-fold cross validation:\n')
ECH = EnsembleVoteClassifier(clfs=[LR, RF, GBC], voting="hard")
ECS = EnsembleVoteClassifier(clfs=[LR, RF, GBC], voting="soft", weights=[1,1,1])
for clf, label in zip([ECH, ECS],
['Ensemble Hard Voting',
'Ensemble Soft Voting']):
scores = model_selection.cross_val_score(clf, X_train, y_train, cv=5, scoring="accuracy")
print("Train CV Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))
md = clf.fit(X, y)
clfs.append(md)
print("Test Accuracy: %0.2f " % (metrics.accuracy_score(clf.predict(X_test), y_test)))
#----output----
5-fold cross validation:
Train CV Accuracy: 0.75 (+/- 0.02) [Ensemble Hard Voting]
Test Accuracy: 0.93
Train CV Accuracy: 0.76 (+/- 0.02) [Ensemble Soft Voting]
Test Accuracy: 0.95
Listing 4-20Ensemble Voting Model
堆垛
David h . WOL pert(1992 年)在他与 Neural Networks journal 发表的文章中提出了堆叠泛化的概念,通常被称为“堆叠”。在堆叠中,最初在训练/测试数据集上训练不同类型的多个基础模型。理想的情况是混合使用不同的模型(kNN、bagging、boosting 等)。)这样他们就可以了解问题的某一部分。在第 1 级,使用基本模型的预测值作为特征,并训练一个模型,该模型称为元模型。因此,组合单个模型的学习将导致提高的准确性。这是一个简单的一级堆叠,同样,您可以堆叠不同类型的模型的多个级别(图 4-14 )。
图 4-14
简单的二级堆叠模型
让我们应用之前在糖尿病数据集上讨论的堆叠概念,并比较基本模型与元模型的准确性(清单 4-21 )。
# Classifiers
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
import warnings
warnings.filterwarnings("ignore", category=FutureWarning)
seed = 2019
np.random.seed(seed) # seed to shuffle the train set
# read the data in
df = pd.read_csv("Data/Diabetes.csv")
X = df.iloc[:,0:8] # independent variables
y = df['class'].values # dependent variables
#Normalize
X = StandardScaler().fit_transform(X)
# evaluate the model by splitting into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=seed)
kfold = model_selection.StratifiedKFold(n_splits=5, random_state=seed)
num_trees = 10
verbose = True # to print the progress
clfs = [KNeighborsClassifier(),
RandomForestClassifier(n_estimators=num_trees, random_state=seed),
GradientBoostingClassifier(n_estimators=num_trees, random_state=seed)]
# Creating train and test sets for blending
dataset_blend_train = np.zeros((X_train.shape[0], len(clfs)))
dataset_blend_test = np.zeros((X_test.shape[0], len(clfs)))
print('5-fold cross validation:\n')
for i, clf in enumerate(clfs):
scores = model_selection.cross_val_score(clf, X_train, y_train, cv=kfold, scoring="accuracy")
print("##### Base Model %0.0f #####" % i)
print("Train CV Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std()))
clf.fit(X_train, y_train)
print("Train Accuracy: %0.2f " % (metrics.accuracy_score(clf.predict(X_train), y_train)))
dataset_blend_train[:,i] = clf.predict_proba(X_train)[:, 1]
dataset_blend_test[:,i] = clf.predict_proba(X_test)[:, 1]
print("Test Accuracy: %0.2f " % (metrics.accuracy_score(clf.predict(X_test), y_test)))
print ("##### Meta Model #####")
clf = LogisticRegression()
scores = model_selection.cross_val_score(clf, dataset_blend_train, y_train, cv=kfold, scoring="accuracy")
clf.fit(dataset_blend_train, y_train)
print("Train CV Accuracy: %0.2f (+/- %0.2f)" % (scores.mean(), scores.std()))
print("Train Accuracy: %0.2f " % (metrics.accuracy_score(clf.predict(dataset_blend_train), y_train)))
print("Test Accuracy: %0.2f " % (metrics.accuracy_score(clf.predict(dataset_blend_test), y_test)))
#----output----
5-fold cross validation:
##### Base Model 0 #####
Train CV Accuracy: 0.71 (+/- 0.03)
Train Accuracy: 0.83
Test Accuracy: 0.75
##### Base Model 1 #####
Train CV Accuracy: 0.73 (+/- 0.02)
Train Accuracy: 0.98
Test Accuracy: 0.79
##### Base Model 2 #####
Train CV Accuracy: 0.74 (+/- 0.01)
Train Accuracy: 0.80
Test Accuracy: 0.80
##### Meta Model #####
Train CV Accuracy: 0.99 (+/- 0.02)
Train Accuracy: 0.99
Test Accuracy: 0.77
Listing 4-21Model Stacking
超参数调谐
ML 过程中的主要目标和挑战之一是基于数据模式和观察到的证据来提高性能分数。为了实现这一目标,几乎所有的 ML 算法都有一组特定的参数,需要从数据集进行估计,这将使性能得分最大化。假设这些参数是您需要调整到不同值的旋钮,以找到使您获得最佳模型精度的最佳参数组合(图 4-15 )。选择一个好的超参数的最好方法是通过试错所有可能的参数值组合。Scikit-learn 提供 GridSearchCV 和 RandomSearchCV 函数,以促进超参数调整的自动化和可重复方法。
图 4-15
超参数调谐
网格搜索
对于给定的模型,您可以定义一组想要尝试的参数值。然后,使用 Scikit-learn 的 GridSearchCV 功能,为您提供的超参数值预设列表的所有可能组合构建模型,并根据交叉验证分数选择最佳组合。GridSearchCV 有两个缺点:
-
计算量大:很明显,参数值越多,网格搜索的计算量就越大。考虑一个例子,其中有五个参数,假设您想为每个参数尝试五个值,这将导致 5∫5 = 3,125 个组合。进一步乘以所使用的交叉验证折叠数(例如,如果 k-fold 为 5,则 3125÷5 = 15,625 个模型拟合)。
-
不完全最优但接近最优的参数 : GridSearch 将查看您为数字参数提供的固定点,因此很有可能会错过位于固定点之间的最优点。例如,假设您想要尝试决策树模型的‘n _ estimators’:[100,250,500,750,1000]的固定点,并且最优点可能位于两个固定点之间。然而,GridSearch 并不是为了在固定点之间进行搜索而设计的。
让我们在 Pima 糖尿病数据集上为 RandomForest 分类器尝试 GridSearchCV,以找到最佳参数值(清单 4-22 )。
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
seed = 2017
# read the data in
df = pd.read_csv("Data/Diabetes.csv")
X = df.iloc[:,:8].values # independent variables
y = df['class'].values # dependent variables
#Normalize
X = StandardScaler().fit_transform(X)
# evaluate the model by splitting into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=seed)
kfold = model_selection.StratifiedKFold(n_splits=5, random_state=seed)
num_trees = 100
clf_rf = RandomForestClassifier(random_state=seed).fit(X_train, y_train)
rf_params = {
'n_estimators': [100, 250, 500, 750, 1000],
'criterion': ['gini', 'entropy'],
'max_features': [None, 'auto', 'sqrt', 'log2'],
'max_depth': [1, 3, 5, 7, 9]
}
# setting verbose = 10 will print
the progress for every 10 task completion
grid = GridSearchCV(clf_rf, rf_params, scoring="roc_auc", cv=kfold, verbose=10, n_jobs=-1)
grid.fit(X_train, y_train)
print ('Best Parameters: ', grid.best_params_)
results = model_selection.cross_val_score(grid.best_estimator_, X_train,y_train, cv=kfold)
print ("Accuracy - Train CV: ", results.mean())
print ("Accuracy - Train : ", metrics.accuracy_score(grid.best_estimator_.predict(X_train), y_train))
print ("Accuracy - Test : ", metrics.accuracy_score(grid.best_estimator_.predict(X_test), y_test))
#----output----
Fitting 5 folds for each of 200 candidates, totalling 1000 fits
Best Parameters: {'criterion': 'entropy', 'max_depth': 5, 'max_features': 'log2', 'n_estimators': 500}
Accuracy - Train CV: 0.7447905849775008
Accuracy - Train : 0.8621973929236499
Accuracy - Test : 0.7965367965367965
Listing 4-22Grid Search
for Hyperparameter Tuning
随机搜索
顾名思义,RandomSearch 算法尝试给定参数的一系列值的随机组合。数字参数可以指定为一个范围(与 GridSearch 中的固定值不同)。您可以控制想要执行的随机搜索的迭代次数。众所周知,与 GridSearch 相比,可以在更短的时间内找到非常好的组合;但是,您必须仔细选择参数的范围和随机搜索迭代的次数,因为它可能会错过迭代次数较少或范围较小的最佳参数组合。
让我们使用与 GridSearch 相同的组合来尝试 RandomSearchCV,并比较时间/准确性(清单 4-23 )。
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint as sp_randint
# specify parameters and distributions to sample from
param_dist = {'n_estimators':sp_randint(100,1000),
'criterion': ['gini', 'entropy'],
'max_features': [None, 'auto', 'sqrt', 'log2'],
'max_depth': [None, 1, 3, 5, 7, 9]
}
# run randomized search
n_iter_search = 20
random_search = RandomizedSearchCV(clf_rf, param_distributions=param_dist, cv=kfold, n_iter=n_iter_search, verbose=10, n_jobs=-1, random_state=seed)
random_search.fit(X_train, y_train)
# report(random_search.cv_results_)
print ('Best Parameters: ', random_search.best_params_)
results = model_selection.cross_val_score(random_search.best_estimator_, X_train,y_train, cv=kfold)
print ("Accuracy - Train CV: ", results.mean())
print ("Accuracy - Train : ", metrics.accuracy_score(random_search.best_estimator_.predict(X_train), y_train))
print ("Accuracy - Test : ", metrics.accuracy_score(random_search.best_estimator_.predict(X_test), y_test))
#----output----
Fitting 5 folds for each of 20 candidates, totalling 100 fits
Best Parameters: {'criterion': 'entropy', 'max_depth': 3, 'max_features': None, 'n_estimators': 694}
Accuracy - Train CV: 0.7542402215299411
Accuracy - Train : 0.7802607076350093
Accuracy - Test : 0.8051948051948052
Listing 4-23Random Search for Hyperparameter Tuning
请注意,在这种情况下,使用 RandomSearchCV,我们能够以 100 次拟合获得与 GridSearchCV 的 1000 次拟合相当的精度结果。
图 4-16 是两个参数之间网格搜索与随机搜索结果差异的示例说明(不是实际表示)。假设 max_depth 的最佳区域位于 3 和 5 之间(蓝色阴影),n_estimators 的最佳区域位于 500 和 700 之间(琥珀色阴影)。组合参数的理想最佳值将位于各个区域的相交处。这两种方法都能够找到接近最优的参数,而不一定是完美的最佳点。
图 4-16
网格搜索与随机搜索
贝叶斯优化
一个关键的新兴超参数调整技术是贝叶斯优化,使用参数及其相关目标值的观测融合的高斯过程回归。贝叶斯优化的目标是在尽可能少的迭代中找到未知函数的最大值。与网格和随机搜索相比,关键区别在于空间对于每个超参数具有概率分布,而不是离散值。这种技术特别适合于高成本函数的优化,在这种情况下,勘探和开发之间的平衡非常重要。虽然这种技术对连续变量很有效,但是没有直观的方法来处理离散参数。参考清单 4-24 中随机搜索超参数调整的简单代码实现示例。
您可以在 https://github.com/fmfn/BayesianOptimization
了解更多关于包装和示例的信息
# pip install bayesian-optimization
from bayes_opt import BayesianOptimization
from sklearn.model_selection import cross_val_score
from bayes_opt.util import Colours
from sklearn.ensemble import RandomForestClassifier as RFC
def rfc_cv(n_estimators, min_samples_split, max_features, data, targets):
"""Random Forest cross validation.
This function will instantiate a random forest classifier with parameters
n_estimators, min_samples_split, and max_features. Combined with data and
targets this will in turn be used to perform cross-validation. The result of cross validation is returned. Our goal is to find combinations of n_estimators, min_samples_split, and
max_features that minimzes the log loss.
"""
estimator = RFC(
n_estimators=n_estimators,
min_samples_split=min_samples_split,
max_features=max_features,
random_state=2
)
cval = cross_val_score(estimator, data, targets,
scoring='neg_log_loss', cv=4)
return cval.mean()
def optimize_rfc(data, targets):
"""Apply Bayesian Optimization to Random Forest parameters."""
def rfc_crossval(n_estimators, min_samples_split, max_features):
"""Wrapper of RandomForest cross validation.
Notice how we ensure n_estimators and min_samples_split are casted
to integer before we pass them along. Moreover, to avoid max_features
taking values outside the (0, 1) range, we also ensure it is capped
accordingly.
"""
return rfc_cv(
n_estimators=int(n_estimators),
min_samples_split=int(min_samples_split),
max_features=max(min(max_features, 0.999), 1e-3),
data=data,
targets=targets,
)
optimizer = BayesianOptimization(
f=rfc_crossval,
pbounds={
"n_estimators": (10, 250),
"min_samples_split": (2, 25),
"max_features": (0.1, 0.999),
},
random_state=1234,
verbose=2
)
optimizer.maximize(n_iter=10)
print("Final result:", optimizer.max)
return optimizer
print(Colours.green("--- Optimizing Random Forest ---"))
optimize_rfc(X_train, y_train)
#----output----
--- Optimizing Random Forest ---
| iter | target | max_fe... | min_sa... | n_esti... |
-------------------------------------------------------------
| 1 | -0.5112 | 0.2722 | 16.31 | 115.1 |
| 2 | -0.5248 | 0.806 | 19.94 | 75.42 |
| 3 | -0.5075 | 0.3485 | 20.44 | 240.0 |
| 4 | -0.528 | 0.8875 | 10.23 | 130.2 |
| 5 | -0.5098 | 0.7144 | 18.39 | 98.86 |
| 6 | -0.51 | 0.999 | 25.0 | 176.7 |
| 7 | -0.5113 | 0.7731 | 24.94 | 249.8 |
| 8 | -0.5339 | 0.999 | 2.0 | 250.0 |
| 9 | -0.5107 | 0.9023 | 24.96 | 116.2 |
| 10 | -0.8284 | 0.1065 | 2.695 | 10.04 |
| 11 | -0.5235 | 0.1204 | 24.89 | 208.1 |
| 12 | -0.5181 | 0.1906 | 2.004 | 81.15 |
| 13 | -0.5203 | 0.1441 | 2.057 | 185.3 |
| 14 | -0.5257 | 0.1265 | 24.85 | 153.1 |
| 15 | -0.5336 | 0.9906 | 2.301 | 219.3 |
=============================================================
Final result: {'target': -0.5075247858575866, 'params': {'max_features': 0.34854136537364394, 'min_samples_split': 20.443060083305443, 'n_estimators': 239.95344488408924}}
Listing 4-24Random Search for Hyperparameter Tuning
时序物联网数据的降噪
在过去的十年里,技术的软件和硬件方面都有了巨大的增长,这催生了互联世界或物联网(IoT)的概念。这意味着物理设备、日常物品和硬件形式安装有微型传感器,以不同参数的形式持续捕获机器状态,并扩展到互联网连接,以便这些设备能够相互通信/交互,并可以远程监控/控制。预测分析是处理挖掘物联网数据以获得洞察力的 ML 领域。概括来说,有两个关键方面。一个是异常检测,这是一个尽早识别前兆故障特征(异常行为,也称为异常)的过程,以便可以计划必要的措施来避免阻碍故障,例如设备电压或温度的突然升高或降低。另一个是剩余使用寿命(RUL)预测。这是一个从异常检测的角度预测 RUL 或离即将发生的故障有多远的过程。我们可以使用回归模型来预测 RUL。
我们从传感器收集的数据集容易受到噪声的影响,尤其是高带宽测量,如振动或超声信号。傅立叶变换是一种众所周知的技术,它允许我们在频域或频谱域进行分析,以获得对高带宽信号(如振动测量曲线)的更深入了解。傅立叶是一系列正弦波,傅立叶变换实质上是将信号分解成单独的正弦波分量。然而,傅立叶变换的缺点是,如果信号的频谱分量随时间快速变化,它不能提供局部信息。傅立叶变换的主要缺点是,一旦信号从时域变换到频域,所有与时间相关的信息都会丢失。小波变换解决了傅里叶变换的主要缺点,是高带宽信号处理的理想选择。有大量的文献解释小波变换及其应用,所以在本书中你不会得到太多的细节。本质上,小波可以用来将信号分解成一系列系数。前几个系数代表最低频率,后几个系数代表最高频率。通过移除较高频率的系数,然后用截断的系数重构信号,我们可以平滑信号,而不用像移动平均那样平滑所有感兴趣的峰值。
小波变换将时间序列信号分解为两部分,一部分是低频或低通滤波器,用于平滑原始信号近似值,另一部分是高频或高通滤波器,用于产生详细的局部特性,例如异常,如图 4-17 所示。
图 4-17。
小波分解的滤波概念
小波变换的优势之一是通过迭代获得多分辨率来执行多级分解过程的能力,其中近似值依次被连续分解,使得一个信号可以被分解成许多较低分辨率的分量。小波变换函数 f(x)是一个数列,其中称为母小波函数的小波基ψ用于分解。
)
其中, j 0 是被称为近似或缩放系数的任意起始比例。Wψ(j,k)称为细节或小波系数。
import pywt
from statsmodels.robust import mad
import pandas as pd
import numpy as np
df = pd.read_csv('Data/Temperature.csv')
# Function to denoise the sensor data using wavelet transform
def wp_denoise(df):
for column in df:
x = df[column]
wp = pywt.WaveletPacket(data=x, wavelet="db7", mode="symmetric")
new_wp = pywt.WaveletPacket(data=None, wavelet="db7", mode="sym")
for i in range(wp.maxlevel):
nodes = [node.path for node in wp.get_level(i, 'natural')]
# Remove the high and low pass signals
for node in nodes:
sigma = mad(wp[node].data)
uthresh = sigma * np.sqrt( 2*np.log( len( wp[node].data ) ) )
new_wp[node] = pywt.threshold(wp[node].data, value=uthresh, mode="soft")
y = new_wp.reconstruct(update=False)[:len(x)]
df[column] = y
return df
# denoise the sensor data
df_denoised = wp_denoise(df.iloc[:,3:4])
df['Date'] = pd.to_datetime(df['Date'])
plt.figure(1)
ax1 = plt.subplot(221)
df['4030CFDC'].plot(ax=ax1, figsize=(8, 8), title='Signal with noise')
ax2 = plt.subplot(222)
df_denoised['4030CFDC'].plot(ax=ax2, figsize=(8, 8), title='Signal without noise')
plt.tight_layout()
#----output----
Listing 4-25Wavelet Transform Implementation
摘要
在这一步中,我们已经了解了可能妨碍模型准确性的各种常见问题,例如没有为类创建、方差和偏差选择最佳概率截止点。我们还简要介绍了不同的模型调整技术,如 bagging、boosting、集成投票、网格搜索/随机搜索和贝叶斯优化技术,用于超参数调整。我们还研究了物联网数据的降噪技术。简而言之,我们只看了所讨论的每个主题中最重要的方面,以帮助您入门。然而,每种算法都有更多的优化选项,而且每种技术都在快速发展。所以我鼓励你留意他们各自的官方托管网页和 GitHub 资源库(表 4-2 )。
表 4-2
额外资源
|名字
|
网页
|
Github 知识库
|
| — | — | — |
| Scikit-learn | http://scikit-learn.org/stable/#
| https://github.com/scikit-learn/scikit-learn
|
| Xgboost | https://xgboost.readthedocs.io/en/latest/
| https://github.com/dmlc/xgboost
|
| 贝叶斯优化 | 不适用的 | https://github.com/fmfn/BayesianOptimization
|
| 小波变换 | https://pywavelets.readthedocs.io/en/latest/#
| https://github.com/PyWavelets/pywt
|
我们已经到达了步骤 4 的末尾,这意味着您已经通过了机器学习旅程的一半。在下一章,我们将学习文本挖掘技术。
五、文本挖掘和推荐系统
人工智能的一个关键领域是自然语言处理(NLP),或众所周知的文本挖掘,它涉及教计算机如何从文本中提取意义。在过去的 20 年里,随着互联网世界的爆炸和社交媒体的兴起,大量有价值的数据以文本的形式产生。从文本数据中挖掘出有意义的模式的过程称为文本挖掘。本章概述了高级文本挖掘过程、关键概念和涉及的常用技术。
除了 Scikit-learn 之外,还有许多已建立的面向 NLP 的库可供 Python 使用,而且数量还在不断增加。表 5-1 根据截至 2016 年的贡献者数量列出了最受欢迎的图书馆。
表 5-1
流行的 Python 文本挖掘库
|包名
|
贡献者数量(2019 年)
|
许可证
|
描述
|
| — | — | — | — |
| 我是 NLTK | Two hundred and fifty-five | 街头流氓 | 它是最流行和最广泛使用的工具包,主要用于支持 NLP 的研究和开发 |
| 玄诗 | Three hundred and eleven | LGPL-2 突击步枪 | 主要用于大型语料库的主题建模、文档索引和相似性检索 |
| 宽大的 | Three hundred | 用它 | 使用 Python + Cython 构建,用于 NLP 概念的高效生产实现 |
| 文本 blob | Thirty-six | 用它 | 它是 NLTK 和模式库的包装器,便于访问它们的功能。适合快速原型制作 |
| 懂得多种语言的 | Twenty-two | GPL-3 | 这是一个多语言文本处理工具包,支持大量多语言应用程序。 |
| 模式 | Nineteen | BSD-3 | 这是一个 Python 的 web 挖掘模块,具有抓取、NLP、机器学习和网络分析/可视化功能。 |
注意
另一个著名的库是 Stanford CoreNLP,这是一套基于 Java 的工具包。有许多 Python 包装器可用于同样的目的;然而,到目前为止,这些包装器的贡献者数量还很少。
文本挖掘过程概述
整个文本挖掘过程可以大致分为以下四个阶段,如图 5-1 所示:
图 5-1
文本挖掘过程概述
-
文本数据汇编
-
文本数据预处理
-
数据探索或可视化
-
模型结构
数据汇编(文本)
据观察,任何企业都有 70%的可用数据是非结构化的。第一步是整理来自不同来源(如开放式反馈)的非结构化数据;电话;电子邮件支持;在线聊天;以及 Twitter、LinkedIn 和脸书等社交媒体网络。汇集这些数据并应用挖掘/机器学习(ML)技术来分析它们,为组织提供了在客户体验中构建更多力量的宝贵机会。
有几个库可用于从所讨论的不同格式中提取文本内容。到目前为止,为多种格式提供简单、单一接口的最好的库是“textract”(开源 MIT 许可证)。请注意,到目前为止,这个库/包适用于 Linux 和 Mac OS,但不适用于 Windows。表 5-2 列出了支持的格式。
表 5-2
textract 支持的格式
|格式
|
支持方式
|
附加信息
|
| — | — | — |
| 。csv /.eml /。json /。odt /。txt / | Python 内置 | |
| 。文件 | 反词 | www.winfield.demon.nl/
|
| 。文档 | Python-docx | https://python-docx.readthedocs.io/en/latest/
|
| 。电子书 | 电子书 | https://github.com/aerkalov/ebooklib
|
| 。gif /.jpg /。jpeg /。png /。tiff /。tif(基准) | tessera CT ocr | https://github.com/tesseract-ocr
|
| 。html /。html 文件的后缀 | Beautifulsoup4 | http://beautiful-soup-4.readthedocs.io/en/latest/
|
| . mp3 / .ogg / .wav 档案 | 演讲识别和 sox | URL 1:URL 2: http://sox.sourceforge.net/
|
| 。味精 | 味精提取器 | https://github.com/mattgwwalker/msg-extractor
|
| 。可移植文档格式文件的扩展名(portable document format 的缩写) | pdftotext 和 pdfminer.six | URL 1:URL 2: https://github.com/pdfminer/pdfminer.six
|
| 。附 | Python-pptx | https://python-pptx.readthedocs.io/en/latest/
|
| 。著名图象处理软件 | PS2 文本 | http://pages.cs.wisc.edu/~ghost/doc/pstotext.htm
|
| 。普适文本格式 | Unrtf | www.gnu.org/software/unrtf/
|
| . xlsx / .xls 档 | xlrd . xlrd . xlrd . xlrd . xlrd . xlrd . xlrd . xlrd . xlrd | https://pypi.python.org/pypi/xlrd
|
让我们看看商业世界中最普遍的格式的代码:pdf、jpg 和音频文件(清单 5-1 )。注意,从其他格式中提取文本也相对简单。
# You can read/learn more about latest updates about textract on their official documents site at http://textract.readthedocs.io/en/latest/
import textract
# Extracting text from normal pdf
text = textract.process('Data/PDF/raw_text.pdf', language="eng")
# Extrcting text from two columned pdf
text = textract.process('Data/PDF/two_column.pdf', language="eng")
# Extracting text from scanned text pdf
text = textract.process('Data/PDF/ocr_text.pdf', method="tesseract", language="eng")
# Extracting text from jpg
text = textract.process('Data/jpg/raw_text.jpg', method="tesseract", language="eng")
# Extracting text from audio file
text = textract.process('Data/wav/raw_text.wav', language="eng")
Listing 5-1Example Code for Extracting Data from pdf, jpg, Audio
社会化媒体
你知道吗,在线新闻和社交网络服务提供商 Twitter 拥有 3.2 亿用户,平均每天有 4200 万条活跃推文!(来源:smart insights 2016 年全球社交媒体研究总结)
让我们了解如何探索社交媒体的丰富信息(我将考虑 Twitter 作为一个例子),以探索关于一个选定的主题正在谈论什么(图 5-2 )。大多数论坛都为开发者提供了访问帖子的 API。
图 5-2
提取 Twitter 帖子进行分析
步骤 1—获取访问密钥(一次性活动)。采取以下步骤来设置一个新的 Twitter 应用程序,以获得消费者/访问密钥、秘密和令牌(不要与未经授权的人共享密钥令牌)。
-
点击“创建新应用”
-
填写所需信息,然后点击“创建您的 Twitter 应用程序”
-
您将在“密钥和访问令牌”选项卡下获得访问详细信息
第二步——获取推文。一旦有了授权秘密和访问令牌,就可以使用清单 5-2 代码示例来建立连接。
#Import the necessary methods from tweepy library
import tweepy
from tweepy.streaming import StreamListener
from tweepy import OAuthHandler
from tweepy import Stream
#provide your access details below
access_token = "Your token goes here"
access_token_secret = "Your token secret goes here"
consumer_key = "Your consumer key goes here"
consumer_secret = "Your consumer secret goes here"
# establish a connection
auth = tweepy.auth.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
api = tweepy.API(auth)
Listing 5-2
Twitter Authentication
让我们假设你想了解关于 iPhone 7 及其相机功能的讨论。所以,我们来拉十个最近的帖子。
注意:根据帖子的数量,您最多只能获取 10 到 15 天内关于某个主题的历史用户帖子。
#fetch recent 10 tweets containing words iphone7, camera
fetched_tweets = api.search(q=['iPhone 7','iPhone7','camera'], result_type="recent", lang="en", count=10)
print ("Number of tweets: ",len(fetched_tweets))
#----output----
Number of tweets: 10
# Print the tweet text
for tweet in fetched_tweets:
print ('Tweet ID: ', tweet.id)
print ('Tweet Text: ', tweet.text, '\n')
#----output----
Tweet ID: 825155021390049281
Tweet Text: RT @volcanojulie: A Tau Emerald dragonfly. The iPhone 7 camera is exceptional!
#nature #insect #dragonfly #melbourne #australia #iphone7 #…
Tweet ID: 825086303318507520
Tweet Text: Fuzzy photos? Protect your camera lens instantly with #iPhone7 Full Metallic Case. Buy now! https://t.co/d0dX40BHL6 https://t.co/AInlBoreht
您可以将有用的特征捕捉到数据帧中,以供进一步分析(清单 5-3 )。
# function to save required basic tweets info to a dataframe
def populate_tweet_df(tweets):
#Create an empty dataframe
df = pd.DataFrame()
df['id'] = list(map(lambda tweet: tweet.id, tweets))
df['text'] = list(map(lambda tweet: tweet.text, tweets))
df['retweeted'] = list(map(lambda tweet: tweet.retweeted, tweets))
df['place'] = list(map(lambda tweet: tweet.user.location, tweets))
df['screen_name'] = list(map(lambda tweet: tweet.user.screen_name, tweets))
df['verified_user'] = list(map(lambda tweet: tweet.user.verified, tweets))
df['followers_count'] = list(map(lambda tweet: tweet.user.followers_count, tweets))
df['friends_count'] = list(map(lambda tweet: tweet.user.friends_count, tweets))
# Highly popular user's tweet could possibly seen by large audience, so lets check the popularity of user
df['friendship_coeff'] = list(map(lambda tweet: float(tweet.user.followers_count)/float(tweet.user.friends_count), tweets))
return df
df = populate_tweet_df(fetched_tweets)
print df.head(10)
#---output----
id text
0 825155021390049281 RT @volcanojulie: A Tau Emerald dragonfly. The...
1 825086303318507520 Fuzzy photos? Protect your camera lens instant...
2 825064476714098690 RT @volcanojulie: A Tau Emerald dragonfly. The...
3 825062644986023936 RT @volcanojulie: A Tau Emerald dragonfly. The...
4 824935025217040385 RT @volcanojulie: A Tau Emerald dragonfly. The...
5 824933631365779458 A Tau Emerald dragonfly. The iPhone 7 camera i...
6 824836880491483136 The camera on the IPhone 7 plus is fucking awe...
7 823805101999390720 'Romeo and Juliet' Ad Showcases Apple's iPhone...
8 823804251117850624 iPhone 7 Images Show Bigger Camera Lens - I ha...
9 823689306376196096 RT @computerworks5: Premium HD Selfie Stick &a...
retweeted place screen_name verified_user
0 False Melbourne, Victoria MonashEAE False
1 False California, USA ShopNCURV False
2 False West Islip, Long Island, NY FusionWestIslip False
3 False 6676 Fresh Pond Rd Queens, NY FusionRidgewood False
4 False Iphone7review False
5 False Melbourne; Monash University volcanojulie False
6 False Hollywood, FL Hbk_Mannyp False
7 False Toronto.NYC.the Universe AaronRFernandes False
8 False Lagos, Nigeria moyinoluwa_mm False
9 False Iphone7review False
followers_count friends_count friendship_coeff
0 322 388 0.829897
1 279 318 0.877358
2 13 193 0.067358
3 45 218 0.206422
4 199 1787 0.111360
5 398 551 0.722323
6 57 64 0.890625
7 18291 7 2613.000000
8 780 302 2.582781
9 199 1787 0.111360
Listing 5-3Save Features to Dataframe
除了主题,您还可以选择一个专注于某个主题的 screen_name。让我们看看(列表 5-4 )网名为 Iphone7review 的帖子。
# For help about api look here http://tweepy.readthedocs.org/en/v2.3.0/api.html
fetched_tweets = api.user_timeline(id='Iphone7review', count=5)
# Print the tweet text
for tweet in fetched_tweets:
print 'Tweet ID: ', tweet.id
print 'Tweet Text: ', tweet.text, '\n'
#----output----
Tweet ID: 825169063676608512
Tweet Text: RT @alicesttu: iPhone 7S to get Samsung OLED display next year #iPhone https://t.co/BylKbvXgAG #iphone
Tweet ID: 825169047138533376
Tweet Text: Nothing beats the Iphone7! Who agrees? #Iphone7 https://t.co/e03tXeLOao
Listing 5-4Example Code for Extracting Tweets Based on Screen Name
快速浏览这些帖子,人们通常可以得出结论,iPhone 7 的摄像头功能得到了积极的评价。
数据预处理(文本)
该步骤处理净化合并的文本以去除噪声,从而确保有效的句法、语义文本分析,以便从文本中获得有意义的见解。下面简要介绍一些常见的清洁步骤。
转换为小写并标记化
在这里,所有的数据都被转换成小写。这是为了防止像“like”或“LIKE”这样的词被解释为不同的词。Python 提供了一个函数 lower() 将文本转换成小写。
标记化是将一大组文本分解成更小的有意义的块,如句子、单词、短语的过程。
句子标记化
NLTK(自然语言工具包)库提供 sent_tokenize 用于句子级标记化,它使用一个预训练的模型 PunktSentenceTokenize 来确定标点符号和标记欧洲语言句子结尾的字符(清单 5-5 )。
import nltk
from nltk.tokenize import sent_tokenize
text='Statistics skills, and programming skills are equally important for analytics. Statistics skills and domain knowledge are important for analytics. I like reading books and traveling.'
sent_tokenize_list = sent_tokenize(text)
print(sent_tokenize_list)
#----output----
['Statistics skills, and programming skills are equally important for analytics.', 'Statistics skills, and domain knowledge are important for analytics.', 'I like reading books and travelling.']
Listing 5-5Example Code for Sentence Tokenizing
NLTK 总共支持 17 种欧洲语言的句子标记化。清单 5-6 给出了为特定语言加载标记化模型的示例代码,作为 nltk.data 的一部分保存为 pickle 文件
import nltk.data
spanish_tokenizer = nltk.data.load('tokenizers/punkt/spanish.pickle')
spanish_tokenizer.tokenize('Hola. Esta es una frase espanola.')
#----output----
['Hola.', 'Esta es una frase espanola.']
Listing 5-6Sentence Tokenizing for European Languages
单词标记化
NLTK 的 word_tokenize 函数是一个包装器函数,由 TreebankWordTokenizer 调用 tokenize(清单 5-7 )。
from nltk.tokenize import word_tokenize
print word_tokenize(text)
# Another equivalent call method using TreebankWordTokenizer
from nltk.tokenize import TreebankWordTokenizer
tokenizer = TreebankWordTokenizer()
print (tokenizer.tokenize(text))
#----output----
['Statistics', 'skills', ',', 'and', 'programming', 'skills', 'are', 'equally', 'important', 'for', 'analytics', '.', 'Statistics', 'skills', ',', 'and', 'domain', 'knowledge', 'are', 'important', 'for', 'analytics', '.', 'I', 'like', 'reading', 'books', 'and', 'travelling', '.']
Listing 5-7Example Code for Word Tokenizing
消除噪音
你应该删除所有与文本分析无关的信息。这可以被视为文本分析的噪声。最常见的干扰是数字、标点符号、停用词、空格等。(列表 5-8 )。
*数字:*数字被删除,因为它们可能不相关并且不包含有价值的信息。
def remove_numbers(text):
return re.sub(r'\d+', ", text)
text = 'This is a sample English sentence, \n with whitespace and numbers 1234!'
print ('Removed numbers: ', remove_numbers(text))
#----output----
Removed numbers: This is a sample English sentence,
with whitespace and numbers!
Listing 5-8Example Code for Removing Noise from Text
*标点:*为了更好地识别每个单词,从数据集中删除标点字符,需要将其删除。例如:“like”和“like”或“coca-cola”和“CocaCola”会被解释为不同的单词,如果不去掉标点符号的话(清单 5-9 )。
import string
# Function to remove punctuations
def remove_punctuations(text):
words = nltk.word_tokenize(text)
punt_removed = [w for w in words if w.lower() not in string.punctuation]
return " ".join(punt_removed)
print (remove_punctuations('This is a sample English sentence, with punctuations!'))
#----output----
This is a sample English sentence with punctuations
Listing 5-9Example Code for Removing Punctuations from Text
*停用词:*像“the”、“and”和“or”这样的词是没有意义的,会给分析增加不必要的干扰。由于这个原因,它们被移除(列表 5-10 )。
from nltk.corpus import stopwords
# Function to remove stop words
def remove_stopwords(text, lang="english"):
words = nltk.word_tokenize(text)
lang_stopwords = stopwords.words(lang)
stopwords_removed = [w for w in words if w.lower() not in lang_stopwords]
return " ".join(stopwords_removed)
print (remove_stopwords('This is a sample English sentence'))
#----output----
sample English sentence
Listing 5-10Example Code for Removing Stop Words from the Text
注意
*删除自己的停用词(如果需要)。*某些单词可能在特定领域中非常常用。除了英语停用词,我们还可以删除我们自己的停用词。我们自己的停用词的选择可能取决于话语的领域,并且可能直到我们做了一些分析之后才变得明显。
*空白:*通常在文本分析中,多余的空白(空格、制表符、回车、换行符)会被识别为一个单词。这种异常可以通过该步骤中的基本编程程序来避免(列表 5-11 )。
# Function to remove whitespace
def remove_whitespace(text):
return " ".join(text.split())
text = 'This is a sample English sentence, \n with whitespace and numbers 1234!'
print ('Removed whitespace: ', remove_whitespace(text))
#----output----
Removed whitespace: This is a sample English sentence, with whitespace and numbers 1234!
Listing 5-11Example Code for Removing Whitespace from Text
词性标注
词性标注是分配特定语言词性的过程,如名词、动词、形容词、副词等。,对于给定文本中的每个单词。
NLTK 支持多种 PoS 标记模型,默认的标记器是 maxent_treebank_pos_tagger,使用的是 Penn(宾夕法尼亚大学)treebank 语料库(表 5-3 )。同样有 36 个可能的 PoS 标签。语法分析器将一个句子表示为一棵有三个子树的树:名词短语(NP)、动词短语(VP)和句号(。).树的根将是 s。清单 5-12 和 5-13 为您提供了词性标注和可视化句子树的示例代码。
表 5-3
NLTK PoS 标签
|词性标注
|
简短描述
|
| — | — |
| maxent_treebank_pos_tagger | 这是基于最大熵(ME)分类原则训练的华尔街日报子集的宾夕法尼亚树银行语料库 |
| 布里特格 | Brill 的基于规则的转换标记器 |
| CRFTagger | 条件随机场 |
| HiddenMarkovModelTagger | 隐马尔可夫模型(hmm)主要用于将正确的标签序列分配给序列数据,或者评估给定标签和数据序列的概率 |
| 洪博塔格 | 与 HunPos 开源 Pos 标记器接口的模块 |
| 感知标签 | 基于 Matthew Honnibal 提出的平均感知机技术 |
| 森纳塔格 | 使用神经网络架构的语义/句法提取 |
| SequentialBackoffTagger | 从左到右顺序标记句子的类 |
| 斯坦福·波斯塔格 | 斯坦福大学的研究者和开发者 |
| 三硝基甲苯 | Thorsten Brants 实现“TnT——统计词性标注器” |
from nltk import chunk
tagged_sent = nltk.pos_tag(nltk.word_tokenize('This is a sample English sentence'))
print (tagged_sent)
tree = chunk.ne_chunk(tagged_sent)
tree.draw() # this will draw the sentence tree
#----output----
[('This', 'DT'), ('is', 'VBZ'), ('a', 'DT'), ('sample', 'JJ'), ('English', 'JJ'), ('sentence', 'NN')]
Listing 5-12Example Code for PoS Tagging the Sentence and Visualizing the Sentence Tree
# To use PerceptronTagger
from nltk.tag.perceptron import PerceptronTagger
PT = PerceptronTagger()
print (PT.tag('This is a sample English sentence'.split()))
#----output----
[('This', 'DT'), ('is', 'VBZ'), ('a', 'DT'), ('sample', 'JJ'), ('English', 'JJ'), ('sentence', 'NN')]
# To get help about tags
nltk.help.upenn_tagset('NNP')
#----output----
NNP: noun, proper, singular
Listing 5-13Example Code for Using Perceptron Tagger and Getting Help on Tags
堵塞物
词干是转化为词根的过程。它使用一种算法来删除英语单词的常见词尾,如“ly”、“es”、“ed”和“s”。例如,假设在进行分析时,您可能希望将“小心”、“关心”、“关心”和“关心地”视为“关心”,而不是单独的单词。图 5-3 中列出了三种最广泛使用的词干算法。清单 5-14 提供了词干提取的示例代码。
图 5-3
最流行的 NLTK 词干分析器
from nltk import PorterStemmer, LancasterStemmer, SnowballStemmer
# Function to apply stemming to a list of words
def words_stemmer(words, type="PorterStemmer", lang="english", encoding="utf8"):
supported_stemmers = ["PorterStemmer","LancasterStemmer","SnowballStemmer"]
if type is False or type not in supported_stemmers:
return words
else:
stem_words = []
if type == "PorterStemmer":
stemmer = PorterStemmer()
for word in words:
stem_words.append(stemmer.stem(word).encode(encoding))
if type == "LancasterStemmer":
stemmer = LancasterStemmer()
for word in words:
stem_words.append(stemmer.stem(word).encode(encoding))
if type == "SnowballStemmer":
stemmer = SnowballStemmer(lang)
for word in words:
stem_words.append(stemmer.stem(word).encode(encoding))
return " ".join(stem_words)
words = 'caring cares cared caringly carefully'
print ("Original: ", words)
print ("Porter: ", words_stemmer(nltk.word_tokenize(words), "PorterStemmer"))
print ("Lancaster: ", words_stemmer(nltk.word_tokenize(words), "LancasterStemmer"))
print ("Snowball: ", words_stemmer(nltk.word_tokenize(words), "SnowballStemmer"))
#----output----
Original: caring cares cared caringly carefully
Porter: care care care caringly care
Lancaster: car car car car car
Snowball: care care care care care
Listing 5-14Example Code for Stemming
词汇化
它是转换到字典基本形式的过程。为此,你可以使用 WordNet,这是一个大型的英语词汇数据库,通过它们的语义关系连接在一起。它就像一个词库:它根据单词的意思将单词组合在一起(列表 5-15 )。
from nltk.stem import WordNetLemmatizer
wordnet_lemmatizer = WordNetLemmatizer()
# Function to apply lemmatization to a list of words
def words_lemmatizer(text, encoding="utf8"):
words = nltk.word_tokenize(text)
lemma_words = []
wl = WordNetLemmatizer()
for word in words:
pos = find_pos(word)
lemma_words.append(wl.lemmatize(word, pos).encode(encoding))
return " ".join(lemma_words)
# Function to find part of speech tag for a word
def find_pos(word):
# Part of Speech constants
# ADJ, ADJ_SAT, ADV, NOUN, VERB = 'a', 's', 'r', 'n', 'v'
# You can learn more about these at http://wordnet.princeton.edu/wordnet/man/wndb.5WN.html#sect3
# You can learn more about all the penn tree tags at https://www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html
pos = nltk.pos_tag(nltk.word_tokenize(word))[0][1]
# Adjective tags - 'JJ', 'JJR', 'JJS'
if pos.lower()[0] == 'j':
return 'a'
# Adverb tags - 'RB', 'RBR', 'RBS'
elif pos.lower()[0] == 'r':
return 'r'
# Verb tags - 'VB', 'VBD', 'VBG', 'VBN', 'VBP', 'VBZ'
elif pos.lower()[0] == 'v':
return 'v'
# Noun tags - 'NN', 'NNS', 'NNP', 'NNPS'
else:
return 'n'
print ("Lemmatized: ", words_lemmatizer(words))
#----output----
Lemmatized: care care care caringly carefully
In the preceding case,'caringly'/'carefully' are inflected forms of care and they are an entry word listed in WordNet Dictionary so they are retained in their actual form itself.
Listing 5-15Example Code for Lemmatization
NLTK 英语 WordNet 包括大约 155,287 个单词和 117,000 个同义词集。对于给定的单词,WordNet 包括/提供了定义、示例、同义词(一组相似的名词、形容词、动词)、反义词(意思与另一个相反)等。清单 5-16 提供了 wordnet 的示例代码。
from nltk.corpus import wordnet
syns = wordnet.synsets("good")
print "Definition: ", syns[0].definition()
print "Example: ", syns[0].examples()
synonyms = []
antonyms = []
# Print synonums and antonyms (having opposite meaning words)
for syn in wordnet.synsets("good"):
for l in syn.lemmas():
synonyms.append(l.name())
if l.antonyms():
antonyms.append(l.antonyms()[0].name())
print ("synonyms: \n", set(synonyms))
print ("antonyms: \n", set(antonyms))
#----output----
Definition: benefit
Example: [u'for your own good', u"what's the good of worrying?"]
synonyms:
set([u'beneficial', u'right', u'secure', u'just', u'unspoilt', u'respectable', u'good', u'goodness', u'dear', u'salutary', u'ripe', u'expert', u'skillful', u'in_force', u'proficient', u'unspoiled', u'dependable', u'soundly', u'honorable', u'full', u'undecomposed', u'safe', u'adept', u'upright', u'trade_good', u'sound', u'in_effect', u'practiced', u'effective', u'commodity', u'estimable', u'well', u'honest', u'near', u'skilful', u'thoroughly', u'serious'])
antonyms:
set([u'bad', u'badness', u'ill', u'evil', u'evilness'])
Listing 5-16Example Code for Wordnet
N-grams
文本挖掘中的一个重要概念是 n 元文法,它基本上是来自给定的大文本序列的 n 个项目的一组共现或连续序列。这里的项目可以是单词、字母和音节。让我们考虑一个例句,试着提取 n 的不同值的 n-grams(清单 5-17 )。
from nltk.util import ngrams
from collections import Counter
# Function to extract n-grams from text
def get_ngrams(text, n):
n_grams = ngrams(nltk.word_tokenize(text), n)
return [ ' '.join(grams) for grams in n_grams]
text = 'This is a sample English sentence'
print ("1-gram: ", get_ngrams(text, 1))
print ("2-gram: ", get_ngrams(text, 2))
print ("3-gram: ", get_ngrams(text, 3))
print ("4-gram: ", get_ngrams(text, 4))
#----output----
1-gram:['This', 'is', 'a', 'sample', 'English', 'sentence']
2-gram:['This is', 'is a', 'a sample', 'sample English', 'English sentence']
3-gram:['This is a', 'is a sample', 'a sample English', 'sample English sentence']
4-gram: ['This is a sample', 'is a sample English', 'a sample English sentence']
Listing 5-17Example Code for Extracting n-grams from the Sentence
注意
1-gram 也称为 unigram 二元模型和三元模型分别是二元模型和三元模型。
N-gram 技术相对简单,简单的增加 n 的值会给我们更多的上下文。它在概率语言模型中广泛用于预测序列中的下一项。例如,当用户键入时,搜索引擎使用这种技术来预测/推荐序列中下一个字符/单词的可能性(清单 5-18 )。
text = 'Statistics skills, and programming skills are equally important for analytics. Statistics skills and domain knowledge are important for analytics'
# remove punctuations
text = remove_punctuations(text)
# Extracting bigrams
result = get_ngrams(text,2)
# Counting bigrams
result_count = Counter(result)
# Converting the result to a data frame
import pandas as pd
df = pd.DataFrame.from_dict(result_count, orient="index")
df = df.rename(columns={'index':'words', 0:'frequency'}) # Renaming index and column name
print (df)
#----output----
frequency
are equally 1
domain knowledge 1
skills are 1
knowledge are 1
programming skills 1
are important 1
skills and 2
for analytics 2
and domain 1
important for 2
and programming 1
Statistics skills 2
equally important 1
analytics Statistics 1
Listing 5-18Example Code for Extracting 2-grams from the Sentence and Storing in a Dataframe
一袋单词
文本必须用数字表示才能应用任何算法。单词包(BoW)是一种计算文档中单词出现次数的方法,而不考虑语法和单词顺序的重要性。这可以通过创建术语文档矩阵(TDM)来实现。它只是一个矩阵,以术语为行,以文档名为列,以词频计数为矩阵的单元(图 5-4 )。让我们通过一个例子来学习创建 TDM:考虑三个包含一些文本的文本文档。Sklearn 在 feature_extraction.text 下提供了很好的函数,将一个文本文档集合转换成字数矩阵(清单 5-19 )。
图 5-4
术语文档矩阵
import os
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
# Function to create a dictionary with key as file names and values as text for all files in a given folder
def CorpusFromDir(dir_path):
result = dict(docs = [open(os.path.join(dir_path,f)).read() for f in os.listdir(dir_path)],
ColNames = map(lambda x: x, os.listdir(dir_path)))
return result
docs = CorpusFromDir('Data/')
# Initialize
vectorizer = CountVectorizer()
doc_vec = vectorizer.fit_transform(docs.get('docs'))
#create dataFrame
df = pd.DataFrame(doc_vec.toarray().transpose(), index = vectorizer.get_feature_names())
# Change column headers to be file names
df.columns = docs.get('ColNames')
print (df)
#----output----
Doc_1.txt Doc_2.txt Doc_3.txt
analytics 1 1 0
and 1 1 1
are 1 1 0
books 0 0 1
domain 0 1 0
equally 1 0 0
for 1 1 0
important 1 1 0
knowledge 0 1 0
like 0 0 1
programming 1 0 0
reading 0 0 1
skills 2 1 0
statistics 1 1 0
travelling 0 0 1
Listing 5-19Creating a Term Document Matrix from a Corpus of Sample Documents
注意
术语文档矩阵(TDM)是术语文档矩阵的转置。在 TDM 中,行是文档名,列标题是术语。两者都是矩阵格式,有助于进行分析;然而,由于术语的数量通常比文档数大得多,所以通常使用 TDM。在这种情况下,拥有更多行比拥有大量列更好。
术语频率-逆文档频率(TF-IDF)
在信息检索领域,TF-IDF 是一种很好的统计方法,可以反映术语与文档集合或语料库中文档的相关性。我们来分解一下 TF_IDF,应用一个例子来更好的理解。
术语频率将告诉你一个给定术语出现的频率。
TF (term) = )
例如,考虑包含 100 个单词的文档,其中单词“ML”出现三次,则 TF (ML) = 3 / 100 = 0.03
文档频率会告诉你一个术语有多重要。
DF (term) = )
假设我们有一千万个文档,单词 ML 出现在其中的一千个文档中,那么 DF (ML) = 1000 / 10,000,000 = 0.0001。
为了归一化,我们以 log (d/D)为例,log (0.0001) = -4
如前例所示,D > d 和 log (d/D)通常会给出负值。因此,为了解决这个问题,让我们对 log 表达式内部的比率进行反演,这就是所谓的逆文档频率(IDF)。本质上,我们正在压缩价值的尺度,以便可以平滑地比较非常大或非常小的数量。
IDF(期限)= )
继续前面的例子,IDF(ML) = log(10,000,000 / 1,000) = 4。
TF-IDF 是数量的重量乘积;对于前面的例子,TF-IDF (ML) = 0.03 * 4 = 0.12。Sklearn 提供了一个函数 TfidfVectorizer,为文本计算 TF-IDF;然而,默认情况下,它使用 L2 归一化对术语向量进行归一化,并且通过将文档频率加 1 来平滑 IDF,以防止零划分(列表 5-20 )。
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
doc_vec = vectorizer.fit_transform(docs.get('docs'))
#create dataFrame
df = pd.DataFrame(doc_vec.toarray().transpose(), index = vectorizer.get_feature_names())
# Change column headers to be file names
df.columns = docs.get('ColNames')
print (df)
#----output----
Doc_1.txt Doc_2.txt Doc_3.txt
analytics 0.276703 0.315269 0.000000
and 0.214884 0.244835 0.283217
are 0.276703 0.315269 0.000000
books 0.000000 0.000000 0.479528
domain 0.000000 0.414541 0.000000
equally 0.363831 0.000000 0.000000
for 0.276703 0.315269 0.000000
important 0.276703 0.315269 0.000000
knowledge 0.000000 0.414541 0.000000
like 0.000000 0.000000 0.479528
programming 0.363831 0.000000 0.000000
reading 0.000000 0.000000 0.479528
skills 0.553405 0.315269 0.000000
statistics 0.276703 0.315269 0.000000
travelling 0.000000 0.000000 0.479528
Listing 5-20Create a Term Document Matrix (TDM) with TF-IDF
数据探索(文本)
在这一阶段,探索语料库以理解共同的关键词、内容、关系以及噪声的存在和水平。这可以通过创建基本统计数据和采用可视化技术来实现,如词频计数、词共现或相关图等。,这将有助于我们发现隐藏的模式,如果有的话。
频率表
这个可视化呈现了一个条形图,其长度对应于特定单词出现的频率。让我们为 Doc_1.txt 文件绘制一个频率图(清单 5-21 )。
words = df.index
freq = df.ix[:,0].sort(ascending=False, inplace=False)
pos = np.arange(len(words))
width=1.0
ax=plt.axes(frameon=True)
ax.set_xticks(pos)
ax.set_xticklabels(words, rotation="vertical", fontsize=9)
ax.set_title('Word Frequency Chart')
ax.set_xlabel('Words')
ax.set_ylabel('Frequency')
plt.bar(pos, freq, width, color="b")
plt.show()
#----output----
Listing 5-21Example Code for Frequency Chart
词云
这是文本数据的可视化表示,有助于从数据中的重要关键词出现的角度获得高层次的理解。wordcloud 包可以用来生成字体大小与其频率相关的单词(清单 5-22 )。
from wordcloud import WordCloud
# Read the whole text.
text = open('Data/Text_Files/Doc_1.txt').read()
# Generate a word cloud image
wordcloud = WordCloud().generate(text)
# Display the generated image:
# the matplotlib way:
import matplotlib.pyplot as plt
plt.imshow(wordcloud.recolor(random_state=2017))
plt.title('Most Frequent Words')
plt.axis("off")
plt.show()
#----output----
Listing 5-22Example Code for the Word Cloud
从上面的图表中我们可以看出,相对而言,“技能”出现的次数最多。
词汇离差图
这个图有助于确定一个单词在一系列文本句子中的位置。x 轴上是单词偏移量,y 轴上的每一行代表整个文本,标记表示感兴趣的单词的一个实例(清单 5-23 )。
from nltk import word_tokenize
def dispersion_plot(text, words):
words_token = word_tokenize(text)
points = [(x,y) for x in range(len(words_token)) for y in range(len(words)) if words_token[x] == words[y]]
if points:
x,y=zip(*points)
else:
x=y=()
plt.plot(x,y,"rx",scalex=.1)
plt.yticks(range(len(words)),words,color="b")
plt.ylim(-1,len(words))
plt.title("Lexical Dispersion Plot")
plt.xlabel("Word Offset")
plt.show()
text = 'statistics skills and programming skills are equally important for analytics. statistics skills and domain knowledge are important for analytics'
dispersion_plot(text, ['statistics', 'skills', 'and', 'important'])
#----output----
Listing 5-23Example Code for Lexical Dispersion Plot
共生矩阵
计算文本序列中单词之间的共现将有助于解释单词之间的关系。共现矩阵告诉我们每个单词与当前单词共现了多少次。将这个矩阵进一步绘制成热图是一个强大的可视化工具,可以有效地发现单词之间的关系(清单 5-24 )。
import statsmodels.api as sm
import scipy.sparse as sp
# default unigram model
count_model = CountVectorizer(ngram_range=(1,1))
docs_unigram = count_model.fit_transform(docs.get('docs'))
# co-occurrence matrix in sparse csr format
docs_unigram_matrix = (docs_unigram.T * docs_unigram)
# fill same word cooccurence to 0
docs_unigram_matrix.setdiag(0)
# co-occurrence matrix in sparse csr format
docs_unigram_matrix = (docs_unigram.T * docs_unigram) docs_unigram_matrix_diags = sp.diags(1./docs_unigram_matrix.diagonal())
# normalized co-occurence matrix
docs_unigram_matrix_norm = docs_unigram_matrix_diags * docs_unigram_matrix
# Convert to a dataframe
df = pd.DataFrame(docs_unigram_matrix_norm.todense(), index = count_model.get_feature_names())
df.columns = count_model.get_feature_names()
# Plot
sm.graphics.plot_corr(df, title='Co-occurrence Matrix', xnames=list(df.index))
plt.show()
#----output----
Listing 5-24Example Code for Cooccurrence Matrix
模型结构
您现在可能已经很熟悉了,建模是理解和建立变量之间关系的过程。到目前为止,您已经学习了如何从各种来源提取文本内容,预处理以消除噪声,以及执行探索性分析以获得关于手头文本数据的基本理解/统计。现在,您将学习对处理后的数据应用 ML 技术来构建模型。
文本相似度
这是指示两个对象有多相似的度量,通过用对象的特征(这里是文本)表示的维度的距离度量来描述。较小的距离表示高度相似,反之亦然。请注意,相似性是高度主观的,取决于领域或应用。对于文本相似性,选择合适的距离度量来获得更好的结果是很重要的。有各种各样的距离度量,欧几里德度量是最常见的,它是两点之间的直线距离。然而,在文本挖掘领域已经进行了大量的研究,以了解余弦距离更适合于文本相似性。
让我们看一个简单的例子(表 5-4 )来更好地理解相似性。考虑包含某些简单文本关键词的三个文档,并假设前两个关键词是“事故”和“纽约”目前,忽略其他关键字,让我们基于这两个关键字的频率来计算文档的相似性。
表 5-4
样本术语文档矩阵
|文档编号
|
“事故”计数
|
“纽约”伯爵
|
| — | — | — |
| one | Two | eight |
| Two | three | seven |
| three | seven | three |
图 5-5 描绘了在二维图表上绘制文档词向量点。请注意,余弦相似性方程是两个数据点之间角度的表示,而欧几里德距离是数据点之间直线差的平方根。余弦相似性方程将产生一个介于 0 和 1 之间的值。余弦角越小,余弦值越大,表示相似度越高。在这种情况下,欧几里得距离将导致零。让我们将这些值放入公式中,找出文档 1 和文档 2 之间的相似性。
欧几里德距离(doc1,doc2) = ) = 1.41 = 0
余弦(doc1,doc2) = ) = 0.98,其中
文档 1 = (2,8)
文档 2 = (3,7)
1 号文件。doc 2 =(2 * 3+8 * 7)=(56+6)= 62
||doc1|| = ` ) = 8.24
||doc2|| = ) = 7.61
同样,让我们找出文档 1 和 3 之间的相似之处(图 5-5 )。
欧几里德距离(doc1,doc3) = ) = 0
余弦(doc1,doc3)= ) = 0.60
图 5-5
欧几里德与余弦
根据余弦方程,文件 1 和文件 2 有 98%相似;这可能意味着这两个文档更多地讨论了纽约,而文档 3 可以被认为更多地关注于“事故”然而,有几次提到了纽约,导致文档 1 和 3 之间有 60%的相似性。
清单 5-25 为图 5-5 中给出的例子提供了计算余弦相似度的示例代码。
from sklearn.metrics.pairwise import cosine_similarity
print "Similarity b/w doc 1 & 2: ", cosine_similarity([df['Doc_1.txt']], [df['Doc_2.txt']])
print "Similarity b/w doc 1 & 3: ", cosine_similarity([df['Doc_1.txt']], [df['Doc_3.txt']])
print "Similarity b/w doc 2 & 3: ", cosine_similarity([df['Doc_2.txt']], [df['Doc_3.txt']])
#----output----
Similarity b/w doc 1 & 2: [[ 0.76980036]]
Similarity b/w doc 1 & 3: [[ 0.12909944]]
Similarity b/w doc 2 & 3: [[ 0.1490712]]
Listing 5-25Example Code for Calculating Cosine Similarity for Documents
文本聚类
例如,我们将使用 20 个新闻组数据集,它由 20 个主题的 18,000 多篇新闻组帖子组成。您可以在 http://qwone.com/~jason/20Newsgroups/
了解更多关于数据集的信息。让我们加载数据并检查主题名称(清单 5-26 )。
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import Normalizer
from sklearn import metrics
from sklearn.cluster import KMeans, MiniBatchKMeans
import numpy as np
# load data and print topic names
newsgroups_train = fetch_20newsgroups(subset='train')
print(list(newsgroups_train.target_names))
#----output----
['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']
Listing 5-26Example Code for Text Clustering
为了简单起见,我们只过滤三个主题。假设我们不知道题目。让我们运行聚类算法,检查每个聚类的关键字。
categories = ['alt.atheism', 'comp.graphics', 'rec.motorcycles']
dataset = fetch_20newsgroups(subset='all', categories=categories, shuffle=True, random_state=2017)
print("%d documents" % len(dataset.data))
print("%d categories" % len(dataset.target_names))
labels = dataset.target
print("Extracting features from the dataset using a sparse vectorizer")
vectorizer = TfidfVectorizer(stop_words='english')
X = vectorizer.fit_transform(dataset.data)
print("n_samples: %d, n_features: %d" % X.shape)
#----output----
2768 documents
3 categories
Extracting features from the dataset using a sparse vectorizer
n_samples: 2768, n_features: 35311
潜在语义分析(LSA)
LSA 是一种数学方法,它试图揭示文档集合中的潜在关系。它不是孤立地查看每个文档,而是将所有文档作为一个整体来查看,并查看其中的术语来确定关系。让我们通过对数据运行奇异值分解(SVD)来执行 LSA,以降低维数。
矩阵 A 的奇异值分解= U * ∑ * V T
r =矩阵 X 的秩
U =列正交 m * r 矩阵
∑ =对角 r∫r 矩阵,奇异值按降序排序
V =列正交 r∫n 矩阵
在我们的例子中,我们有三个主题、2768 个文档和 35311 个单词的词汇表(图 5-6 )。
原始矩阵= 276835311 ~ 10 8
- SVD = 3 * 2768+3+3 * 35311 ~ 105.3
得到的 SVD 占用的空间比原始矩阵少大约 460 倍。清单 5-27 提供了通过 SVD 实现 LSA 的示例代码。
注意
潜在语义分析(LSA)和潜在语义索引(LSI)是同一个东西,后者的名称有时被用来特指为搜索(信息检索)而索引一组文档。
图 5-6
奇异值分解
from sklearn.decomposition import TruncatedSVD
# Lets reduce the dimensionality to 2000
svd = TruncatedSVD(2000)
lsa = make_pipeline(svd, Normalizer(copy=False))
X = lsa.fit_transform(X)
explained_variance = svd.explained_variance_ratio_.sum()
print("Explained variance of the SVD step: {}%".format(int(explained_variance * 100)))
#----output----
Explained variance of the SVD step: 95%
Listing 5-27Example Code for LSA Through SVD
清单 5-28 是在 SVD 输出上运行 k-means 聚类的示例代码。
from __future__ import print_function
km = KMeans(n_clusters=3, init='k-means++', max_iter=100, n_init=1)
# Scikit learn provides MiniBatchKMeans to run k-means in batch mode suitable for a very large corpus
# km = MiniBatchKMeans(n_clusters=5, init='k-means++', n_init=1, init_size=1000, batch_size=1000)
print("Clustering sparse data with %s" % km)
km.fit(X)
print("Top terms per cluster:")
original_space_centroids = svd.inverse_transform(km.cluster_centers_)
order_centroids = original_space_centroids.argsort()[:, ::-1]
terms = vectorizer.get_feature_names()
for i in range(3):
print("Cluster %d:" % i, end=“)
for ind in order_centroids[i, :10]:
print(' %s' % terms[ind], end=“)
print()
#----output----
Top terms per cluster:
Cluster 0: edu graphics university god subject lines organization com posting uk
Cluster 1: com bike edu dod ca writes article sun like organization
Cluster 2: keith sgi livesey caltech com solntze wpd jon edu sandvik
Listing 5-28k-means Clustering on SVD Dataset
清单 5-29 是在 SVD 数据集上运行层次聚类的示例代码。
from sklearn.metrics.pairwise import cosine_similarity
dist = 1 - cosine_similarity(X)
from scipy.cluster.hierarchy import ward, dendrogram
linkage_matrix = ward(dist) #define the linkage_matrix using ward clustering pre-computed distances
fig, ax = plt.subplots(figsize=(8, 8)) # set size
ax = dendrogram(linkage_matrix, orientation="right")
plt.tick_params(axis= 'x', which="both")
plt.tight_layout() #show plot with tight layout
plt.show()
#----output----
Listing 5-29Hierarchical Clustering on SVD Dataset
主题建模
主题建模算法使您能够在大量文档中发现隐藏的主题模式或主题结构。最流行的主题建模技术是 LDA 和 NMF。
潜在狄利克雷分配
LDA 是由大卫·布雷、吴恩达和迈克尔·乔丹在 2003 年提出的图形模型(图 5-7 )。
图 5-7
LDA 图模型
LDA 是由 P ( d 、w=p(d)*p(θ)
其中,φ(z)=主题的词分布,
α =在每个文档主题分布之前的狄利克雷参数,
β =
狄利克雷参数在每文档单词分布之前,
θ(d)=文档的主题分布。
LDA 的目标是最大化投影主题均值之间的分离,最小化每个投影主题内的方差。因此,LDA 通过执行如下所述的三个步骤将每个主题定义为一个单词包(图 5-8 )。
图 5-8
潜在狄利克雷分配
步骤 1:初始化 k 个簇,并将每个文档中的每个单词分配给 k 个主题中的一个。
步骤 2:根据 a)一个文档的单词与一个主题的比例,以及 b)一个主题在所有文档中的比例,将单词重新分配给一个新的主题。
第三步:重复第二步,直到产生连贯的主题。
清单 5-30 提供了实现 LDA 的示例代码。
from sklearn.decomposition import LatentDirichletAllocation
# continuing with the 20 newsgroup dataset and 3 topics
total_topics = 3
lda = LatentDirichletAllocation(n_components=total_topics,
max_iter=100,
learning_method='online',
learning_offset=50.,
random_state=2017)
lda.fit(X)
feature_names = np.array(vectorizer.get_feature_names())
for topic_idx, topic in enumerate(lda.components_):
print("Topic #%d:" % topic_idx)
print(" ".join([feature_names[i] for i in topic.argsort()[:-20 - 1:-1]]))
#----output----
Topic #0:
edu com writes subject lines organization
article posting university nntp host don like god uk ca just bike know graphics
Topic #1:
anl elliptical maier michael_maier qmgate separations imagesetter 5298 unscene appreshed linotronic l300 iici amnesia glued veiw halftone 708 252 dot
Topic #2:
hl7204 eehp22 raoul vrrend386 qedbbs choung qed daruwala ims kkt briarcliff kiat philabs col op_rows op_cols keeve 9327 lakewood gans
Listing 5-30Example Code for LDA
非负矩阵分解
NMF 是一种用于多元数据的分解方法,由 V = MH 给出,其中 V 是矩阵 W 和 H 的乘积。W 是特征中单词等级的矩阵,H 是系数矩阵,每行是一个特征。这三个矩阵没有负元素(列表 5-31 )。
from sklear.n.decomposition import NMF
nmf = NMF(n_components=total_topics, random_state=2017, alpha=.1, l1_ratio=.5)
nmf.fit(X)
for topic_idx, topic in enumerate(nmf.components_):
print("Topic #%d:" % topic_idx)
print(" ".join([feature_names[i] for i in topic.argsort()[:-20 - 1:-1]]))
#----output----
Topic #0:
edu com god writes article don subject lines organization just university bike people posting like know uk ca think host
Topic #1:
sgi livesey keith solntze wpd jon caltech morality schneider cco moral com allan edu objective political cruel atheists gap writes
Topic #2:
sun east green ed egreen com cruncher microsystems ninjaite 8302 460 rtp 0111 nc 919 grateful drinking pixel biker showed
Listing 5-31Example Code for Nonnegative Matrix Factorization
文本分类
将文本特征表示为数字的能力为运行分类 ML 算法提供了机会。让我们使用 20 个新闻组数据的子集来构建一个分类模型并评估其准确性(清单 5-32 )。
categories = ['alt.atheism', 'comp.graphics', 'rec.motorcycles', 'sci.space', 'talk.politics.guns']
newsgroups_train = fetch_20newsgroups(subset='train', categories=categories, shuffle=True, random_state=2017, remove=('headers', 'footers', 'quotes'))
newsgroups_test = fetch_20newsgroups(subset='test', categories=categories,
shuffle=True, random_state=2017, remove=('headers', 'footers', 'quotes'))
y_train = newsgroups_train.target
y_test = newsgroups_test.target
vectorizer = TfidfVectorizer(sublinear_tf=True, smooth_idf = True, max_df=0.5, ngram_range=(1, 2), stop_words="english")
X_train = vectorizer.fit_transform(newsgroups_train.data)
X_test = vectorizer.transform(newsgroups_test.data)
print("Train Dataset")
print("%d documents" % len(newsgroups_train.data))
print("%d categories" % len(newsgroups_train.target_names))
print("n_samples: %d, n_features: %d" % X_train.shape)
print("Test Dataset")
print("%d documents" % len(newsgroups_test.data))
print("%d categories" % len(newsgroups_test.target_names))
print("n_samples: %d, n_features: %d" % X_test.shape)
#----output----
Train Dataset
2801 documents
5 categories
n_samples: 2801, n_features: 241036
Test Dataset
1864 documents
5 categories
n_samples: 1864, n_features: 241036
Listing 5-32Example Code Text Classification on 20 News Groups Dataset
让我们构建一个简单的朴素贝叶斯分类模型,并评估其准确性。本质上,我们可以用任何其他分类算法来代替朴素贝叶斯,或者使用集成模型来建立一个有效的模型(清单 5-33 )。
from sklearn.naive_bayes import MultinomialNB
from sklearn import metrics
clf = MultinomialNB()
clf = clf.fit(X_train, y_train)
y_train_pred = clf.predict(X_train)
y_test_pred = clf.predict(X_test)
print ('Train accuracy_score: ', metrics.accuracy_score(y_train, y_train_pred))
print ('Test accuracy_score: ',metrics.accuracy_score(newsgroups_test.target, y_test_pred))
print ("Train Metrics: ", metrics.classification_report(y_train, y_train_pred))
print ("Test Metrics: ", metrics.classification_report(newsgroups_test.target, y_test_pred))
#----output----
Train accuracy_score: 0.9760799714387719
Test accuracy_score: 0.8320815450643777
Train Metrics: precision recall f1-score support
0 1.00 0.97 0.98 480
1 1.00 0.97 0.98 584
2 0.91 1.00 0.95 598
3 0.99 0.97 0.98 593
4 1.00 0.97 0.99 546
micro avg 0.98 0.98 0.98 2801
macro avg 0.98 0.98 0.98 2801
weighted avg 0.98 0.98 0.98 2801
Test Metrics: precision recall f1-score support
0 0.91 0.62 0.74 319
1 0.90 0.90 0.90 389
2 0.81 0.90 0.86 398
3 0.80 0.84 0.82 394
4 0.78 0.86 0.82 364
micro avg 0.83 0.83 0.83 1864
macro avg 0.84 0.82 0.83 1864
weighted avg 0.84 0.83 0.83 1864
Listing 5-33Example Code Text Classification Using Multinomial Naïve Bayes
情感分析
发现和分类一段文本(如评论/反馈文本)中表达的观点的过程被称为情感分析。这种分析的预期结果是确定作者对某个主题、产品、服务等的态度。是中性的、正的或负的(列表 5-34 )。
from nltk.sentiment.vader import SentimentIntensityAnalyzer
from nltk.sentiment.util import *
data = pd.read_csv('Data/customer_review.csv')
SIA = SentimentIntensityAnalyzer()
data['polarity_score']=data.Review.apply(lambda x:SIA.polarity_scores(x)['compound'])
data['neutral_score']=data.Review.apply(lambda x:SIA.polarity_scores(x)['neu'])
data['negative_score']=data.Review.apply(lambda x:SIA.polarity_scores(x)['neg'])
data['positive_score']=data.Review.apply(lambda x:SIA.polarity_scores(x)['pos'])
data['sentiment']=“
data.loc[data.polarity_score>0,'sentiment']='POSITIVE'
data.loc[data.polarity_score==0,'sentiment']='NEUTRAL'
data.loc[data.polarity_score<0,'sentiment']='NEGATIVE'
data.head()
data.sentiment.value_counts().plot(kind='bar',title="sentiment analysis")
plt.show()
#----output----
ID Review polarity_score
0 1 Excellent service my claim was dealt with very... 0.7346
1 2 Very sympathetically dealt within all aspects ... -0.8155
2 3 Having received yet another ludicrous quote fr... 0.9785
3 4 Very prompt and fair handling of claim. A mino... 0.1440
4 5 Very good and excellent value for money simple... 0.8610
neutral_score negative_score positive_score sentiment
0 0.618 0.000 0.382 POSITIVE
1 0.680 0.320 0.000 NEGATIVE
2 0.711 0.039 0.251 POSITIVE
3 0.651 0.135 0.214 POSITIVE
4 0.485 0.000 0.515 POSITIVE
Listing 5-34Example Code for Sentiment Analysis
深度自然语言处理(DNLP)
首先,让我澄清一下,DNLP 不会被误认为是深度学习 NLP。诸如主题建模之类的技术通常被称为浅层 NLP,其中您试图通过语义或句法分析方法从文本中提取知识(即,试图通过保留相似的词并在句子/文档中占据较高权重来形成组)。浅 NLP 比 n-gram 噪音小;然而,关键的缺点是,它没有指定项目在句子中的作用。相比之下,DNLP 侧重于语义方法。也就是说,它检测句子内的关系,并且进一步地,它可以被表示或表达为形式的复杂结构,例如句法分析的句子中的主语:谓语:宾语(称为三元组或三元组),以保留上下文。句子由参与者、动作、对象和命名实体(人、组织、地点、日期等)的任意组合组成。).例如,考虑句子“爆胎被司机换了。”这里“司机”是主语(演员),“被替换”是谓语(动作),“爆胎”是宾语。所以这个三元组就是 driver:replaced:tire,它抓住了句子的上下文。请注意,三元组是广泛使用的形式之一,您可以根据手头的领域或问题形成类似的复杂结构。
对于 ae 演示,我将使用 sopex 包,它使用了斯坦福核心 NLP 树解析器(清单 5-35 )。
from chunker import PennTreebackChunker
from extractor import SOPExtractor
# Initialize chunker
chunker = PennTreebackChunker()
extractor = SOPExtractor(chunker)
# function to extract triples
def extract(sentence):
sentence = sentence if sentence[-1] == '.' else sentence+'.'
global extractor
sop_triplet = extractor.extract(sentence)
return sop_triplet
sentences = [
'The quick brown fox jumps over the lazy dog.',
'A rare black squirrel has become a regular visitor to a suburban garden',
'The driver did not change the flat tire',
"The driver crashed the bike white bumper"
]
#Loop over sentence and extract triples
for sentence in sentences:
sop_triplet = extract(sentence)
print sop_triplet.subject + ':' + sop_triplet.predicate + ':' + sop_triplet.object
#----output----
fox:jumps:dog
squirrel:become:visitor
driver:change:tire
driver:crashed:bumper
Listing 5-35Example Code for Deep NLP
Word2Vec
谷歌的托马斯·米科洛夫(Tomas Mikolov)领导的团队在 2013 年创建了 Word2Vec(单词到向量)模型,该模型使用文档来训练神经网络模型,以最大化给定单词的上下文的条件概率。
它使用两种模型:CBOW 和 skip-gram。
图 5-9
2 窗口的跳过程序
-
连续单词袋(CBOW)模型从周围上下文单词的窗口中预测当前单词,或者在给定一组上下文单词的情况下,预测该上下文中可能出现的缺失单词。CBOW 的训练速度比 skip-gram 更快,对频繁出现的单词的准确率更高。
-
连续跳格模型使用当前单词预测上下文单词的周围窗口,或者给定一个单词,预测在该上下文中可能出现在它附近的其他单词的概率。众所周知,Skip-gram 对常用词和生僻字都有很好的效果。让我们看一个例句,为 2 的窗口创建一个跳转程序(图 5-9 )。用黄色突出显示的单词是输入单词。
你可以为 Word2Vec 下载谷歌的预训练模型(从以下链接),它包括从谷歌新闻数据集中的 1000 亿个单词中提取的 300 万个单词/短语。
URL: https://drive.google.com/file/d/0B7XkCwpI5KDYNlNUTTlSS21pQmM/edit
清单 5-36 提供了 Word2Vec 实现的示例代码。
import gensim
# Load Google's pre-trained Word2Vec model.
model = gensim.models. KeyedVectors.load_word2vec_format('Data/GoogleNews-vectors-negative300.bin', binary=True)
model.most_similar(positive=['woman', 'king'], negative=['man'], topn=5)
#----output----
[(u'queen', 0.7118192911148071),
(u'monarch', 0.6189674139022827),
(u'princess', 0.5902431607246399),
(u'crown_prince', 0.5499460697174072),
(u'prince', 0.5377321243286133)]
model.most_similar(['girl', 'father'], ['boy'], topn=3)
#----output----
[(u'mother', 0.831214427947998),
(u'daughter', 0.8000643253326416),
(u'husband', 0.769158124923706)]
model.doesnt_match("breakfast cereal dinner lunch".split())
#----output----
'cereal'
Listing 5-36Example Code for Word2Vec
可以在自己的数据集上训练一个 Word2Vec 模型。需要记住的关键模型参数是尺寸、窗口、最小计数和 sg(列表 5-37 )。
大小:向量的维数。较大的大小值需要更多的训练数据,但可以产生更精确的模型。
对于 CBOW 模型 sg = 0,对于 skip-gram 模型 SG = 1。
min_count:忽略总频率低于此的所有单词。
窗口:句子中当前单词和预测单词之间的最大距离。
sentences = [['cigarette','smoking','is','injurious', 'to', 'health'],['cigarette','smoking','causes','cancer'],['cigarette','are','not','to','be','sold','to','kids']]
# train word2vec on the two sentences
model = gensim.models.Word2Vec(sentences, min_count=1, sg=1, window = 3)
model.most_similar(positive=['cigarette', 'smoking'], negative=['kids'], topn=1)
#----output----
[('injurious', 0.16142114996910095)]
Listing 5-37Example Code for Training word2vec on Your Own Dataset
推荐系统
用户体验的个性化已经成为一个高度优先的事项,并成为以消费者为中心的行业的新口号。你可能已经观察到电子商务公司为你投放个性化广告,建议你购买什么,阅读哪些新闻,观看哪个视频,在哪里/吃什么,以及你可能有兴趣在社交媒体网站上与谁(朋友/专业人士)交往。推荐系统是核心的信息过滤系统,旨在预测用户偏好,并帮助推荐正确的项目,以创建用户特定的个性化体验。推荐系统有两种:1)基于内容的过滤和 2)协同过滤(图 5-10 )。
图 5-10
推荐系统
基于内容的过滤
这种类型的系统侧重于项目的相似性属性来给你推荐。通过一个例子可以很好地理解这一点:如果一个用户购买了某一特定类别的商品,来自同一类别的其他类似商品会被推荐给该用户(参见图 5-10 )。
基于项目的相似性推荐算法可以表示为:
)
协同过滤(CF)
CF 侧重于用户的相似性属性,即它基于一个相似性度量从大的用户群中找到具有相似品味的人。实践中有两种类型的 CF 实现:基于内存的和基于模型的。
基于记忆的类型主要基于相似度算法;该算法查看相似人喜欢的项目,以创建推荐的排序列表。然后,您可以对排名列表进行排序,向用户推荐前 n 个项目。
基于用户的相似性推荐算法可以表示为:
)
让我们考虑一个电影评级的示例数据集(图 5-11 ),并应用基于项目和基于用户的推荐来获得更好的理解。清单 5-38 提供了一个推荐系统的示例代码。
图 5-11
电影分级样本数据集
import numpy as np
import pandas as pd
df = pd.read_csv('Data/movie_rating.csv')
n_users = df.userID.unique().shape[0]
n_items = df.itemID.unique().shape[0]
print ('\nNumber of users = ' + str(n_users) + ' | Number of movies = ' + str(n_items))
#----output----
Number of users = 7 | Number of movies = 6
# Create user-item similarity matrices
df_matrix = np.zeros((n_users, n_items))
for line in df.itertuples():
df_matrix[line[1]-1, line[2]-1] = line[3]
from sklearn.metrics.pairwise import pairwise_distances
user_similarity = pairwise_distances(df_matrix, metric="cosine")
item_similarity = pairwise_distances(df_matrix.T, metric="cosine")
# Top 3 similar users for user id 7
print ("Similar users for user id 7: \n", pd.DataFrame(user_similarity).loc[6,pd.DataFrame(user_similarity).loc[6,:] > 0].sort_values(ascending=False)[0:3])
#----output----
Similar users for user id 7:
3 8.000000
0 6.062178
5 5.873670
# Top 3 similar items for item id 6
print ("Similar items for item id 6: \n", pd.DataFrame(item_similarity).loc[5,pd.DataFrame(item_similarity).loc[5,:] > 0].sort_values(ascending=False)[0:3])
#----output----
0 6.557439
2 5.522681
3 4.974937
Listing 5-38Example Code for a Recommender System
让我们将基于用户的预测和基于项目的预测公式构建为一个函数。应用此函数来预测评级,并使用均方根误差(RMSE)来评估模型性能(列表 5-39 )。
# Function for item based rating prediction
def item_based_prediction(rating_matrix, similarity_matrix):
return rating_matrix.dot(similarity_matrix) / np.array([np.abs(similarity_matrix).sum(axis=1)])
# Function for user based rating prediction
def user_based_prediction(rating_matrix, similarity_matrix):
mean_user_rating = rating_matrix.mean(axis=1)
ratings_diff = (rating_matrix - mean_user_rating[:, np.newaxis])
return mean_user_rating[:, np.newaxis] + similarity_matrix.dot(ratings_diff) / np.array([np.abs(similarity_matrix).sum(axis=1)]).T
item_based_prediction = item_based_prediction(df_matrix, item_similarity)
user_based_prediction = user_based_prediction(df_matrix, user_similarity)
# Calculate the RMSE
from sklearn.metrics import mean_squared_error
from math import sqrt
def rmse(prediction, actual):
prediction = prediction[actual.nonzero()].flatten()
actual = actual[actual.nonzero()].flatten()
return sqrt(mean_squared_error(prediction, actual))
print ('User-based CF RMSE: ' + str(rmse(user_based_prediction, df_matrix)))
print ('Item-based CF RMSE: ' + str(rmse(item_based_prediction, df_matrix)))
#----output----
User-based CF RMSE: 1.0705767849
Item-based CF RMSE: 1.37392288971
y_user_based = pd.DataFrame(user_based_prediction)
# Predictions for movies that the user 6 hasn't rated yet
predictions = y_user_based.loc[6,pd.DataFrame(df_matrix).loc[6,:] == 0]
top = predictions.sort_values(ascending=False).head(n=1)
recommendations = pd.DataFrame(data=top)
recommendations.columns = ['Predicted Rating']
print (recommendations)
#----output----
Predicted Rating
1 2.282415
y_item_based = pd.DataFrame(item_based_prediction)
# Predictions for movies that the user 6 hasn't rated yet
predictions = y_item_based.loc[6,pd.DataFrame(df_matrix).loc[6,:] == 0]
top = predictions.sort_values(ascending=False).head(n=1)
recommendations = pd.DataFrame(data=top)
recommendations.columns = ['Predicted Rating']
print (recommendations)
#----output----
Predicted Rating
5 2.262497
Listing 5-39Example Code for Recommender System and Accuracy Evaluation
基于每个用户,推荐电影《星际穿越》(索引号 5)。根据项目,推荐的电影是《复仇者联盟》(索引号 1)。
基于模型的 CF 是基于矩阵分解(MF)的,如奇异值分解和 NMF 等。让我们看看如何使用 SVD 实现(清单 5-40 )。
# calculate sparsity level
sparsity=round(1.0-len(df)/float(n_users∗n_items),3)
print 'The sparsity level of is ' + str(sparsity∗100) + '%'
import scipy.sparse as sp
from scipy.sparse.linalg import svds
# Get SVD components from train matrix. Choose k.
u, s, vt = svds(df_matrix, k = 5)
s_diag_matrix=np.diag(s)
X_pred = np.dot(np.dot(u, s_diag_matrix), vt)
print ('User-based CF MSE: ' + str(rmse(X_pred, df_matrix)))
#----output----
The sparsity level of is 0.0%
User-based CF MSE: 0.015742898995
Listing 5-40Example Code for Recommender System Using SVD
注意,在我们的例子中,数据集很小,因此稀疏度是 0%。推荐你在 MovieLens 100k 数据集上试试这个方法,可以从 https://grouplens.org/datasets/movielens/100k/
下载。
摘要
在这一步中,您学习了文本挖掘过程的基础,以及从各种文件格式中提取文本的不同工具/技术。您还了解了从数据中去除噪声的基本文本预处理步骤,以及更好地理解手头语料库的不同可视化技术。然后,您学习了各种模型,这些模型可以用来理解关系并从数据中获得洞察力。
我们还学习了两种重要的推荐系统方法,如基于内容的过滤和协同过滤。