স্পার্স ম্যাট্রিক্স টেকনিক ও কম্পিউটেশনাল কমপ্লেক্সিটি
এই পাঠে যা শিখবেন
- স্পার্স ম্যাট্রিক্স কী এবং বাস্তব-বিশ্বে কোথায় দেখা যায় (M3/L15-এর ট্রাইডায়াগোনাল সিস্টেমের সংযোগ)
- COO ও DOK স্পার্স স্টোরেজ ফরম্যাট হাতে বাস্তবায়ন করা
- ডেন্স বনাম স্পার্স স্টোরেজের প্রকৃত মেমরি-ফুটপ্রিন্ট তুলনা করা
- ম্যাট্রিক্স-ভেক্টর গুণে ডেন্স বনাম স্পার্স পদ্ধতির প্রকৃত অপারেশন-কাউন্ট গণনা করে তুলনা করা
১ · স্পার্স ম্যাট্রিক্স কী এবং কেন এটি সর্বব্যাপী
স্পার্স ম্যাট্রিক্সSparse Matrixএমন একটি ম্যাট্রিক্স যার এন্ট্রির একটি বড় অংশ শূন্য — বিপরীতে যে ম্যাট্রিক্সে বেশিরভাগ এন্ট্রি ননজিরো তাকে ডেন্স ম্যাট্রিক্স বলা হয়।
হলো এমন একটি ম্যাট্রিক্স যেখানে ননজিরো এন্ট্রির সংখ্যা মোট এন্ট্রির তুলনায় খুবই কম। এটি কোনো বিরল বিশেষ
কেস নয় — বরং বেশিরভাগ বাস্তব সংখ্যাগত সমস্যায় স্বাভাবিক অবস্থা: M3/L15-এ দেখা ট্রাইডায়াগোনাল সিস্টেম
(প্রতিটি সারিতে সর্বোচ্চ ৩টি ননজিরো এন্ট্রি), ফাইনাইট-এলিমেন্ট/ফাইনাইট-ডিফারেন্স মেশ (M6-M7-এ প্রতিটি
গ্রিড-পয়েন্ট শুধু তার প্রতিবেশীদের সাথে সংযুক্ত), সোশ্যাল-নেটওয়ার্ক গ্রাফ, বা রেকমেন্ডেশন সিস্টেমের
ইউজার-আইটেম ম্যাট্রিক্স — এই সবগুলোতেই বেশিরভাগ এন্ট্রি শূন্য। একটি সাধারণ list of list
(ডেন্স) রিপ্রেজেন্টেশন এই শূন্যগুলোও সমান জায়গা নিয়ে সংরক্ষণ করে ফেলে — যা অপ্রয়োজনীয় মেমরি ও গণনার
অপচয়।
২ · COO ও DOK — দুটি সরল স্পার্স স্টোরেজ ফরম্যাট
প্রতিটি ননজিরো এন্ট্রির জন্য একটি
(row, col, value) টাপল সংরক্ষণ করা হয় — সরল, তৈরি করা সহজ, কিন্তু একটি নির্দিষ্ট এন্ট্রি খুঁজতে পুরো লিস্ট স্ক্যান করতে হতে পারে।Python-এর নেটিভ
dict ব্যবহার করে {(row, col): value} ম্যাপিং সংরক্ষণ করা হয় — নির্দিষ্ট এন্ট্রি খুঁজতে দ্রুত (হ্যাশ লুকআপ), এন্ট্রি যোগ/পরিবর্তন সহজ।স্পার্স ফরম্যাট শুধু মেমরি নয়, অপারেশন কাউন্টও কমায় — ম্যাট্রিক্স-ভেক্টর গুণে শুধু ননজিরো এন্ট্রির জন্যই গণনা করলে হয় (
../design-and-analysis-of-algorithms/index.html-এ সাধারণ জটিলতা-বিশ্লেষণের গভীর আলোচনা)।৩ · একটি সত্যিকারের তুলনা — মেমরি ফুটপ্রিন্ট ও অপারেশন কাউন্ট
নিচের কোড সেলে একটি ১২×১২ ম্যাট্রিক্স র্যান্ডমভাবে তৈরি করা হয়েছে (মাত্র ~১২% এন্ট্রি
ননজিরো রাখা হয়েছে, ফিক্সড সিড দিয়ে যাতে ফলাফল পুনরুৎপাদনযোগ্য হয়), তারপর ডেন্স
(list of list) ও COO স্টোরেজে সংরক্ষণ করে প্রকৃত সংখ্যা গণনা করে দুটোর
মেমরি-ফুটপ্রিন্ট তুলনা করা হয়েছে। এরপর একই ম্যাট্রিক্স দিয়ে ম্যাট্রিক্স-ভেক্টর গুণ করে দুটো পদ্ধতিতে
সত্যিকারের মাল্টিপ্লাই-অপারেশনের সংখ্যা গোনা হয়েছে — অনুমান নয়, প্রতিটি গুণ অপারেশনে একটি কাউন্টার
বাস্তবে বৃদ্ধি করে।
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)।
একটি ম্যাট্রিক্স যত স্পার্স, ডেন্স ও স্পার্স রিপ্রেজেন্টেশনের মধ্যে মেমরি ও অপারেশন-কাউন্ট পার্থক্য তত
নাটকীয় হয় — বড় স্কেলে (উদাহরণস্বরূপ, লক্ষ লক্ষ সারি/কলামের একটি ফাইনাইট-এলিমেন্ট ম্যাট্রিক্স, যেখানে
৯৯.৯%+ শূন্য) এই পার্থক্যই একটি সমস্যা সমাধানযোগ্য বনাম মেমরি ফুরিয়ে যাওয়ার মধ্যে পার্থক্য গড়ে দেয়।
প্রোডাকশন সাইন্টিফিক-কম্পিউটিং লাইব্রেরিগুলোর (যেমন 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 তুলনা ঠিক একই নীতির একটি সরল সংস্করণ — ম্যাট্রিক্সের গঠন (কোথায় ননজিরো আছে) জানা থাকলে
অপ্রয়োজনীয় শূন্য-গুণ এড়িয়ে বাস্তব অপারেশন-কাউন্ট কমানো যায়।
অনুশীলন
-
চিন্তা করুন: উপরের কোড সেলে
n = 12-কেn = 24-এ বাড়িয়ে একইdensity = 0.12রাখলে, ডেন্স ও COO matvec-এর অপারেশন-কাউন্টের অনুপাত (এখন6.86x) বাড়বে না কমবে বলে আপনার ধারণা?একই থাকার কথা — কারণ ডেন্স অপারেশন সবসময়
n²(মোট এন্ট্রি সংখ্যা), আর স্পার্স অপারেশনdensity × n²-এর কাছাকাছি (ননজিরো সংখ্যা)। যেহেতুdensityস্থির রাখা হয়েছে, অনুপাতটি মোটামুটি1/density ≈ 8.33-এর কাছাকাছি থাকার কথা,n-এর মান নির্বিশেষে। -
পরীক্ষা করুন: উপরের কোড সেলে
n = 12-কেn = 24-এ পরিবর্তন করে Run চেপে নতুনnonzero_count,dense_ops,sparse_opsও অপারেশন-অনুপাত দেখুন, তারপর আপনার অনুমানের সাথে মিলিয়ে দেখুন।n = 24-এ মোট এন্ট্রি576, এবংdensity = 0.12বজায় থাকায় ননজিরো সংখ্যা এবার আনুপাতিকভাবে বাড়বে (আগের ২১-এর প্রায় ৪ গুণ, যেহেতুn²চারগুণ হয়েছে)। ডেন্স ও স্পার্স উভয় অপারেশন-কাউন্টই বাড়বে, কিন্তু তাদের অনুপাত (আগের প্রশ্নে অনুমান করা মতো) মোটামুটি একই থাকবে, কারণ উভয়ইdensity-এর সমানুপাতিক হারে বৃদ্ধি পায়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- M3 · বিশেষ ম্যাট্রিক্স — ট্রাইডায়াগোনাল ও স্পার্স সিস্টেম L15 এই পাঠের ভিত্তি — থমাস অ্যালগরিদম ও ট্রাইডায়াগোনাল সিস্টেমের অপারেশন-কাউন্ট বিশ্লেষণ।
- Design and Analysis of Algorithms কোর্স সহোদর কোর্স অ্যালগরিদমের জটিলতা-প্রমাণ ও Big-O বিশ্লেষণের গভীর, আনুষ্ঠানিক আলোচনা — এই পাঠ শুধু ব্যবহারিক অপারেশন-কাউন্ট দেখিয়েছে।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ এরর অ্যানালাইসিস থেকে ক্যাপস্টোন পর্যন্ত — Numerical Methods কোর্সের সব পাঠ এক জায়গায়।