5

最近几天我一直在尝试将此 js 脚本转换为 python 代码。

到目前为止,我的实现(主要是盲目的 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

问题:

  1. 我完全不了解javascript。

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

    所以到目前为止我的问题是为什么会出现这个异常 [TypeError for a list object, falsely refer to a dictionary object (which is hashable)]?

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

4

3 回答 3

15

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

数据结构

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

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

  • 如果我们使用包装列表的defaultdict操作内存会更容易,因此我们可以跳过“检查状态是否不存在,然后执行 X”,而只执行 X。

因此,我们将 afrom collections import defaultdict放在顶部并更改markov.memory定义方式:

memory = defaultdict(list)

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

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

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

我们将稍微改变一下用途genInitial

产量和迭代

一个在 js 中不存在但在 Python 中确实存在的强大概念是yield运算符(请参阅此问题以获得很好的解释)。

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)

因为我们的记忆是 a defaultdict,所以我们不必担心 key 不存在。

在路边小便

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

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 ('', '')

(我将班级名称从 更改为markovMarkov因为每次班级以小写字母开头时我都会畏缩)。我将其保存为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'): ['']}

拉上拉链准备好开车了吗?我们还是得用这个链子!

改变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 个月后仍然会有所帮助。

于 2013-06-25T17:38:18.633 回答
1

复杂的答案

这里的问题learnPart是试图使用 getInitial 的返回值,它是一个列表,作为字典的键。列表是可变的,因此不可散列,这意味着它们不能用作字典的键。

您可以尝试将此行添加到learnPart

def learnPart(key, value):
    key = tuple(key) #<-----Try adding this line
    if not mem[key]:
        mem[key] = []
    mem[key] = value
    return mem

但我认为这不会解决所有问题。

简单的答案

那里有很多用 Python 编写的马尔可夫链实现。在 Github 上快速搜索得到 168 个项目: https ://github.com/search?l=Python&q=markov+chain

于 2013-02-11T17:41:34.163 回答
1

我做了一个简化版本的代码:

import re
class Brain():
    H = ''
    def learn(self, txt):
        self.H = txt
    def ask(self,ask):
        H=self.H
        ask = re.compile(r"%s(.*)"%(ask),re.I|re.DOTALL)
        m = ask.search(H)
        print m.group(1)

这是执行:

>>> import brain
>>> bob = brain.Brain()
>>> bob.learn('Mary had a little lamb' )
>>> bob.ask('Mary had')
'a little lamb'

我同意这不完全是马尔可夫链算法。但它有几个优点:

一世。您可以提供ask()如上所示的原始文本。

ii. 它的代码行数更少。

iii. 希望它更容易理解。

于 2016-04-30T11:42:16.497 回答