写这篇文章的人正在讲话。很高兴您发现它有用!现在,我对马尔可夫链的第一个实现实际上是用 Python 实现的,所以这个答案将重点讨论如何以更 Python 的方式编写它。我将展示如何制作 2 阶马尔可夫链,因为它们很容易讨论,但您当然可以通过一些修改将其制作为 N 阶。
数据结构
在js中,两个突出的数据结构是通用对象和数组(它是通用对象的扩展)。然而,在 Python 中,您可以选择其他选项来进行更细粒度的控制。以下是两种实现的主要区别:
所以,我们贴一个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 个月后这仍然会有帮助。