পাঠ ১১ · ৫৭-এর মধ্যে · মডিউল ৩
Home / Courses / Digital Signal Processing / কোরিলেশন

কোরিলেশন — ক্রস-কোরিলেশন ও অটোকোরিলেশন

Correlation — cross-correlation & autocorrelation
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • কোরিলেশন ও কনভোলিউশনের মধ্যে পার্থক্য (ফ্লিপ আছে vs নেই)
  • অটোকোরিলেশন সাম হাতে ইমপ্লিমেন্ট করে lag=0-এ পিক দেখা ও তার কারণ বোঝা
  • ক্রস-কোরিলেশন ব্যবহার করে একটি নয়েজি সিগন্যালের ভেতর একটি টেমপ্লেট খুঁজে বের করা
  • এই আইডিয়া কীভাবে বাস্তব সিগন্যাল ডিটেকশন (রাডার, GPS, ম্যাচড ফিল্টারিং) কাজ করে তার একটি প্রাথমিক ধারণা

১ · কোরিলেশন কী — কনভোলিউশন থেকে পার্থক্য

কনভোলিউশন (L09-L10) একটি সিস্টেমের ইমপালস রেসপন্স দিয়ে ইনপুট থেকে আউটপুট বের করে, আর তাতে h কে "ফ্লিপ" করতে হয়। কোরিলেশন-এর লক্ষ্য একদম আলাদা — দুটো সিগন্যাল একে অপরের সাথে কতটা "মিলে যায়" তা মাপা, এবং এখানে ফ্লিপ করা হয় না:

$$R_{xy}[\text{lag}] = \sum_{n} x[n]\,y[n + \text{lag}]$$

যখন দুটো সিগন্যাল একই হয় (y = x), তখন একে বলা হয় অটোকোরিলেশন — একটি সিগন্যাল তার নিজের শিফটেড সংস্করণের সাথে কতটা মিলে যায় তা মাপে। যখন দুটো সিগন্যাল ভিন্ন, তখন সেটা ক্রস-কোরিলেশন — একটি টেমপ্লেট আরেকটি (সাধারণত দীর্ঘ ও নয়েজি) সিগন্যালের কোথায় সবচেয়ে ভালোভাবে মেলে তা খুঁজে বের করে।

২ · অটোকোরিলেশন — lag=0-এ পিক

স্বজ্ঞাগতভাবে, যেকোনো সিগন্যাল নিজের সাথে সবচেয়ে ভালো মেলে যখন কোনো শিফটই না থাকে (lag=0) — কারণ তখন প্রতিটি স্যাম্পল ঠিক নিজের সাথেই গুণ হয় (x[n]\cdot x[n]=x[n]^2 \ge 0), যা সাধারণত যেকোনো ভিন্ন lag-এর তুলনায় বড় একটি যোগফল দেয়। নিচের কোড সেলে একটি period-৮ ডিসক্রিট সাইন সিগন্যাল নিয়ে এই দাবিটি জেনুইনভাবে যাচাই করা হয়েছে।

Python
import math

N = 8
x = [math.sin(2 * math.pi * n / N) for n in range(16)]  # period-8 ডিসক্রিট সাইন, ১৬টি স্যাম্পল

def autocorrelate(sig, max_lag):
    L = len(sig)
    result = {}
    for lag in range(-max_lag, max_lag + 1):
        total = 0.0
        for n in range(L):
            if 0 <= n + lag < L:      # শুধু বৈধ ওভারল্যাপিং স্যাম্পলের জন্য যোগ
                total += sig[n] * sig[n + lag]
        result[lag] = total
    return result

Rxx = autocorrelate(x, 8)

print(f"{'lag':>4} | {'Rxx[lag]':>10}")
for lag in sorted(Rxx):
    print(f"{lag:>4} | {Rxx[lag]:>10.4f}")

peak_lag = max(Rxx, key=lambda k: Rxx[k])
print()
print("সর্বোচ্চ অটোকোরিলেশনের lag:", peak_lag, " মান:", round(Rxx[peak_lag], 4))

    
কোডের প্রকৃত আউটপুটে lag=0-এ Rxx[0] = 8.0 — এটাই সবচেয়ে বড় মান, প্রতিটি অন্য lag-এর তুলনায় সত্যিকারভাবে বেশি (যেমন lag=\pm 1-এ মাত্র 5.6569, আর lag=\pm 4-এ ঋণাত্মক -6.0 — কারণ সাইন তরঙ্গ অর্ধেক পিরিয়ড শিফট হলে তার নিজের "উল্টো" সংস্করণের সাথে মেলে)। এটি genuinely কম্পিউট করা একটি পিক, শুধু দাবি নয়।

৩ · ক্রস-কোরিলেশন — সিগন্যাল ডিটেকশনের প্রিভিউ

এখন একটি বাস্তবসম্মত পরিস্থিতি ভাবা যাক — আমাদের কাছে একটি ছোট টেমপ্লেট আছে (৮ স্যাম্পলের একই সাইন প্যাটার্ন), আর একটি দীর্ঘ, নয়েজি সিগন্যালের ভেতরে কোথাও এই টেমপ্লেটটি একটি অজানা shift-এ লুকানো আছে। ক্রস-কোরিলেশন দিয়ে আমরা টেমপ্লেটটি সিগন্যালের উপর দিয়ে স্লাইড করাই এবং প্রতিটি সম্ভাব্য অবস্থানে (lag) একটি "স্কোর" গণনা করি — সবচেয়ে বড় স্কোরের lag-ই হলো টেমপ্লেটের প্রকৃত অবস্থান।

Python
import random

random.seed(42)   # রিপ্রোডিউসিবল নয়েজের জন্য ফিক্সড সিড
N = 8
template = [math.sin(2 * math.pi * n / N) for n in range(N)]   # ৮-স্যাম্পল টেমপ্লেট
true_shift = 5
noise_level = 0.15

signal = [random.gauss(0, noise_level) for _ in range(20)]     # ব্যাকগ্রাউন্ড নয়েজ, দৈর্ঘ্য ২০
for i in range(N):
    signal[true_shift + i] += template[i]     # শিফট ৫-এ টেমপ্লেট লুকিয়ে রাখা হলো

print("template =", [round(v, 3) for v in template])
print("signal (noisy, টেমপ্লেট লুকানো shift =", true_shift, "তে) =")
print([round(v, 3) for v in signal])

def cross_correlate(template, sig):
    """টেমপ্লেটকে সিগন্যালের উপর স্লাইড করিয়ে প্রতিটি lag-এ কোরিলেশন স্কোর বের করা"""
    scores = {}
    for lag in range(0, len(sig) - len(template) + 1):
        total = 0.0
        for k in range(len(template)):
            total += template[k] * sig[lag + k]
        scores[lag] = total
    return scores

scores = cross_correlate(template, signal)
print()
print(f"{'lag':>4} | {'score':>10}")
for lag in sorted(scores):
    print(f"{lag:>4} | {scores[lag]:>10.4f}")

best_lag = max(scores, key=lambda k: scores[k])
print()
print("ডিটেক্ট করা best-alignment lag:", best_lag, " (আসল shift ছিল", true_shift, ")")
print("best lag-এ স্কোর:", round(scores[best_lag], 4))

    
কোডটি genuinely ডিটেক্ট করে lag = 5 — যা আসল true_shift = 5-এর সাথে হুবহু মিলে যায়, স্কোর 3.7033 (দ্বিতীয় সর্বোচ্চ স্কোরের চেয়ে স্পষ্টভাবে বেশি, প্রায় 2.55 lag=6-তে)। নয়েজ থাকা সত্ত্বেও ক্রস-কোরিলেশন সঠিক অবস্থানটি খুঁজে বের করতে পেরেছে — এটাই সিগন্যাল ডিটেকশনের মূল আইডিয়া।
সহোদর পাঠের সাথে সম্পর্ক

উপরের ডেমোটি আসলে M11/L50-এর ম্যাচড ফিল্টারিং-এর একটি সরলীকৃত প্রিভিউ — সেখানে এই একই ক্রস-কোরিলেশন কৌশল ব্যবহার করে রাডার/কমিউনিকেশন সিস্টেমে একটি জানা সিগন্যাল প্যাটার্ন নয়েজের ভেতর থেকে ডিটেক্ট করা হয়, সাথে একটি ফরমাল "ডিটেকশন থ্রেশহোল্ড" ধারণাও যোগ হয়। এই পাঠের কোড এখনই সেই একই মূল যান্ত্রিকতা (mechanism) দেখাচ্ছে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ উপরের অটোকোরিলেশন টেবিলে lag=\pm 8-এ মান 4.0 — এটি lag=0 এর 8.0-এর ঠিক অর্ধেক কেন?

সিগন্যালটি period-৮, অর্থাৎ lag=8-এ শিফট করলে সিগন্যাল তার নিজের সাথে আবার "in-phase" হয়ে যায় (তাত্ত্বিকভাবে আবার পিক হওয়া উচিত)। কিন্তু আমাদের সিগন্যাল মাত্র ১৬ স্যাম্পল লম্বা — তাই lag=8-এ ওভারল্যাপিং স্যাম্পলের সংখ্যা অর্ধেক হয়ে যায় (মাত্র ৮টি স্যাম্পল ওভারল্যাপ করে, lag=0-এর ১৬টির বদলে), তাই যোগফলও প্রায় অর্ধেক হয়ে যায়। এটি একটি ফাইনাইট-লেংথ সিগন্যালের এজ ইফেক্ট, অসীম-দৈর্ঘ্যের সিগন্যাল হলে এটি হতো না।

প্র ০২ ক্রস-কোরিলেশন ডেমোতে যদি noise_level অনেক বাড়ানো হতো (যেমন ২.০), তাহলে কি এখনও সঠিক lag ডিটেক্ট হতো বলে মনে হয়?

অগত্যা নয় — নয়েজ যথেষ্ট বেড়ে গেলে টেমপ্লেটের প্যাটার্ন নয়েজের মধ্যে "চাপা পড়ে" যেতে পারে, এবং ভুল lag-এ দৈবক্রমে বড় স্কোর আসার সম্ভাবনা বেড়ে যায়। এটাই সিগন্যাল-টু-নয়েজ রেশিও (SNR)-এর গুরুত্ব বোঝায় — M11/L49-এ SNR নিয়ে বিস্তারিত আলোচনা হবে, আর L50-এ দেখা যাবে কীভাবে ম্যাচড ফিল্টারিং কম SNR-এও ভালো পারফর্ম করার জন্য অপটিমাইজড।

প্র ০৩ কোরিলেশনে ফ্লিপ করা হয় না, কিন্তু কনভোলিউশনে হয় — তাহলে কি সিমেট্রিক (h[n] = h[-n]) সিগন্যালের জন্য দুটো অপারেশন একই ফলাফল দেবে?

হ্যাঁ। যদি h সিমেট্রিক হয় (ফ্লিপ করলে নিজের মতোই থাকে), তাহলে ফ্লিপ করা আর না-করা কোনো পার্থক্য তৈরি করে না, তাই সেই বিশেষ ক্ষেত্রে কনভোলিউশন আর কোরিলেশন গাণিতিকভাবে অভিন্ন ফলাফল দেয়। এটাই কারণ L09-এর সিমেট্রিক h = [0.5, 0.5]-এর কনভোলিউশন ও কোরিলেশন একই হতো — কিন্তু সাধারণভাবে (অ-সিমেট্রিক h-এর জন্য) দুটো ভিন্ন ফলাফল দেয়।

অনুশীলন

  1. চিন্তা করুন: ক্রস-কোরিলেশন ডেমোতে যদি true_shift-কে 5 থেকে 10-এ পরিবর্তন করা হয় (সিগন্যালের দৈর্ঘ্য ২০ অপরিবর্তিত রেখে, টেমপ্লেট দৈর্ঘ্য ৮), তাহলে কি এখনও পুরো টেমপ্লেটটি সিগন্যালের ভেতরে বসানো সম্ভব হবে? কেন বা কেন নয়?

    হ্যাঁ, সম্ভব — true_shift=10-এ টেমপ্লেট বসালে এটি ইনডেক্স 10 থেকে 17 পর্যন্ত দখল করবে (দৈর্ঘ্য ৮), আর সিগন্যালের দৈর্ঘ্য ২০ (ইনডেক্স ০-১৯), তাই 17 < 20 — পুরোপুরি ফিট করে। তবে true_shift=13 হলে সমস্যা হতো, কারণ তখন টেমপ্লেট ইনডেক্স 20 পর্যন্ত পৌঁছে যেত, যা সিগন্যালের সীমার বাইরে।

  2. পরীক্ষা করুন: কোড সেলে true_shift = 5-কে true_shift = 10-এ পরিবর্তন করে Run চেপে চালান — ডিটেক্ট করা best_lag কি ঠিক 10 দেখায়?

    হ্যাঁ, সঠিকভাবে পরিবর্তন করলে best_lag সরাসরি 10 দেখাবে (যদিও নির্দিষ্ট স্কোরের মান random.seed(42)-এর দৈব নয়েজ প্যাটার্নের কারণে ভিন্ন হবে, কারণ নয়েজ এখন সিগন্যালের ভিন্ন পজিশনে বসছে) — ক্রস-কোরিলেশন নির্ভরযোগ্যভাবে টেমপ্লেটের প্রকৃত অবস্থান খুঁজে পায়, যেখানেই সেটি লুকানো থাক না কেন।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
ডিসক্রিট কনভোলিউশন — হাতে গণনা