强化学习在成语接龙比赛中的应用

题目:

裁判任意给出一个成语,比赛双方在有限的时间里轮流进行成语对答,要求:
1. 成语的首字要与上一个成语的尾字同声同调;
2. 当前比赛出现的所有成语不能再次出现;
3. 必须为四字成语

分析:

看到这个题目,笔者本能的想法是用现成代码跑一跑。但是在git上搜不到能赢得比赛的成语接龙代码,大多数代码只是实现了成语接龙的功能,随机找出符合规则的成语,不足以想赢得比赛,所以打算自己尝试。

重新分析一遍规则吧!若不考虑对手失误,说出重复成语或非四字成语的情况,也不考虑对手的知识量比你大,掌握了一些你词表里没有的成语,只考虑依靠算法获胜的话,则可以理解为这是一道典型的博弈问题。你获胜条件就是在规定时间内,你说出一个成语,你的对手并没有可用的成语,让首字与其同声同调。

思路:

尝试用强化学习的Q-learning解决。

Q-learning核心思想就是在某个状态s下,经过某个动作a,使当前状态更新到下一个状态\tilde{s},记录状态-动作对为Q(s, a),通过定义这个过程中的奖励R(s,a)和观察下一个状态下所有可能动作的Q(\tilde{s}, \tilde{a})的最大值,来更新Q表。经过多个episodes的训练,最终得到整张参数表Q,公式如下:

Q(s,a)\leftarrow Q(s,a)+\alpha [R(s,a)+\gamma{\max}_\tilde{a}Q(\tilde{s},\tilde{a})-Q(s,a) ]

在预测某一个状态s_k下应采取的动作时,只要求出Q(s_k)中所有可能actions的argmax即可。

成语接龙比赛的关键点在于reward表的定义和变化。一开始reward表中奖励较高(reward=50)的是那些尾字拼音独一无二不可接的成语。但在比赛过程中,若某个首字拼音唯一的成语说完后,可能会有一些成语变成尾字拼音不可接,那么这些成语的reward将瞬间变大(从-1变为50)。因此在说出每一轮成语时,有一定的概率要对reward表中的某些值进行更新。

做法:

第一部分:设置参数(学习率lr、衰减值gamma)、第一轮比赛开始前重置Q表、每次比赛前重置成语与拼音映射关系和R矩阵。idioms_dict的key是每一个成语,value是(首字拼音, 尾字拼音);piny2id和id2piny是拼音索引;piny2idiom是拼音与成语的映射关系。

class Clever(object):
    def __init__(self, name, is_train=True):
        self.name = name
        self.idioms_dict = {}
        self.piny2id = {}
        self.id2piny = {}
        self.piny2idiom = {}
        self.lose = '{} lose!'.format(name)
        self.gamma = 0.9
        self.lr = 0.5
        self.r_matrix = None
        self.reset()
        self.q_table = self.build_q_table()
        if not is_train:
            model = np.load(MODEL)
            self.q_table = np.array(model['q_table'])

    def build_q_table(self):
        """ 用0初始化Q表"""
        vocab_size = len(self.piny2id)
        return np.array([[0] * vocab_size] * vocab_size)

    def build_r_matrix(self):
        """一轮比赛未开始时,需要重置R矩阵"""
        vocab_size = len(self.piny2id)
        matrix = np.array([[-1] * vocab_size] * vocab_size)
        for idiom, (f_piny, l_piny) in self.idioms_dict.items():
            score = 0
            if l_piny not in self.piny2idiom:
                score = 50
            matrix[self.piny2id[f_piny]][self.piny2id[l_piny]] = score
        return matrix

    def reset(self):
        """重置比赛,建立成语到拼音的映射关系,重置R矩阵"""
        vocab = load_vocab(VOCAB)
        for n, (piny, _) in enumerate(vocab):
            self.piny2id[piny] = n
            self.id2piny[n] = piny
        data = load_data(DATA)
        for idiom, f_piny, l_piny in data:
            self.idioms_dict[idiom] = (f_piny, l_piny)
            if f_piny in self.piny2idiom:
                self.piny2idiom[f_piny] += [idiom]
            else:
                self.piny2idiom[f_piny] = [idiom]
        self.r_matrix = self.build_r_matrix()

第二部分:裁判以任意成语开始比赛,并且在每一次接龙,出现的成语不能重复出现(包括对手说的和裁判说的)。

    def start(self, idiom):
        """ 裁判给出第一个成语"""
        f_piny, l_piny = get_pinyin(idiom, None)
        self.pop(f_piny, idiom)

    def pop(self, f_piny, idiom):
        """在映射关系中删除成语和相应拼音"""
        if f_piny in self.piny2idiom:
            self.piny2idiom[f_piny] = [i for i in self.piny2idiom[f_piny] if i != idiom]
        if idiom in self.idioms_dict:
            self.idioms_dict.pop(idiom)

第三部分:训练部分随机对出符合条件的成语,更新Q表,最终保存模型。

    def random_play(self, idiom):
        """随机对出符合条件的成语"""
        f_piny, l_piny = get_pinyin(idiom, None)
        self.pop(f_piny, idiom)
        idioms = self.piny2idiom.get(l_piny, None)
        if not idioms:
            return
        picked = random.choice(idioms)
        return picked

    def play(self, idiom):
        """Q learning训练部分"""
        picked = self.random_play(idiom)
        if picked is None:
            return
        f_piny, l_piny = get_pinyin(picked, None)
        for idiom, (f_piny, l_piny) in self.idioms_dict.items():
            score = 0
            if l_piny not in self.piny2idiom:
                score = 50
            self.r_matrix[self.piny2id[f_piny]][self.piny2id[l_piny]] = score
        s = self.piny2id[f_piny]
        a = self.piny2id[l_piny]
        new_q = 100
        if l_piny in self.piny2idiom:
            new_idioms = self.piny2idiom[l_piny]
            new_q = []
            for new_idiom in new_idioms:
                new_f_piny, new_l_piny = get_pinyin(new_idiom)
                new_q.append(self.q_table[self.piny2id[new_f_piny], self.piny2id[new_l_piny]])
        self.q_table[s, a] = self.q_table[s, a] + self.lr * (
                self.r_matrix[s, a] + self.gamma * np.max(new_q) - self.q_table[s, a])
        return picked

    def save(self):
        """存储Q表"""
        np.savez(MODEL, q_table=self.q_table)

第四部分:推理部分从Q表中找出每一步得分最高的成语作为输出。

    def real_play(self, idiom):
        """Q learning推理部分"""
        f_piny, l_piny = get_pinyin(idiom, None)
        self.pop(f_piny, idiom)
        idioms = self.piny2idiom.get(l_piny, None)
        if not idioms:
            return
        q_max = -1
        selected = None
        for idiom in idioms:
            f_piny, l_piny = get_pinyin(idiom, None)
            s = self.piny2id[f_piny]
            a = self.piny2id[l_piny]
            q = self.q_table[s, a]
            if q > q_max:
                q_max = q
                selected = idiom
        return selected

实际效果

在训练阶段,获胜概率在五成左右,因为没用到Q表。预测阶段的对手有随机选手rand和另一个强化选手rl。在训练1000场比赛后,与rand比赛的胜率接近100%,而与和自己一样的rl比赛,先手的胜率在九成以上。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 他打破了我对胖子的传统观念,他是一个很man的胖子,直到现在都是。 他篮球打的好、声音浑厚好听,他胖,...
    清艳芳华阅读 360评论 0 2
  • 1.感恩理发师为我洗头吹发,让我干干净净清清爽爽漂漂亮亮的 2.感恩老公花了很多的心思为工厂招收零时工 3.感恩婆...
    茹sunyufang阅读 202评论 0 0
  • 起因: 由于在最近接手了一个关于导航的App,发现地图页面跳来跳去实在是卡顿地不行(运行在车载设备上的APP,机器...
    helang1991阅读 2,377评论 2 0
  • 生而为人,好苦。 人生好苦,太多无奈。 做不到没心没肺的活着。 善良是我的底线,却没有被眷顾, 被夺走,初尝生活的...
    你好Turbobi阅读 198评论 0 0