Przejdź do głównej zawartości

Multi-armed bandit - Upper Confidence Bound

· 6 min aby przeczytać

Ten post jest częścią moich zmagań z książką "Reinforcement Learning: An Introduction" autorstwa Richarda S. Suttona i Andrew G. Barto. Pozostałe posty systematyzujące moją wiedzę i prezentujące napisany przeze mnie kod można znaleźć pod tagiem Sutton & Barto i w repozytorium dloranc/reinforcement-learning-an-introduction.


W multi-armed bandit, aby znaleźć najlepszą akcję potrzebujemy eksploracji, gdyż wartość każdej akcji jest niepewna. Wartość akcji się zmienia, gdy co jakiś czas wykonujemy akcję i dowiadujemy się o otrzymanej nagrodzie. Im częściej dana akcja została wybrana, tym większą mamy pewność, że wartość tej akcji jest właściwa. Do tej pory jednak nie uwzględnialiśmy tego dość intuicyjnego spostrzeżenia w naszych obliczeniach. Akcje były wybierane losowo, bez uwzględniania tego czy wartości akcji są najbliżej tej najlepszej, bądź tego jak bardzo oszacowania są pewne.

Przypomnijmy jak wybieraliśmy najlepszą akcję:

Atarg maxaQt(a)A_t \doteq \underset{a}{\argmax}\> Q_t(a)

Co odpowiada tym liniom kodu:

argmax = np.argmax(self.rewards)
return argmax

Z metody choose_action:

def choose_action(self):
rand = np.random.uniform(0, 1)

if rand > self.epsilon:
# exploit
argmax = np.argmax(self.rewards)
return argmax
else:
# explore
return randint(0, self.arms - 1)

Skorzystajmy z poniższego wzoru:

Atarg maxa[Qt(a)+clogtNt(a)]A_t \doteq \underset{a}{\argmax}\> \Bigg[Q_t(a) + c \sqrt{\frac{\log{t}}{N_t(a)}}\,\Bigg]

Gdzie logt\log{t} to logarytm naturalny (czyli o podstawie ee), a Nt(a)N_t(a) oznacza liczbę wykonanych akcji aa.

Część z pierwiastkiem w powyższym wzorze mierzy niepewność w oszacowaniu wartości akcji aa.

Kod wygląda następująco:

def choose_action(self):
rand = np.random.uniform(0, 1)

if rand > self.epsilon:
# exploit
ucb = self.rewards + \
self.c * np.sqrt(np.log(self.t + 1) / (self.action_count + 1))

return np.argmax(ucb)
else:
# explore
return randint(0, self.arms - 1)

W konstruktorze dodałem c i t:

def __init__(self, arms, pulls, epsilon, c=0):
self.t = 0
self.c = c

c jest parametrem, który kontroluje stopień eksploracji.

Wygenerowałem tabelkę z możliwymi wartościami dla jakiejś akcji aa:

ttNtN_tlogt\log{t}clogtNt,c=2c\sqrt{\frac{\log{t}}{N_t}}, c = 2
1100
210,30102999571,0973240099
310,47712125471,3814792864
410,60205999131,5518504971
510,69897000431,6720885196
620,77815125041,2475185372
720,845098041,3000754132
820,9030899871,3439419534
920,95424250941,3814792864
10211,4142135624
1131,04139268521,1783563044
1231,0791812461,1995450505
1331,11394335231,218711534
1431,14612803571,2361920216
1531,17609125911,2522466525
1641,20411998271,0973240099
1741,23044892141,1092560216
1841,25527250511,1203894435
1941,2787536011,13081988
2041,30102999571,1406270186
2151,32221929471,0284821028
2251,34242268081,036309869
2351,3617278361,0437347694
2451,38021124171,0507944582
2551,39794000871,0575216343
2661,4149733480,9712443386
2761,43136376420,9768533715
2861,44715803130,9822280901
2961,46239799790,9873864485
3061,47712125470,9923444478
3171,49136169380,9231504115
3271,50514997830,9274080558
3371,51851393990,9315161036
3471,5314789170,9354842648
3571,54406804440,939321349
3681,55630250080,8821288173
3781,56820172410,885494699
3881,57978359660,8887585714
3991,5910646070,8409160632
40101,60205999130,8005148322
41111,61278385670,7658112411
42121,62324929040,7355835077
43131,63346845560,70894688
44141,64345267650,6852429551
45151,65321251380,6639703836
46161,66275783170,6447398374
47171,67209785790,6272438044
48181,68124123740,6112357678
49191,690196080,59651551
50201,69897000430,5829185199
51211,70757017610,5703082168
52221,71600334360,5585701459
53231,72427586960,5476075824
54241,73239375980,5373381555
55251,74036268950,5276912263
56261,7481880270,5186058273
57271,75587485570,5100290269
58281,76342799360,501914619
59291,77085201160,4942220654

Jak widać po wartościach z tabelki, wraz z upływem czasu tt ogólnie wartość niepewności w pierwiastku maleje, ale jeśli akcja nie była wybierana, to niepewność nieco wzrasta.

Podsumowanie

Sposób takiego wybierania akcji w zależności od niepewności oznacza się skrótem UCB (upper confidence bound). Jest to metoda statystyczna związana z przedziałami ufności, a przynajmniej tak to rozumiem. Mało co już pamiętam ze statystyki, nigdy nie byłem w niej dobry. UCB jest to całkiem dobra metoda, ale Sutton & Barto ostrzegają, że słabo się sprawdza w problemach niestacjonarnych albo w problemach, w których mamy do czynienia z dużą przestrzenią stanu (large state space, dobrze to przetłumaczyłem na polski?).

Kod można zobaczyć tutaj.

Na sam koniec jeszcze wykresy:

średnie nagrody

Całkiem nieźle, średnio UCB wypada lepiej od wersji bez jeśli chodzi o średnie nagrody. Interesujący jest ten skok i spadek na początku działania algorytmu.

Z optymalnymi akcjami jest gorzej: optymalne akcje

Nic dziwnego, eksploracja zachodzi częściej.

Dla jednego przebiegu MAB: jeden przebieg MAB