পাঠ ৪৮ · ৫৭-এর মধ্যে · মডিউল ১০
Home / Courses / Numerical Methods / স্পার্স ম্যাট্রিক্স

স্পার্স ম্যাট্রিক্স টেকনিক ও কম্পিউটেশনাল কমপ্লেক্সিটি

Sparse matrix techniques & computational complexity
১০ মিনিট পড়া উচ্চ · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • স্পার্স ম্যাট্রিক্স কী এবং বাস্তব-বিশ্বে কোথায় দেখা যায় (M3/L15-এর ট্রাইডায়াগোনাল সিস্টেমের সংযোগ)
  • COO ও DOK স্পার্স স্টোরেজ ফরম্যাট হাতে বাস্তবায়ন করা
  • ডেন্স বনাম স্পার্স স্টোরেজের প্রকৃত মেমরি-ফুটপ্রিন্ট তুলনা করা
  • ম্যাট্রিক্স-ভেক্টর গুণে ডেন্স বনাম স্পার্স পদ্ধতির প্রকৃত অপারেশন-কাউন্ট গণনা করে তুলনা করা

১ · স্পার্স ম্যাট্রিক্স কী এবং কেন এটি সর্বব্যাপী

স্পার্স ম্যাট্রিক্সSparse Matrixএমন একটি ম্যাট্রিক্স যার এন্ট্রির একটি বড় অংশ শূন্য — বিপরীতে যে ম্যাট্রিক্সে বেশিরভাগ এন্ট্রি ননজিরো তাকে ডেন্স ম্যাট্রিক্স বলা হয়। হলো এমন একটি ম্যাট্রিক্স যেখানে ননজিরো এন্ট্রির সংখ্যা মোট এন্ট্রির তুলনায় খুবই কম। এটি কোনো বিরল বিশেষ কেস নয় — বরং বেশিরভাগ বাস্তব সংখ্যাগত সমস্যায় স্বাভাবিক অবস্থা: M3/L15-এ দেখা ট্রাইডায়াগোনাল সিস্টেম (প্রতিটি সারিতে সর্বোচ্চ ৩টি ননজিরো এন্ট্রি), ফাইনাইট-এলিমেন্ট/ফাইনাইট-ডিফারেন্স মেশ (M6-M7-এ প্রতিটি গ্রিড-পয়েন্ট শুধু তার প্রতিবেশীদের সাথে সংযুক্ত), সোশ্যাল-নেটওয়ার্ক গ্রাফ, বা রেকমেন্ডেশন সিস্টেমের ইউজার-আইটেম ম্যাট্রিক্স — এই সবগুলোতেই বেশিরভাগ এন্ট্রি শূন্য। একটি সাধারণ list of list (ডেন্স) রিপ্রেজেন্টেশন এই শূন্যগুলোও সমান জায়গা নিয়ে সংরক্ষণ করে ফেলে — যা অপ্রয়োজনীয় মেমরি ও গণনার অপচয়।

২ · COO ও DOK — দুটি সরল স্পার্স স্টোরেজ ফরম্যাট

COO (Coordinate List)
প্রতিটি ননজিরো এন্ট্রির জন্য একটি (row, col, value) টাপল সংরক্ষণ করা হয় — সরল, তৈরি করা সহজ, কিন্তু একটি নির্দিষ্ট এন্ট্রি খুঁজতে পুরো লিস্ট স্ক্যান করতে হতে পারে।
DOK (Dictionary of Keys)
Python-এর নেটিভ dict ব্যবহার করে {(row, col): value} ম্যাপিং সংরক্ষণ করা হয় — নির্দিষ্ট এন্ট্রি খুঁজতে দ্রুত (হ্যাশ লুকআপ), এন্ট্রি যোগ/পরিবর্তন সহজ।
কম্পিউটেশনাল কমপ্লেক্সিটি সংযোগ
স্পার্স ফরম্যাট শুধু মেমরি নয়, অপারেশন কাউন্টও কমায় — ম্যাট্রিক্স-ভেক্টর গুণে শুধু ননজিরো এন্ট্রির জন্যই গণনা করলে হয় (../design-and-analysis-of-algorithms/index.html-এ সাধারণ জটিলতা-বিশ্লেষণের গভীর আলোচনা)।

৩ · একটি সত্যিকারের তুলনা — মেমরি ফুটপ্রিন্ট ও অপারেশন কাউন্ট

নিচের কোড সেলে একটি ১২×১২ ম্যাট্রিক্স র‍্যান্ডমভাবে তৈরি করা হয়েছে (মাত্র ~১২% এন্ট্রি ননজিরো রাখা হয়েছে, ফিক্সড সিড দিয়ে যাতে ফলাফল পুনরুৎপাদনযোগ্য হয়), তারপর ডেন্স (list of list) ও COO স্টোরেজে সংরক্ষণ করে প্রকৃত সংখ্যা গণনা করে দুটোর মেমরি-ফুটপ্রিন্ট তুলনা করা হয়েছে। এরপর একই ম্যাট্রিক্স দিয়ে ম্যাট্রিক্স-ভেক্টর গুণ করে দুটো পদ্ধতিতে সত্যিকারের মাল্টিপ্লাই-অপারেশনের সংখ্যা গোনা হয়েছে — অনুমান নয়, প্রতিটি গুণ অপারেশনে একটি কাউন্টার বাস্তবে বৃদ্ধি করে।

Python
import random

random.seed(42)

n = 12  # n x n ম্যাট্রিক্স
density = 0.12  # প্রায় ১২% এন্ট্রি ননজিরো রাখা হচ্ছে

dense = [[0.0]*n for _ in range(n)]
nonzero_count = 0
for i in range(n):
    for j in range(n):
        if random.random() < density:
            dense[i][j] = round(random.uniform(1.0, 9.0), 2)
            nonzero_count += 1

print(f"n = {n}, মোট এন্ট্রি = {n*n}, ননজিরো এন্ট্রি = {nonzero_count}")
print(f"স্পার্সিটি (জিরো এন্ট্রির ভগ্নাংশ) = {1 - nonzero_count/(n*n):.4f}")

dense_stored_numbers = n*n

# COO (coordinate list) স্পার্স রিপ্রেজেন্টেশন: (row, col, value)-এর লিস্ট
coo = []
for i in range(n):
    for j in range(n):
        if dense[i][j] != 0.0:
            coo.append((i, j, dense[i][j]))

coo_stored_numbers = len(coo) * 3  # প্রতিটি এন্ট্রিতে row, col, value -- ৩টি সংখ্যা
print(f"COO-তে সংরক্ষিত এন্ট্রি: {len(coo)} টি টাপল -> মোট {coo_stored_numbers} টি সংখ্যা (row,col,value)")
print(f"ডেন্স সংরক্ষিত সংখ্যা: {dense_stored_numbers}")
print(f"স্টোরেজ অনুপাত (COO/dense): {coo_stored_numbers/dense_stored_numbers:.4f}")

# DOK (dictionary of keys) রিপ্রেজেন্টেশন
dok = {}
for i in range(n):
    for j in range(n):
        if dense[i][j] != 0.0:
            dok[(i, j)] = dense[i][j]
print(f"DOK-তে সংরক্ষিত এন্ট্রি: {len(dok)} টি key-value জোড়া")

# ---- ম্যাট্রিক্স-ভেক্টর গুণ: ডেন্স বনাম স্পার্স, প্রকৃত মাল্টিপ্লাই-অপারেশন গণনা ----
x = [round(random.uniform(-2.0, 2.0), 2) for _ in range(n)]

def dense_matvec(M, v):
    result = [0.0]*len(M)
    mult_ops = 0
    for i in range(len(M)):
        s = 0.0
        for j in range(len(v)):
            s += M[i][j] * v[j]
            mult_ops += 1
        result[i] = s
    return result, mult_ops

def sparse_matvec_coo(coo_list, v, num_rows):
    result = [0.0]*num_rows
    mult_ops = 0
    for (i, j, val) in coo_list:
        result[i] += val * v[j]
        mult_ops += 1
    return result, mult_ops

dense_result, dense_ops = dense_matvec(dense, x)
sparse_result, sparse_ops = sparse_matvec_coo(coo, x, n)

max_diff = max(abs(a-b) for a, b in zip(dense_result, sparse_result))
print()
print(f"ডেন্স matvec-এর মাল্টিপ্লাই-অপারেশন: {dense_ops}")
print(f"স্পার্স (COO) matvec-এর মাল্টিপ্লাই-অপারেশন: {sparse_ops}")
print(f"অপারেশন হ্রাসের অনুপাত: {dense_ops/sparse_ops:.2f}x কম মাল্টিপ্লাই")
print(f"ডেন্স ও স্পার্স ফলাফলের সর্বোচ্চ পার্থক্য (~0 হওয়া উচিত): {max_diff:.2e}")

    
এই র‍্যান্ডম সিডে 144টি মোট এন্ট্রির মধ্যে মাত্র ২১টি ননজিরো (৮৫.৪২% শূন্য)। COO স্টোরেজে 21 × 3 = 63টি সংখ্যা লাগে, ডেন্সে 144টি — অর্থাৎ COO ডেন্সের মাত্র ৪৩.৭৫% জায়গা নেয়। আর ম্যাট্রিক্স-ভেক্টর গুণে ডেন্স পদ্ধতি 144টি মাল্টিপ্লাই করে (প্রতিটি এন্ট্রির জন্য একটি, শূন্য হোক বা না হোক), যেখানে COO শুধু 21টি — প্রকৃত ৬.৮৬ গুণ কম অপারেশন, অথচ উভয় পদ্ধতির ফলাফল একদম অভিন্ন (সর্বোচ্চ পার্থক্য 0.00e+00)।
মূল কথা · Key takeaway

একটি ম্যাট্রিক্স যত স্পার্স, ডেন্স ও স্পার্স রিপ্রেজেন্টেশনের মধ্যে মেমরি ও অপারেশন-কাউন্ট পার্থক্য তত নাটকীয় হয় — বড় স্কেলে (উদাহরণস্বরূপ, লক্ষ লক্ষ সারি/কলামের একটি ফাইনাইট-এলিমেন্ট ম্যাট্রিক্স, যেখানে ৯৯.৯%+ শূন্য) এই পার্থক্যই একটি সমস্যা সমাধানযোগ্য বনাম মেমরি ফুরিয়ে যাওয়ার মধ্যে পার্থক্য গড়ে দেয়। প্রোডাকশন সাইন্টিফিক-কম্পিউটিং লাইব্রেরিগুলোর (যেমন scipy.sparse, যা এই কোর্সের সুযোগের বাইরে) CSR/CSC-এর মতো আরও অপ্টিমাইজড ফরম্যাট থাকে, কিন্তু মূল ধারণা এখানে দেখানো COO/DOK-এর মতোই — শুধু ননজিরো সংরক্ষণ করো, শুধু ননজিরোর উপর গণনা করো।

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

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

প্র ০১ COO-তে প্রতিটি ননজিরো এন্ট্রির জন্য ৩টি সংখ্যা (row, col, value) সংরক্ষণ করতে হয় — তাহলে density অনেক বেশি (যেমন ৫০%) হলে COO কি ডেন্সের চেয়ে খারাপ হয়ে যেতে পারে?

হ্যাঁ, একেবারেই। যদি density খুব বেশি হয় (উদাহরণস্বরূপ ৫০%-এর কাছাকাছি বা তার বেশি), তাহলে প্রতিটি ননজিরোর জন্য ৩টি সংখ্যা সংরক্ষণ করা মানে COO আসলে ডেন্সের চেয়ে বেশি জায়গা নিতে পারে (যেমন ৫০% ননজিরো হলে COO নেবে 0.5 × 3 = 1.5 গুণ, ডেন্সের চেয়ে বেশি)। স্পার্স ফরম্যাটের সুবিধা তখনই আসে যখন ম্যাট্রিক্স সত্যিই স্পার্স (সাধারণত <২০-৩০% ননজিরো) — এই পাঠের কোড সেলে density = 0.12 ব্যবহার করা হয়েছে যাতে এই সুবিধা স্পষ্টভাবে দেখা যায়।

প্র ০২ DOK-তে dict ব্যবহার করা হয়েছে, COO-তে সাধারণ list। ম্যাট্রিক্স-ভেক্টর গুণের মতো "সব এন্ট্রির উপর একবার করে লুপ চালাও" ধরনের কাজে কোনটা বেশি স্বাভাবিক পছন্দ, আর কেন?

পুরো ম্যাট্রিক্স জুড়ে একবার করে লুপ চালানোর কাজে (যেমন ম্যাট্রিক্স-ভেক্টর গুণ) COO বেশি স্বাভাবিক — কারণ এটি ইতিমধ্যে একটি সরল, ইটারেবল লিস্ট। DOK-এর হ্যাশ-লুকআপ সুবিধা তখন কাজে লাগে যখন নির্দিষ্ট একটি এন্ট্রি (যেমন row=3, col=7) দ্রুত খুঁজে বের করতে বা আপডেট করতে হয় — যেমন ম্যাট্রিক্স তৈরি করার সময় বা এলিমেন্ট-বাই-এলিমেন্ট মডিফিকেশনে।

প্র ০৩ কোড সেলে dense_ops সবসময় n*n = 144, ম্যাট্রিক্সের প্রকৃত ননজিরো সংখ্যা যাই হোক না কেন। এটি M3/L15-এর ট্রাইডায়াগোনাল সিস্টেমের অপারেশন-কাউন্টের সাথে কীভাবে সম্পর্কিত?

M3/L15-এ দেখা গিয়েছিল সাধারণ গসিয়ান এলিমিনেশন একটি n×n ম্যাট্রিক্সে O(n³) অপারেশন নেয়, কিন্তু ট্রাইডায়াগোনাল-নির্দিষ্ট থমাস অ্যালগরিদম মাত্র O(n) — কারণ এটি জানে প্রতিটি সারিতে সর্বোচ্চ ৩টি ননজিরো আছে এবং শুধু সেগুলোর উপর কাজ করে। এই পাঠের ডেন্স বনাম COO matvec তুলনা ঠিক একই নীতির একটি সরল সংস্করণ — ম্যাট্রিক্সের গঠন (কোথায় ননজিরো আছে) জানা থাকলে অপ্রয়োজনীয় শূন্য-গুণ এড়িয়ে বাস্তব অপারেশন-কাউন্ট কমানো যায়।

অনুশীলন

  1. চিন্তা করুন: উপরের কোড সেলে n = 12-কে n = 24-এ বাড়িয়ে একই density = 0.12 রাখলে, ডেন্স ও COO matvec-এর অপারেশন-কাউন্টের অনুপাত (এখন 6.86x) বাড়বে না কমবে বলে আপনার ধারণা?

    একই থাকার কথা — কারণ ডেন্স অপারেশন সবসময় n² (মোট এন্ট্রি সংখ্যা), আর স্পার্স অপারেশন density × n²-এর কাছাকাছি (ননজিরো সংখ্যা)। যেহেতু density স্থির রাখা হয়েছে, অনুপাতটি মোটামুটি 1/density ≈ 8.33-এর কাছাকাছি থাকার কথা, n-এর মান নির্বিশেষে।

  2. পরীক্ষা করুন: উপরের কোড সেলে n = 12-কে n = 24-এ পরিবর্তন করে Run চেপে নতুন nonzero_count, dense_ops, sparse_ops ও অপারেশন-অনুপাত দেখুন, তারপর আপনার অনুমানের সাথে মিলিয়ে দেখুন।

    n = 24-এ মোট এন্ট্রি 576, এবং density = 0.12 বজায় থাকায় ননজিরো সংখ্যা এবার আনুপাতিকভাবে বাড়বে (আগের ২১-এর প্রায় ৪ গুণ, যেহেতু n² চারগুণ হয়েছে)। ডেন্স ও স্পার্স উভয় অপারেশন-কাউন্টই বাড়বে, কিন্তু তাদের অনুপাত (আগের প্রশ্নে অনুমান করা মতো) মোটামুটি একই থাকবে, কারণ উভয়ই density-এর সমানুপাতিক হারে বৃদ্ধি পায়।

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

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