机器学习 - 特征选择

1. 子集搜索与评价

我们可以使用很多属性来描述一个对象,比如对于一个西瓜,色泽,根蒂,敲声,触感,纹理等等。如果我们需要判断一个瓜是否是好瓜,那么可能仅仅需要敲声,颜色,根蒂就够了,而没有必要带入过多的特征。(这通常是在较小数据下,保证能获得较好效果的同时节约计算资源和时间;但是在大数据情况下,很多可能看似无用的特征,有可能含有隐藏模式;因为大数据不仅仅是量很大,而且数据多样性很好。)

相关特征:对当前学习有用的特征。

无关特征:对当前学习无用的特征。

冗余特征:当前特征能由其他特征组合变换出来,比如对西瓜而言,已经知道了质量和体积,就不需要再把密度作为特征了。


1.1 子集搜索

通过对所有特征构成的集合进行筛选,称为子集搜索;然后暴力的子集搜索会导致组合爆炸问题,是绝对不可行的。因此常用的是贪心的方法:前向搜索,后向搜索

贪心的子集搜索通常都是根据业务背景寻找一个比较可靠的特征子集baseline,前向搜索就是逐渐向这个baseline添加特征,然后对子集进行评判;否则不添加该特征;显然前向搜索是一个特征逐渐增多的过程。后向搜索是baseline就是所有特征,然后不断的去掉特征,然后对子集评价,变差了就保留当前特征,否则去掉当前特征。


1.2 子集评价

对子集搜索之后就需要对子集进行评价,然后选出最具价值的子集。其基本思想就是按照当前特征子集对整个数据集进行划分;在每一种划分里面计算熵,所有划分加权求和得到当前特征子集下的熵。数据集的熵 - 当前特征子集的熵 = 信息增益。信息增益越大,代表当前特征子集能提供更好的可分性。

# -*- coding: utf-8 -*-
# 计算当前数据集特征子集(所有特征)的信息增益
import numpy as np

path = "../select_features/data/"

def gen_data():
    """ 生成一个标称型数据集 """
    np.random.seed(1)
    train_x = np.random.randint(0, 2, (100, 3))
    train_y = np.random.randint(0, 2, (100, 1))
    train = np.concatenate((train_x, train_y), axis=1)
    return train

def cal_entropy(train, array, mode):
    """ 计算熵 """
    if mode == "all":
        current_small_dataSet = train
    else:
        condition = (train[:, 0] == array[0]) & (train[:, 1] == array[1]) & (train[:, 2] == array[2])
        current_small_dataSet = train[condition]
    
    sample_cnt = len(current_small_dataSet)
    sample_0 = sum(current_small_dataSet[:, 3] == 0)
    sample_1 = sample_cnt - sample_0
    
    # 计算平均熵
    P0 = sample_0 / sample_cnt; P1 = sample_1 / sample_cnt
    return -(sample_cnt / len(train)) * (P0 * np.log2(P0) + P1 * np.log2(P1))
    
    
def cal_infoGain(train):
    """ 选择特征
   由于有三个特征, 每个特征有两个值, 因此能够将原始数据集划分为8个子数据集; 
   对每一个子数据集计算一次熵, 8个熵加权求和就是划分之后的熵;
    1. {0, 0, 0};
    2. {0, 0, 1};
    3. {0, 1, 0};
    4. {0, 1, 1};
    5. {1, 0, 0};
    6. {1, 0, 1};
    7. {1, 1, 0};
    8. {1, 1, 1};
    """
    arr = np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], 
                   [1, 0, 0], [1, 0, 1],[1, 1, 0], [1, 1, 1]])
    
    # 1. 计算整个数据集的熵
    Ent = cal_entropy(train, arr, "all")
    for i in range(len(arr)):
        # 2. 计算按照当前特征子集对数据集进行划分, 得到的加权熵
        Ent = Ent - cal_entropy(train, arr[i], None)
    return Ent


if __name__ == "__main__":
    train = gen_data()
    infoGain = cal_infoGain(train)


2. 过滤式选择

过滤式特征选择先对数据集进行特征选择,然后在训练模型。Relief - relevant features算法是一种著名的过滤式特征选择算法。该算法引入了一个长度为特征个数的向量,向量中的每个值代表特征的重要性。算法步骤:

1)初始化特征重要性向量,初始化特征重要性调节步长;

2)随机抽取一部分样本;

3)遍历所有特征,对每一个特征按4)调节对应的特征重要性向量;

4)对部分样本中的每一个样本xi,找出该样本在所有同类样本中最相似的一个xt,找出该样本在所有异类样本中最相似的一个xy,计算xi到xt的距离,xi到xy的距离。如果xi到xt的距离大于xi到xy的距离,则减小该特征的重要性。反之,增大。

# -*- coding: utf-8 -*-
# 实现relief算法, 并考察其在西瓜数据集3.0上的运行结果
import os
import pickle
import numpy as np
import pandas as pd

path = "../select_features/data/"

def load_data():
    df = pd.read_excel(path + "watermelon_3.0.xlsx", header=None)
    watermelon = df.values.T
    np.random.seed(1)
    watermelon[:, 2] = np.random.randint(0, 2, (len(watermelon), ))
    return watermelon

def cal_distance(x1, x2, is_numerical):
    """ 计算x1, x2之间的距离 
    x1:             样本x1
    x2:             样本x2
    is_numerical:   如[1, 1, 0], 表示前面两列是数值型数据, 第三列是标称型数据
    """
    distance = 0
    for i in range(len(is_numerical)):
        if is_numerical[i] == 1:
            distance += abs(x1[i] - x2[i])
        else:
            if x1[i] != x2[i]:
                distance += 1
    return distance

def nearest_sample(train):
    """ 计算每个样本的最邻近样本
    train:      训练数据, 最后一列为label
    distance:   key为样本, value = [0类样本到当前样本的最小距离, 1类样本到当前样本的最小距离]
    """
    if os.path.exists(path + "distance.pkl"):
        return pickle.load(open(path + "distance.pkl", "rb"))
    else:
        distance = {}
        distance_0 = float("inf"); distance_1 = float("inf")
        length, features = train.shape
        for i in range(length):
            for j in range(length):
                if i != j:
                    temp = cal_distance(train[i, :features-1], train[j, :features-1])
                    if train[j, features-1] == 0:
                        if temp < distance_0:
                            distance_0 = temp
                    if train[j, features-1] == 1:
                        if temp < distance_1:
                            distance_1 = temp
            distance[i] = [distance_0, distance_1]
        pickle.dump(distance, open(path + "distance.pkl", "wb"))
    return distance


if __name__ == "__main__":
    # 1. 导入数据
    watermelon = load_data()
    
    # 1. 初始化
    samples, features = watermelon.shape
    is_numerical = [1, 1, 0]    # 是否数值型数据
    fea_importance = [1, 1, 1]  # 特征重要性
    step = 0.1                  # 特征重要性调节步长
    
    # 2. 对数值型数据归一化
    for i in is_numerical:
        if i == 1:
            watermelon[:, i] = (watermelon[:, i] - min(watermelon[:, i])) / (max(watermelon[:, i]) - min(watermelon[:, i]))
    
    # 3. 调节特征重要性向量
    distance = nearest_sample(watermelon)
    for i in range(features-1):
        for j in range(samples):
            # 找出同类样本中最小距离
            distance_t = distance[j][int(watermelon[i, features-1])]
            
            # 找出异类样本中最小距离
            distance_y = distance[j][1 - int(watermelon[i, features-1])]
            if distance_t >= distance_y:
                fea_importance[i] -= step
            else:
                fea_importance[i] += step
貌似这个西瓜数据集有点问题,和书上的不太一样。


3. 包裹式选择

包裹式选择和过滤式选择不一样的地方在于,直接使用学习器的结果作为特征子集的评价标准。缺点是带来了非常大的计算量。


4. 嵌入式选择与L1正则化

嵌入式特征选择是将特征选择与学习器的学习过程结合在一起。lasso是这类算法的典型代表,通过加入一范数作为正则项,可以使得许多特征的系数向0靠近,相当于进行了特征选择。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值