写这篇文章的人说话。很高兴你发现它有用!现在,我的马尔可夫链的第一个实现实际上是在 Python 中,所以这个答案将集中在如何以更 Pythonic 的方式编写它。我将展示如何制作一个 2 阶马尔可夫链,因为它们很容易讨论,但您当然可以通过一些修改使其成为 N 阶。
数据结构
在 js 中,两个突出的数据结构是泛型对象和数组(它是泛型对象的扩展)。然而,在 Python 中,您还有其他选项可以进行更细粒度的控制。以下是两种实现的主要区别:
因此,我们将 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 ('', '')
(我将班级名称从 更改为markov
,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'): ['']}
拉上拉链准备好开车了吗?我们还是得用这个链子!
改变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 个月后仍然会有所帮助。