11.1 试编程实现Relief算法,并考察其在西瓜数据集3.0上的运行结果。
本题采用Relief算法处理二分类任务,虽然书上只要求对连续属性归一化,但我将离散属性的值转化为了1,2,3,如果不对离散属性归一化,显然在查找近邻时连续属性不能有效发挥作用,因此需要将数据的离散属性和连续属性都进行归一化。另外,在计算连续属性的相关统计量时,本题是二元分类,因此可以对书上公式11.3进行化简,得到下式,可稍微简化计算:
显然,本题主要分为三步:1、数据归一化;2、求取各点近邻near-hit和near-miss;3、求得相关统计量。
由输出结果可见,分类能力最强的属性是纹理,其次是根蒂,……很明显,与其他人用未归一化离散属性的结果有所差异,这也说明此方法采用距离得到近邻来计算统计量,在维数较高,数据复杂时的效果可能不是太好。
代码如下:
# -*- coding: utf-8 -*-
# 特征选择方法:Relief
import numpy as np
label = {0:'色泽', 1:'根蒂', 2:'敲声', 3:'纹理', 4:'脐部', 5:'触感', 6:'密度', 7:'含糖率'}
D = np.array([
[1, 1, 1, 1, 1, 1, 0.697, 0.460, 1],
[2, 1, 2, 1, 1, 1, 0.774, 0.376, 1],
[2, 1, 1, 1, 1, 1, 0.634, 0.264, 1],
[1, 1, 2, 1, 1, 1, 0.608, 0.318, 1],
[3, 1, 1, 1, 1, 1, 0.556, 0.215, 1],
[1, 2, 1, 1, 2, 2, 0.403, 0.237, 1],
[2, 2, 1, 2, 2, 2, 0.481, 0.149, 1],
[2, 2, 1, 1, 2, 1, 0.437, 0.211, 1],
[2, 2, 2, 2, 2, 1, 0.666, 0.091, 0],
[1, 3, 3, 1, 3, 2, 0.243, 0.267, 0],
[3, 3, 3, 3, 3, 1, 0.245, 0.057, 0],
[3, 1, 1, 3, 3, 2, 0.343, 0.099, 0],
[1, 2, 1, 2, 1, 1, 0.639, 0.161, 0],
[3, 2, 2, 2, 1, 1, 0.657, 0.198, 0],
[2, 2, 1, 1, 2, 2, 0.360, 0.370, 0],
[3, 1, 1, 3, 3, 1, 0.593, 0.042, 0],
[1, 1, 2, 2, 2, 1, 0.719, 0.103, 0]])
m = len(D)
# 数据归一化
temp = D[:,:-1]
D[:,:-1] = (temp-np.min(temp,axis=0)) / np.ptp(temp, axis=0)
# 按照顺序保存各样本的nh和nm近邻
data = D[:,:-1]
nh_set, nm_set = [],[]
for i in range(m):
data -= data[i,:]
li = np.argsort(np.linalg.norm(data, axis=1))
for order in range(1,m): # li[0]代表其本身的id,因此索引应该从1开始
if D[li[order],-1] == D[i,-1]:
nh_set.append(li[order])
break
for order in range(1,m): # li[0]代表其本身的id,因此索引应该从1开始
if D[li[order],-1] != D[i,-1]:
nm_set.append(li[order])
break
# 计算相关统计量
n = len(label)
score = [0]*n
for attr in range(n-2):
for i in range(m):
if data[i, attr] != data[nh_set[i], attr]:
score[attr] -= 1
if data[i, attr] != data[nm_set[i], attr]:
score[attr] += 1
for attr in [-2,-1]:
for i in range(m):
a = 2*data[i, attr] - data[nh_set[i], attr] - data[nm_set[i], attr]
b = data[nh_set[i], attr] - data[nm_set[i], attr]
score[attr] += a*b
# 由大到小输出各个特征
output = sorted([(label[i],k) for i,k in enumerate(score)], key=lambda li: -li[1])
print(output)
输出如下:
[('纹理', 11), ('根蒂', 4), ('含糖率', 1.7657963416588451), ('敲声', 1),
('脐部', 0), ('密度', -0.60651295746574851), ('触感', -2), ('色泽', -5)]