文章目录
一、算法流程图
二、代码步骤
1.第一步:定义类和init方法
2.第二步:数据处理
3.第三步:通过计算距离,找出猜错近邻和猜对近邻
4.第四步:计算特征权重
5.第五步:根据权重过滤式选取特征
6.第六步:选取最终特征
7.第七步:定义主函数
三、为什么要写relief算法?以及解决什么问题?
算法流程
第一步:定义类和init方法
方便函数调用参数,只需要一次向类中导入参数即可,不用重复导入参数
class Filter:
def __init__(self, data_df, sample_rate, t, k):
"""
#
:param data_df: 数据框(字段为特征,行为样本)
:param sample_rate: 抽样比例
:param t: 统计量分量阈值
:param k: 选取的特征的个数
"""
self.__data = data_df
self.__feature = data_df.columns
self.__sample_num = int(round(len(data_df) * sample_rate))#round函数:四舍五入
self.__t = t
self.__k = k
第二步:数据处理
将读取到的数据特征值中离散型处理成连续型,比如色泽:青绿,属于离散型,密度:0.679,属于连续型。
def get_data(self):
new_data = pd.DataFrame()#建立一个空二维表
for one in self.__feature[:-1]:#遍历循环每个特征
col = self.__data[one]#读取全部样本中其中一个特征
# 判断读取到的特征是否全为数值类,如果字符串中全为数字 ,则不作改变写进新的二维表new_data里面,否则处理成数值类型写进二维表
if (str(list(col)[0]).split(".")[0]).isdigit() or str(list(col)[0]).isdigit()\
or (str(list(col)[0]).split('-')[-1]).split(".")[-1].isdigit():#isdigit函数:如果是字符串包含数字返回ture,否则返回false
new_data[one] = self.__data[one]
else:
keys = list(set(list(col)))#set函数:删除重复值,得到一个特征的类别,如色泽:青绿、浅白、乌黑
values = list(range(len(keys)))#遍历循环len(keys),色泽特征为例,三个特征类别,则得到三个数0、1、2
new = dict(zip(keys, values))#dict函数就是创建一个字典,zip函数矩阵中的元素对应打包成一个元组列表
new_data[one] = self.__data[one].map(new)#map函数:将new: {'青绿': 0, '浅白': 1, '乌黑': 2}在col列表里做一个映射
new_data[self.__feature[-1]] = self.__data[self.__feature[-1]]#瓜的类别属性不做改变
return new_data
get_data(self)函数:经过处理数据,用0、1、2这样的数字去取代特征的中文类别,拿色泽为例,色泽=['青绿','浅白','乌黑'] 替换为 色泽=['0' , '1' , '2'],完成所有替换后返回一个连续型数据类型的数据集。
第三步:通过计算距离,找出猜错近邻和猜对近邻
def get_neighbors(self, row):
df = self.get_data()
#row是一行一行(一个一个)样本
row_type = row[df.columns[-1]]#矩阵最后一列:瓜的类别,如好瓜、坏瓜、模糊
#下面进行分类,与读取到的样本类型相同的分为 “同类”,不相同的分为 “异类”,储存在两个数据集中
right_df = df[df[df.columns[-1]] == row_type].drop(columns=[df.columns[-1]])#筛选出数据集类别与读取样本同类的样本,删除数据集最后一列
#将删除后的数据集储存在right_df中,原数据集df保持不变。
wrong_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))#lambda函数:定义一个隐函数, mat函数:转换为矩阵,方便线性代数的操作
#eulidsim函数是自己定义的一个计算距离的函数
right_sim = right_df.apply(f, axis=1)#apply函数:就是将right_df里面的每个变量,axis=1按行的顺序计算,
#apply函数解释链接: https://www.cnblogs.com/xiaodongsuibi/p/8927688.html
right_sim_two = right_sim.drop(right_sim.idxmin())#idxmin()函数:获取某行最小的序列号,里面删除了读取样本本身的那个数据
#之所以在同类中删除距离最小的那个数据,是因为在原数据集中,包括了本身的那个数据
wrong_sim = wrong_df.apply(f, axis=1)
return right_sim_two.idxmin(), wrong_sim.idxmin()
get_neighbors()函数:随机从数据集中选取一个样本X,识别样本X所属的类别,如好瓜、坏瓜、模糊。在数据集中将与样本X同类的数据分类为同类数据集,不同类的分为异类数据集,得到两个数据集。计算样本X与两个数据集中的所有样本的欧式距离,因为同类样本数据集中有一个是数据样本X,要将这个样本删了,因为返回最近近邻的数据时,样本X与样本X的距离肯定是最小的。然后分别返回两个数据集中与样本X距离最近的数据样本,同类数据集中的最近距离样本即为猜对近邻,异类数据集中的最近距离样本即为猜错近邻。
第四步:计算特征权重
def get_weight(self, feature, index, NearHit, NearMiss):
data = self.__data.drop(self.__feature[-1], axis=1)#西瓜数据集
row = data.iloc[index]#选取到的数据样本,命名为样本X
nearhit = data.iloc[NearHit]#猜对近邻数据样本
nearmiss = data.iloc[NearMiss]#猜错近邻数据样本
#将特征的数据类型分类,分类连续型和离散型,不同类型的数据,用不同的方法计算特征距离
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 = pow(round(abs(row[feature] - nearhit[feature]) / (max_feature - min_feature), 2), 2)#猜对近邻特征权重
wrong = pow(round(abs(row[feature] - nearmiss[feature]) / (max_feature - min_feature), 2), 2)#猜错近邻特征权重
else:
# 离散型特征距离计算方式:
right = 0 if row[feature] == nearhit[feature] else 1#如果猜对近邻特征与样本X一样。则返回0,不一样返回1
wrong = 0 if row[feature] == nearmiss[feature] else 1#如果猜错近邻特征与样本X一样。则返回0,不一样返回1
w = wrong - right#right越小,说明两个同类样本的特征越相似,反之,则同类样本差距越大。
#wrong越小,说明两个异类样本的特征越相似,反之,则异类样本差距越大。
#w越大,说明wrong越大,ringt越小,则表示同类的样本间,该特征的数值差距小,不同类的差距大,说明以该特征区分不同类别,比较容易。
return w
计算出每个数据样本中每个特征的权重w
第五步:根据权重过滤式选取特征
def relief(self):
sample = self.get_data()
m, n = np.shape(self.__data) # m为行数,n为列数
score = []
sample_index = random.sample(range(0, m), self.__sample_num)#random.sample函数:从指定序列中随机获取指定长度的片断。sample函数不会修改原有序列。
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行索引为 %s ,NearMiss行索引为 %s' % (num, i, NearHit, NearMiss))
for f in self.__feature[0:-1]:
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)#将字典的格式换成表格
print ('采样各样本特征权重如下:')
#print (f_w)
print ('平均特征权重如下:')
print (f_w.mean())
return f_w.mean()
将所有样本的特征权重采集到一个字典里,再将字典类型的数据集转换成表格,表格行标签为样本属性,列标签为样本序号,表格内为权重。再将每个属性的特征权重取均值,即每列数据取均值,得到每个属性的平均特征权重。
第六步:选取最终特征
def get_final(self):
f_w = pd.DataFrame(self.relief(), columns=['weight'])
print("f_w:",f_w)
final_feature_t = f_w[f_w['weight'] > self.__t]#设置阈值,将大于阈值的属性筛选出来
print ("final_feature_t:",final_feature_t)
final_feature_k = f_w.sort_values('weight').head(self.__k)#sort_values函数:排序函数,https://blog.csdn.net/qq_24753293/article/details/80692679
print ("final_feature_k:",final_feature_k)
return final_feature_t, final_feature_k
将数据集中的平均特征权重转换成一个表格形式,方便我们研究数据,设置一个阈值,将符合我们预期的特征筛选出来并且将平均特征权重做一个排序。到这里为止,我们就对数据集中的特征做了一个筛选,返回出我们需要的特征。
第七步:定义主函数
if __name__ == '__main__':
data = pd.read_csv('1.csv',encoding="gbk")[['色泽', '根蒂', '敲声', '纹理', '脐部', '触感', '密度', '含糖率', '类别']]
f = Filter(data, 1, 0.2, 8)
f.relief()
f.get_final()
为什么要写relief算法?以及解决什么问题?
为什么写relief算法,因为relief算法简单,可操作性强。
解决什么问题?当我们遇到维数灾难问题的时候,主要是因为属性太多,我们需要将一些影响极小的属性进行一个剔除,所以就引入特征选择,这其实跟降维差不多。
然而relief算法就是特征选择的一个方法,通过对所有样本的一个过滤,过滤后给每个特征赋予一个权重,我们根据一个权重的大小排序来筛选出对类别影响大的特征。
以后的运用:比如我们建立一个模型,去判断不同属性对信用程度的一个影响,例如其中属性包括财富、性格、颜值、年龄等,我们给这些属性一个权重,比如颜值这个属性的权重极小,也就是说你颜值如何跟你守不守信关系极小,所以我们根据计算出来的属性权重值做一个排序,将一些对我们分类的目的影响极小的属性进行一个剔除。
附上代码+数据:https://gitee.com/liu_ji_duan/DuanGe/tree/master/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/RELIEF%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0