الگوریتم پس‌رو-پیش‌رو

الگوریتم پس‌رو-پیش‌رو یک الگوریتم استنباطی آماری برای مدل پنهان مارکف است که احتمال پسین توزیع حاشیه‌ای تمام متغیرهای حالت پنهان با توالی مشاهده‌شدهٔ     را محاسبه می‌کند؛ یعنی برای تمام متغیرهای حالت پنهان  توزیع  را محاسبه می‌کند. به این عمل استنباطی معمولاً «صاف‌کردن» می‌گویند. این الگوریتم از اصل برنامه‌نویسی پویا برای محاسبه کارآمد مقادیر مورد نیاز برای به دست آوردن احتمال پسین توضیح حاشیه‌ای، در دو پاس استفاده می‌کند. اولین پاس به جلو (پس) حرکت می‌کند در حالی که پاس دوم در همان زمان به عقب (پیش) می‌رود. از این رو نام پس‌رو-پیش‌رو را برای این الگوریتم انتخاب کردند.

در اصل اصطلاح «پس‌رو-پیش‌رو» به تمام الگوریتم‌هایی که به کلاس عمومی الگوریتم‌هایی که به صورت پس‌رونده-پیش‌رونده بر روی توالی‌ها عملیات انجام می‌دهند اطلاق می‌شود. در این مفهوم، توصیفات ارائه شده در باقی‌ماندهٔ این مقاله تنها به یک نمونهٔ خاص از این کلاس اشاره می‌کند.

بررسی اجمالی 
در پاس اول، این الگوریتم مجموعهٔ احتمالاتی را محاسبه می‌کند که برای تمام ، احتمال پایان یافتن در یکی k حالت مشاهده‌شدهٔ اول در توالی است؛ یعنی . در پاس دوم، الگوریتم مجموعهٔ احتمالاتی را محاسبه می‌کند که احتمال مشاهده کردن مشاهدات باقی مانده با شروع از نقطه K را به ما می‌دهد؛ یعنی . این دو مجموعهٔ توزیعات احتمالاتی با ترکیب شدن با هم می‌توانند توزیع هر حالتی در هر زمانی را با داشتن توالی آن به دست آورند:
 
در قدم آخر با استفاده از قضیهٔ بیز و استقلال مشروط  و  مقادیر .

همان‌طور که در بالا ذکر شده این الگوریتم شامل سه مرحله است:
 محاسبه کردن احتمالات رو به جلو (پسین)
 محاسبه کردن احتمالات رو به عقب (پیشین)
 محاسبه کردن مقادیر «صاف شده».

الگوریتم پس‌رو-پیش‌رو می‌تواند محتمل‌ترین حالت را در هر نقطهٔ زمانی پیدا کند اما نمی‌تواند برای پیدا کردن محتمل‌ترین توالی حالت‌ها استفاده شود (به الگوریتم ویتربی رجوع کنید)

احتمالات رو به جلو (پسین) 
در توضیحات پیش رو به جای توزیع احتمالاتی، از ماتریس احتمالاتی استفاده می‌شود. در حالت عمومی از الگوریتم پس‌رو-پیش‌رو می‌توان هم در مدل‌های پیوسته و هم در مدل‌های گسستهٔ احتمالاتی استفاده کرد.

ما توزیعات احتمالاتی مربوط به مدل پنهان مارکف را به ماتریس احتمالاتی تبدیل می‌کنیم. احتمالات \ (با متغیر تصادفی ) که تمامی حالات مدل پنهان مارکف را بیان می‌کند، به صورت ماتریس احتمالاتی   نشان داده خواهند شد که در آن ستون‌ها با شاخص  نمایندهٔ حالت پایانی هستند و ردیف‌ها با شاخص  نمایندهٔ حالت آغازین. گذار(انتقال) از حالت بردار ردیفی  به حالت بردار ردیفی افزایش یافته  به صورت  نشان داده می‌شود. مثال زیر نشان دهندهٔ یک سیستم است که احتمال ماندن یک حالت در جای خودش بعد از هر مرحله ۷۰٪ و احتمال تبدیل شدن به حالت دیگر ۳۰٪ است. ماتریس انتقال به صورت زیر است:

در مدل مارکف معمولی ما حالت فعلی را در این ماتریس ضرب می‌کردیم تا احتمالات حالت بعدی را به دست آوریم. اما در مدل پنهان مارکف حالت فعلی پنهان است و در عوض ما وقایع مرتبط با حالت فعلی را مشاهده می‌کنیم. یک ماتریس وقایع به صورت زیر است که احتمال مشاهده شدن هر واقعه را در هر حالت خاص بیان می‌کند:

در این مثال احتمال مشاهده شدن واقعهٔ یک زمانی که در حالت اول هستیم ۹۰٪ است در حالی که احتمال مشاهده شدن واقعهٔ دو زمانی که در حالت اول هستیم ۱۰٪ می‌باشد. به همین ترتیب واقعهٔ یک تنها ۲۰٪ اوقات مشاهده می‌شود اگر در حالت دوم باشیم و واقعهٔ دو با احتمال ۸۰٪ در حالت دوم قابل مشاهده است. هر بردار افقی دلخواه نشان دهندهٔ یک حالت از سیستم است() و احتمال مشاهدهٔ واقعه j به صورت زیر است:

ما می‌توانیم با جبر ماتریسی این عمل را به این صورت نشان بدهیم که بردار افقی   را در ماتریس مشاهده  ضرب کنیم. این ماتریس یک ماتریس قطری است یعنی به جز قطر اصلی تمامی درایه‌های آن صفر هستند و در درایه‌های قطر اصلی آن احتمال رویداد آن واقعه خاص در حالت مربوط به آن درایه وجود دارد. ماتریس مشاهدهٔ برای واقعهٔ یک به صورت زیر است:

این به ما اجازه می‌دهد تا با استفاده از قضیهٔ بیز بردار احتمالاتی غیرنرمال شدهٔ  را به دست آوریم. چون می‌دانیم احتمال این که هر عنصر رویداد یک را تولید کند به صورت زیر است:

اکنون ما می‌توانیم این روش کلی را به مجموعه ای از مشاهدات خود اختصاص دهیم. فرض می‌کنیم بردار حالت اولیه ما ,>(که می‌تواند به صورت بازگشتی بهینه شود). به این صورت شروع می‌کنیم:

این فرایند می‌تواند رو به جلو (پس) تکرار شود و از مشاهدات اضافی و جدید نیز استفاده شود:

نتیجه یک بردار پسین غیرنرمال احتمالاتی است. i اُمین ورودی این بردار نتیجه می‌دهد:

به‌طور معمول ما در هر مرحله این بردار را نرمال می‌کنیم (جمع ورودی‌ها را یک می‌کنیم) یک عامل مقیاس گذاری را در هر مرحله به صورت زیر معرفی می‌کنیم:

که در آن   نشان دهنده بردار مقیاس‌پذیر از مرحله قبلی و   نشان دهنده عامل مقیاس است که باعث می‌شود مجموع ورودی‌ها یک شود. ضرب عوامل مقیاس احتمال کامل مشاهدهٔ وقایع را بدون در نظر گرفتن شرایط نهایی به ما می‌دهد:

ای به ما اجازه می‌دهد که بردار احتمالاتی مقیاس‌پذیر را این‌گونه تفسیر کنیم:

پس تا به حال نتیجه گرفتین که ضرب عوامل مقیاس، احتمال حقیقی مشاهدهٔ توالی مورد نظر تا زمان t را مهیا می‌کند و این که بردار احتمالاتی اسکیل شده به ما احتمال بودن در هر حالت را در زمان می‌دهد.

احتمالات رو به عقب (پیشین) 
با یک روش مشابه می‌توان احتمالات پیشین را محاسبه کرد. به صورت زیر:

حالا ما فرض می‌کنیم که در حالت خاص هستیم و چون این حالت فرض شده‌است یعنی احتمال این حالت ۱۰۰٪ است پس به این صورت در می‌آید:

توجه کنید که ما در حال حاضر از یک بردار عمودی استفاده می‌کنیم و بردارهای احتمالاتی پسین ما افقی بودند. پس می‌توانیم عملیات زیر را انجام دهیم:

می‌توانیم این بردار را هم (مانند بخش قبل) نرمال کنیم اما معمولاً این کار را انجام نمی‌دهند. هر ورودی احتمال واقعه‌ای در آینده را نشان می‌دهد و نرمال کردن این بردار معادل استفاده از قضیهٔ بیز برای پیدا کردن احتمال هر حالت برای ایجاد کردن واقعه‌های آینده است. در این‌جا هم مانند قسمت پسین از همان  استفاده می‌کنیم تنها   اسکیل شده نیست. عملیات به صورت زیر است:

که در آن  نشان دهنده بردار اسکیل شده قبلی است. از این معادله می‌توان نتیجه زیر را گرفت:

این معادله برای این مفید است که اجازه می‌دهد تا احتمال کامل بودن در یک حالت در زمان داده شده t را داشته با ضرب کردن این مقادیر داشته باشیم:

برای درک این موضوع توجه داشته باشید که  احتمال مشاهده کردن حالت داده شده را از طریق گذشتن از حالت  در زمان t مشخص می‌کند. این احتمال شامل احتمالات پسین است که که تمام وقایع تا زمان t را پوشش می‌دهد و همچنین احتمال‌های پیشین که شامل تمام وقایع آینده می‌شود. این همان شمارنده‌ای است که در معادله به دنبالش بودیم و بر احتمال حقیقی توالی مشاهده شده تقسیم می‌کنیم تا بردار را نرمال کنیم و تنها احتمال . این ارزش‌ها گاهی اوقات به نام "ارزش‌های هموار (صاف شده)" خوانده می‌شوند زیرا برای به دست آوردن آن‌ها از احتمالات پسین و پیشین استفاده شده‌است تا احتمال نهایی را محاسبه کند.

بنابراین مقادیر  احتمال بودن در هر حالت را در زمان t ارائه می‌کند. به این ترتیب آن‌ها برای تعیین محتمل‌ترین حالت به کار می‌روند. لازم است ذکر شود که اصطلاح «محتمل‌ترین حالت» تا حدودی مبهم است. برای توضیح باید گفت که محتمل‌ترین حالت، حالتی است که بیشترین احتمال برای درست بودن در یک نقطه معین را دارد اما محاسبه توالی احتمالات حالت‌ها احتمالاً نتواند به ما محتمل‌ترین توالی را بدهد. این به خاطر این هست که احتمال برای هر نقطه به صورت مستقل از یک‌دیگر محاسبه می‌شود و احتمال شرطی میان‌حالتی محاسبه نمی‌شود و این به این معنی است که احتمالات حالت‌ها در زمان‌های t و t+1 متفاوت هست و ما با این فرمول نمی‌توانیم یک توالی را برای زمان‌های جلوتر محاسبه کنیم. به زبان ریاضی: .

برای به دست آوردن محتمل‌ترین دنباله از حالت‌ها که دنبالهٔ مشاهدات را تولید می‌کنند، می‌توانید از الگوریتم ویتربی استفاده کنید

مثال 
این مثال منشاءاش را از جهان چتری(umbrella world) در راسل و نوریگ ۲۰۱۰ فصل ۱۵, صفحات ۵۶۶ می‌آورد که در آن ما می‌خواهم با مشاهداتمان از چتر داشتن یا نداشتن یک مرد وضعیت آب و هوا را نتیجه‌گیری کنیم. ما دو حالت مختلف را برای آب و هوا فرض می‌کنیم: حالت اول = باران ببارد؛ حالت دوم = باران نبارد. ما فرض می‌کنیم که آب و هوا است ۷۰٪ شانس ماندن در همان وضعیت خودش را دارد و ۳۰٪ شانس برای تغییر حالت دادن. پس ماتریس تغییرات ما به شکل زیر خواهد شد:

ما همچنین فرض کنیم که هر حالت دو واقعه را دربردارد: واقعه اول = مرد چتر بیاورد؛ واقعه دوم = مرد چتر نیاورد. پس احتمالات چتر آوردن و چتر نیاوردن را برای هر حالت (بارانی و غیر بارانی) در ماتریس احتمالات می‌نویسیم. فرض کنید احتمال این که بارانی باشد و چتر بیاورد ۹۰٪؛ بارانی باشد و چتر بیاورد ۱۰٪؛ بارانی نباشد و چتر بیاورد ۲۰٪؛ بارانی نباشد و چتر نیاورد ۸۰٪ است:

با داشتن این اطلاعات بعد از آن که توالی وقایع را به‌ترتیب {چتر، چتر، بدون چتر، چتر، چتر} مشاهده کردیم می‌توانیم محاسبات خود را شروع کنیم:

توجه داشته باشید که  متفاوت است چون مرد «بدون چتر» مشاهده شده و در بقیه موارد «با چتر» دیده شده‌است.

شروع می‌کنیم به محاسبهٔ احتمالات پیش‌رو، بنابراین یک بردار اولیه می‌گیریم:

به این دلیل بردار اولیه را انتخاب می‌کنیم، چون نمی‌دانیم قبل از مشاهدات ما آب و هوا در چه حالتی بود (بارانی بود یا نبود). بردار اولیه ما باید یک بردار افقی باشد. برای راحت‌تر شدن محاسباتمان این بردار را ترانهاده می‌کنیم. معادلهٔ ما به صورت زیر می‌شود:

به جای:

توجه کنید که ماتریس انتقال نیز ترانهاده شده اما در مثال ما ترانهاده این ماتریس با خودش برابر است (چون ماتریس متقارن است). انجام این محاسبات و نرمال کردن این نتایج را فراهم می‌کند:

شروع می‌کنیم به محاسبهٔ احتمالات پس‌رو، بنابراین یک بردار اولیه می‌گیریم:

و بعد از آن شروع می‌کنیم به انجام محاسبات (مثل قبل):

در نهایت، ما احتمالات صاف‌شده را محاسبه می‌کنیم. این نتایج باید نرمال هم بشوند (جمع ورودی‌ها یک شود) چون ما در احتمالات پیشین، احتمالات را با < اسکیل نکرده‌بودیم.

دقت داشته باشید که ارزش  دقیقاً برابر است با  و همچنین ارزش  دقیقاً برابر است با . این بدین دلیل است که هر دو  و  دارایاحتمالات پیشین و حالات‌های پایانی یکسان هستند و شامل تمامی مشاهدات ما هستند.

اگرچه،  تنها در زمانی برابر  است که تمامی بردارهای حالت اولیه یکسان داشته باشند (یعنی تمامی آن‌ها ورودی برابر داشته باشند). وقتی شرایط چنین نیست e  باید با بردار حالت اولیه ترکیب شود تا محتمل‌ترین حالت اولیه را پیدا کنیم. ما همچنان می‌دانیم که احتمالات پسین خودشان به اندازهٔ کافی اعتبار دارند تا محتمل‌ترین حالت پایانی را محاسبه کنند. به همین ترتیب احتمالات پسین هم می‌توانند با بردارهای حالات اولیه ترکیب شوند تا محتمل‌ترین حالت آغازین طبق مشاهدات را به ما بدهند. احتمالات پسین و پیشین با ترکیب با هم می‌توانند احتمال حالات محتمل آغازین و پایانی را محاسبه کنند.

محاسبات فوق نشان می‌دهند که محتمل‌ترین حالت آب و هوا برای هر روز به غیر از روز سوم، آب و هوای «بارانی» است. البته این محاسبات به ما بیشتر از این می‌گویند، چون آن‌ها می‌توانند احتمالات هر حالت را در زمان‌های مختلف به ما ارائه کنند. شاید مهمتر از همه بدست آوردن اطلاع دربارهٔ  حال ما با استفاده از این اطلاعات و محاسبات می‌توانیم حالات مختلف آب و هوای فردا را تنها با احتمال مشاهده کردن چتر پیش‌بینی کنیم.

عملکرد 
با استفاده از الگوریتم جستجوی جامع برای حل این مسئله ما باید تمامی  تا توالی حالات را تولید کنیم و احتمال هر یک از حالات آن را با استفاده از توالی وقایع (رویدادها) محاسبه کنیم. این رویکرد دارای پیچیدگی زمانی  که در آن  طول دنباله (توالی) و  تعداد نمادهای استفاده شده در الفبای حالات است. این پیچیدگی زمانی برای مسائل کاربردی غیرقابل تحمل است زیرا تعداد توالی گره‌های حالات پنهان محتمل در عمل بسیار زیاد است. در این صورت، الگوریتم پس‌رو-پیش‌رو می‌تواند این مسئله را در پیچیدگی زمانی .

یک الگوریتم حافظه محور بهینه‌تر از الگوریتم پس‌رو-پیش‌رو وجود دارد به نام الگوریتم جزیره که استفادهٔ از حافظهٔ کمتر را با کشیدن زمان بیشتر تعویض کرده. این الگوریتم دارای پیچیدگی زمان  است اما تنها از پیچیدگی حافظهٔ  استفاده می‌کند. اما بر روی یک کامپیوتر با تعداد نامحدودی پردازنده (تعدادی زیادتر از حجم محاسبات) پیچیدگی زمانی این الگوریتم می‌تواند به  کاهش یابد درحالی که هنوز از پیچیدگی حافظه  استفاده می‌کند.

علاوه بر این، این الگوریتم‌ها طوری تکوین یافته‌اند تا با استفاده از الگوریتم‌های صاف کردن برخط(online smoothing) مانند الگوریتم fixed-lag smoothing (FLS) مقادیر  به صورت کارآمد محاسبه کنند راسل و نووریگ ۲۰۱۰ شکل ۱۵٫۶ صفحهٔ ۵۸۰.

شبه کد 
Backward(guessState, sequenceIndex):
  if sequenceIndex is past the end of the sequence, return 1
  if (guessState, sequenceIndex) has been seen before, return saved result
  result = ۰
  for each neighboring state n:
  result = result + (transition probability from guessState to
  n given observation element at sequenceIndex)
  * Backward(n, sequenceIndex+1)
  save result for (guessState, sequenceIndex)
  return result

مثال با زبان پایتون 
با توجه HMM(مدل پنهان مارکف) (دقیقاً مانند الگوریتم ویتربی) در زبان برنامه‌نویسی پایتون نشان می‌دهیم:states = ('Healthy', 'Fever')
end_state = 'E'

observations = ('normal', 'cold', 'dizzy')

start_probability = {'Healthy': 0.6, 'Fever': 0.4}

transition_probability = {
   'Healthy' : {'Healthy': 0.69, 'Fever': 0.3, 'E': 0.01},
   'Fever' : {'Healthy': 0.4, 'Fever': 0.59, 'E': 0.01},
   }

emission_probability = {
   'Healthy' : {'normal': 0.5, 'cold': 0.4, 'dizzy': 0.1},
   'Fever' : {'normal': 0.1, 'cold': 0.3, 'dizzy': 0.6},
   }ما می‌توانیم پیاده‌سازی را بدین شکل بنویسیم:def fwd_bkw(observations, states, start_prob, trans_prob, emm_prob, end_st):
    # forward part of the algorithm
    fwd = []
    f_prev = {}
    for i, observation_i in enumerate(observations):
        f_curr = {}
        for st in states:
            if i == 0:
                # base case for the forward part
                prev_f_sum = start_prob[st]
            else:
                prev_f_sum = sum(f_prev[k]*trans_prob[k][st] for k in states)

            f_curr[st] = emm_prob[st][observation_i] * prev_f_sum

        fwd.append(f_curr)
        f_prev = f_curr

    p_fwd = sum(f_curr[k] * trans_prob[k][end_st] for k in states)

    # backward part of the algorithm
    bkw = []
    b_prev = {}
    for i, observation_i_plus in enumerate(reversed(observations[1:]+(None,))):
        b_curr = {}
        for st in states:
            if i == 0:
                # base case for backward part
                b_curr[st] = trans_prob[st][end_st]
            else:
                b_curr[st] = sum(trans_prob[st][l] * emm_prob[l][observation_i_plus] * b_prev[l] for l in states)

        bkw.insert(0,b_curr)
        b_prev = b_curr

    p_bkw = sum(start_prob[l] * emm_prob[l][observations[0]] * b_curr[l] for l in states)

    # merging the two parts
    posterior = []
    for i in range(len(observations)):
        posterior.append({st: fwd[i][st] * bkw[i][st] / p_fwd for st in states})

    assert p_fwd == p_bkw
    return fwd, bkw, posteriorتابع fwd_bkw این ورودی‌ها را می‌گیرد:
observations دنبالهٔ مشاهدات ما است مثلاً ['normal', 'cold', 'dizzy'];
states همان مجموعه ما از حالات پنهان است؛
start_prob احتمالات آغازین ما است؛ trans_prob احتمالات گذار ما است احتمالات و emm_prob احتمالات انتشار ما.

برای سادگی کد، ما فرض می‌کنیم که توالی مشاهدات ما observations خالی نیست و trans_prob[i][j] و [i][j]emm_prob تعریف شده‌است برای تمام حالات i,j.

در مثال اجرا شده، الگوریتم پس‌رو-پیش‌رو به صورت زیر استفاده شده‌است:def example():
    return fwd_bkw(observations,
                   states,
                   start_probability,
                   transition_probability,
                   emission_probability,
                   end_state)>>> for line in example():
...     print(*line)
...
{'Healthy': 0.3, 'Fever': 0.04000000000000001} {'Healthy': 0.0892, 'Fever': 0.03408} {'Healthy': 0.007518, 'Fever': 0.028120319999999997}
{'Healthy': 0.0010418399999999998, 'Fever': 0.00109578} {'Healthy': 0.00249, 'Fever': 0.00394} {'Healthy': 0.01, 'Fever': 0.01}
{'Healthy': 0.8770110375573259, 'Fever': 0.1229889624426741} {'Healthy': 0.623228030950954, 'Fever': 0.3767719690490461} {'Healthy': 0.2109527048413057, 'Fever': 0.7890472951586943}

جستارهای وابسته 
 الگوریتم باوم-ولچ
 الگوریتم ویتربی
 الگوریتم بی‌سی‌جی‌آر

منابع 

 Lawrence R. Rabinerآموزش در مخفی مارکوف و مدل‌های انتخاب شده در برنامه‌های کاربردی در گفتار. مجموعه مقالات IEEE, 77 (2), p.  257-286 فوریه 1989. ۱۰٫۱۱۰۹/۵٫۱۸۶۲۶

پیوند به بیرون 
 تعاملی گسترده برای آموزش به جلو–رو به عقب الگوریتم (صفحه گسترده و مقاله با گام به گام از طریق راه رفتن)
 آموزش پنهان مارکوف مدل از جمله رو به جلو–رو به عقب الگوریتم
 مجموعه ای از الگوریتم‌های هوش مصنوعی اجرا در جاوا (از جمله HMM و رو به جلو–رو به عقب الگوریتم)

آشکارسازی و اصلاح خطا
الگوریتم‌های یادگیری ماشینی
برنامه‌نویسی پویا
مدل‌های مارکوف
ویکی‌سازی رباتیک