数据挖掘(一)
文章目录
数据挖掘旨在让计算机根据已有数据做出决策。决策可以是预测明天的天气、拦截垃圾邮件、检测网站的语言,或者在约会网站上发现新的恋爱对象。数据挖掘方面的应用已经有很多,新的应用也在源源不断地出现。数据挖掘涉及算法、统计学、工程学、最优化理论和计算机科学相关领域的知识。除此之外,我们还会用到语言学、神经科学、城市规划等其他领域的概念或知识。要想充分发挥数据挖掘的威力,通常需要在算法中整合这些属于特定领域的知识。
数据挖掘的第一步一般是创建数据集。数据集能够描述真实世界的某一方面。数据集主要包括两个部分:
- 表示真实世界中物体的样本。样本可以是一本书,一张照片,一个人或是其他任何物体。
- 描述数据集中样本的特征。特征可以是长度、单词频率、创建时间等。
接下来是调整算法。每种数据挖掘算法都有参数,它们或者是算法自身包含的,或者是使用者添加的。这些参数会影响算法的具体决策。抽取特征是数据挖掘过程的一个重要环节。
scikit-learn
是用Python开发的机器学习库,它包含大量机器学习算法、数据集、工具和框架。它以Python科学计算的相关工具集为基础,其中numpy
和scipy
等都针对数据处理任务进行过优化,因此速度快、扩展性强。采用anaconda工具,自带scikit-learn
库。
亲和性分析示例
数据挖掘有个常见的应用场景。顾客在购买一件商品时,商家可以趁机了解他们还想买什么,以便把多数顾客愿意同时购买的商品放到一起销售以提升销售额。当商家收集到足够多的数据时,就可以对其进行亲和性分析,已确定哪些商品适合放在一起出售。
- 亲和性分析根据样本个体之间的相似度,确定它们之间的亲疏。亲和性分析的应用场景如下:
- 向网站用户提供多样化的服务或投放定向广告。
- 为了向用户推荐商品,而卖给他们一些与之相关的小玩意儿。
- 根据基因寻找有亲缘关系的人。
- 亲和性有多种测量方法,比如统计两件商品一起出售的频率或者顾客购买了商品1后再买商品2的比率等等。
- 商品推荐服务背后的思路:人们之前经常同时购买的两件商品,以后也很可能会同时购买,无论是线上还是线下。这种想法很容易转化为算法,顾客购买商品后,再向他们推荐商品前,先查询一下历史交易数据,找到以往他们购买同样商品的交易数据,看看同时购买了什么,再把它们推荐给顾客即可。
- 数据挖掘入门性质的实例,只考虑一次购买两种商品的情况。如果一个人买了商品X,那么他很有可能购买商品Y。
- 在
numpy
中加载数据集
python
import numpy as np
dataset_filename = 'affinity_dataset.txt'
X = np.loadtxt(dataset_filename)
n_samples, n_features = X.shape
print('数据集有{0}个样本和{1}个特征'.format(n_samples, n_features))
数据集有100个样本和5个特征
- 查看数据集前5行,运行结果为前5次交易中顾客都买了什么,横着看,每次看一行,第一行表示第一条交易数据所包含的商品。竖着看,每一列代表一种商品。以面包、牛奶、奶酪、苹果和香蕉五种商品为例,从第一条交易数据中,我们可以看到顾客购买了奶酪、苹果和香蕉。每个特征只有两个可能的值0或1,表示是否购买了商品。
shell
features = ["面包", "牛奶", "奶酪", "苹果", "香蕉"]
print(X[:5])
\[0. 0. 1. 1. 1.
1. 1. 0. 1. 0.
1. 0. 1. 1. 0.
0. 0. 1. 1. 1.
0. 1. 0. 0. 1.\]
- 实现简单的排序规则,我们要找出"如果一个人买了商品X,那么他很有可能购买商品Y"的规则,简单的方法是找出数据集中所有同时购买的两件商品。找出规则后判断其优劣,我们挑好的规则用。一条规则由前提条件和结论两部分组成。规则的优劣常用的衡量方法是支持度和置信度。
- 支持度指数据集中规则应验次数,有时候还需要对支持度进行规范化,也就是再除以规则有效前提下的总数量。支持度衡量的是给定规则应验的比例。
- 置信度衡量的是规则准确率如何,也就是符合给定条件的所有规则里,和当前规则结论一致的比例有多大。计算方法为首先统计当前规则的出现次数,再用它来除以条件相同的规则数量。
- 求"如果顾客购买了苹果,他们也会购买香蕉"这条规则的支持度和置信度。
- 通过检测sample[3]的值是否为1,可以确定顾客是否买了苹果;同理检测sample[4]的值可以确定顾客是否买了香蕉。
python
num_apple_purchases = 0
for sample in X:
if sample[3] == 1:
num_apple_purchases += 1
print("{0}人买了苹果".format(num_apple_purchases))
36人买了苹果
- 我们需要统计数据集中所有规则的相关数据。首先分别为规则应验和规则无效这两种情况创建字典。字典的键是由条件和结论组成的元组,元祖元素为特征在特征列表中的索引值,不要用实际特征名。比如"如果顾客购买了苹果,他们也会购买香蕉"就用(3, 4)表示。如果某个个体的条件和结论均与给定规则相符,就表示给定规则对该个体适用。
shell
rule_valid = 0
rule_invalid = 0
for sample in X:
if sample[3] == 1:
if sample[4] == 1:
rule_valid += 1
else:
rule_invalid += 1
print("发现{0}条规则有效,{1}条规则无效".format(rule_valid, rule_invalid))
发现21条规则有效,15条规则无效
python
support = rule_valid
confidence = rule_valid / num_apple_purchases
print('支持度是{0},置信度是{1:.3f}'.format(support, confidence))
print('换成百分比是{0:.1f}%'.format(100*confidence))
支持度是21,置信度是0.583
换成百分比是58.3%
- 为了计算所有规则的置信度和支持度,创建几个字典用来存放计算结果,需要统计的量有规则应验、规则无效和条件相同的规则数量。
python
# defaultdict如果查找的键不存在,返回一个默认值
from collections import defaultdict
valid_rules = defaultdict(int)
invalid_rules = defaultdict(int)
num_occurences = defaultdict(int)
# 对样本的每个个体及个体的每个特征值进行处理
for sample in X:
for premise in range(n_features):
# 第一个特征premise为规则的前提条件------顾客购买了某种商品
# 如果条件满足,该条件的出现次数加1;不满足,继续监测下一个
if sample[premise] == 0: continue
num_occurences[premise] += 1
for conclusion in range(n_features):
# 跳过条件和结论相同的情况
if premise == conclusion:
continue
# 如果规则应验,就增加一次
if sample[conclusion] == 1:
valid_rules[(premise, conclusion)] += 1
else:
invalid_rules[(premise, conclusion)] += 1
support = valid_rules
confidence = defaultdict(float)
for premise, conclusion in valid_rules.keys():
confidence[(premise, conclusion)] = valid_rules[(premise, conclusion)] / num_occurences[premise]
for premise, conclusion in confidence:
premise_name = features[premise]
conclusion_name = features[conclusion]
print('规则:如果顾客买了{0},他们也会购买{1}'.format(premise_name, conclusion_name))
print('- 置信度: {0:.3f}'.format(confidence[(premise, conclusion)]))
print('- 支持度:{0}'.format(support[(premise, conclusion)]))
部分输出结果如下:
规则:如果顾客买了cheese,他们也会购买apples
-
置信度: 0.610
-
支持度:25
python
# 声明函数接收参数有前提条件和结论的特征索引值、支持度字典、置信度字典以及特征列表
# 输出每条规则及其支持度和置信度
def print_rule(premise, conclusion, support, confidence, features):
premise_name = features[premise]
conclusion_name = features[conclusion]
print("规则:如果顾客买了{0},他们也会购买{1}".format(premise_name, conclusion_name))
print(" - 置信度: {0:.3f}".format(confidence[(premise, conclusion)]))
print(" - 支持度: {0}".format(support[(premise, conclusion)]))
premise = 1
conclusion = 3
print_rule(premise, conclusion, support, confidence, features)
- 得到所有规则的支持度和置信度后,为了找出最佳规则,还需要根据支持度和置信度对规则进行排序。字典的items函数返回包含字典所有元素的列表。首先对支持度字典进行排序,字典中的元素默认没有前后顺序,itemgetter()类作为键,可以对嵌套列表进行排序,itemgetter(1)表示以字典各元素的值即支持度作为排序依据。同理,我们可以输出置信度最高的规则。
python
from pprint import pprint
pprint(list(support.items()))
((2, 3), 25), ((2, 4), 27), ((3, 2), 25), ((3, 4), 21), ((4, 2), 27), ((4, 3), 21), ((0, 1), 14), ((0, 3), 5), ((1, 0), 14), ((1, 3), 9), ((3, 0), 5), ((3, 1), 9), ((0, 2), 4), ((2, 0), 4), ((1, 4), 19), ((4, 1), 19), ((0, 4), 17), ((4, 0), 17), ((1, 2), 7), ((2, 1), 7)
python
from operator import itemgetter
sorted_support = sorted(support.items(), key=itemgetter(1), reverse=True)
# 输出支持度最高的前3条规则
for index in range(5):
print('规则 #{0}'.format(index+1))
(premise, conclusion) = sorted_support[index][0]
print_rule(premise, conclusion, support, confidence, features)
规则 #1
规则:如果顾客买了奶酪,他们也会购买香蕉- 置信度: 0.659 - 支持度: 27
规则 #2
规则:如果顾客买了香蕉,他们也会购买奶酪- 置信度: 0.458 - 支持度: 27
规则 #3
规则:如果顾客买了奶酪,他们也会购买苹果- 置信度: 0.610 - 支持度: 25
分类问题的简单示例
在亲和性分析示例中,我们是寻找数据集中不同变量之间的相关性。而分类问题,只关注类别这个变量。如果我们关心的是怎么让顾客买更多的苹果,就可以把是否购买苹果作为类别,使用分类方法 ,只寻找促成顾客购买苹果的规则。分类应用的目标是根据已知类别的数据集,经过训练得到一个分类的模型,再用模型对类别位置的数据进行分类。
- 我们使用Iris植物分类数据集,
scikit-learn
库中内置了,该数据集中每条数据有四个特征sepal length、sepal width、petal length、petal with,分别表示萼片和花瓣的长与宽,单位是厘米。该数据集有三种类别:山鸢尾(Iris Setosa)、变色鸢尾(Iris Versicolour)和维吉尼亚鸢尾(Iris Virginica)。我们通过分类的目的是根据植物的特征推测它的种类。
python
from sklearn.datasets import load_iris
dataset = load_iris()
X = dataset.data
y = dataset.target
# 查看数据集
print(dataset.DESCR)
# 150, 4
n_samples, n_features = X.shape
- 数据集中各特征值是连续型,也就是有无数个可能的值,如果两个值相近,表示相似度很大。类别的取值是离散型,类别值不能根据数值大小比较相似性。Iris数据集用不同的数字表示不同的类别。
python
# 简单的离散化算法,确定一个阈值,将低于阈值的特征值置为0,高于阈值的置为1,把某项特征的阈值设定为该特征所有特征值的均值
import numpy as np
# mean(axis=0)从上往下按列计算平均值,axis=1则是从左往右按行计算
attribute_means = X.mean(axis=0)
# 得到一个长度为4的数组,正好是特征的数量,数组的第一项是第一个特征的均值
assert attribute_means.shape == (n_features,)
# 把数据集打散,将连续的特征值转换为类别值
X_d = np.array(X >= attribute_means, dtype='int')
- 实现OneR算法
-
OneR(One Rule,一条规则)算法思路是根据已有数据中具有相同特征值的特体最可能属于哪个类别进行分类,我们只选取四个特征中分类效果最好的一个用作分类依据。
-
算法首先遍历每个特征的每一个取值,对于每一个特征值,统计它在各个类别中的出现次数,找到它出现次数最多的类别,并统计它在其他类别中的出现次数。计算每个特征值的错误率,方法为把它的各个取值的错误率相加,选取错误率最低的特征作为唯一的分类准则,用于接下来的分 类。
python
from sklearn import model_selection
# train_test_split把数组或矩阵分成训练和测试子集
# test_size如果是浮点数需要在0.0~1.0之间表示在测试分成的子集的比例,如果是整数表示测试样例的个数,如果没有设置,则和训练train大小一致,如果都没有设置,则为0.25
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=0.3)
print('一共有{0}个训练样本,{1}个测试样本'.format(y_train.shape, y_test.shape))
一共有(105,)个训练样本,(45,)个测试样本
python
from collections import defaultdict
from operator import itemgetter
def train(X, y_true, feature):
'''使用OneR算法计算给定特征的预测值和误差
X: array [n_samples, n_features] 保存数据集的二维数组,每一行是一个样本,每一列是一个特征
y_true: array [n_samples,] 保存类值的一维数组, 对应于X, 这样y_true[i]是样本X[i]的类值
feature: 一个整数,对应于我们要测试的变量的索引
'''
n_samples, n_features = X.shape
# 检查feature是不是一个有效数字
assert 0 <= feature < n_features
# 获取变量的所有唯一值
values = set(X[:, feature])
# 存储返回的条件数组,预测器字典的键是特征值,值为类别
predictors = dict()
errors = []
for current_value in values:
most_frequent_class, error = train_feature_value(X, y_true, feature, current_value)
predictors[current_value] = most_frequent_class
errors.append(error)
# 计算使用此特征进行分类的总误差
total_error = sum(errors)
return predictors, total_error
# 根据待预测数据的某项特征值预测类别并给出错误率
# 参数是数据集、类别数组、选好的特征索引值和特征值
def train_feature_value(X, y_true, feature, value):
# 创建字典统计给出某些预测的频率
class_counts = defaultdict(int)
# 遍历每个样本计算每个分类/值对的频率
for sample, y in zip(X, y_true):
if sample[feature] == value:
class_counts[y] += 1
# 对class_counts字典排序找到最大值,就能找出具有给定特征值的个体在哪个类别中出现次数最多
sorted_class_counts = sorted(class_counts.items(), key=itemgetter(1), reverse=True)
most_frequent_class = sorted_class_counts[0][0]
n_samples = X.shape[1]
# 计算规则的错误率
error = sum([class_count for class_value, class_count in class_counts.items()
if class_value != most_frequent_class ])
return most_frequent_class, error
all_predictors = {variable: train(X_train, y_train, variable) for variable in range(X_train.shape[1])}
errors = {variable: error for variable, (mapping, error) in all_predictors.items()}
best_variable, best_error = sorted(errors.items(), key=itemgetter(1))[0]
print('最好的模型是基于变量{0},误差是{1:.2f}'.format(best_variable, best_error))
model = {'variable': best_variable, 'predictor': all_predictors[best_variable][0]}
print(model)
最好的模型是基于变量3,误差是4.00
{'variable': 3, 'predictor': {0.2: 0, 1.0: 1, 0.3: 0, 1.4: 1, 1.5: 1, 1.8: 2, 1.2: 1, 1.3: 1, 2.3: 2, 1.9: 2, 2.0: 2, 2.1: 2, 2.5: 2, 1.6: 1, 1.1: 1, 1.7: 2, 0.1: 0, 2.2: 2, 0.4: 0, 2.4: 2}}
- 我们把机器学习流程分为训练和测试两个阶段。在训练阶段,我们从数据集中取一部分数据创建模型。在测试阶段,我们测试模型在数据集上的分类效果。考虑到模型的目标是对新个体进行分类,因此不能使用测试数据训练模型,可能会导致过拟合问题。
- 过拟合是指模型在训练集上表现很好,但对于没有见过的数据表现很差。我们可以简单处理,把数据集分为两个小部分,分别用于训练和测试。
python
# 一次对多条数据进行预测
def predict(X_test, model):
variable = model['variable']
predictor = model['predictor']
y_predicted = np.array([predictor[int(sample[variable])]] for sample in X_test)
return y_predicted
y_predicted = predict(X_test, model)
accuracy = np.mean(y_predicted == y_test)*100
print('测试正确率:{:.1f}%'.format(accuracy))
测试正确率:95.6%
完整实例
python
import numpy as np
from sklearn.datasets import load_iris
from sklearn import model_selection
from collections import defaultdict
from operator import itemgetter
def train(X, y_true, feature):
'''使用OneR算法计算给定特征的预测值和误差'''
n_samples, n_features = X.shape
assert 0 <= feature < n_features
values = set(X[:, feature])
predictors = dict()
errors = []
for current_value in values:
most_frequent_class, error = train_feature_value(X, y_true, feature, current_value)
predictors[current_value] = most_frequent_class
errors.append(error)
total_error = sum(errors)
return predictors, total_error
def train_feature_value(X, y_true, feature, value):
class_counts = defaultdict(int)
for sample, y in zip(X, y_true):
if sample[feature] == value:
class_counts[y] += 1
sorted_class_counts = sorted(class_counts.items(), key=itemgetter(1), reverse=True)
most_frequent_class = sorted_class_counts[0][0]
error = sum([class_count for class_value, class_count in class_counts.items() if class_value != most_frequent_class])
return most_frequent_class, error
def predict(X_test, model):
variable = model['variable']
predictor = model['predictor']
y_predicted = np.array([predictor[int(sample[variable])] for sample in X_test])
return y_predicted
dataset = load_iris()
X = dataset.data
y = dataset.target
#print(dataset.DESCR)
n_samples, n_features = X.shape
print('样本数:{0},特征数:{1}'.format(n_samples, n_features))
# axis:0从上往下计算列的平均值
attribute_means = X.mean(axis=0)
assert attribute_means.shape == (n_features,)
# 离散化:每列的大于平均值为1,否则为0
X_d = np.array(X >= attribute_means, dtype='int')
#print('离散后的数组:')
#print(X_d)
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=0.3)
print('共有{0}个训练样本,{1}个测试样本'.format(y_train.shape, y_test.shape))
all_predictors = {variable: train(X_train, y_train, variable) for variable in range(X_train.shape[1])}
errors = {variable: error for variable, (mapping, error) in all_predictors.items()}
best_variable, best_error = sorted(errors.items(), key=itemgetter(1))[0]
print('最好的模型是基于变量{0},误差是{1:.2f}'.format(best_variable, best_error))
model = {'variable': best_variable, 'predictor': all_predictors[best_variable][0]}
print(model)
y_predicted = predict(X_test, model)
accuracy = np.mean(y_predicted == y_test)*100
print('测试正确率:{:.1f}%'.format(accuracy))
est_variable, best_error = sorted(errors.items(), key=itemgetter(1))[0]
print('最好的模型是基于变量{0},误差是{1:.2f}'.format(best_variable, best_error))
model = {'variable': best_variable, 'predictor': all_predictors[best_variable][0]}
print(model)
y_predicted = predict(X_test, model)
accuracy = np.mean(y_predicted == y_test)*100
print('测试正确率:{:.1f}%'.format(accuracy))