পাঠ ৪৭ · ৫৭-এর মধ্যে · মডিউল ১০

সিঙ্গুলার ভ্যালু ডিকম্পোজিশন (SVD)

Singular Value Decomposition (SVD)
১১ মিনিট পড়া উচ্চ · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • SVD-এর সংজ্ঞা এবং U, Σ, V-এর জ্যামিতিক অর্থ (ঘূর্ণন-প্রসারণ-ঘূর্ণন)
  • সিঙ্গুলার ভ্যালু ও AᵀA-এর আইগেনভ্যালুর মধ্যে সম্পর্ক, এবং কেন এই সম্পর্কটি M8-এর পাওয়ার মেথডকে পুনর্ব্যবহারযোগ্য করে তোলে
  • SVD-এর বাস্তব প্রয়োগ — ডাইমেনশনালিটি রিডাকশন, লো-র‍্যাংক অ্যাপ্রক্সিমেশন, সিউডো-ইনভার্স
  • একটি সত্যিকারের, চলমান ডেমো যা প্রথম সিঙ্গুলার ভ্যালু/ভেক্টর জোড়া বের করে ও rank-1 রিকনস্ট্রাকশনের প্রকৃত এরর গণনা করে
  • এই সরলীকৃত পদ্ধতির সীমাবদ্ধতা — এটি কেন প্রোডাকশন-গ্রেড SVD-এর বিকল্প নয়

১ · SVD কী — সংজ্ঞা ও জ্যামিতিক অর্থ

SVDSingular Value Decompositionযেকোনো ম্যাট্রিক্সকে তিনটি সরল রূপান্তরে (ঘূর্ণন, স্কেলিং, ঘূর্ণন) ভেঙে ফেলার একটি পদ্ধতি — বর্গাকার বা আইগেনভ্যালু-সংজ্ঞায়িত হওয়ার প্রয়োজন ছাড়াই যেকোনো m×n ম্যাট্রিক্সের জন্য কাজ করে। হলো এমন একটি ডিকম্পোজিশন যা যেকোনো m × n ম্যাট্রিক্স A-কে তিনটি ম্যাট্রিক্সের গুণফলে লেখে:

$$A = U \Sigma V^T$$

যেখানে U (m × m) ও V (n × n) হলো অর্থোগোনাল ম্যাট্রিক্স (এদের কলামগুলো একে অপরের সাথে লম্ব এবং একক-দৈর্ঘ্যের — বিশুদ্ধ ঘূর্ণন/প্রতিফলন প্রতিনিধিত্ব করে), আর Σ (m × n) একটি ডায়াগোনাল ম্যাট্রিক্স যার ডায়াগোনাল এন্ট্রিগুলো সিঙ্গুলার ভ্যালু (σ₁ ≥ σ₂ ≥ ... ≥ 0), সবসময় অ-ঋণাত্মক এবং বৃহৎ থেকে ক্ষুদ্র ক্রমে সাজানো। জ্যামিতিকভাবে, যেকোনো লিনিয়ার ট্রান্সফর্মেশন A তিনটি ধাপে ভেঙে ফেলা যায় — প্রথমে একটি ঘূর্ণন (Vᵀ), তারপর অক্ষ বরাবর প্রসারণ/ সংকোচন (Σ), তারপর আরেকটি ঘূর্ণন (U)। AᵀA-এর আইগেনভেক্টরগুলোই V-এর কলাম, আর সিঙ্গুলার ভ্যালুগুলো AᵀA-এর আইগেনভ্যালুর বর্গমূল — এই সম্পর্কটিই নিচের ডেমোর ভিত্তি।

২ · কেন SVD গুরুত্বপূর্ণ

ডাইমেনশনালিটি রিডাকশন / লো-র‍্যাংক অ্যাপ্রক্সিমেশন
সবচেয়ে বড় কয়েকটি সিঙ্গুলার ভ্যালু ও তাদের ভেক্টর রেখে বাকিগুলো বাদ দিলে একটি ম্যাট্রিক্সের সবচেয়ে ভালো লো-র‍্যাংক অ্যাপ্রক্সিমেশন পাওয়া যায় — ইমেজ কম্প্রেশন, নয়েজ রিডাকশন ও ডেটা কম্প্রেশনে ব্যবহৃত হয়।
সিউডো-ইনভার্স (Moore-Penrose)
যখন A বর্গাকার নয় বা সিঙ্গুলার (ইনভার্স অস্তিত্বহীন), SVD দিয়ে একটি সাধারণীকৃত ইনভার্স গণনা করা যায় — লিস্ট-স্কয়ার্স সমস্যার (M4/L20) সবচেয়ে সাধারণ সমাধান দেয়।
PCA-এর ভিত্তি
প্রিন্সিপাল কম্পোনেন্ট অ্যানালাইসিস (PCA) মূলত ডেটা ম্যাট্রিক্সের SVD-ই — সিঙ্গুলার ভেক্টরগুলো প্রিন্সিপাল কম্পোনেন্ট দেয় (M8/L41-এ আইগেনভ্যালু-PCA সংযোগ দেখুন, এবং Machine Learning কোর্সে বিস্তারিত প্রয়োগ)।
সরলীকরণের ঘোষণা · Scope note

একটি প্রোডাকশন-গ্রেড SVD রুটিন একসাথে সব সিঙ্গুলার ভ্যালু ও ভেক্টর বের করে, এবং সরাসরি AᵀA না গুণেই (কারণ সেটা সংখ্যাগতভাবে কন্ডিশন নাম্বার বর্গ করে ফেলে, নির্ভুলতা কমায়) Golub-Kahan বাইডায়াগোনালাইজেশনের মতো অনেক বেশি স্থিতিশীল কৌশল ব্যবহার করে। নিচের ডেমো ইচ্ছাকৃতভাবে এই জটিলতা এড়িয়ে শুধু প্রথম (বৃহত্তম) সিঙ্গুলার ভ্যালু/ভেক্টর জোড়া বের করে, M8-এর পাওয়ার মেথড সরাসরি পুনর্ব্যবহার করে — এটি একটি শিক্ষামূলক ইলাস্ট্রেশন, প্রোডাকশন রুটিন নয়।

৩ · একটি সরলীকৃত হাতে-লেখা ডেমো — AᵀA-এর উপর পাওয়ার মেথড

যেকোনো ম্যাট্রিক্স A-এর জন্য, AᵀA সবসময় সিমেট্রিক এবং পজিটিভ সেমি-ডেফিনিট — অর্থাৎ এর সব আইগেনভ্যালু বাস্তব ও অ-ঋণাত্মক, তাই বর্গমূল নেওয়া সবসময় সংজ্ঞায়িত। M8/L38-এর পাওয়ার মেথড (বারবার ম্যাট্রিক্স-ভেক্টর গুণ ও নরমালাইজ করা) এখানে সরাসরি প্রয়োগ করা যায় — এটি AᵀA-এর ডমিনেন্ট (বৃহত্তম) আইগেনভ্যালু λ₁ ও তার আইগেনভেক্টর v₁-এ কনভার্জ করবে। তারপর σ₁ = √λ₁ (প্রথম সিঙ্গুলার ভ্যালু), এবং u₁ = (1/σ₁) A v₁ (প্রথম লেফট সিঙ্গুলার ভেক্টর)।

Python
import math

# A হলো 3x2 ম্যাট্রিক্স (৩টি সারি, ২টি কলাম) -- একটি ইলাস্ট্রেটিভ উদাহরণ
A = [
    [3.0, 1.0],
    [1.0, 3.0],
    [1.0, 1.0],
]

def transpose(M):
    rows, cols = len(M), len(M[0])
    return [[M[r][c] for r in range(rows)] for c in range(cols)]

def matmul(M1, M2):
    r1, c1 = len(M1), len(M1[0])
    r2, c2 = len(M2), len(M2[0])
    result = [[0.0]*c2 for _ in range(r1)]
    for i in range(r1):
        for j in range(c2):
            s = 0.0
            for k in range(c1):
                s += M1[i][k]*M2[k][j]
            result[i][j] = s
    return result

def matvec(M, v):
    return [sum(M[i][j]*v[j] for j in range(len(v))) for i in range(len(M))]

def vec_norm(v):
    return math.sqrt(sum(x*x for x in v))

def normalize(v):
    n = vec_norm(v)
    return [x/n for x in v]

At = transpose(A)
AtA = matmul(At, A)
print("A^T A =", AtA)
print()

# M8-এর পাওয়ার মেথড, এখানে A^T A-এর উপর প্রয়োগ করা হচ্ছে
v = [1.0, 0.0]
print(f"{'ইটারেশন':>8} | {'v (normalized)':<26} | {'lambda estimate':>16}")
lambda_est = None
for i in range(1, 13):
    w = matvec(AtA, v)
    lambda_est = vec_norm(w)
    v = normalize(w)
    print(f"{i:8d} | {str([round(x,6) for x in v]):<26} | {lambda_est:16.8f}")

lambda1 = lambda_est
sigma1 = math.sqrt(lambda1)
v1 = v
print()
print(f"কনভার্জড ডমিনেন্ট আইগেনভ্যালু (lambda1): {lambda1:.6f}")
print(f"রাইট সিঙ্গুলার ভেক্টর v1: {[round(x,6) for x in v1]}")
print(f"প্রথম সিঙ্গুলার ভ্যালু sigma1 = sqrt(lambda1): {sigma1:.6f}")

# u1 = (1/sigma1) * A v1
Av1 = matvec(A, v1)
u1 = [x/sigma1 for x in Av1]
print(f"লেফট সিঙ্গুলার ভেক্টর u1 = (1/sigma1) A v1: {[round(x,6) for x in u1]}")
print(f"u1-এর নর্ম (~1 হওয়া উচিত): {vec_norm(u1):.6f}")

# rank-1 রিকনস্ট্রাকশন sigma1 * u1 * v1^T
recon = [[sigma1*u1[i]*v1[j] for j in range(2)] for i in range(3)]
print()
print("Rank-1 রিকনস্ট্রাকশন sigma1*u1*v1^T:")
for row in recon:
    print([round(x, 6) for x in row])
print("মূল A:")
for row in A:
    print(row)

diff = [[A[i][j]-recon[i][j] for j in range(2)] for i in range(3)]
frob_err = math.sqrt(sum(x*x for row in diff for x in row))
print()
print(f"রেসিডুয়ালের Frobenius নর্ম (A - rank-1 reconstruction): {frob_err:.6f}")

    
মাত্র ৯-১০ ইটারেশনেই lambda-এর মান ঠিক ১৮.০০-এ কনভার্জ করে যায় (কারণ AᵀA = [[11,7],[7,11]]-এর আইগেনভ্যালু বিশুদ্ধ পাটিগণিতেই ১৮ ও ৪), তাই σ₁ = √18 ≈ 4.242641, v1 ≈ [0.707107, 0.707107] (অর্থাৎ 1/√2 দিকে), এবং u1 ≈ [0.666667, 0.666667, 0.333333]। rank-1 রিকনস্ট্রাকশন σ1·u1·v1ᵀ দেয় [[2,2],[2,2],[1,1]] — মূল A = [[3,1],[1,3],[1,1]]-এর সাথে তুলনা করলে রেসিডুয়ালের Frobenius নর্ম ঠিক ২.০ — এই বাকি অংশটাই দ্বিতীয় সিঙ্গুলার ভ্যালু/ভেক্টর জোড়া প্রতিনিধিত্ব করে, যা এই সরলীকৃত ডেমো বের করেনি (প্রোডাকশন SVD উভয় জোড়াই একসাথে বের করে ফেলত)।
মূল কথা · Key takeaway

SVD হলো ম্যাট্রিক্সকে "ঘূর্ণন-প্রসারণ-ঘূর্ণন"-এ ভাঙার সবচেয়ে সাধারণ পদ্ধতি — বর্গাকার হওয়ার প্রয়োজন নেই, এবং সবসময় অস্তিত্ব থাকে। M8-এর পাওয়ার মেথড AᵀA-তে প্রয়োগ করে একটি সিঙ্গুলার ভ্যালু/ভেক্টর জোড়া বের করা সম্ভব, যা SVD-এর কোর ধারণাটি স্পষ্ট করে — কিন্তু সব জোড়া দক্ষভাবে ও সংখ্যাগতভাবে স্থিতিশীলভাবে বের করতে প্রোডাকশন লাইব্রেরি (যেমন LAPACK-ভিত্তিক রুটিন) প্রয়োজন, যা এই কোর্সের সুযোগের বাইরে।

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

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

প্র ০১ পাওয়ার মেথড AᵀA-এর উপর প্রয়োগ করলে কেন শুধু প্রথম (বৃহত্তম) সিঙ্গুলার ভ্যালু/ভেক্টর জোড়া পাওয়া যায়, বাকি সব নয়?

M8/L38-এ দেখা গেছে পাওয়ার মেথড বারবার ম্যাট্রিক্স-ভেক্টর গুণ করলে যেকোনো শুরুর ভেক্টর ধীরে ধীরে ডমিনেন্ট (বৃহত্তম আইগেনভ্যালুর) দিকে ঝুঁকে পড়ে, কারণ প্রতিটি গুণে সেই দিকের উপাদান অন্য দিকগুলোর তুলনায় দ্রুত বৃদ্ধি পায়। তাই এই মেথড স্বভাবতই শুধু সবচেয়ে বড় আইগেনভ্যালু/আইগেনভেক্টর জোড়ায় কনভার্জ করে — বাকি (ছোট) সিঙ্গুলার ভ্যালু/ভেক্টর জোড়া বের করতে হলে ডিফ্লেশন (প্রথম জোড়ার অবদান বাদ দিয়ে আবার চালানো) বা সম্পূর্ণ ভিন্ন অ্যালগরিদম প্রয়োজন — যা এই সরলীকৃত ডেমো করেনি।

প্র ০২ AᵀA সবসময় সিমেট্রিক ও পজিটিভ সেমি-ডেফিনিট কেন, এবং এটি কেন গুরুত্বপূর্ণ যে σ1 = √lambda1 গণনা করার আগে?

(AᵀA)ᵀ = AᵀAᵀᵀ = AᵀA, তাই এটি সবসময় সিমেট্রিক — আর যেকোনো ভেক্টর x-এর জন্য xᵀ(AᵀA)x = (Ax)ᵀ(Ax) = ‖Ax‖² ≥ 0, তাই এটি সবসময় পজিটিভ সেমি-ডেফিনিট। এই দুটি বৈশিষ্ট্যই গ্যারান্টি দেয় AᵀA-এর সব আইগেনভ্যালু বাস্তব ও অ-ঋণাত্মক — তাই math.sqrt(lambda1) কখনো নেগেটিভ নাম্বার বা জটিল সংখ্যার সমস্যায় পড়বে না, যা এই ডেমোর কোড সেলকে নিরাপদে চালানো নিশ্চিত করে।

প্র ০৩ rank-1 রিকনস্ট্রাকশনের পর রেসিডুয়াল Frobenius নর্ম ঠিক ২.০ এসেছে। এই সংখ্যাটি কী প্রতিনিধিত্ব করে, এবং কীভাবে এটি কমানো যেত?

একটি সম্পূর্ণ SVD-তে A = σ1·u1·v1ᵀ + σ2·u2·v2ᵀ (এই 2×3 ম্যাট্রিক্সের জন্য ঠিক দুটি সিঙ্গুলার ভ্যালু আছে)। রিকনস্ট্রাকশনে শুধু প্রথম টার্ম ব্যবহার করায় σ2·u2·v2ᵀ অংশটাই "মিসিং" থেকে যায় — সেটাই রেসিডুয়াল ২.০-এর উৎস। দ্বিতীয় সিঙ্গুলার ভ্যালু/ভেক্টর জোড়া যোগ করলে (যা এই ডেমো বের করেনি) রেসিডুয়াল একদম শূন্যে নেমে আসত, কারণ তখন পূর্ণ A সঠিকভাবে পুনর্গঠিত হয়ে যেত।

অনুশীলন

  1. চিন্তা করুন: উপরের কোড সেলে A-এর প্রথম সারি [3.0, 1.0]-কে [5.0, 1.0]-এ পরিবর্তন করলে AᵀA-এর আইগেনভ্যালুগুলো (এবং তাই sigma1) কি আগের চেয়ে বড় হবে না ছোট, আপনার কী মনে হয়?

    A-এর একটি এন্ট্রি বড় করলে (৩ থেকে ৫) সাধারণত ম্যাট্রিক্সের সামগ্রিক "মাত্রা" বাড়ে (বড় সংখ্যাগুলো ম্যাট্রিক্স-ভেক্টর গুণে বেশি অবদান রাখে), তাই AᵀA-এর আইগেনভ্যালুও সাধারণত বাড়বে — অর্থাৎ sigma1 আগের 4.2426-এর চেয়ে বড় হওয়ার কথা।

  2. পরীক্ষা করুন: উপরের কোড সেলে A-এর প্রথম সারি [5.0, 1.0]-এ পরিবর্তন করে Run চেপে AᵀA, lambda1 ও sigma1-এর নতুন মান দেখুন এবং আপনার অনুমান যাচাই করুন।

    এই পরিবর্তনে AᵀA = [[27,7],[7,11]] হয়ে যায় (আগের [[11,7],[7,11]]-এর বদলে), এবং পাওয়ার মেথড একটি বড় ডমিনেন্ট আইগেনভ্যালুতে কনভার্জ করবে — sigma1 স্পষ্টভাবে আগের 4.2426-এর চেয়ে বড় হয়ে যাবে, যা নিশ্চিত করে বড় ম্যাট্রিক্স এন্ট্রি বৃহত্তর "স্ট্রেচিং" (সিঙ্গুলার ভ্যালু) নির্দেশ করে।

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

আগের পাঠ
লিনিয়ার প্রোগ্রামিং — সিমপ্লেক্স মেথড