本博文通过参考《深入浅出强化学习原理入门》的第一章与《Reinforcement Learning: An Introduction》的Chapter 1部分和 知乎专栏,解释什么是强化学习算法。

一、强化学习解决什么问题?

强化学习的经典应用案例有:非线性二级摆系统(非线性控制问题)、棋类游戏、机器人学习站立和走路、无人驾驶、机器翻译、人机对话 等。概括来说,强化学习所能解决的问题为序贯决策问题,就是需要连续不断做出决策,才能实现最终目标的问题。强化学习与其它的机器学习方法相比,专注于从交互中进行以目标为导向的学习。

二、强化学习如何解决问题?

强化学习主要是学习What to do(在当前情况下该如何选择动作),就是如何将情况(situations)与动作(actions)对应起来,以达到最大化的数值奖励信号。

2.1、强化学习的基本框架

强化学习的基本框架如下图所示,强化学习的主体一般被称为agent,其与environment互动以达到某种目的。所有强化学习的agent有明确的目标,能够感知全部或者部分environment,并且选择action执行来影响environment。除此之外,还要假设在最开始,agent需要在对于所面对的environment一无所知的情况下采取行动。
强化学习框架
强化学习的三个特征:(1)强化学习是一个闭环问题。(2)没有直接对该如何选择action的指示,需要试探搜索去发现哪个动作会产生最大的数值奖励。(3)动作不光会影响直接的奖励,还会影响接下来的环境状态。

2.2、强化学习系统的要素

一个强化学习系统一般包括如下的几个要素:
(1)policy(策略)
policy是从感知到的environment的state到在这些state下要执行的action的映射。这对应于心理学中stimulus-response rules or associations(应激-反应规则或关联)。在一些情况下,policy可以是简单的函数或者是查表。而在别的情况下,可能会需要更多的计算,例如暴力搜索。policy是强化学习agent的核心,因为一个policy便可以单独决定agent的行为。另外,policy也可能是随机的。
(2)reward signal(奖励信号)
reward signal定义了强化学习问题的目标(goal)。在每个时间步内,environment给agent发送一个奖励值。agent唯一的目标便是在长时间运行中最大化接收到的总的reward。reward signal定义了一件事对于agent来说是好是坏。任一时刻发送给agent的reward取决于agent当前的action以及当前environment的state。agent影响reward signal的唯一方式就是通过action直接影响reward,或者通过改变environment的state间接影响。reward signal是改变policy的首要基础。如果一个policy选择了低reward的action,则之后在同样情况下,policy可能会改为选其他的action。总的来说,reward signal可能是environment state和采取的action的函数(也可以带有随机性)。
(3)value function(值函数)
reward signal表示的是在直接感受下哪个是好的。而value function则是表示从长期来看,什么是好的。也就是state的value(价值)是一个agent从当前state开始,在未来期望累积的总的reward。reward定义的是环境state直接的好处,而value是state长期的好处,考虑了之后可能会出现的state以及这些state下的reward。例如一个state可能会产生一个低的reward,但是有着高的value,因为跟着它的通常是一些会产生高reward的state。反之亦然。

reward是首要的,而value是其次的,因为没有reward就没有value。但是当我们做决策并对决策进行评估时,更关注的是value。对于action的选择是基于value来判断的。我们追寻的是带来最高value的state而不是最高的reward,因为这些action从长远上来讲会获得最多的reward。但不幸的是,决定value要比决定reward难得多。reward是由environment直接给出的,但是value是需要对agent的整个执行时间内的状况进行观察,以此对value进行估计和重估计。事实上,强化学习算法中最重要的部分就是如何有效地估计value。
(4)model of the environment(环境模型)
model of the environment是用来模拟真实environment的行径的,或者说是对environment会如何表现的推断。例如,给定一个state和action,这个model会预测因此产生的下一state和下一reward。model一般用在planning(规划)里,可以在真正执行之前通过考虑未来的可能情况来决定一系列动作。利用model和planning用来解决强化学习问题的方法被称为model-based方法,作为更简单的model-free方法的对照。model-free方法主要是指通过trial-and-error(试错)学习,是planning的对立。(就是planning不需要直接执行action与真实的environment互动,可以对environment建模,然后自己跟这个environment模型互动。这样就叫planning)

2.3、强化学习与监督学习的区别

监督学习需要的是标签数据,通过训练获得模型来推断那些不在训练集中的样本的标签应该是什么,监督学习的主要目的是总结概括。强化学习需要的是带有回报的交互数据,agent需要通过从自己的经验中学习如何使自己的利益最大化。

2.4、强化学习与非监督学习的区别

非监督学习一般是寻找未标注数据中同一类的结构信息,而强化学习专注于为agent实现reward最大化。

三、强化学习实例

3.1、训练Tic-Tac-Toe游戏玩家的强化学习理解

Tic-Tac-Toe(井字棋)游戏:两名玩家轮流在一个三乘三的棋盘上比赛。 一个玩家画X和另一个玩家画O,直到一个玩家通过在水平,垂直或对角线上连续放置三个标记来获胜(下图表示画X的玩家获胜)。如果整个表都被填满了而没有人获胜,那么比赛就是平局。 由于一个比较熟练的玩家可以保证不败,我们假设我们的对手并不是一个完美的玩家,有时他会下错地方。我们认为输和平局对我们来说是一样坏的,我们该如何构建一个玩家,其能够发现对手的不完美并且学习如何最大化自己获胜的可能性?
在这里插入图片描述
本博文介绍基于value function的强化学习来训练tic-tac-toe游戏玩家的方法。首先对这个游戏每个可能的state建立一个数表,每个数都是从这个state开始获胜概率的最新估计。我们把这个估计看做这个state的value。若State A有比B更高的value,说明A比B好,也就是当前估计下,从state A开始获胜的概率比从B开始大。

假设我们是下X的,那么所有有三个X在一行(一列,斜对角)的state的获胜概率是1,所有三个O在一行(一列,斜对角)或者填满(平局)的state的获胜概率是0,其余所有state的初始概率是0.5,代表我们猜测我们有一半几率获胜。

强化学习训练过程中,需要权衡Exploration 和 Exploitation的平衡关系(参考资料4),下面的Tic-Tac-Toe游戏的python代码利用的是 g r e e d greed greed and ε − g r e e d \varepsilon - greed εgreed 的方法。与对手玩多轮游戏。为了选择下一步该下在哪,我们检测了我们每一个可能的落子之后产生的state,在表中查询他们当前的value。一般情况下,我们是greedy的,也就是选择那些会导致有最高value的state的地方落子,也就是有最高获胜可能性的地方。偶尔,我们会在其他位置里随机选一个落子。这种叫exploratory(试探性的)的落子,因为这能让我们去经历之前没见过的state。

在玩这个游戏的过程中,不停改变这些state的value,尽可能让它们更准确地估计获胜概率。每一次落子之后,将之前state的value向之后state的value靠近。 s s s表示落子之前的state, s ′ s' s表示落子之后的state, V ( S ) V\left( S \right) V(S)表示 s s s对应的value,对于 V ( S ) V\left( S \right) V(S)的更新可以写为:

V ( S ) ← V ( S ) + α [ V ( S ′ ) − V ( S ) ] V\left( S \right) \leftarrow V\left( S \right) + \alpha \left[ {V\left( {S'} \right) - V\left( S \right)} \right] V(S)V(S)+α[V(S)V(S)]

α \alpha α为步长,是一个小的分数,影响学习率。这种更新规则被称作temporal-difference(时间差分)学习方法。因为其更新是基于两个不同时间对value的估计之差 V ( S ′ ) − V ( S ) {V\left( {S'} \right) - V\left( S \right)} V(S)V(S)的。
如果步长参数随着时间适当减小,对于任意固定的对手,这个方法是可以收敛到每个state下我们真实的获胜概率的。更进一步地,我们所选择的落子的地方(除了exploratory的选择)事实上都是对抗对手最优的选择。也就是说,这种方法能够收敛到最优的policy。

强化学习可以应用于系统的高阶和低阶。虽然井字游戏的玩家只学习了游戏的基本动作,但没有什么能阻止强化学习在更高的层次上进行,在这个层次上,每个动作“可能本身就是一种精心设计的解决问题方法的应用”。在分层学习系统中,强化学习可以在多个层次上同时进行。

3.2、训练Tic-Tac-Toe游戏玩家的python代码

import numpy as np
import pickle

BOARD_ROWS = 3   ##棋盘行数
BOARD_COLS = 3   ##棋盘列数
BOARD_SIZE = BOARD_ROWS * BOARD_COLS

class State:  
    def __init__(self):
        # the board is represented by an n * n array,
        # 1 represents a chessman of the player who moves first,
        # -1 represents a chessman of another player
        # 0 represents an empty position
        self.data = np.zeros((BOARD_ROWS, BOARD_COLS))
        self.winner = None
        self.hash_val = None
        self.end = None

    # compute the hash value for one state, it's unique
    def hash(self):
        '''计算棋盘不同state对应的hash值
        '''
        if self.hash_val is None:
            self.hash_val = 0
            for i in self.data.reshape(BOARD_ROWS * BOARD_COLS):
                if i == -1:
                    i = 2
                self.hash_val = self.hash_val * 3 + i
        return int(self.hash_val)

    # check whether a player has won the game, or it's a tie(监测是否有玩家获胜或平局)
    def is_end(self):
        if self.end is not None:
            return self.end
        results = []
        # check row
        for i in range(0, BOARD_ROWS):
            results.append(np.sum(self.data[i, :]))
        # check columns
        for i in range(0, BOARD_COLS):
            results.append(np.sum(self.data[:, i]))

        # check diagonals
        results.append(0)
        for i in range(0, BOARD_ROWS):
            results[-1] += self.data[i, i]
        results.append(0)
        for i in range(0, BOARD_ROWS):
            results[-1] += self.data[i, BOARD_ROWS - 1 - i]

        for result in results:
            if result == 3:
                self.winner = 1
                self.end = True
                return self.end
            if result == -3:
                self.winner = -1
                self.end = True
                return self.end

        # whether it's a tie(当棋盘下满还没有胜负,则游戏为平局)
        sum = np.sum(np.abs(self.data))
        if sum == BOARD_ROWS * BOARD_COLS:
            self.winner = 0
            self.end = True
            return self.end

        # game is still going on
        self.end = False
        return self.end

    # @symbol: 1 or -1
    # put chessman symbol in position (i, j)
    def next_state(self, i, j, symbol):
        new_state = State()
        new_state.data = np.copy(self.data)
        new_state.data[i, j] = symbol
        return new_state

    # print the board
    def print(self):
        for i in range(0, BOARD_ROWS):
            print('-------------')
            out = '| '
            for j in range(0, BOARD_COLS):
                if self.data[i, j] == 1:
                    token = '*'
                if self.data[i, j] == 0:
                    token = '0'
                if self.data[i, j] == -1:
                    token = 'x'
                out += token + ' | '
            print(out)
        print('-------------')

def get_all_states_impl(current_state, current_symbol, all_states):
    for i in range(0, BOARD_ROWS):
        for j in range(0, BOARD_COLS):
            if current_state.data[i][j] == 0:
                newState = current_state.next_state(i, j, current_symbol)
                newHash = newState.hash()
                if newHash not in all_states.keys():
                    isEnd = newState.is_end()
                    all_states[newHash] = (newState, isEnd)
                    if not isEnd:
                        get_all_states_impl(newState, -current_symbol, all_states)

def get_all_states():
    current_symbol = 1
    current_state = State()
    all_states = dict()
    all_states[current_state.hash()] = (current_state, current_state.is_end())
    get_all_states_impl(current_state, current_symbol, all_states)
    return all_states

# all possible board configurations
all_states = get_all_states()

class Judger:
    # @player1: the player who will move first, its chessman will be 1
    # @player2: another player with a chessman -1
    # @feedback: if True, both players will receive rewards when game is end
    def __init__(self, player1, player2):
        self.p1 = player1
        self.p2 = player2
        self.current_player = None
        self.p1_symbol = 1
        self.p2_symbol = -1
        self.p1.set_symbol(self.p1_symbol)
        self.p2.set_symbol(self.p2_symbol)
        self.current_state = State()

    def reset(self):
        self.p1.reset()
        self.p2.reset()

    def alternate(self):
        while True:
            yield self.p1
            yield self.p2

    # @print: if True, print each board during the game
    def play(self, print=False):
        alternator = self.alternate()
        self.reset()
        current_state = State()
        self.p1.set_state(current_state)
        self.p2.set_state(current_state)
        while True:
            player = next(alternator)
            if print:
                current_state.print()
            [i, j, symbol] = player.act()
            next_state_hash = current_state.next_state(i, j, symbol).hash()
            current_state, is_end = all_states[next_state_hash]
            self.p1.set_state(current_state)
            self.p2.set_state(current_state)
            if is_end:
                if print:
                    current_state.print()
                return current_state.winner

# AI player
class Player:
    # @step_size: the step size to update estimations
    # @epsilon: the probability to explore
    def __init__(self, step_size=0.1, epsilon=0.1):
        self.estimations = dict()
        self.step_size = step_size
        self.epsilon = epsilon
        self.states = []
        self.greedy = []

    def reset(self):
        self.states = []
        self.greedy = []

    def set_state(self, state):
        self.states.append(state)
        self.greedy.append(True)

    def set_symbol(self, symbol):
        self.symbol = symbol
        for hash_val in all_states.keys():
            (state, is_end) = all_states[hash_val]
            if is_end:
                if state.winner == self.symbol:
                    self.estimations[hash_val] = 1.0
                elif state.winner == 0:
                    # we need to distinguish between a tie and a lose
                    self.estimations[hash_val] = 0.5
                else:
                    self.estimations[hash_val] = 0
            else:
                self.estimations[hash_val] = 0.5

    # update value estimation
    def backup(self):
        # for debug
        # print('player trajectory')
        # for state in self.states:
        #     state.print()

        self.states = [state.hash() for state in self.states]

        for i in reversed(range(len(self.states) - 1)):
            state = self.states[i]
            td_error = self.greedy[i] * (self.estimations[self.states[i + 1]] - self.estimations[state])
            self.estimations[state] += self.step_size * td_error

    # choose an action based on the state
    def act(self):
        state = self.states[-1]
        next_states = []
        next_positions = []
        for i in range(BOARD_ROWS):
            for j in range(BOARD_COLS):
                if state.data[i, j] == 0:
                    next_positions.append([i, j])
                    next_states.append(state.next_state(i, j, self.symbol).hash())

        if np.random.rand() < self.epsilon:
            action = next_positions[np.random.randint(len(next_positions))]
            action.append(self.symbol)
            self.greedy[-1] = False
            return action

        values = []
        for hash, pos in zip(next_states, next_positions):
            values.append((self.estimations[hash], pos))
        np.random.shuffle(values)
        values.sort(key=lambda x: x[0], reverse=True)
        action = values[0][1]
        action.append(self.symbol)
        return action

    def save_policy(self):
        with open('policy_%s.bin' % ('first' if self.symbol == 1 else 'second'), 'wb') as f:
            pickle.dump(self.estimations, f)

    def load_policy(self):
        with open('policy_%s.bin' % ('first' if self.symbol == 1 else 'second'), 'rb') as f:
            self.estimations = pickle.load(f)

# human interface
# input a number to put a chessman
# | q | w | e |
# | a | s | d |
# | z | x | c |
class HumanPlayer:
    def __init__(self, **kwargs):
        self.symbol = None
        self.keys = ['q', 'w', 'e', 'a', 's', 'd', 'z', 'x', 'c']
        self.state = None
        return

    def reset(self):
        return

    def set_state(self, state):
        self.state = state

    def set_symbol(self, symbol):
        self.symbol = symbol
        return

    def backup(self, _):
        return

    def act(self):
        self.state.print()
        key = input("Input your position:")
        data = self.keys.index(key)
        i = data // int(BOARD_COLS)
        j = data % BOARD_COLS
        return (i, j, self.symbol)

def train(epochs):
    player1 = Player(epsilon=0.01)
    player2 = Player(epsilon=0.01)
    judger = Judger(player1, player2)
    player1_win = 0.0
    player2_win = 0.0
    for i in range(1, epochs + 1):
        winner = judger.play(print=False)
        if winner == 1:
            player1_win += 1
        if winner == -1:
            player2_win += 1
        print('Epoch %d, player 1 win %.02f, player 2 win %.02f' % (i, player1_win / i, player2_win / i))
        player1.backup()
        player2.backup()
        judger.reset()
    player1.save_policy()
    player2.save_policy()

def compete(turns):
    player1 = Player(epsilon=0)
    player2 = Player(epsilon=0)
    judger = Judger(player1, player2)
    player1.load_policy()
    player2.load_policy()
    player1_win = 0.0
    player2_win = 0.0
    for i in range(0, turns):
        winner = judger.play()
        if winner == 1:
            player1_win += 1
        if winner == -1:
            player2_win += 1
        judger.reset()
    print('%d turns, player 1 win %.02f, player 2 win %.02f' % (turns, player1_win / turns, player2_win / turns))

# The game is a zero sum game. If both players are playing with an optimal strategy, every game will end in a tie.
# So we test whether the AI can guarantee at least a tie if it goes second.
def play():
    while True:
        player1 = HumanPlayer()
        player2 = Player(epsilon=0)
        judger = Judger(player1, player2)
        player2.load_policy()
        winner = judger.play()
        if winner == player2.symbol:
            print("You lose!")
        elif winner == player1.symbol:
            print("You win!")
        else:
            print("It is a tie!")

if __name__ == '__main__':
    ##两个AI玩家进行强化学习训练
    train(int(1e5))
    ##两个AI玩家进行强化学习测试
    compete(int(1e3))
    ##human玩家与AI玩家比赛
    play()

参考资料

1、《深入浅出强化学习原理入门》
2、《Reinforcement Learning: An Introduction》
3、https://zhuanlan.zhihu.com/p/50347818
4、https://blog.csdn.net/LagrangeSK/article/details/81010195

Logo

苏州本地的技术开发者社区,在这里可以交流本地的好吃好玩的,可以交流技术,可以交流招聘等等,没啥限制。

更多推荐