[英]Using multiprocessing module to runs parallel processes where one is fed (dependent) by the other for Viterbi Algorithm
I have recently played around with Python's multiprocessing module to speed up the forward-backward algorithm for Hidden Markov Models as forward filtering and backward filtering can run independently. 我最近使用了Python的多处理模块,以加快Hidden Markov模型的前向后退算法,因为前向过滤和后向过滤可以独立运行。 Seeing the run-time halve was awe-inspiring stuff.
看到一半的运行时间是令人敬畏的。
I now attempt to include some multiprocessing in my iterative Viterbi algorithm.In this algorithm, the two processes I am trying to run are not independent. 我现在尝试在我的迭代维特比算法中包含一些多处理程序。在该算法中,我尝试运行的两个进程不是独立的。 The val_max part can run independently but arg_max[t] depends on val_max[t-1].
val_max部分可以独立运行,但arg_max [t]取决于val_max [t-1]。 So I played with the idea that one can run val_max as a separate process and then arg_max also as a separate process which can be fed by val_max.
因此,我想到了可以将val_max作为单独的进程运行,然后将arg_max也作为可以由val_max提供的单独进程运行的想法。
I admit to be a bit out of my depth here and do not know much about multiprocessing other than watching some basic video's on it as well as browsing blogs. 我承认我在这里有点儿不了解,对多处理的了解不多,除了观看一些基本视频并浏览博客外。 I provide my attempt below, but it does not work.
我在下面提供了我的尝试,但是没有用。
import numpy as np
from time import time,sleep
import multiprocessing as mp
class Viterbi:
def __init__(self,A,B,pi):
self.M = A.shape[0] # number of hidden states
self.A = A # Transition Matrix
self.B = B # Observation Matrix
self.pi = pi # Initial distribution
self.T = None # time horizon
self.val_max = None
self.arg_max = None
self.obs = None
self.sleep_time = 1e-6
self.output = mp.Queue()
def get_path(self,x):
# returns the most likely state sequence given observed sequence x
# using the Viterbi algorithm
self.T = len(x)
self.val_max = np.zeros((self.T, self.M))
self.arg_max = np.zeros((self.T, self.M))
self.val_max[0] = self.pi*self.B[:,x[0]]
for t in range(1, self.T):
# Indepedent Process
self.val_max[t] = np.max( self.A*np.outer(self.val_max[t-1],self.B[:,obs[t]]) , axis = 0 )
# Dependent Process
self.arg_max[t] = np.argmax( self.val_max[t-1]*self.A.T, axis = 1)
# BACKTRACK
states = np.zeros(self.T, dtype=np.int32)
states[self.T-1] = np.argmax(self.val_max[self.T-1])
for t in range(self.T-2, -1, -1):
states[t] = self.arg_max[t+1, states[t+1]]
return states
def get_val(self):
'''Independent Process'''
for t in range(1,self.T):
self.val_max[t] = np.max( self.A*np.outer(self.val_max[t-1],self.B[:,self.obs[t]]) , axis = 0 )
self.output.put(self.val_max)
def get_arg(self):
'''Dependent Process'''
for t in range(1,self.T):
while 1:
# Process info if available
if self.val_max[t-1].any() != 0:
self.arg_max[t] = np.argmax( self.val_max[t-1]*self.A.T, axis = 1)
break
# Else sleep and wait for info to arrive
sleep(self.sleep_time)
self.output.put(self.arg_max)
def get_path_parallel(self,x):
self.obs = x
self.T = len(obs)
self.val_max = np.zeros((self.T, self.M))
self.arg_max = np.zeros((self.T, self.M))
val_process = mp.Process(target=self.get_val)
arg_process = mp.Process(target=self.get_arg)
# get first initial value for val_max which can feed arg_process
self.val_max[0] = self.pi*self.B[:,obs[0]]
arg_process.start()
val_process.start()
arg_process.join()
val_process.join()
Note: get_path_parallel does not have backtracking yet. 注意:get_path_parallel还没有回溯。
It would seem that val_process and arg_process never really run. 似乎val_process和arg_process从未真正运行过。 Really not sure why this happens.
真的不确定为什么会这样。 You can run the code on the Wikipedia example for the viterbi algorithm.
您可以在Wikipedia示例上运行viterbi算法的代码。
obs = np.array([0,1,2]) # normal then cold and finally dizzy
pi = np.array([0.6,0.4])
A = np.array([[0.7,0.3],
[0.4,0.6]])
B = np.array([[0.5,0.4,0.1],
[0.1,0.3,0.6]])
viterbi = Viterbi(A,B,pi)
path = viterbi.get_path(obs)
I also tried using Ray. 我也尝试使用Ray。 However, I had no clue what I was really doing there.
但是,我不知道自己在那儿实际上在做什么。 Can you please help recommend me what to do in order to get the parallel version to run.
您能否帮我推荐我该怎么做才能使并行版本运行。 I must be doing something very wrong but I do not know what.
我一定做错了什么,但我不知道怎么做。
Your help would be much appreciated. 您的帮助将不胜感激。
I have managed to get my code working thanks to @SıddıkAçıl. 感谢@SıddıkAçıl,我设法使我的代码正常工作。 The producer-consumer pattern is what does the trick.
生产者-消费者模式是解决问题的关键。 I also realised that the processes can run successfully but if one does not store the final results in a "result queue" of sorts then it vanishes.
我还意识到,这些过程可以成功运行,但是如果没有将最终结果存储在某种“结果队列”中,则该过程将消失。 By this I mean, that I filled in values in my numpy arrays val_max and arg_max by allowing the process to start() but when I called them, they were still np.zero arrays.
我的意思是,我通过允许进程start()在numpy数组val_max和arg_max中填充值,但是当我调用它们时,它们仍然是np.zero数组。 I verified that they did fill up to the correct arrays by printing them just as the process is about to terminate (at last self.T in iteration).
我验证了它们确实可以通过打印它们来填充正确的数组,就像进程将要终止时一样(最后是self.T在迭代中)。 So instead of printing them, I just added them to a multiprocessing Queue object on the final iteration to capture then entire filled up array.
因此,我没有打印它们,而是在最后一次迭代中将它们添加到多处理Queue对象中,以捕获整个填充数组。
I provide my updated working code below. 我在下面提供了更新的工作代码。 NOTE: it is working but takes twice as long to complete as the serial version.
注意:它正在工作,但完成所需的时间是串行版本的两倍。 My thoughts on why this might be so is as follows:
我对为什么会这样的想法如下:
I will update if I learn anything new. 如果我学到新东西,我会更新。 If you perhaps know the real reason for why my concurrent code is so much slower, please do let me know.
如果您可能知道导致我的并发代码这么慢的真正原因,请告诉我。 Here is the code:
这是代码:
import numpy as np
from time import time
import multiprocessing as mp
class Viterbi:
def __init__(self,A,B,pi):
self.M = A.shape[0] # number of hidden states
self.A = A # Transition Matrix
self.B = B # Observation Matrix
self.pi = pi # Initial distribution
self.T = None # time horizon
self.val_max = None
self.arg_max = None
self.obs = None
self.intermediate = mp.Queue()
self.result = mp.Queue()
def get_path(self,x):
'''Sequential/Serial Viterbi Algorithm with backtracking'''
self.T = len(x)
self.val_max = np.zeros((self.T, self.M))
self.arg_max = np.zeros((self.T, self.M))
self.val_max[0] = self.pi*self.B[:,x[0]]
for t in range(1, self.T):
# Indepedent Process
self.val_max[t] = np.max( self.A*np.outer(self.val_max[t-1],self.B[:,obs[t]]) , axis = 0 )
# Dependent Process
self.arg_max[t] = np.argmax( self.val_max[t-1]*self.A.T, axis = 1)
# BACKTRACK
states = np.zeros(self.T, dtype=np.int32)
states[self.T-1] = np.argmax(self.val_max[self.T-1])
for t in range(self.T-2, -1, -1):
states[t] = self.arg_max[t+1, states[t+1]]
return states
def get_val(self,intial_val_max):
'''Independent Poducer Process'''
val_max = intial_val_max
for t in range(1,self.T):
val_max = np.max( self.A*np.outer(val_max,self.B[:,self.obs[t]]) , axis = 0 )
#print('Transfer: ',self.val_max[t])
self.intermediate.put(val_max)
if t == self.T-1:
self.result.put(val_max) # we only need the last val_max value for backtracking
def get_arg(self):
'''Dependent Consumer Process.'''
t = 1
while t < self.T:
val_max =self.intermediate.get()
#print('Receive: ',val_max)
self.arg_max[t] = np.argmax( val_max*self.A.T, axis = 1)
if t == self.T-1:
self.result.put(self.arg_max)
#print('Processed: ',self.arg_max[t])
t += 1
def get_path_parallel(self,x):
'''Multiprocessing producer-consumer implementation of Viterbi algorithm.'''
self.obs = x
self.T = len(obs)
self.arg_max = np.zeros((self.T, self.M)) # we don't tabulate val_max anymore
initial_val_max = self.pi*self.B[:,obs[0]]
producer_process = mp.Process(target=self.get_val,args=(initial_val_max,),daemon=True)
consumer_process = mp.Process(target=self.get_arg,daemon=True)
self.intermediate.put(initial_val_max) # initial production put into pipeline for consumption
consumer_process.start() # we can already consume initial_val_max
producer_process.start()
#val_process.join()
#arg_process.join()
#self.output.join()
return self.backtrack(self.result.get(),self.result.get()) # backtrack takes last row of val_max and entire arg_max
def backtrack(self,val_max_last_row,arg_max):
'''Backtracking the Dynamic Programming solution (actually a Trellis diagram)
produced by Multiprocessing Viterbi algorithm.'''
states = np.zeros(self.T, dtype=np.int32)
states[self.T-1] = np.argmax(val_max_last_row)
for t in range(self.T-2, -1, -1):
states[t] = arg_max[t+1, states[t+1]]
return states
if __name__ == '__main__':
obs = np.array([0,1,2]) # normal then cold and finally dizzy
T = 100000
obs = np.random.binomial(2,0.3,T)
pi = np.array([0.6,0.4])
A = np.array([[0.7,0.3],
[0.4,0.6]])
B = np.array([[0.5,0.4,0.1],
[0.1,0.3,0.6]])
t1 = time()
viterbi = Viterbi(A,B,pi)
path = viterbi.get_path(obs)
t2 = time()
print('Iterative Viterbi')
print('Path: ',path)
print('Run-time: ',round(t2-t1,6))
t1 = time()
viterbi = Viterbi(A,B,pi)
path = viterbi.get_path_parallel(obs)
t2 = time()
print('\nParallel Viterbi')
print('Path: ',path)
print('Run-time: ',round(t2-t1,6))
Welcome to SO. 欢迎来到SO。 Consider taking a look at producer-consumer pattern that is heavily used in multiprocessing.
考虑看一下在多处理中大量使用的生产者-消费者模式。
Beware that multiprocessing in Python reinstantiates your code for every process you create on Windows . 请注意,Python中的多处理会为您在Windows上创建的每个进程重新实例化代码。 So your Viterbi objects and therefore their Queue fields are not the same.
因此,您的Viterbi对象及其队列字段不同。
Observe this behaviour through: 通过以下方式观察此行为:
import os
def get_arg(self):
'''Dependent Process'''
print("Dependent ", self)
print("Dependent ", self.output)
print("Dependent ", os.getpid())
def get_val(self):
'''Independent Process'''
print("Independent ", self)
print("Independent ", self.output)
print("Independent ", os.getpid())
if __name__ == "__main__":
print("Hello from main process", os.getpid())
obs = np.array([0,1,2]) # normal then cold and finally dizzy
pi = np.array([0.6,0.4])
A = np.array([[0.7,0.3],
[0.4,0.6]])
B = np.array([[0.5,0.4,0.1],
[0.1,0.3,0.6]])
viterbi = Viterbi(A,B,pi)
print("Main viterbi object", viterbi)
print("Main viterbi object queue", viterbi.output)
path = viterbi.get_path_parallel(obs)
There are three different Viterbi objects as there are three different processes. 由于存在三个不同的过程,因此存在三个不同的Viterbi对象。 So, what you need in terms of parallelism is not processes.
因此,就并行性而言,您需要的不是流程。 You should explore the
threading
library that Python offers. 您应该探索Python提供的
threading
库。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.