将文本预测脚本 [Markov Chain] 从 javascript 转换为 python

2024-04-11

过去几天我一直在尝试转换这个js脚本 http://blog.javascriptroom.com/2013/01/21/markov-chains/到Python代码。

到目前为止我的实现(主要是blindfull cp,一些小的修复):

import random
class markov:
    memory = {}
    separator = ' '
    order = 2

    def getInitial(self):
        ret = []
        for i in range(0, self.order, 1):
            ret.append('')
        return ret

    def breakText(self, txt, cb):
        parts = txt.split(self.separator)
        prev = self.getInitial()
        def step(self):
            cb(prev, self.next)
            prev.shift()#Javascript function.
            prev.append(self.next)
        #parts.forEach(step) # - step is the function above.
        cb(prev, '')

    def learn(self, txt):
        mem = self.memory
        def learnPart(key, value):
            if not mem[key]:
                mem[key] = []
            mem[key] = value
            return mem
        self.breakText(txt, learnPart)

    def step(self, state, ret):
        nextAvailable = self.memory[state] or ['']
        self.next = nextAvailable[random.choice(nextAvailable.keys())]
        if not self.next:
            return ret
        ret.append(next)
        nextState = state.slice(1)
        return self.step(nextState, ret)

    def ask(self, seed):
        if not seed:
            seed = self.genInitial()
        seed = seed + self.step(seed, []).join(self.separator)
        return seed

Issues:

  1. 我对 javascript 完全一无所知。

  2. 当我尝试“学习”一些文本到“马尔可夫”类对象时[例如:a=markov(); a.learn("sdfg");] 我收到以下错误:“TypeError: unhashable type: 'list'”,对于“learnPart”函数中的“mem”字典,“learn”函数的成员。

    所以到目前为止我的问题是为什么会发生这个异常[列表对象的TypeError,错误地引用字典对象(可散列)]?

预先感谢您的任何建议、方向、要点、帮助:D


写这篇文章的人正在讲话。很高兴您发现它有用!现在,我对马尔可夫链的第一个实现实际上是用 Python 实现的,所以这个答案将重点讨论如何以更 Python 的方式编写它。我将展示如何制作 2 阶马尔可夫链,因为它们很容易讨论,但您当然可以通过一些修改将其制作为 N 阶。

数据结构

在js中,两个突出的数据结构是通用对象和数组(它是通用对象的扩展)。然而,在 Python 中,您可以选择其他选项来进行更细粒度的控制。以下是两种实现的主要区别:

  • 我们链中的状态实际上是一个元组——一个不可变的、有序的结构,具有固定数量的元素。我们总是想要n元素(在本例中,n=2)并且它们的顺序有意义。

  • 如果我们使用默认字典 http://docs.python.org/2/library/collections.html#collections.defaultdict包装一个列表,因此我们可以跳过“检查状态是否不存在,然后执行 X”,而只执行 X。

所以,我们贴一个from collections import defaultdict在顶部并更改方式markov.memory被定义为:

memory = defaultdict(list)

现在我们改变markov.getInitial返回一个元组(记住这解释了 order-2 链):

def getInitial(self):
    return ('', '')

(如果你想进一步扩展它,你可以使用一个非常简洁的 Python 技巧:tuple([''] * 2)会返回同样的东西。您可以使用而不是空字符串None)

我们将开始改变用途genInitial一会儿。

产量和迭代

js 中尚不存在但 Python 中确实存在的一个强大概念是yield操作员 (看到这个问题 https://stackoverflow.com/questions/231767/the-python-yield-keyword-explained以获得很好的解释)。

Python的另一个特点是它的通用性for环形。您可以轻松地浏览几乎所有内容,包括生成器(使用yield)。将两者结合起来,我们可以重新定义breakText:

def breakText(self, txt):
    #our very own (ε,ε)
    prev = self.getInitial()

    for word in txt.split(self.separator):
        yield prev, word
        #will be explained in the next paragraph
        prev = (prev[1], word)

    #end-of-sentence, prev->ε
    yield prev, ''

上面神奇的部分,prev = (prev[1], word)可以通过例子最好地解释:

>>> a = (0, 1)
>>> a
(0, 1)
>>> a = (a[1], 2)
>>> a
(1, 2)

这就是我们在单词列表中前进的方式。现在我们开始讨论什么用途breakText, 重新定义markov.learn:

def learn(self, txt):
    for part in self.breakText(txt):
        key = part[0]
        value = part[1]

        self.memory[key].append(value)

因为我们的记忆是一个defaultdict,我们不必担心密钥不存在。

在路边小便休息

好的,我们已经实现了一半的链,是时候看看它的实际效果了!到目前为止我们所拥有的:

from collections import defaultdict

class Markov:
    memory = defaultdict(list)
    separator = ' '

    def learn(self, txt):
        for part in self.breakText(txt):
            key = part[0]
            value = part[1]

            self.memory[key].append(value)

    def breakText(self, txt):
        #our very own (ε,ε)
        prev = self.getInitial()

        for word in txt.split(self.separator):
            yield prev, word
            prev = (prev[1], word)

        #end-of-sentence, prev->ε
        yield (prev, '')

    def getInitial(self):
        return ('', '')

(我将类名更改为markov to Markov因为每次课程以小写字母开头时我都会感到畏缩)。我将其另存为brain.py并加载了Python。

>>> import brain
>>> bob = brain.Markov()
>>> bob.learn('Mary had a little lamb')
>>> bob.memory
defaultdict(<class 'list'>, {('had', 'a'): ['little'], ('Mary', 'had'): ['a'], ('', ''): ['Mary'], ('little', 'lamb'): [''], ('a', 'little'): ['lamb'], ('', 'Mary'): ['had']})

成功!让我们更仔细地看看结果,看看我们是否正确:

{ ('', ''): ['Mary'],
  ('', 'Mary'): ['had'],
  ('Mary', 'had'): ['a'],
  ('a', 'little'): ['lamb'],
  ('had', 'a'): ['little'],
  ('little', 'lamb'): ['']}

zips up准备好继续开车了吗?我们还得use这条链子!

改变step功能

我们已经满足了需要重新制作的内容step。我们有defaultdict,所以我们可以使用random.choice马上,我可以稍微作弊,因为我知道链的顺序。如果我们将其视为一个在链中执行一步的函数(我在原始文章中的错误 - 命名错误的函数),我们也可以摆脱递归(有点悲伤)。

def step(self, state):
    choice = random.choice(self.memory[state] or [''])

    if not choice:
        return None

    nextState = (state[1], choice)
    return choice, nextState

我很遗憾地添加了or ['']因为random.choice抱怨空列表。最后,我们将大部分逻辑移至ask(句子的实际结构):

def ask(self, seed=False):
    ret = []

    if not seed:
        seed = self.getInitial()

    while True:
        link = self.step(seed)

        if link is None:
            break

        ret.append(link[0])
        seed = link[1]

    return self.separator.join(ret)

是的,有点恶心。我们本可以给予step一个更好的名字,并把它变成了发电机,但我和怀孕的妻子见面迟到了,她即将生下一个孩子,她把我被拖走的车里的炉子着火了!我最好快点!

压轴大结局

但首先,和鲍勃谈谈:

from collections import defaultdict
import random

class Markov:
    memory = defaultdict(list)
    separator = ' '

    def learn(self, txt):
        for part in self.breakText(txt):
            key = part[0]
            value = part[1]

            self.memory[key].append(value)

    def ask(self, seed=False):
        ret = []

        if not seed:
            seed = self.getInitial()

        while True:
            link = self.step(seed)

            if link is None:
                break

            ret.append(link[0])
            seed = link[1]

        return self.separator.join(ret)

    def breakText(self, txt):
        #our very own (ε,ε)
        prev = self.getInitial()

        for word in txt.split(self.separator):
            yield prev, word
            prev = (prev[1], word)

        #end-of-sentence, prev->ε
        yield (prev, '')

    def step(self, state):
        choice = random.choice(self.memory[state] or [''])

        if not choice:
            return None

        nextState = (state[1], choice)
        return choice, nextState

    def getInitial(self):
        return ('', '')

并加载它:

>>> import brain
>>> bob = brain.Markov()
>>> bob.learn('Mary had a little lamb')
>>> bob.ask()
'Mary had a little lamb'
>>> bob.learn('Mary had a giant crab')
>>> bob.ask(('Mary', 'had'))
'a giant crab'

当然,这个概念还有改进和扩展的空间。但如果我只是给你答案,那就没有任何乐趣了。

希望 4 个月后这仍然会有帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将文本预测脚本 [Markov Chain] 从 javascript 转换为 python 的相关文章

随机推荐