pairwise voting以及trueskill
一、pairwise voting
1.1、定义及应用
pairwise voting是一种用于集体决策的方法,也被称为两两对比投票。
- 参与者被要求对给定的样本进行两两对比,然后选择他们认为更优的样本,或者选择两个样本的相对次序==》样本对生成;
- 然后,根据所有参与者的选择,对每个选项进行计分,最终得出一个总分来比较选项之间的相对优劣==》更新样本能力值。
通过使用pairwise voting,可以避免在复杂多元的选择中出现投票过于分散的情况,而是通过逐一比较,逐步确定最佳选项。
这种方法在众多决策情境中都有应用,包括团队合作、项目选择、政治选举等。
有一些工具和库可以实现pairwise voting:
- Tallyfy:是一个在线工具,提供了一个直观的界面,让用户可以轻松地进行两两对比并进行投票。它还提供了结果计算和报告生成的功能。
- Decision Lab:是一个决策分析和决策支持工具,提供了一个用户友好的界面,可以创建投票任务并邀请参与者进行投票。还提供结果分析和可视化的功能。
- Rankade:是一个用于比较和排名的平台,允许用户创建投票任务,并进行两两对比。它使用TrueSkill算法来计算最终的排名。
- Python库 - voteit:voteit是一个Python库,专门用于实现各种投票方法,包括pairwise voting。它提供了一系列用于创建投票任务和计算结果的函数和类。
- Rankit:Rankit是一个R包,用于进行排名和优选分析。它提供了许多排名方法,包括pairwise voting。Rankit还支持结果可视化和统计分析,以及进行不同偏好和权重的尝试。
- 也可以尝试自己开发投票界面
1.2、pairwise voting核心模块
1.2.1、备选样本对生成
任务描述:
对 N N N个样本进行pairwise voting,保证每个样本至少参与 C C C次比较(可以略微超过 C C C次)
为每个参与者分配 P P P道题目(每个题目都包含两个样本,两两比较)
思路:
- 每个样本赋予初始次数 C C C,次数不为0的样本为可选样本;
- 从可选样本中选择第1个样本,从剩余可选的样本中选择第2个样本,组成1个样本对;
- 更新每个样本的可选次数
- 可选样本较少时,需要特殊处理(部分样本被选择次数可能超过 C C C次);
- 为每个参与者分配 P P P道题目(题库)
1.2.1、投票任务创建
任务描述:
前端页面展示 + 后台数据生成及结果存储
- 开始页面:供参与者填写信息;投票页面:供参与者比较两个样本并投票;
- 参与者填写信息后开始pairwise voting,后台从题库中拉取 P P P道题目;参与者投票之后记录样本两两比较的结果
思路:
- 初始页面:供参与者填写信息,之后开始pairwise voting(后台记录参与者的信息并从题库中拉取 P P P道题目);
- 投票页面:参与者比较两个样本并投票(后台记录参与者的信息、题目中的样本ID,样本的比较结果);
二、trueskill算法
2.1、定义及应用
trueskill算法是一种用于评估和排名参与者技能水平的算法,基于概率论和统计学原理,通过比较参与者之间的比赛结果,推断和调整每个参与者的技能等级。最初用于计算和更新玩家在Microsoft Xbox Live游戏平台上的技能等级,也可以在体育竞技、推荐系统和排名系统等领域应用。
trueskill算法使用正态分布来表示参与者的能力水平,其中每个参与者都有一个技能值和一个对应的标准差。
- 技能值表示参与者的真实技能,标准差表示技能估计的不确定性或波动范围;
- 当参与者进行比赛或对决时,trueskill算法会根据比赛结果来更新每个参与者的技能值和标准差
- 胜者会增加他们的技能值,而失败者会减少他们的技能值;
- 算法还会根据比赛结果的不确定性来调整参与者的标准差。
trueskill算法具体细节可阅读该博文:现有的评分和排名算法_评分四等分法csdn-CSDN博客
2.2、trueskill库
在python中,可以使用trueskill
库来实现trueskill算法的功能。
-
要使用
trueskill
库,首先需要安装:pip install trueskill
-
使用时在代码中导入
trueskill
库:import trueskill
-
导入
trueskill
库后,可以创建参与者的技能对象,并使用算法来进行比赛结果的更新和技能等级的调整。
import trueskill
# 创建参与者的技能对象
player1 = trueskill.Rating()
player2 = trueskill.Rating()
# 模拟比赛结果
results = [(player1, ), (player2, )] # player1获胜,player2失败
# 更新技能等级
new_ratings = trueskill.rate(results)
# 输出更新后的技能等级
print(new_ratings)
三、示例
3.1、生成样本对
示例描述:
对300个样本进行pairwise voting,保证每个样本至少参与30次比较(可以超过30次)
为每个参与者分配100道题目(每个题目都包含两个样本,两两比较)
编程思路:
- 每个样本赋予初始次数30,次数不为0的样本为可选样本;
- 从可选样本中选择第1个样本,从剩余可选的样本中选择第2个样本,组成1个样本对;
- 可选样本较少时,需要特殊处理(部分样本被选择次数可能超过30次)
import random
import pprint
import pandas as pd
import numpy as np
def get_pariwise_voting_options(sample_list, pairwise_num, respondent_num):
# 所有样本的副本
copy = sample_list[:]
# 每个样本剩余可以被选择的次数
sample_selected_list = [pairwise_num] * (len(sample_list))
# 记录完成选择的样本(暂时没有使用,考虑删除)
sample_selected_index_list = []
# 记录样本对
result_list = []
# 记录打乱顺序的样本对
result_shuffled_list = []
# 记录分组后的样本对
result_grouped_list = []
# TODO:循环停止条件可靠性待确认
while max(sample_selected_list) > 0:
# 随机选择一个样本
first_sample = random.choice(sample_list)
# 删除第一个样本(直接循环到可选次数为0)
sample_list.remove(first_sample)
sample_selected_index_list.append(first_sample)
# 保存一份副本(再选出第2个样本后即时删除,避免重复选择)
sample_list_copy = sample_list[:]
# 循环到可选次数为0
while sample_selected_list[first_sample] > 0:
# 记录样本对
pairwise = [first_sample]
# 从剩下的样本中选择第二个样本,更新选择次数
# 若第2个可选择(不重复)的样本均已经选择
if len(sample_list_copy) == 0:
# 若此时仅剩1个未完成选择的样本(此时没有可选择的第2个样本)
if len(sample_list) == 0:
# 从原始样本副本中删除第一个样本,从剩下的样本中随机选择第2个样本
copy.remove(first_sample)
second_sample = random.choice(copy)
# 从未完成选择的样本中选择第2个样本
else:
second_sample = random.choice(sample_list)
pairwise.append(second_sample)
# 更新可选次数
sample_selected_list[first_sample] -= 1
sample_selected_list[second_sample] -= 1
result_list.append(pairwise)
else:
second_sample = random.choice(sample_list_copy)
if sample_selected_list[second_sample] > 0:
pairwise.append(second_sample)
# 更新可选次数
sample_selected_list[first_sample] -= 1
sample_selected_list[second_sample] -= 1
result_list.append(pairwise)
# 避免重复选择,从副本中删除已经选择的样本
sample_list_copy.remove(second_sample)
print(result_list)
# 获取打乱顺序的样本对
result_shuffled_index_list = list(range(len(result_list)))
random.shuffle(result_shuffled_index_list)
for result_shuffled_index in result_shuffled_index_list:
result_shuffled_list.append(result_list[result_shuffled_index])
print(result_shuffled_list)
file_name = 'demo.txt'
file_obj = open(file_name, 'a')
# 样本对分组,每组respondent_num个
for i in range(0, len(result_shuffled_list), respondent_num):
if len(result_shuffled_list[i:i + respondent_num]) <= respondent_num:
break
for count in range(respondent_num):
# 保存组号、第1个样本序号、第2个样本序号
file_obj.write(str(i // respondent_num + 1) + '\t')
file_obj.write(str(result_shuffled_list[i + count][0]) + '\t')
file_obj.write(str(result_shuffled_list[i + count][1]) + '\t\n')
result_grouped_list.append(result_shuffled_list[i:i + respondent_num])
file_obj.close()
return result_grouped_list
if __name__ == '__main__':
# 样本数
sample_num = 300
# 每个样本的出现次数
pairwise_num = 30
# 每个受访者接收的题目数
respondent_num = 100
# 从0开始到300
sample_list = list(range(sample_num + 1))
result_grouped_list = get_pariwise_voting_options(sample_list, pairwise_num, respondent_num)
# pprint.pprint(result_grouped_list)
样本对记录如下:
样本对组号 | 样本1ID | 样本2ID |
---|---|---|
1 | 249 | 10 |
1 | 206 | 281 |
… | … | … |
45 | 70 | 153 |
3.2、更新样本能力值
示例描述:
每个样本初始能力值为25,根据pairwise voting(两两比较)的结果更新各个样本的能力值
pairwise voting的结果表格(safety.csv)如下:
样本对组号 | 样本1ID | 样本2ID | win | lose | gender | age |
---|---|---|---|---|---|---|
1 | 249 | 10 | 249 | 10 | male | 25 |
1 | 206 | 281 | 281 | 206 | male | 25 |
… | … | … | … | … | … | … |
45 | 70 | 153 | 153 | 70 | male | 25 |
import numpy as np
import pandas as pd
import trueskill
from trueskill import Rating, rate_1vs1
if __name__ == '__main__':
path = r"C:\Users\20458\Desktop\\"
file_name = r"safety.csv"
data = pd.read_csv(path + file_name)
# 获取所有图片的序号(用于比较的对象)
merged_arr = np.concatenate((data['win'].values, data['lose'].values))
pic_list = list(set(merged_arr))
# 为每个图片创建对象
rating_list = [0] * (max(pic_list) + 1)
for i in pic_list:
rating_list[i] = trueskill.Rating()
# 遍历表格中的每行(图片两两比较的结果),更新图片的“能力值”,初始值为25
for i in data.index.values:
pic_win = data.loc[i, 'win']
pic_lose = data.loc[i, 'lose']
try:
new_win, new_lose = rate_1vs1(rating_list[pic_win], rating_list[pic_lose])
rating_list[pic_win] = new_win
rating_list[pic_lose] = new_lose
except IndexError:
print(i + 2, data.loc[i, 'No.'], pic_win, pic_lose)
# 保存结果
file_name = file_name.split('.')[0] + '.txt'
file_obj = open(file_name, 'a')
for pic_index, rating in enumerate(rating_list):
if isinstance(rating, Rating):
print(pic_index, rating.mu)
file_obj.write(str(pic_index) + '\t')
file_obj.write(str(rating.mu) + '\t\n')
file_obj.close()
各个样本的最终能力值记录如下:
样本ID | 样本能力值 |
---|---|
0 | 28.218 |
1 | 25.572 |
… | … |
300 | 21.683 |
四、相关链接
trueskill算法相关:现有的评分和排名算法_评分四等分法csdn-CSDN博客
trueskill库api:https://github.com/sublee/trueskill