目录
1. 数据集
本次代码的数据集为MUSK数据集,用MUSK1数据集作为训练集,用MUSK2数据集前十个包的数据作为测试集,数据集来源为 http://www.cs.columbia.edu/~andrews/mil/datasets.html
2. 距离度量
本次代码中包与包之间的距离度量采用平均Hausdorff距离,计算公式如下:
3. Bamic聚类算法描述
-
随机选则k个训练包作为初始的簇中心,并且这k个包各自为一个单独的簇
-
对于每个包,找出距离它最近的那个簇,并把它划分为目标簇中
-
在每个簇中找出一个包,使得这个包与其他所有包之间的距离和最小,并将这个包作为新的簇中心
-
重复第二个和第三个步骤,不断更新簇与簇中心,直到不再发生变化
-
得到k个簇和簇中心
4. BARTMIP算法描述
-
首先用Bamic算法将训练集聚类得到k簇和簇中心
-
将训练集中的每个训练包映射为一个与包的标签相关联的k维向量,向量中每个元素的值为该包到第i (1 <= i <= k)个簇之间的距离
-
使用给定的基础监督学习算法从转换后的特征向量中训练一个预测器
-
将测试包也转化为一个k维特征向量
-
根据训练出的预测器得到测试包的标签
5. 评估聚类结果的质量
将每个包Xi中的每一个实例都赋予权重1 / |Xi|,对于每一个簇Gj,用Wlj (l∈{0,1})来表示Gj中所有标签为l的包的实例的权重之和。用Wj来表示Gj中所有示例的权重之和,即,同时可得
。最后通过加权平均纯度avgpurity和加权平均熵值avgentropy来评估聚类结果的质量。avgpurity值越大,聚类结果越好;avgentropy值越小,聚类结果越好。公式如下:
6. python代码实现
import math
import random
import matplotlib.pyplot as plt
# 导入数据集文件并对数据进行处理
def loadDataset(fileName: str):
bags = list() # 用bags列表保存每个包,每个包用一个包含该包的所有实例下标的列表表示
labels = list() # 用labels列表保存每条实例所在的包的标签
X = list() # 用X列表表示实例空间,保存所有的实例数据
with open(fileName, "r") as f:
i = 0
temp = list()
for line in f.readlines():
# 处理数据集文件中的每一行,将每行中的实例向量提取出来
instance = line.strip('\n').split(' ')
instance.pop(0)
# 用tempInstance列表保存每一个具体的实例数据
tempInstance = list()
for item in instance:
item = item.split(':')
tempInstance.append(float(item[1]))
X.append(tempInstance)
# 处理数据集文件中的每一行,将每个包里面的实例的下标提取出来
bagSequence = line.strip('\n').split(' ')[0].split(':')
labels.append(int(bagSequence[2])) # 提取每个实例所在的包的标签
if int(bagSequence[1]) == i:
temp.append(int(bagSequence[0]))
else:
bags.append(temp)
temp = list()
i += 1
temp.append(int(bagSequence[0]))
bags.append(temp)
return bags, labels, X
# 计算两个包之间的平均Hausdorff距离
def aveH(A: list, B: list, X: list):
sumA = 0
sumB = 0
# 重新定义列表bagA和bagB来表示这两个包,每个包中保存的是真正的实例数据而不再是下标
bagA = list()
bagB = list()
# 将A包中的所有实例数据加入到bagA列表中
for index in A:
bagA.append(X[index])
# 将B包中的所有实例数据加入到bagB列表中
for index in B:
bagB.append(X[index])
for a in bagA:
dist = list()
for b in bagB:
# 求 ||a-b||,即实例a与b之间的欧几里得距离
Eucleide = 0
for i in range(0, len(a)):
Eucleide += (a[i] - b[i]) ** 2
dist.append(math.sqrt(Eucleide))
# 求出每个a与所有的b之间最小的欧几里得距离,并相加
dist.sort()
sumA += dist[0]
for b in bagB:
dist = list()
for a in bagA:
# 求 ||b-a||,即实例b与a之间的欧几里得距离
Eucleide = 0
for i in range(0, len(b)):
Eucleide += (b[i] - a[i]) ** 2
dist.append(math.sqrt(Eucleide))
# 求出每个b与所有的a之间最小的欧几里得距离,并相加
dist.sort()
sumB += dist[0]
return (sumA + sumB) / (len(A) + len(B)) # 返回值为计算出的平均Hausdorff距离
# 用Bamic算法进行聚类
def Bamic(bags: list, X: list, k: int):
# 用Groups列表来保存每一个簇
Groups = list()
# 用duplicate列表来保存上一次迭代后的簇
duplicate = list()
# 随机选择k个包作为初始的簇中心
Medoids = random.sample(bags, k)
# 用times记录迭代次数
times = 0
while True:
# 迭代次数+1
times += 1
# 记录上一次迭代后的簇
duplicate = Groups
Groups = list()
# 各个初始的簇中的元素为簇中心
for item in Medoids:
G = list()
G.append(item)
Groups.append(G)
# 找出距离每个包最近的那个簇
for bag in bags:
dist = list()
# 计算包与每个簇之间的距离,即与簇中心的距离
for i in range(0, len(Medoids)):
dist.append(aveH(bag, Medoids[i], X))
# 求出目标簇的下标
min = 0
for i in range(0, len(dist)):
if dist[i] < dist[min]:
min = i
# 将该包划分为目标簇中
if bag not in Groups[min]:
Groups[min].append(bag)
# 在每个簇中找出一个包,使得这个包距离该簇中其他所有包的距离和最小,该包即为新的簇中心
for j in range(0, len(Groups)):
# dist列表记录簇中所有包到其他包之间的距离和
dist = list()
for i in range(0, len(Groups[j])):
# 用变量sum记录每个包到其他所有包之间的距离和
sum = 0
for bag in Groups[j]:
sum += aveH(Groups[j][i], bag, X)
dist.append(sum)
# 找出距离和最小的那个包
min = 0
for i in range(0, len(dist)):
if dist[i] < dist[min]:
min = i
# 将它作为新的簇中心
Medoids[j] = Groups[j][min]
# 如果簇没有发生变化,则结束循环
if Groups == duplicate:
break
return Groups, Medoids
# 计算聚类结果的加权平均纯度和加权平均熵值,衡量聚类结果的好坏
def assessQuality(Groups: list, k: int, labels: list):
avgpurity = 0 # 加权平均纯度
avgentropy = 0 # 加权平均熵值
# N的值即为包的数量
N = 0
for G in Groups:
N += len(G)
# 给每个包中的每个实例赋予权重1 / |Xi|
weight = list()
for G in Groups:
tempW = list()
for bag in G:
w = 1 / len(bag)
tempW.append(w)
weight.append(tempW)
for i in range(0, k):
W1 = 0
W0 = 0
W = 0
# 求每个簇的W0和W1
for j in range(0, len(Groups[i])):
for m in range(0, len(Groups[i][j])):
if labels[Groups[i][j][m]] == 1:
W1 += weight[i][j]
else:
W0 += weight[i][j]
W = W0 + W1
maxW = W0 if W0 > W1 else W1
# 根据公式计算出加权平均纯度
avgpurity += maxW / N
temp0 = (W0 / W) * math.log2(W0 / W) if W0 > 0 else 0
temp1 = (W1 / W) * math.log2(W1 / W) if W1 > 0 else 0
# 根据公式计算出加权平均熵值
avgentropy += -1 * (temp0 + temp1) * (W / N)
return avgpurity, avgentropy
# 用BARTMIP算法进行预测
def BARTMIP(Medoids: list, trainBags: list, testBag: list, trainX: list, testX: list, labels: list, k: int):
Tr = list()
for bag in trainBags:
# 将每个训练包转化为特征向量,用featureVector表示
featureVector = list()
# 用temp数组保存每个训练包的特征向量与包的标签组成的二元组
temp = list()
for j in range(0, k):
featureVector.append(aveH(bag, Medoids[j], trainX))
temp.append(featureVector)
temp.append(labels[bag[0]])
Tr.append(temp)
# 将测试包也转化为特征向量
testVector = list()
for j in range(0, k):
testVector.append(aveH(testBag, Medoids[j], testX))
# 这里用KNN作为基础监督学习器
distance = list()
# 计算测试包到每个训练包之间的距离
for vecotor in Tr:
dis = 0
temp = list()
for i in range(0, len(vecotor[0])):
dis += (testVector[i] - vecotor[0][i]) ** 2 # 欧几里得距离
temp.append(math.sqrt(dis))
temp.append(vecotor[1])
distance.append(temp) # distance列表保存测试包到每个训练包之间的距离以及该训练包的标签
# 冒泡排序对distance数组进行升序排序
for i in range(1, len(distance)):
for j in range(0, len(distance) - i):
if distance[j][0] > distance[j + 1][0]:
temp = distance[j]
distance[j] = distance[j + 1]
distance[j + 1] = temp
positive = 0
negative = 0
# 对排序后的distance数组,统计前三十个标签值的数量,数量最多的标签即为预测的标签
for i in range(0, 29):
if distance[i][1] == 1:
positive += 1
else:
negative += 1
return 1 if positive > negative else -1
if __name__ == '__main__':
# 导入Musk1数据集并对数据进行处理
bags, labels, X = loadDataset("D:\\多实例学习/代码/dataSet/musk1norm.txt")
kValues = [5, 20, 35, 50, 65, 80] # 保存每个k值
# 记录论文中的实验数据,并分别对其加上误差值和减去误差值,形成误差范围
avgpurityFromPaper = [0.587, 0.812, 0.867,
0.902, 0.942, 0.978] # 论文中的加权平均纯度
avgpurityDeviation = [0.016, 0.05, 0.027,
0.026, 0.015, 0.013] # 论文中加权平均纯度的误差值
avgpurityUp = list() # 论文中的加权平均纯度加上误差值
avgpurityDown = list() # 论文中的加权平均纯度减去误差值
for i in range(0, len(avgpurityFromPaper)):
avgpurityUp.append(avgpurityFromPaper[i] + avgpurityDeviation[i])
avgpurityDown.append(avgpurityFromPaper[i] - avgpurityDeviation[i])
avgentropyFromPaper = [0.953, 0.513, 0.369,
0.245, 0.1410, 0.05] # 论文中的加权平均熵值
avgentropyDeviation = [0.034, 0.088, 0.0620,
0.056, 0.041, 0.03] # 论文中加权平均熵值的误差值
avgentropyUp = list() # 论文中的加权平均熵值加上误差值
avgentropyDown = list() # 论文中的加权平均熵值减去误差值
for i in range(0, len(avgentropyFromPaper)):
avgentropyUp.append(avgentropyFromPaper[i] + avgentropyDeviation[i])
avgentropyDown.append(avgentropyFromPaper[i] - avgentropyDeviation[i])
avgpurities = list() # 保存每个k值所对应的加权平均纯度
avgentropies = list() # 保存每个k值所对应的加权平均熵值
i = 0
for k in kValues:
# 对于每个k值用Bamic算法对MUSK1里面的包进行聚类
Groups, Medoids = Bamic(bags, X, k) # Groups保存聚类后的簇,Medoids保存聚类后的簇中心
# 计算每次聚类后的加权平均纯度和加权平均熵值
avgpurity, avgentropy = assessQuality(Groups, k, labels)
avgpurities.append(avgpurity)
avgentropies.append(avgentropy)
print('当k = '+str(k)+'时,avgpurity = ' +
str(avgpurities[i])+',avgentropy = '+str(avgentropy))
i += 1
# 绘制加权平均纯度折线图
plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示汉字
plt.plot(kValues, avgpurityUp, color='orangered',
marker='o', linestyle='-', label='论文中的实验数据加上误差值')
plt.plot(kValues, avgpurities, color='blueviolet',
marker='D', linestyle='-.', label='代码跑出的结果')
plt.plot(kValues, avgpurityDown, color='orangered',
marker='o', linestyle=':', label='论文中的实验数据减去误差值')
plt.legend() # 显示图例
plt.xlabel("k值") # X轴标签
plt.ylabel("加权平均纯度") # Y轴标签
plt.show()
# 绘制加权平均熵值折线图
plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示汉字
plt.plot(kValues, avgentropyUp, color='orangered',
marker='o', linestyle='-', label='论文中的实验数据加上误差值')
plt.plot(kValues, avgentropies, color='blueviolet',
marker='D', linestyle='-.', label='代码跑出的结果')
plt.plot(kValues, avgentropyDown, color='orangered',
marker='o', linestyle=':', label='论文中的实验数据减去误差值')
plt.legend() # 显示图例
plt.xlabel("k值") # X轴标签
plt.ylabel("加权平均熵值") # Y轴标签
plt.show()
# 求BARTMIP算法预测的准确率,测试数据集为MUSK2数据集中的前十个包
testBags, testLabels, testX = loadDataset(
"D:\\多实例学习/代码/dataSet/testBARTMIP.txt")
Groups, Medoids = Bamic(bags, X, 5)
true = 0
false = 0
for i in range(0, len(testBags)):
if BARTMIP(Medoids, bags, testBags[i], X, testX, labels, 5) == testLabels[testBags[i][0]]:
true += 1
else:
false += 1
print('BARTMIP准确率为:', true / len(testBags))
7. 代码运行结果
7.1 加权平均纯度结果
下图为论文中的实验数据:
下图为Bamic聚类算法的加权平均纯度随着聚类组数量的增加而变化的折线图,其中X轴表示k值,Y轴表示加权平均纯度值。上方的红色实线中的点表示论文中的实验数据加上误差值,下方的红色虚线中的点表示论文中的实验数据减去误差值,每个k值对应的这两个点之间就是误差范围。图中紫色虚线中的点就是本次代码跑出的结果。
从上图可以看出,本次代码跑出的结果中有四个点都在误差范围内,而另外两个点虽然在误差范围之外,但偏离得比较少,因此本次代码运行出的加权平均纯度和论文中的实验数据还是比较吻合的。
7.2 加权平均熵值结果
下图为论文中的实验数据:
下图为Bamic聚类算法的加权平均熵值随着聚类组数量的增加而变化的折线图,其中X轴表示k值,Y轴表示加权平均熵值。
可以看出本次代码运行出的加权平均熵值和论文中的实验数据也是比较吻合的。
7.3 BARTMIP算法正确率结果
本次代码中MUSK1数据集为训练集,MUSK2数据集中的前10个包组成测试集,预测正确率结果为80%