一个简单的Agent

dudu

理论名词解释

SAOP: Stacked Alternating Offers Protocol

所谓堆叠式交替报价协议(SAOP,这个谷歌翻译真是绝了😐 ),我们可以理解为一个negotiation协议,它是双方(Bilateral)交替报价,如果你报出当前的offer,那好,对方可以做出三个反应来应对你的offer:

  1. 对方也出一个offer👊 ,看你同意不同意。(这意味着对方不同意你的offer,所以提出了一个新的,堆叠覆盖了你的offer)。
  2. 对方同意你的offer🤝 。皆大欢喜。
  3. 对方选择离开,不跟你进行交易😭 。这种情况基本上很少考虑,因为对方的目的就是最大化utility,如果对方就这么一走了之,那么utility就为0。

其实你们上课会提到其他的protocal。但是coursework就是只有SAOP协议的形式。

Reservation Value

Reservation Value(保留值),可以理解为你的底线,utility如果低于这个值,你就直接拒绝交易了。这就有点像妈妈👩‍🦳在菜市场买一个市场价为20块的鱼🐟,砍价到1块钱,渔贩子的reservation value是2块,所以他干脆就不卖给妈妈了。

需要注意的是,有时候比赛会设定这样的规则,如果双方都没达成交易,那双方各分0.5 utility。这时候,你可以设定一个reservation value=0.5,如果对面报价低于0.5,那你索性跟他死磕到底,反正最后最低也能拿到0.5 utility。

但是如果比赛规则为,双方没达成交易,两边都得0分,那reservation value还是设低点吧,有总比没有好🤦‍♀️ 。

Time Pressure

Time pressure描述的是一种情境,意味你的出价策略会随着seconds或者rounds流逝而发生改变。比如你会不断自己的offer的utility来尽快达成协议。

lab2 里提到了一个discount factor \(\delta\) (打折因子)💰。什么意思呢?就是有些时候的negotiation会加入discount factor,你的offer的 utility会随着时间的流逝,而大打折扣。这会促进双方都快点达成协议,而不是死耗着。

当然,coursework应该不需要考虑这种情况。

运行你的Agent

在进行这一部分之前,得确定你已经完成了Quick Start章节的环境的安装并且已经成功的把这个视频里的操作完成哈😊。

创建一个Agent

(下面的操作都是在你们的lab2中) 1. 首先创建一个新的package,命名为groupn,其中n是你们组完队之后老师用邮箱发给你们的组号。 2. 从文件夹 bilateralexamples 复制文件 RandomBidderExample.java 进入你们的package。 3. RandomBidderExample.java 文件,将其包名(第一行)改为你们之前的定义的报名,比如group10。 4. 把class改名为 Myagent。 5. (Optional) 在getDescription()函数中,改变对你agent的描述。(虽然这步没什么用,纯属好玩)

完成上面5步,至此,你已经有了自己的agent😝!只不过他只是一个example复制来的,就是一个没有复杂算法的工具人🤖。之所以要复制而来,那是因为,你的agent里的代码都需要保持类似的结构,从而才能在Genius里运行。

可选步骤

Lab2 第2部分说的是,告诉你每次都要编译一下自己的java文件(右键 click compile groupn.java),这样才能运行。 第3部分其实是可选的,就是给你添加了每个API方法的英文文档在编译器中。

理解你的Agent有哪些属性和方法

从现在开始的每一章节,每个实验都会开始介绍Genius的一些特性与方法💆‍♂️。这是比较重要的内容,因为你们的算法创新其实都是建立于Genius提供的API之上的哈。

UtilitySpace

首先你们所要做的,就是将下面的代码复制到init()函数里,也就是super.init(info);的后面。或许你们有些人没有java基础,或者有些人只是复习了java的数据结构,对下面的还是一头雾水😭。没关系,我会带你们一行行的理解代码哒,到后面你们熟练了自然而然熟能生巧😁。哦,对了,在插入代码的过程中,可能会报错,就是要你import相应的包。你把箭头放在标红的地方,然后上面应该有提示import class。

AbstractUtilitySpace utilitySpace = info.getUtilitySpace();
AdditiveUtilitySpace additiveUtilitySpace = (AdditiveUtilitySpace) utilitySpace;

List< Issue > issues = additiveUtilitySpace.getDomain().getIssues();

for (Issue issue : issues) {
    int issueNumber = issue.getNumber();
    System.out.println(">> " + issue.getName() + " weight: " + additiveUtilitySpace.getWeight(issueNumber));

    // Assuming that issues are discrete only
    IssueDiscrete issueDiscrete = (IssueDiscrete) issue;
    EvaluatorDiscrete evaluatorDiscrete = (EvaluatorDiscrete) additiveUtilitySpace.getEvaluator(issueNumber);

    for (ValueDiscrete valueDiscrete : issueDiscrete.getValues()) {
        System.out.println(valueDiscrete.getValue());
        System.out.println("Evaluation(getValue): " + evaluatorDiscrete.getValue(valueDiscrete));
        try {
            System.out.println("Evaluation(getEvaluation): " + evaluatorDiscrete.getEvaluation(valueDiscrete));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • init() 这是你的Agent的初始化函数,可以把它当做是整个Agent的入口🚪。也就是说,比赛的刚开始,你需要把所有的状态准备好,最主要的是预测自己的模型。这么说有点抽象,那我打个比方吧,就想运动员准备比赛一样,比赛前需要补能量,热身啥的,咱们的agent也需要时间去做准备。也就是在1分钟左右的时间内,去计算自己的模型。然后才进行Negotiation。在这里,咱们先别管如何预测自己的模型。只需要知道init的作用就好啦。

  • super.init(info) 这个是继承父类的初始化方法。封装,继承和多态,是面向对象的三个基本特性。没有面向对象语言基础(c++,java,python)的童鞋,可以补一补相关的内容。当然,你也可以不去理解这个代码的意思🙅‍♀️,不影响你后面的设计。

  • 下面的代码的意思为首先要通过info.getUtilitySpace()获得一个类型为AbstractUtilitySpaceutilitySpace。这个utilitySpace就是你个人的preference profile,然后你需要将它转为AdditiveUtilitySpace,因为我们只考虑这个情况。🙋‍♂️有人肯定会问,不是不知道自己的preference嘛,不是要自己计算的嘛。对,这个实验只是帮你了解这些用法,所以才用这个方法。真实比赛的时候,是获取不到自己真实的utilitySpace

AbstractUtilitySpace utilitySpace = info.getUtilitySpace();
AdditiveUtilitySpace additiveUtilitySpace = (AdditiveUtilitySpace) utilitySpace;
  • 对于当前的utilitySpace,你可以通过getDomain()方法获得当前的domain下的一个对象。然后再使用getIssues()方法,可以获得这个对象下的所有的issues,存放在一个java数据结构List里,List里存放的都是Issue类型的数据。🧠Java新手可要好好品味这句话的意思噢。。List <Issue>这是Java中泛型的概念,需要了解噢。
List< Issue > issues = additiveUtilitySpace.getDomain().getIssues();
  • for (Issue issue : issues)这是java for循环的一种特殊的写法,有点像python中的for issue in issues。剩下的操作就是遍历获得你的utilitySpace下的issue的索引号int issueNumber = issue.getNumber()和名字issue.getName(),还有其所占的权重additiveUtilitySpace.getWeight(issueNumber))。🤷‍♀️这节实验基本上就是让你熟悉API的。

  • 下面的代码实际上是为了获得你对不同issue下的item的evaluation。在那之前,我们需要将这些issue转化为离散类型的(discrete)。🐨因为我们只考虑离散类型的实验。

    // Assuming that issues are discrete only
    IssueDiscrete issueDiscrete = (IssueDiscrete) issue;
    EvaluatorDiscrete evaluatorDiscrete = (EvaluatorDiscrete) additiveUtilitySpace.getEvaluator(issueNumber);
  • 又来一层for循环,这些的for循环是为了打印出不同的value和evaluation的值。至于最后的try catch异常捕获的方法,如果java新手不了解的话,可以了解一下。当然你不了解,对之后的实验也没多大的影响的哈😊。
    for (ValueDiscrete valueDiscrete : issueDiscrete.getValues()) {
        System.out.println(valueDiscrete.getValue());
        System.out.println("Evaluation(getValue): " + evaluatorDiscrete.getValue(valueDiscrete));
        try {
            System.out.println("Evaluation(getEvaluation): " + evaluatorDiscrete.getEvaluation(valueDiscrete));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 最后,你运行你的agent,IDEA的控制台会输入如下的输出。🦄这实际上就是把agent在当前剧本下的weight, value, evaluation打印出来而已。最后还是要强调一下,这些东西是帮你了解Genius,但是最后的比赛,你是不会直接通过additiveUtilitySpace获得你在当前剧本下的preference🙉。
>> Food weight: 0.19
Chips and Nuts
Evaluation(getValue): 3
Evaluation(getEvaluation): 1.0
Finger-Food
Evaluation(getValue): 2
Evaluation(getEvaluation): 0.6666666666666666

Concession Strategy

从现在开始,我们需要讲解一下协商策略。协商策略包含三个可能的actions:

  1. 接受对手的offer 🔨

  2. 自己报价一个offer ✂️

  3. 结束negotiation 🙅‍♀️

字面上很容易理解这三个行为,但是如何去实现这些行为呢?这就用到了Genius给我们提供的interface啦。

  • public void receiveMessage(AgentID sender, Action action) 这个函数,就是用来接收对手offer之后,处理对手信息的地方。🏡什么叫处理信息呢?这里我就拿我的agent举例吧。比如说我要计算对手模型,就是对手每一次报价,我都把它的报价存起来,然后进行计算,算出对手的preference。那么我就会在这个函数里调用一个方法,专门把对手的offer存放在一个地方,用来计算。

  • 下面的函数是用来做出行为的。可以看到,这里设置了,如果在最后时刻,如果获得到的效用大于我自己的reservation value,那么我就接受offer,不然,我就结束协商。(这个策略行为就很头铁,就是到最后时刻才做出决定🐷)。那时间在[0,0.99]他要干嘛呢?他要return new Offer(getPartyId(), generateRandomBidAboveTarget());,也就是报价offer,且这个offer是radomly generate(随便报价的),虽然是random generation,但是它也会限定,报的bid是要大于我自己设定的target的。😅 你们可以自己看一下这个函数的实现哈。比赛的时候,你们可不能用随机报价啊,那分数会很低的。

    @Override
    public Action chooseAction(List<Class<? extends Action>> possibleActions) 
    {
        // Check for acceptance if we have received an offer
        if (lastOffer != null)
            if (timeline.getTime() >= 0.99)
                if (getUtility(lastOffer) >= utilitySpace.getReservationValue()) 
                    return new Accept(getPartyId(), lastOffer);
                else
                    return new EndNegotiation(getPartyId());

        // Otherwise, send out a random offer above the target utility 
        return new Offer(getPartyId(), generateRandomBidAboveTarget());
    }

Tournaments

竞标赛(Tournaments)其实大家也不用怎么看,因为你们自己模拟调试的时候都是1v1的,只有最后比赛的时候才考虑竞标赛模式。

总结

最重要的还是熟悉init(),receiveMessage(),chooseAction()这三个函数哈。因为你的agent是肯定需要用到这三个函数的💪。