অ্যাডাপটিভ ফিল্টার — LMS অ্যালগরিদমের ভিত্তি
এই পাঠে যা শিখবেন
- অ্যাডাপটিভ ফিল্টারের ধারণা ও স্থির ফিল্টার (M৭-M৮) থেকে এর পার্থক্য
- LMS আপডেট নিয়ম ও কেন এটি গ্র্যাডিয়েন্ট-ডিসেন্ট-সদৃশ একটি পদ্ধতি
- Python-এ স্যাম্পল-বাই-স্যাম্পল LMS বাস্তবায়ন করে প্রকৃত এরর-কনভার্জেন্স দেখা
- স্টেপ-সাইজ $\mu$-এর গুরুত্ব — খুব ছোট হলে ধীর শেখা, খুব বড় হলে ডাইভার্জেন্স
১ · অ্যাডাপটিভ ফিল্টার ও LMS আপডেট নিয়ম
M৩০-M৩৯-এর FIR/IIR ফিল্টার সবসময় ডিজাইন-টাইমে হিসেব করা স্থির কোয়েফিসিয়েন্ট ব্যবহার করে। কিন্তু বাস্তব পরিস্থিতিতে (যেমন একটি টেলিফোন লাইনের অজানা ইকো, বা সময়ের সাথে বদলানো ইন্টারফেরেন্স) সঠিক কোয়েফিসিয়েন্ট আগে থেকে জানা সম্ভব না — সেখানে দরকার একটি অ্যাডাপটিভ ফিল্টারAdaptive Filterএকটি ফিল্টার যা তার নিজের ইনপুট/এরর সিগন্যাল পর্যবেক্ষণ করে স্যাম্পল-বাই-স্যাম্পল তার নিজস্ব কোয়েফিসিয়েন্ট আপডেট করে।, যা রান-টাইমে নিজেই তার কোয়েফিসিয়েন্ট "শিখে"।
সবচেয়ে জনপ্রিয় অ্যাডাপটিভ অ্যালগরিদম LMS (Least Mean Squares)। একটি $M$-ট্যাপ FIR ফিল্টারের জন্য, প্রতিটি স্যাম্পল $n$-এ:
$$y[n] = \sum_{i=0}^{M-1} w_i \, x[n-1-i], \qquad e[n] = d[n] - y[n], \qquad w_i \leftarrow w_i + \mu \, e[n] \, x[n-1-i]$$এখানে $d[n]$ কাঙ্ক্ষিত (desired) মান, $y[n]$ ফিল্টারের বর্তমান পূর্বাভাস, $e[n]$ এরর, আর $\mu$ স্টেপ-সাইজ — কতটা "সাহসী" আপডেট নেওয়া হবে তা নিয়ন্ত্রণ করে। এই আপডেট নিয়মটি আসলে Numerical Methods কোর্সের গ্র্যাডিয়েন্ট ডিসেন্ট-এরই একটি বিশেষ রূপ — এরর-স্কয়ারকে (cost function) কমানোর জন্য ওজনগুলোকে গ্র্যাডিয়েন্টের বিপরীত দিকে ছোট ছোট ধাপে সরানো হয়, শুধু এখানে প্রতিটি নতুন স্যাম্পলে একবার করে "স্টোকাস্টিক" আপডেট হয়।
LMS ব্যাচ-প্রসেসিং করে না — প্রতিটি নতুন স্যাম্পল আসার সাথে সাথেই একটি আপডেট হয়, তাই এটি সময়ের সাথে বদলানো পরিবেশের সাথেও মানিয়ে নিতে পারে।
ছোট $\mu$ = ধীর কিন্তু স্থিতিশীল কনভার্জেন্স। বড় $\mu$ = দ্রুত কিন্তু অস্থিতিশীলতার ঝুঁকি — নিচে সরাসরি দেখানো হয়েছে।
ইকো ক্যান্সেলেশন (ফোন কল), নয়েজ ক্যান্সেলেশন (হেডফোন), চ্যানেল ইকুয়ালাইজেশন (কমিউনিকেশন) — সবখানেই LMS বা এর ভ্যারিয়েন্ট ব্যবহার হয়।
২ · একটি সত্যিকারের ডেমো — অ্যাডাপটিভ প্রেডিকশন ও এরর-কনভার্জেন্স
নিচের কোডে একটি $2$-ট্যাপ LMS প্রেডিক্টর একটি $4$ Hz সাইন সিগন্যালের ($f_s=32$ Hz, সাথে সামান্য
random.gauss নয়েজ) পরবর্তী মান তার আগের দুটো স্যাম্পল থেকে অনুমান করতে শেখে। স্টেপ-সাইজ
$\mu=0.05$ (ছোট ও স্থিতিশীল)। এরর প্রতি ৪০ ইটারেশনের ব্লকে গড় করে দেখানো হয়েছে, যাতে কনভার্জেন্সের প্রবণতা
পরিষ্কার দেখা যায় (একক স্যাম্পলের এরর নিজেই ওঠানামা করে, কারণ সিগন্যাল পর্যায়বৃত্ত — কিন্তু ব্লক-গড় ধারাবাহিকভাবে
কমে)।
import math, random
random.seed(3)
fs = 32.0
f0 = 4.0
N = 400
noise_std = 0.05
x = [math.sin(2 * math.pi * f0 * n / fs) + random.gauss(0, noise_std) for n in range(N)]
M = 2 # ট্যাপ সংখ্যা
mu = 0.05 # স্টেপ-সাইজ
w = [0.0] * M # শুরুর ওজন (শূন্য থেকে শুরু)
errors = []
for n in range(M, N):
y = sum(w[i] * x[n - 1 - i] for i in range(M)) # পূর্বাভাস
e = x[n] - y # এরর
for i in range(M):
w[i] = w[i] + mu * e * x[n - 1 - i] # LMS আপডেট নিয়ম
errors.append(abs(e))
theoretical_w0 = 2 * math.cos(2 * math.pi * f0 / fs)
print(f"তাত্ত্বিক সর্বোত্তম ওজন (বিশুদ্ধ সাইন প্রেডিক্টর): w0={theoretical_w0:.4f}, w1=-1.0000")
print(f"LMS-এর শেখা চূড়ান্ত ওজন: w0={w[0]:.4f}, w1={w[1]:.4f}")
print()
def avg(lst):
return sum(lst) / len(lst)
block = 40
print(f"{'ইটারেশন':>14} | {'গড় |error|':>11}")
for start in range(0, len(errors), block):
seg = errors[start:start + block]
print(f"{start:>6}-{start+len(seg)-1:<6} | {avg(seg):>11.6f}")
৩ · অতিরিক্ত বড় $\mu$ — একটি সত্যিকারের ডাইভার্জেন্স ডেমো
ঠিক যেমন Numerical Methods কোর্সের গ্র্যাডিয়েন্ট ডিসেন্ট পাঠে খুব বড় লার্নিং-রেট অ্যালগরিদমকে সর্বনিম্ন বিন্দুর চারপাশে "লাফ দিয়ে" দূরে সরিয়ে দেয়, LMS-এও খুব বড় $\mu$ একই সমস্যা করে — প্রতিটি আপডেট এত বড় পদক্ষেপ নেয় যে ওজন সর্বোত্তম মানের কাছে স্থির না হয়ে ক্রমশ দূরে সরে যায়। নিচের কোডে ইচ্ছাকৃতভাবে $\mu=3.0$ (অনেক বড়, একই সিগন্যালের জন্য) ব্যবহার করে (ইটারেশন সংখ্যা মাত্র ৪০-এ সীমাবদ্ধ রেখে, সুরক্ষার জন্য) ওজনের ম্যাগনিটিউড কীভাবে বাড়ে তা দেখানো হয়েছে।
import math, random
random.seed(3)
fs = 32.0
f0 = 4.0
noise_std = 0.05
N_small = 40 # সুরক্ষার জন্য ছোট রাখা হয়েছে
x = [math.sin(2 * math.pi * f0 * n / fs) + random.gauss(0, noise_std) for n in range(N_small)]
M = 2
mu_big = 3.0 # ইচ্ছাকৃতভাবে অনেক বড় স্টেপ-সাইজ
w2 = [0.0] * M
print(f"{'n':>3} | {'|w0|':>14} | {'|w1|':>14} | {'|error|':>14}")
for n in range(M, N_small):
y = sum(w2[i] * x[n - 1 - i] for i in range(M))
e = x[n] - y
for i in range(M):
w2[i] = w2[i] + mu_big * e * x[n - 1 - i]
if n < 12 or n % 4 == 0:
print(f"{n:>3} | {abs(w2[0]):>14.4e} | {abs(w2[1]):>14.4e} | {abs(e):>14.4e}")
LMS একটি সরল, স্যাম্পল-বাই-স্যাম্পল আপডেট নিয়ম ($w_i \leftarrow w_i + \mu e[n] x[n-1-i]$) দিয়ে একটি FIR ফিল্টারকে ডেটা থেকে "শিখতে" দেয় — উপরের ডেমো প্রমাণ করেছে ছোট, স্থিতিশীল $\mu=0.05$-এ এরর ধারাবাহিকভাবে কমে (০.৫৪ থেকে ০.০৮), কিন্তু $\mu=3.0$-এ একই সিগন্যালে ওজন সূচকীয়ভাবে বিস্ফোরিত হয়। এই "স্টেপ-সাইজ বনাম স্থিতিশীলতা" ট্রেড-অফ ঠিক গ্র্যাডিয়েন্ট-ডিসেন্ট-ভিত্তিক যেকোনো লার্নিং অ্যালগরিদমেই দেখা যায়। এখানেই মডিউল ১০ শেষ হয় — পরের মডিউলে (M১১, L৪৮ থেকে) আমরা র্যান্ডম সিগন্যাল, স্টেশনারিটি, SNR ও ম্যাচড ফিল্টারিং-এর মতো পরিসংখ্যানগত DSP ধারণায় প্রবেশ করব।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ LMS-এর শেখা ওজন ($w_0=1.3302,\ w_1=-0.9216$) তাত্ত্বিক সর্বোত্তম মানের ($1.4142,\ -1.0$) সাথে হুবহু মেলেনি কেন?
দুটো কারণ। প্রথমত, সিগন্যালে random.gauss(0, 0.05) নয়েজ যোগ করা আছে, যা তাত্ত্বিক
"বিশুদ্ধ সাইন" সম্পর্ককে সামান্য বিচ্যুত করে। দ্বিতীয়ত, LMS একটি স্টোকাস্টিক
আলগোরিদম — এটি প্রতিটি স্যাম্পলে সত্যিকারের গ্র্যাডিয়েন্টের বদলে একটি নয়েজি, তাৎক্ষণিক অনুমান ব্যবহার করে
আপডেট করে, তাই এমনকি অসীম ইটারেশনেও ওজন সর্বোত্তম মানের চারপাশে সামান্য ওঠানামা করতেই থাকে (একে বলে
"মিসঅ্যাডজাস্টমেন্ট")।
প্র ০২ ব্লক-গড় এরর ব্যবহার না করে যদি প্রতিটি একক স্যাম্পলের $|error|$ সরাসরি প্রিন্ট করা হতো, তাহলে কনভার্জেন্সের প্রবণতা কেমন দেখাত?
অনেক বেশি "নয়েজি" ও অস্পষ্ট দেখাত — একক স্যাম্পলের এরর সাইন সিগন্যালের ফেজের সাথে সাথে ওঠানামা করে (কখনো ফিল্টার কাকতালীয়ভাবে ভালো পূর্বাভাস দেয়, কখনো খারাপ), তাই আসল কনভার্জেন্স-প্রবণতা প্রতি-স্যাম্পল তথ্যে চাপা পড়ে যায়। এটাই এই পাঠে ব্লক-গড় ব্যবহারের কারণ — অনেকটা M৪৪-এর Welch's method-এর মতোই, একাধিক মানের গড় নিলে অন্তর্নিহিত প্রবণতা স্পষ্ট হয়।
প্র ০৩ ডাইভার্জেন্স ডেমোতে ইটারেশন সংখ্যা মাত্র ৪০-এ সীমাবদ্ধ রাখা হয়েছে। এটি কেন গুরুত্বপূর্ণ?
কারণ $\mu=3.0$-এ ওজন সূচকীয় হারে বাড়ে — মাত্র ৩৬ ইটারেশনেই মান $10^7$ ছাড়িয়ে যায়। আরও বেশি ইটারেশন
চালালে সংখ্যাগুলো দ্রুত পাইথনের ফ্লোটিং-পয়েন্ট সীমার কাছাকাছি চলে যেতে পারে বা গণনায় inf/nan
তৈরি করতে পারে — তাই এই কোর্সের CLAUDE.md-এ উল্লেখিত নিয়ম অনুযায়ী ইচ্ছাকৃত অস্থিতিশীলতার ডেমোতে
ইটারেশন-সংখ্যা সবসময় সীমাবদ্ধ (guarded) রাখা হয়, যাতে ডেমোটি নিরাপদ ও দ্রুত থাকে অথচ ডাইভার্জেন্স
প্রবণতা স্পষ্টভাবে দেখানো যায়।
অনুশীলন
-
চিন্তা করুন: প্রথম কোড সেলে
mu = 0.05-কেmu = 0.01-এ (আরও ছোট) বদলালে, একই ৪০০ ইটারেশনের পর এরর কনভার্জেন্স আগের চেয়ে দ্রুত হবে না ধীর হবে বলে আপনার ধারণা?ধীর হবে। ছোট $\mu$ মানে প্রতিটি আপডেট ধাপ ছোট — ওজন সর্বোত্তম মানের দিকে আস্তে আস্তে এগোবে, তাই একই সংখ্যক ইটারেশনে (৪০০) ওজন কম দূরত্ব অতিক্রম করবে এবং শেষ ব্লকের গড় $|error|$ আগের ০.০৮১১-এর চেয়ে বেশি থাকবে।
-
পরীক্ষা করুন: প্রথম কোড সেলে
mu-কে ০.০১-এ পরিবর্তন করে Run চেপে আপনার হিসেব যাচাই করুন — শেষ ব্লকের (৩৬০-৩৯৭) গড় $|error|$ কত হয় দেখুন এবং আগের ০.০৯৮৫-এর সাথে তুলনা করুন।রান করলে দেখা যাবে $\mu=0.01$-এ কনভার্জেন্স সত্যিই ধীর — ৪০০ ইটারেশন শেষেও ব্লক-গড় $|error|$ $\mu=0.05$-এর তুলনায় লক্ষণীয়ভাবে বেশি থাকবে, কারণ অ্যালগরিদমের এখনও সর্বোত্তম ওজনের কাছে পৌঁছানোর জন্য আরও বেশি ইটারেশন দরকার। এটি নিশ্চিত করে $\mu$-এর পছন্দ সরাসরি কনভার্জেন্সের গতির সাথে সমানুপাতিক — যতক্ষণ না তা স্থিতিশীলতার সীমা (৩ নং অংশে দেখানো) ছাড়িয়ে যায়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- র্যান্ডম সিগন্যাল — স্টেশনারিটি ও এরগোডিসিটি পরবর্তী পাঠ মডিউল ১১-এর প্রথম পাঠ — র্যান্ডম সিগন্যালের পরিসংখ্যানগত বৈশিষ্ট্য, যা LMS-এর মতো অ্যাডাপটিভ অ্যালগরিদম বিশ্লেষণের গাণিতিক ভিত্তি।
- ওয়েনার ফিল্টারিং — অপটিমাল লিনিয়ার এস্টিমেশন M১১ LMS আসলে ওয়েনার ফিল্টারের সর্বোত্তম সমাধানের দিকে ধাপে ধাপে অগ্রসর হওয়ার একটি স্টোকাস্টিক পদ্ধতি — সেই সংযোগ পরে স্পষ্ট হবে।
- গ্র্যাডিয়েন্ট ডিসেন্ট সহোদর কোর্স Numerical Methods কোর্সের এই পাঠে গ্র্যাডিয়েন্ট-ভিত্তিক অপটিমাইজেশন ও লার্নিং-রেট সংবেদনশীলতা বিস্তারিত আলোচিত — এই পাঠের LMS তারই একটি DSP-নির্দিষ্ট প্রয়োগ।