1 | package agents.anac.y2019.saga;
|
---|
2 |
|
---|
3 | import java.util.*;
|
---|
4 |
|
---|
5 | import genius.core.AgentID;
|
---|
6 | import genius.core.Bid;
|
---|
7 | import genius.core.actions.Accept;
|
---|
8 | import genius.core.actions.Action;
|
---|
9 | import genius.core.actions.Offer;
|
---|
10 | import genius.core.issue.IssueDiscrete;
|
---|
11 | import genius.core.issue.ValueDiscrete;
|
---|
12 | import genius.core.parties.AbstractNegotiationParty;
|
---|
13 | import genius.core.parties.NegotiationInfo;
|
---|
14 | import genius.core.uncertainty.AdditiveUtilitySpaceFactory;
|
---|
15 | import genius.core.uncertainty.BidRanking;
|
---|
16 | import genius.core.utility.AbstractUtilitySpace;
|
---|
17 | import genius.core.utility.AdditiveUtilitySpace;
|
---|
18 | import genius.core.utility.EvaluatorDiscrete;
|
---|
19 | import genius.core.boaframework.SortedOutcomeSpace;
|
---|
20 | import genius.core.uncertainty.UserModel;
|
---|
21 |
|
---|
22 |
|
---|
23 | public class SAGA extends AbstractNegotiationParty {
|
---|
24 | private Random rnd = new Random();
|
---|
25 |
|
---|
26 | private Bid lastOffer;
|
---|
27 | private boolean isFirst = true;
|
---|
28 | private double firstUtil = 0.95; // 相手の最初のofferの効用値
|
---|
29 |
|
---|
30 | // // テスト用
|
---|
31 | // int round = -1;
|
---|
32 | // private SortedOutcomeSpace sortedOS;
|
---|
33 |
|
---|
34 | @Override
|
---|
35 | public void init(NegotiationInfo info) {
|
---|
36 | super.init(info);
|
---|
37 | }
|
---|
38 |
|
---|
39 | @Override
|
---|
40 | public Action chooseAction(List<Class<? extends Action>> possibleActions) {
|
---|
41 | double time = timeline.getTime();
|
---|
42 | double target = getTarget(time);
|
---|
43 |
|
---|
44 | ////System.out.println("target = " + target);
|
---|
45 |
|
---|
46 | // Check for acceptance if we have received an offer
|
---|
47 | if (lastOffer != null) {
|
---|
48 | double util = utilitySpace.getUtility(lastOffer);
|
---|
49 | if (isAcceptable(time, target, util))
|
---|
50 | return new Accept(getPartyId(), lastOffer);
|
---|
51 | }
|
---|
52 |
|
---|
53 | // // 実験時に見やすいようにスリープ
|
---|
54 | // try {
|
---|
55 | // Thread.sleep(50);
|
---|
56 | // } catch (Exception e) {
|
---|
57 | // System.out.println(e);
|
---|
58 | // }
|
---|
59 |
|
---|
60 | // Otherwise, send out a random offer above the target utility
|
---|
61 | return new Offer(getPartyId(), generateRandomBidAboveTarget(target));
|
---|
62 |
|
---|
63 | // // テスト用 線形に時間譲歩
|
---|
64 | // return new Offer(getPartyId(), sortedOS.getBidNearUtility(1 - timeline.getTime()).getBid());
|
---|
65 |
|
---|
66 | // // テスト用 BidRankingのBidを順に提案
|
---|
67 | // List<Bid> bidOrd = userModel.getBidRanking().getBidOrder();
|
---|
68 | // if (round < bidOrd.size() - 1) round++;
|
---|
69 | // return new Offer(getPartyId(), bidOrd.get(round));
|
---|
70 | }
|
---|
71 |
|
---|
72 | // accept関数
|
---|
73 | private boolean isAcceptable(double time, double target, double util) {
|
---|
74 | // RV以下の提案はすべてReject
|
---|
75 | if (util < utilitySpace.getReservationValue()) return false;
|
---|
76 |
|
---|
77 | double timeA = 0.6; // target以下にAccept率を与え始める時刻
|
---|
78 | double timeB = 0.997; // すべてのbidにAccept率を与え始める時刻
|
---|
79 |
|
---|
80 | if (time <= timeA) {
|
---|
81 | double acceptProb = Math.pow((util - target) / (1.0 - target), Math.pow(3, (0.5 - time) * 2));
|
---|
82 | return (rnd.nextDouble() < acceptProb);
|
---|
83 | } else if (time >= timeB) {
|
---|
84 | double acceptProb = Math.pow(util, 2);
|
---|
85 | return (rnd.nextDouble() < acceptProb);
|
---|
86 | }
|
---|
87 |
|
---|
88 | // 時刻が timeA < time < timeB のとき
|
---|
89 | double APatT = 0.15 * Math.pow((time - timeA) / (1 - timeA), 2);
|
---|
90 | double acceptBase = target - (1 - target) * (time - timeA) / (1 - timeA);
|
---|
91 |
|
---|
92 | if (util > target) {
|
---|
93 | double acceptProb = APatT + (1.0 - APatT) * Math.pow((util - target) / (1.0 - target), Math.pow(3, (0.5 - time) * 2));
|
---|
94 | return (rnd.nextDouble() < acceptProb);
|
---|
95 | } else if (util > acceptBase) {
|
---|
96 | double acceptProb = APatT * Math.pow((util - acceptBase) / (target - acceptBase), 2);
|
---|
97 | return (rnd.nextDouble() < acceptProb);
|
---|
98 | }
|
---|
99 | return false;
|
---|
100 | }
|
---|
101 |
|
---|
102 | // 譲歩関数
|
---|
103 | private double getTarget(double time) {
|
---|
104 | double A = 0.6; // どこまで譲歩するか決めるパラメータ
|
---|
105 | double B = 5; // 譲歩速度(1-time^B)
|
---|
106 |
|
---|
107 | double targetMin = firstUtil + A * (1 - firstUtil);
|
---|
108 | if (targetMin < utilitySpace.getReservationValue())
|
---|
109 | targetMin = utilitySpace.getReservationValue();
|
---|
110 | return targetMin + (1.0 - targetMin) * (1.0 - Math.pow(time, B));
|
---|
111 | }
|
---|
112 |
|
---|
113 | private Bid generateRandomBidAboveTarget(double target) {
|
---|
114 | Bid randomBid;
|
---|
115 | double util;
|
---|
116 | int i = 0;
|
---|
117 | int maxLoop = 500;
|
---|
118 |
|
---|
119 | // try to find a bid above the target utility
|
---|
120 | do {
|
---|
121 | randomBid = generateRandomBid();
|
---|
122 | util = utilitySpace.getUtility(randomBid);
|
---|
123 | }
|
---|
124 | while (util < target && i++ < maxLoop);
|
---|
125 |
|
---|
126 | try {
|
---|
127 | if (i >= maxLoop)
|
---|
128 | return utilitySpace.getMaxUtilityBid();
|
---|
129 | } catch (Exception e) {
|
---|
130 | // Bidが一つもないドメインなんて普通ありえないのでここには来ないはず
|
---|
131 | System.out.println("Exception in generateRandomBidAboveTarget:" + e);
|
---|
132 | }
|
---|
133 | return randomBid;
|
---|
134 | }
|
---|
135 |
|
---|
136 | @Override
|
---|
137 | public void receiveMessage(AgentID sender, Action action) {
|
---|
138 | if (action instanceof Offer) {
|
---|
139 | lastOffer = ((Offer) action).getBid();
|
---|
140 |
|
---|
141 | if (isFirst) {
|
---|
142 | firstUtil = utilitySpace.getUtility(lastOffer);
|
---|
143 | isFirst = false;
|
---|
144 | }
|
---|
145 | }
|
---|
146 | }
|
---|
147 |
|
---|
148 | @Override
|
---|
149 | public String getDescription() {
|
---|
150 | return "Simple Agent based on Genetic Algorithm";
|
---|
151 | }
|
---|
152 |
|
---|
153 | // ここをGAでやってみたいと思います
|
---|
154 | @Override
|
---|
155 | public AbstractUtilitySpace estimateUtilitySpace() {
|
---|
156 | int popSize = 500; // 集団サイズ
|
---|
157 | int maxGeneration = 200; // 打ち切り世代数
|
---|
158 | double crossRate = 3.0; // 交叉回数 = popSize * crossRate
|
---|
159 |
|
---|
160 | // 初期集団の生成
|
---|
161 | List<AbstractUtilitySpace> population = new ArrayList<>();
|
---|
162 | for (int i = 0; i < popSize * (1.0 + crossRate); i++) {
|
---|
163 | population.add(generateRandomUtilitySpace());
|
---|
164 | }
|
---|
165 |
|
---|
166 | // maxGeneration世代まで繰り返す
|
---|
167 | for (int gen = 0; gen < maxGeneration; gen++) {
|
---|
168 | ////System.out.print("gen " + gen + ": ");
|
---|
169 |
|
---|
170 | // 適応度関数の計算
|
---|
171 | List<Double> fitnessList = new ArrayList<>();
|
---|
172 | for (AbstractUtilitySpace ind : population) {
|
---|
173 | fitnessList.add(fitness(ind, false));
|
---|
174 | }
|
---|
175 |
|
---|
176 | // ルーレット選択
|
---|
177 | population = selectByRoulette(population, fitnessList, popSize);
|
---|
178 |
|
---|
179 | // 交叉と突然変異
|
---|
180 | int parentSize = population.size();
|
---|
181 | for (int i = 0; i < popSize * crossRate; i++) {
|
---|
182 | AbstractUtilitySpace parent1 = population.get(rnd.nextInt(parentSize));
|
---|
183 | AbstractUtilitySpace parent2 = population.get(rnd.nextInt(parentSize));
|
---|
184 | AbstractUtilitySpace child = crossover((AdditiveUtilitySpace) parent1, (AdditiveUtilitySpace) parent2);
|
---|
185 | population.add(child);
|
---|
186 | }
|
---|
187 | }
|
---|
188 |
|
---|
189 | // 適応度関数の計算
|
---|
190 | List<Double> fitnessList = new ArrayList<>();
|
---|
191 | for (AbstractUtilitySpace ind : population) {
|
---|
192 | fitnessList.add(fitness(ind, false));
|
---|
193 | }
|
---|
194 |
|
---|
195 | // 適応度が最大の要素を求める
|
---|
196 | double maxFit = -1.0;
|
---|
197 | int maxIndex = -1;
|
---|
198 | for (int i = 0; i < population.size(); i++) {
|
---|
199 | if (fitnessList.get(i) > maxFit) {
|
---|
200 | maxFit = fitnessList.get(i);
|
---|
201 | maxIndex = i;
|
---|
202 | }
|
---|
203 | }
|
---|
204 |
|
---|
205 | ////System.out.println("最終結果:\n" + population.get(maxIndex).toString());
|
---|
206 |
|
---|
207 | // // テスト用
|
---|
208 | // sortedOS = new SortedOutcomeSpace(population.get(maxIndex));
|
---|
209 |
|
---|
210 | return population.get(maxIndex);
|
---|
211 | }
|
---|
212 |
|
---|
213 | private AbstractUtilitySpace generateRandomUtilitySpace() {
|
---|
214 | AdditiveUtilitySpaceFactory additiveUtilitySpaceFactory = new AdditiveUtilitySpaceFactory(getDomain());
|
---|
215 | List<IssueDiscrete> issues = additiveUtilitySpaceFactory.getIssues();
|
---|
216 | for (IssueDiscrete i : issues) {
|
---|
217 | additiveUtilitySpaceFactory.setWeight(i, rnd.nextDouble());
|
---|
218 | for (ValueDiscrete v : i.getValues())
|
---|
219 | additiveUtilitySpaceFactory.setUtility(i, v, rnd.nextDouble());
|
---|
220 | }
|
---|
221 |
|
---|
222 | // Normalize the weights, since we picked them randomly in [0, 1]
|
---|
223 | additiveUtilitySpaceFactory.normalizeWeights();
|
---|
224 |
|
---|
225 | // The factory is done with setting all parameters, now return the estimated utility space
|
---|
226 | return additiveUtilitySpaceFactory.getUtilitySpace();
|
---|
227 | }
|
---|
228 |
|
---|
229 | // 交叉と突然変異を行います
|
---|
230 | private AbstractUtilitySpace crossover(AdditiveUtilitySpace parent1, AdditiveUtilitySpace parent2) {
|
---|
231 | double alpha = 0.3; // BLX-alphaの値
|
---|
232 | double mutateProb = 0.005; // 突然変異確率
|
---|
233 | double low, high;
|
---|
234 | double w1, w2, wChild;
|
---|
235 |
|
---|
236 | AdditiveUtilitySpaceFactory additiveUtilitySpaceFactory = new AdditiveUtilitySpaceFactory(getDomain());
|
---|
237 | List<IssueDiscrete> issues = additiveUtilitySpaceFactory.getIssues();
|
---|
238 | for (IssueDiscrete i : issues) {
|
---|
239 | // 論点の重み
|
---|
240 | w1 = parent1.getWeight(i);
|
---|
241 | w2 = parent2.getWeight(i);
|
---|
242 | low = Math.min(w1, w2) - alpha * Math.abs(w1 - w2);
|
---|
243 | high = Math.max(w1, w2) + alpha * Math.abs(w1 - w2);
|
---|
244 | wChild = rnd.nextDouble() * (high - low) + low;
|
---|
245 | if (wChild < 0.01) wChild = 0.01;
|
---|
246 | additiveUtilitySpaceFactory.setWeight(i, wChild);
|
---|
247 | //突然変異
|
---|
248 | if (rnd.nextDouble() < mutateProb)
|
---|
249 | additiveUtilitySpaceFactory.setWeight(i, rnd.nextDouble());
|
---|
250 |
|
---|
251 | for (ValueDiscrete v : i.getValues()) {
|
---|
252 | // 選択肢の評価値
|
---|
253 | w1 = ((EvaluatorDiscrete) parent1.getEvaluator(i)).getDoubleValue(v);
|
---|
254 | w2 = ((EvaluatorDiscrete) parent2.getEvaluator(i)).getDoubleValue(v);
|
---|
255 | low = Math.min(w1, w2) - alpha * Math.abs(w1 - w2);
|
---|
256 | high = Math.max(w1, w2) + alpha * Math.abs(w1 - w2);
|
---|
257 | wChild = rnd.nextDouble() * (high - low) + low;
|
---|
258 | if (wChild < 0.01) wChild = 0.01;
|
---|
259 | additiveUtilitySpaceFactory.setUtility(i, v, wChild);
|
---|
260 | //突然変異
|
---|
261 | if (rnd.nextDouble() < mutateProb)
|
---|
262 | additiveUtilitySpaceFactory.setUtility(i, v, rnd.nextDouble());
|
---|
263 | }
|
---|
264 |
|
---|
265 | /* 正規分布で交叉
|
---|
266 | if (rnd.nextBoolean())
|
---|
267 | wChild = rnd.nextGaussian() * Math.abs(w1 - w2) + Math.min(w1, w2);
|
---|
268 | else
|
---|
269 | wChild = rnd.nextGaussian() * Math.abs(w1 - w2) + Math.max(w1, w2);
|
---|
270 | */
|
---|
271 | }
|
---|
272 |
|
---|
273 | // Normalize the weights
|
---|
274 | additiveUtilitySpaceFactory.normalizeWeights();
|
---|
275 |
|
---|
276 | return additiveUtilitySpaceFactory.getUtilitySpace();
|
---|
277 | }
|
---|
278 |
|
---|
279 | // 適応度関数
|
---|
280 | private double fitness(AbstractUtilitySpace individual, boolean print) {
|
---|
281 | BidRanking bidRank = userModel.getBidRanking();
|
---|
282 | List<Double> utilList = new ArrayList<>(); // ランキング下位から上位の予測効用値
|
---|
283 |
|
---|
284 | for (Bid b : bidRank.getBidOrder()) {
|
---|
285 | utilList.add(individual.getUtility(b));
|
---|
286 | }
|
---|
287 |
|
---|
288 | // {予測効用値:予測順位} の辞書
|
---|
289 | TreeMap<Double, Integer> map = new TreeMap<>(Collections.reverseOrder());
|
---|
290 | for (double util : utilList) {
|
---|
291 | map.put(util, 1);
|
---|
292 | }
|
---|
293 | int rank = 1;
|
---|
294 | for (Map.Entry<Double, Integer> entry : map.entrySet()) {
|
---|
295 | rank += entry.setValue(rank);
|
---|
296 | }
|
---|
297 |
|
---|
298 | List<Integer> rankList = new ArrayList<>();
|
---|
299 | for (double util : utilList) {
|
---|
300 | rankList.add(map.get(util));
|
---|
301 | }
|
---|
302 | Collections.reverse(rankList); // ランキング上位から下位のBidの予測順位(1,2,3,...って並んでるとうれしい)
|
---|
303 |
|
---|
304 | int sqSum = 0;
|
---|
305 | for (int i = 0; i < rankList.size(); i++) {
|
---|
306 | int diff = i + 1 - rankList.get(i);
|
---|
307 | sqSum += diff * diff;
|
---|
308 | }
|
---|
309 | double spearman = 1.0 - 6.0 * (double) sqSum / (Math.pow(rankList.size(), 3) - rankList.size());
|
---|
310 | double lowDiff = Math.abs(bidRank.getLowUtility() - individual.getUtility(bidRank.getMinimalBid()));
|
---|
311 | double highDiff = Math.abs(bidRank.getHighUtility() - individual.getUtility(bidRank.getMaximalBid()));
|
---|
312 |
|
---|
313 | if (print) {
|
---|
314 | System.out.println("spearman = " + spearman + ", lowDiff = " + lowDiff + ", highDiff = " + highDiff);
|
---|
315 | }
|
---|
316 |
|
---|
317 | return spearman * 10 + (1 - lowDiff) + (1 - highDiff);
|
---|
318 | }
|
---|
319 |
|
---|
320 | // ルーレット選択
|
---|
321 | private List<AbstractUtilitySpace> selectByRoulette(List<AbstractUtilitySpace> population, List<Double> fitnessList, int popSize) {
|
---|
322 | List<AbstractUtilitySpace> nextGeneration = new ArrayList<>();
|
---|
323 |
|
---|
324 | // 適応度が最大の要素を求める
|
---|
325 | double maxFit = -1.0;
|
---|
326 | int maxIndex = -1;
|
---|
327 |
|
---|
328 | double fitSum = 0.0;
|
---|
329 | for (int i = 0; i < fitnessList.size(); i++) {
|
---|
330 | double fit = fitnessList.get(i);
|
---|
331 | if (maxFit < fit) {
|
---|
332 | maxFit = fit;
|
---|
333 | maxIndex = i;
|
---|
334 | }
|
---|
335 | fitSum += fit;
|
---|
336 | }
|
---|
337 |
|
---|
338 | ////System.out.print("average = " + fitSum / population.size() + ", max = " + maxFit + ", ");
|
---|
339 | ////fitness(population.get(maxIndex), true);
|
---|
340 |
|
---|
341 | nextGeneration.add(population.get(maxIndex));
|
---|
342 |
|
---|
343 | for (int i = 0; i < popSize - 1; i++) {
|
---|
344 | double randomNum = rnd.nextDouble() * fitSum;
|
---|
345 | double count = 0.0;
|
---|
346 | for (int n = 0; n < population.size(); n++) {
|
---|
347 | count += fitnessList.get(n);
|
---|
348 | if (count > randomNum) {
|
---|
349 | nextGeneration.add(population.get(n));
|
---|
350 | break;
|
---|
351 | }
|
---|
352 | }
|
---|
353 | }
|
---|
354 |
|
---|
355 | return nextGeneration;
|
---|
356 | }
|
---|
357 | }
|
---|