Relief特征提取算法实战

简介及其演变

Relief算法最早由Kira提出,最初局限于两类数据的分类问题。Relief算法是一种特征权重算法(Feature weighting algorithms),根据各个特征和类别的相关性赋予特征不同的权重,权重小于某个阈值的特征将被移除。Relief算法中特征和类别的相关性是基于特征对近距离样本的区分能力。算法从训练集D中随机选择一个样本R,然后从和R同类的样本中寻找最近邻样本H,称为Near Hit,从和R不同类的样本中寻找最近邻样本M,称为NearMiss,然后根据以下规则更新每个特征的权重:如果R和Near Hit在某个特征上的距离小于R和Near Miss上的距离,则说明该特征对区分同类和不同类的最近邻是有益的,则增加该特征的权重;反之,如果R和Near Hit在某个特征的距离大于R和Near Miss上的距离,说明该特征对区分同类和不同类的最近邻起负面作用,则降低该特征的权重。以上过程重复m次,最后得到各特征的平均权重。特征的权重越大,表示该特征的分类能力越强,反之,表示该特征分类能力越弱。Relief算法的运行时间随着样本的抽样次数m和原始特征个数N的增加线性增加,因而运行效率非常高。

由于Relief算法比较简单,但运行效率高,并且结果也比较令人满意,因此得到广泛应用,但是其局限性在于只能处理两类别数据,因此1994年Kononeill对其进行了扩展,得到了ReliefF作算法,可以处理多类别问题。该算法用于处理目标属性为连续值的回归问题。ReliefF算法在处理多类问题时,每次从训练样本集中随机取出一个样本R,然后从和R同类的样本集中找出R的k个近邻样本(near Hits),从每个R的不同类的样本集中均找出k个近邻样本(near Misses),然后更新每个特征的权重。

转载至https://blog.csdn.net/lj6052317/article/details/85077201


自定义样本

我的训练数据是有27个特征,标签为01的训练数据,文件类型是csv文件。大致如下:
在这里插入图片描述

代码

运行代码需要首先导入对应的包,修改文件上传、保存的位置,重新定义index及data = pd.read_csv(f)[],使之与自己csv中index一致。

import pandas as pd
import numpy as np
import numpy.linalg as la
import random
import csv

'''
适用于多分类问题
'''
class Relief:
    def __init__(self, data_df, sample_rate, t, k):
        """
        #
        :param data_df: 数据框(字段为特征,行为样本)
        :param sample_rate: 抽样比例
        :param t: 统计量分量阈值
        :param k: k近邻的个数
        """
        self.__data = data_df
        self.__feature = data_df.columns
        self.__sample_num = int(round(len(data_df) * sample_rate))
        self.__t = t
        self.__k = k

    # 数据处理(将离散型数据处理成连续型数据,比如字符到数值)
    def get_data(self):
        new_data = pd.DataFrame()
        for one in self.__feature[:-1]:
            col = self.__data[one]
            if (str(list(col)[0]).split(".")[0]).isdigit() or str(list(col)[0]).isdigit() or (str(list(col)[0]).split('-')[-1]).split(".")[-1].isdigit():
                new_data[one] = self.__data[one]
                # print('%s 是数值型' % one)
            else:
                # print('%s 是离散型' % one)
                keys = list(set(list(col)))
                values = list(range(len(keys)))
                new = dict(zip(keys, values))
                new_data[one] = self.__data[one].map(new)
        new_data[self.__feature[-1]] = self.__data[self.__feature[-1]]
        return new_data

    # 返回一个样本的k个猜中近邻和其他类的k个猜错近邻
    def get_neighbors(self, row):
        df = self.get_data()
        row_type = row[df.columns[-1]]
        right_df = df[df[df.columns[-1]] == row_type].drop(columns=[df.columns[-1]])
        aim = row.drop(df.columns[-1])
        f = lambda x: eulidSim(np.mat(x), np.mat(aim))
        right_sim = right_df.apply(f, axis=1)
        right_sim_two = right_sim.drop(right_sim.idxmin())
        right = dict()
        right[row_type] = list(right_sim_two.sort_values().index[0:self.__k])
        # print list(right_sim_two.sort_values().index[0:self.__k])
        lst = [row_type]
        types = list(set(df[df.columns[-1]]) - set(lst))
        wrong = dict()
        for one in types:
            wrong_df = df[df[df.columns[-1]] == one].drop(columns=[df.columns[-1]])
            wrong_sim = wrong_df.apply(f, axis=1)
            wrong[one] = list(wrong_sim.sort_values().index[0:self.__k])
        print(right, wrong)
        return right, wrong

    # 计算特征权重
    def get_weight(self, feature, index, NearHit, NearMiss):
        # data = self.__data.drop(self.__feature[-1], axis=1)
        data = self.__data
        row = data.iloc[index]
        right = 0
        print('####:',NearHit.values())
        for one in list(NearHit.values())[0]:
            nearhit = data.iloc[one]
            if (str(row[feature]).split(".")[0]).isdigit() or str(row[feature]).isdigit() or (str(row[feature]).split('-')[-1]).split(".")[-1].isdigit():
                max_feature = data[feature].max()
                min_feature = data[feature].min()
                right_one = pow(round(abs(row[feature] - nearhit[feature]) / (max_feature - min_feature), 2), 2)
            else:
                print('@@:',row[feature])
                print('$$:',nearhit[feature])
                print('-'*100)
                right_one = 0 if row[feature] == nearhit[feature] else 1
            right += right_one
        right_w = round(right / self.__k, 2)

        wrong_w = 0
        # 样本row所在的种类占样本集的比例
        p_row = round(float(list(data[data.columns[-1]]).count(row[data.columns[-1]])) / len(data), 2)
        for one in NearMiss.keys():
            # 种类one在样本集中所占的比例
            p_one = round(float(list(data[data.columns[-1]]).count(one)) / len(data), 2)
            wrong_one = 0
            for i in NearMiss[one]:
                nearmiss = data.iloc[i]
                if (str(row[feature]).split(".")[0]).isdigit() or str(row[feature]).isdigit() or (str(row[feature]).split('-')[-1]).split(".")[-1].isdigit():
                    max_feature = data[feature].max()
                    min_feature = data[feature].min()
                    wrong_one_one = pow(round(abs(row[feature] - nearmiss[feature]) / (max_feature - min_feature), 2), 2)
                else:
                    wrong_one_one = 0 if row[feature] == nearmiss[feature] else 1
                wrong_one += wrong_one_one

            wrong = round(p_one / (1 - p_row) * wrong_one / self.__k, 2)
            wrong_w += wrong
        w = wrong_w - right_w
        return w

    # 过滤式特征选择
    def reliefF(self):
        sample = self.get_data()
        # print sample
        m, n = np.shape(self.__data)  # m为行数,n为列数
        score = []
        sample_index = random.sample(range(0, m), self.__sample_num)
        print('采样样本索引为 %s ' % sample_index)
        num = 1
        for i in sample_index:    # 采样次数
            one_score = dict()
            row = sample.iloc[i]
            NearHit, NearMiss = self.get_neighbors(row)
            print('第 %s 次采样,样本index为 %s,其NearHit k近邻行索引为 %s ,NearMiss k近邻行索引为 %s' % (num, i, NearHit, NearMiss))
            for f in self.__feature[0:-1]:
                print('***:',f,i,NearHit,NearMiss)
                w = self.get_weight(f, i, NearHit, NearMiss)
                one_score[f] = w
                print('特征 %s 的权重为 %s.' % (f, w))
            score.append(one_score)
            num += 1
        f_w = pd.DataFrame(score)

        #写入文件保存
        #平均值、最大值、所有
        f_w.mean().to_csv('./test.txt', index=True,header=True )
        #f_w.max().to_csv("./test.txt", index=True, header=True)
        #f_w.cummax().to_csv("./test.txt", index=True, header=True)

        print('采样各样本特征权重如下:')
        print( f_w)
        print('平均特征权重如下:')
        print(f_w.mean())
        print('最大特征权重如下:')
        print(f_w.max())

        return f_w.mean()

    # 返回最终选取的特征
    def get_final(self):
        f_w = pd.DataFrame(self.reliefF(), columns=['weight'])
        final_feature_t = f_w[f_w['weight'] > self.__t]
        print('*'*100)
        print(final_feature_t)
        # final_feature_k = f_w.sort_values('weight').head(self.__k)
        # print final_feature_k
        return final_feature_t


# 几种距离求解

#欧氏距离(Euclidean Distance)
def eulidSim(vecA, vecB):
    return la.norm(vecA - vecB)

#余弦相似度
def cosSim(vecA, vecB):
    """
    :param vecA: 行向量
    :param vecB: 行向量
    :return: 返回余弦相似度(范围在0-1之间)
    """
    num = float(vecA * vecB.T)
    denom = la.norm(vecA) * la.norm(vecB)
    cosSim = 0.5 + 0.5 * (num / denom)
    return cosSim

#皮尔逊(皮尔森)相关系数
'''
皮尔森相关系数也称皮尔森积矩相关系数(Pearson product-moment correlation coefficient) ,
是一种线性相关系数,
是最常用的一种相关系数。
记为r,用来反映两个变量XY的线性相关程度,
r值介于-11之间,绝对值越大表明相关性越强。
'''
def pearsSim(vecA, vecB):
    if len(vecA) < 3:
        return 1.0
    else:
        return 0.5 + 0.5 * np.corrcoef(vecA, vecB,rowvar=0)[0][1]


if __name__ == '__main__':
    ##打开文件位置
    with open('./1171.csv', 'r', encoding='gbk') as f:
        data = pd.read_csv(f)[['特征1', '特征2', '特征3', '特征4', '特征5', '特征6', '特征7', '特征8', '特征9', '特征10', '特征11', '特征12', '特征13', '特征14',
                               '特征15', '特征16', '特征17', '特征18', '特征19', '特征20', '特征21', '特征22', '特征23', '特征24', '特征25', '特征26', '特征27','标签']]
        f = Relief(data, 1, 0.5, 2)
        f.reliefF()
        f.get_final()

运行结果

Relief特征提取算法实战_第1张图片


学习时间:

提示:这里可以添加计划学习的时间
例如:
1、 周一至周五晚上 7 点—晚上9点
2、 周六上午 9 点-上午 11 点
3、 周日下午 3 点-下午 6 点


学习产出:

提示:这里统计学习计划的总量
例如:
1、 技术笔记 2 遍
2、CSDN 技术博客 3 篇
3、 学习的 vlog 视频 1 个

你可能感兴趣的:(Python,ReliefF,特征提取)