NumPy indexing ও slicing
এই পাঠে যা শিখবেন
- 1D ও 2D array-এ basic indexing — positive ও negative
- Slicing syntax — start:stop:step, default value, reverse
- 2D slice — row, column, sub-matrix বের করা
- Boolean indexing ও mask combination (& |)
- Fancy indexing — list/array দিয়ে select
- View vs copy — gotcha ও সমাধান
np.where,np.argmax,np.argsort— index-returning function- AI-তে indexing — label filter, top-k prediction
১ · Basic indexing — 1D array
একটি NumPy array থেকে একটি element নেওয়া — list-এর মতোই। arr[i] দিয়ে $i$-তম element পাওয়া যায়। Negative index সবসময় শেষ থেকে গণনা — arr[-1] মানে শেষ element।
import numpy as np
# 1D array
arr = np.array([10, 20, 30, 40, 50])
print(arr[0]) # 10 — প্রথম
print(arr[2]) # 30 — তৃতীয়
print(arr[-1]) # 50 — শেষ
print(arr[-2]) # 40 — শেষ থেকে দ্বিতীয়
# Out of range — IndexError
# print(arr[10]) # IndexError
# Assignment
arr[0] = 99
print(arr) # [99 20 30 40 50]
arr[-1] = শেষ। Single element-এ assignment direct করা যায়।
arr[0] প্রথম, arr[1] দ্বিতীয়। ডান থেকে গুনলে arr[-1] ডানতম। NumPy-তেও ঠিক তাই — শুধু কম্পিউটার শূন্য থেকে গোনা শুরু করে।
২ · Slicing — 1D array-এর অংশ
Slice syntax: arr[start:stop:step]। start include, stop exclude। Default — start=0, stop=len, step=1।
import numpy as np
arr = np.array([10, 20, 30, 40, 50, 60, 70, 80])
# Basic slice
print(arr[2:5]) # [30 40 50] — index 2,3,4
print(arr[:3]) # [10 20 30] — start থেকে
print(arr[5:]) # [60 70 80] — শেষ পর্যন্ত
print(arr[:]) # পুরো array (view)
# Step
print(arr[::2]) # [10 30 50 70] — প্রতি ২য়
print(arr[1::2]) # [20 40 60 80] — index 1 থেকে প্রতি ২য়
# Reverse
print(arr[::-1]) # [80 70 60 50 40 30 20 10]
print(arr[5:1:-1]) # [60 50 40 30] — back-to-front
# Slice-এ assignment — broadcast
arr[2:5] = 0
print(arr) # [10 20 0 0 0 60 70 80]
arr[start:stop] — half-open interval। arr[::-1] idiomatic reverse। Slice-এ assignment করলে — সব position-এ একই value broadcast হয়।
১) start — শুরু (default ০)।
২) stop — শেষ (exclude, default len)।
৩) step — ধাপ (default ১, negative = reverse)।
৩ · 2D array indexing — row ও column
2D array — যেমন একটি গাণিতিক matrix। দু'টি index লাগে: row ও column। NumPy-তে preferred syntax — arr[i, j] (comma দিয়ে), যা arr[i][j]-এর চেয়ে দ্রুত (একবারই memory access)।
import numpy as np
# 3x4 matrix
mat = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
])
print(mat.shape) # (3, 4)
# Single element
print(mat[0, 0]) # 1 — top-left
print(mat[2, 3]) # 12 — bottom-right
print(mat[1, -1]) # 8 — দ্বিতীয় row-এর শেষ
# Whole row
print(mat[0]) # [1 2 3 4]
print(mat[1, :]) # [5 6 7 8] — equivalent
# Whole column
print(mat[:, 0]) # [1 5 9] — প্রথম column
print(mat[:, -1]) # [4 8 12] — শেষ column
# arr[i][j] vs arr[i, j]
print(mat[1][2]) # 7 — কাজ করে কিন্তু slow
print(mat[1, 2]) # 7 — preferred
mat[i, j] = row $i$, column $j$। mat[i, :] = পুরো row $i$। mat[:, j] = পুরো column $j$। : মানে "সব" (এই dimension-এ)।
mat[2, 3] = ৩য় row, ৪র্থ column-এ বসা ছাত্র। mat[2] = ৩য় row-এর সব ছাত্র। mat[:, 3] = সব row-এর ৪র্থ column-এর ছাত্ররা — অর্থাৎ ৪র্থ column-এ বসা সবাই।
৪ · 2D slicing — sub-matrix
2D slice দু'টি dimension-এই কাজ করে। arr[r1:r2, c1:c2] দিয়ে rectangular sub-matrix পাওয়া যায়।
import numpy as np
mat = np.array([
[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12],
[13, 14, 15, 16],
])
# Sub-matrix
print(mat[1:3, 0:2])
# [[ 5 6]
# [ 9 10]]
# All rows, first 2 columns
print(mat[:, :2])
# [[ 1 2]
# [ 5 6]
# [ 9 10]
# [13 14]]
# Last row, all columns
print(mat[-1, :]) # [13 14 15 16]
# Reverse rows (flip vertically)
print(mat[::-1])
# [[13 14 15 16]
# [ 9 10 11 12]
# [ 5 6 7 8]
# [ 1 2 3 4]]
# Every other row & column
print(mat[::2, ::2])
# [[ 1 3]
# [ 9 11]]
# 2D slice-এ assignment
mat[0:2, 0:2] = 0
print(mat)
# [[ 0 0 3 4]
# [ 0 0 7 8]
# [ 9 10 11 12]
# [13 14 15 16]]
mat[1:3, 0:2] মানে — row 1-2, column 0-1। ::2 দিয়ে downsample (প্রতি ২য়) — image processing-এ খুব কাজে আসে।
৫ · Boolean indexing — শর্তভিত্তিক select
একটি comparison operator NumPy array-তে apply করলে — boolean array পাওয়া যায়। সেই maskMask (Boolean array)একটি boolean array — যেখানে True/False দিয়ে প্রতিটি position চিহ্নিত। NumPy-তে এই array দিয়ে indexing করলে — শুধু True position-গুলোর element ফেরে। SQL-এর WHERE clause-এর মতো। দিয়ে original array index করলে — শুধু শর্ত মানা element ফেরে।
import numpy as np
arr = np.array([3, 7, 1, 9, 4, 6, 2, 8])
# Comparison → boolean array
mask = arr > 5
print(mask) # [False True False True False True False True]
# Boolean indexing
print(arr[mask]) # [7 9 6 8]
print(arr[arr > 5]) # একই — inline syntax
# Combining masks — & (and), | (or), ~ (not)
print(arr[(arr > 3) & (arr < 8)]) # [7 4 6]
print(arr[(arr < 3) | (arr > 7)]) # [1 9 2 8]
print(arr[~(arr > 5)]) # [3 1 4 2]
# np.where — index ফেরায়
indices = np.where(arr > 5)
print(indices) # (array([1, 3, 5, 7]),)
print(arr[indices]) # [7 9 6 8]
# Conditional assignment
arr[arr > 5] = 0
print(arr) # [3 0 1 0 4 0 2 0]
# 2D boolean mask
mat = np.array([[1, 2, 3], [4, 5, 6]])
print(mat[mat > 3]) # [4 5 6] — flattened
WHERE clause-এর সমতুল্য। সাবধান: and/or keyword কাজ করে না — bitwise &/| ব্যবহার করুন এবং প্রতিটি condition বন্ধনীতে রাখুন।
arr[arr > 3 & arr < 8] ভুল — operator precedence-এ & আগে চলে। সঠিক: arr[(arr > 3) & (arr < 8)] — বন্ধনী mandatory।
৬ · Fancy indexing — list দিয়ে select
Fancy indexingFancy Indexingএকটি integer array/list দিয়ে multiple position একসাথে select করার কৌশল। Result সবসময় copy (view নয়)। Order arbitrary — আপনি যে order দেবেন সেই order-এই ফেরে। — index হিসেবে list বা array পাস করা। Result সবসময় copy (slice-এর মতো view নয়)।
import numpy as np
arr = np.array([10, 20, 30, 40, 50, 60, 70])
# Multiple indices একসাথে
print(arr[[0, 2, 4]]) # [10 30 50]
print(arr[[6, 0, 3]]) # [70 10 40] — যেকোনো order
print(arr[[0, 0, 1, 1]]) # [10 10 20 20] — repeat OK
# 2D fancy indexing
mat = np.array([
[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12],
])
# Specific rows
print(mat[[0, 2]])
# [[1 2 3]
# [7 8 9]]
# Specific (row, col) pairs
rows = [0, 1, 2, 3]
cols = [0, 1, 2, 0]
print(mat[rows, cols]) # [1 5 9 10] — diagonal-ish
# Fancy + slice combine
print(mat[[0, 2], :2])
# [[1 2]
# [7 8]]
mat[[0, 2]] — ০ ও ২ নম্বর row নেয়।
৭ · View vs Copy — সবচেয়ে বড় gotcha
Slice একটি viewViewএকই memory-তে আলাদা "window"। Slice modify করলে original array-ও বদলায়। Memory efficient — কিন্তু unintended mutation-এর কারণ। আলাদা data চাইলে .copy()। ফেরায় — মানে original array-এর সাথে memory share করে। View modify করলে — original-ও বদলায়। এটি memory efficient, কিন্তু bug-এর সবচেয়ে বড় কারণ।
import numpy as np
# Slice = view (memory shared)
original = np.array([1, 2, 3, 4, 5])
view = original[1:4]
view[0] = 99
print(view) # [99 3 4]
print(original) # [ 1 99 3 4 5] ← original বদলেছে!
# is shared?
print(view.base is original) # True
# Copy — আলাদা memory
original = np.array([1, 2, 3, 4, 5])
copy = original[1:4].copy()
copy[0] = 99
print(copy) # [99 3 4]
print(original) # [1 2 3 4 5] ← অপরিবর্তিত
# Boolean ও fancy indexing — সবসময় copy
arr = np.array([10, 20, 30, 40])
sub = arr[[0, 2]] # fancy
sub[0] = 999
print(arr) # [10 20 30 40] — অপরিবর্তিত
mask_sub = arr[arr > 15] # boolean
mask_sub[0] = 999
print(arr) # [10 20 30 40] — অপরিবর্তিত
def normalize(x): x[:] -= x.mean(); return x — caller-এর array modify করে! সমাধান: function-এর শুরুতে x = x.copy()।
arr.flags.owndata চেক করুন বা .copy() call করুন।
৮ · Index-returning functions
কখনো value নয়, position দরকার — তখন np.wherenp.whereদু'ভাবে কাজ করে — একটি argument-এ True position-এর index ফেরায়; তিন argument-এ where(cond, a, b) — cond True হলে a, না হলে b। NumPy-র ternary operator।, np.argmax, np.argsort ব্যবহার হয়।
import numpy as np
scores = np.array([72, 85, 91, 68, 79, 95, 88])
# argmax / argmin — index of max/min
print(np.argmax(scores)) # 5 — index of 95
print(np.argmin(scores)) # 3 — index of 68
# argsort — sorted order-এর index
order = np.argsort(scores)
print(order) # [3 0 4 1 6 2 5]
print(scores[order]) # [68 72 79 85 88 91 95]
print(scores[order[::-1]]) # descending: [95 91 88 85 79 72 68]
# Top-3 highest
top3_idx = np.argsort(scores)[-3:][::-1]
print(top3_idx) # [5 2 6]
print(scores[top3_idx]) # [95 91 88]
# np.where (1-arg) — index
high = np.where(scores >= 80)
print(high) # (array([1, 2, 5, 6]),)
# np.where (3-arg) — ternary
grade = np.where(scores >= 80, "Pass", "Fail")
print(grade) # ['Fail' 'Pass' 'Pass' 'Fail' 'Fail' 'Pass' 'Pass']
argmax/argmin — classification-এ predicted class বের করে। argsort — top-k recommendation-এ। np.where(cond, a, b) — vectorized if-else।
৯ · AI use case — label filter ও top-k prediction
Indexing AI workflow-এ সর্বত্র। দু'টি সাধারণ pattern — class-wise data filter ও model output থেকে top-k prediction।
import numpy as np
# ── Use case 1: label-wise data filter ──
# 6 sample, 3 feature
X = np.array([
[1.2, 0.5, 3.1], # class 0
[0.8, 1.1, 2.9], # class 1
[1.5, 0.3, 3.5], # class 0
[0.9, 1.4, 2.7], # class 1
[1.7, 0.2, 3.8], # class 0
[1.0, 1.2, 2.8], # class 1
])
y = np.array([0, 1, 0, 1, 0, 1])
# Class 0-এর সব sample
X_class0 = X[y == 0]
print("Class 0 samples:")
print(X_class0)
# প্রতি class-এর mean feature
mean_0 = X[y == 0].mean(axis=0)
mean_1 = X[y == 1].mean(axis=0)
print("Mean class 0:", mean_0)
print("Mean class 1:", mean_1)
# ── Use case 2: top-k prediction ──
# Model output — 5 class-এর probability
probs = np.array([0.05, 0.42, 0.18, 0.27, 0.08])
class_names = np.array(["cat", "dog", "horse", "bird", "fish"])
# Top-1 prediction
top1 = np.argmax(probs)
print(f"Top-1: {class_names[top1]} ({probs[top1]:.2f})")
# Top-3 prediction
top3 = np.argsort(probs)[-3:][::-1]
print(f"Top-3: {class_names[top3]}")
print(f"Probs: {probs[top3]}")
# ── Use case 3: image crop ──
# RGB image — 100x100x3
img = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
# Center crop — 50x50
crop = img[25:75, 25:75]
print(f"Crop shape: {crop.shape}") # (50, 50, 3)
# Red channel only
red = img[:, :, 0]
print(f"Red channel shape: {red.shape}") # (100, 100)
# Downsample 2x
small = img[::2, ::2]
print(f"Downsampled shape: {small.shape}") # (50, 50, 3)
১) arr[i] — single element।
২) arr[a:b:c] — slice (view)।
৩) arr[mask] — boolean (copy)।
৪) arr[[i, j, k]] — fancy (copy)।
৫) arr[np.argsort(...)] — index reorder।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
NumPy slice কেন default-এ view ফেরায়, copy নয়? Performance ও memory perspective থেকে design rationale কী? কখন .copy() অপরিহার্য — এবং কেন এই behavior সবচেয়ে বড় bug-source?
NumPy-র view-by-default behavior — তার সবচেয়ে strong design choice এবং সবচেয়ে controversial পয়েন্ট। বুঝলে — performance + correctness দু'টোই hand-in-hand চলে।
কেন view (memory perspective)?
- একটি ১GB array slice করলেন
arr[:500_000_000]— যদি copy হয় তবে আরেকটি 500MB allocate। Memory ১.৫× explosion। - Real ML pipeline-এ — হাজার হাজার slice operation chain হয়। প্রতি operation copy → memory blow-up।
- View-এ — শুধু metadata (offset, stride, shape) বদলায়। data buffer অপরিবর্তিত। O(১) memory cost।
কেন view (performance perspective)?
- Allocation cost — malloc/free system call expensive।
- Cache locality — view শুধু stride update; data already in CPU cache।
- Copy = O(n) memcpy। Hot loop-এ unacceptable।
- NumPy-র C internal — view creation প্রায় free।
View internals — strides:
- প্রতিটি ndarray-এর
.stridestuple — প্রতি dimension-এ পরবর্তী element-এ যেতে কত byte। - Slice
arr[::2]— শুধু stride দ্বিগুণ। data untouched। - Transpose
arr.T— strides reverse। data untouched। - Reshape contiguous-এ — view, না হলে copy।
কখন .copy() অপরিহার্য:
- Function input mutation এড়াতে:
def normalize(x): x = x.copy(); x -= x.mean(); return x— caller-এর data নিরাপদ। - Slice independent করা: training validation split — পরে modification একে অপরের সাথে interfere না করে।
- Long-lived sub-array: ১GB array থেকে ১MB slice ধরে রাখলে — পুরো ১GB GC হয় না (view base reference রাখে)।
.copy()করলে original free। - Multi-threading: shared mutable state এড়াতে।
সবচেয়ে common bug — silent mutation:
def shuffle_first_half(arr):
half = arr[:len(arr)//2]
np.random.shuffle(half) # original-ও shuffled!
return half
original = np.array([1, 2, 3, 4, 5, 6])
shuffle_first_half(original)
# original এখন corrupt — caller-এর knowledge ছাড়াই
Diagnostic tools:
arr.base— যদি view হয়, parent ফেরায়; copy হলে None।arr.flags.owndata— own data নাকি borrow।np.shares_memory(a, b)— explicit check।
Rule of thumb: Library-quality code লিখলে — input array কখনো mutate করবেন না। Defensive copy at boundary, view inside। এই discipline-ই PyTorch/NumPy-এর success-এর মূল।
মূল উপলব্ধি: View-by-default = performance-first design। Cost: programmer-কে ownership track করতে হয়। ROI-তে worth it — কারণ AI scale-এ ১.৫× memory blow-up unacceptable। এই trade-off accept করাই NumPy-mastery-র প্রবেশদ্বার।
প্র ০২
Boolean indexing internally কীভাবে কাজ করে? arr > 5 থেকে arr[arr > 5] পর্যন্ত — vectorized comparison, mask array, gather operation — এই তিন stage-এ Python-এর জন্য কী trade-off?
Boolean indexing দেখতে magic, ভেতরে তিনটি well-defined stage। প্রতিটি stage NumPy-র C-extension-এ optimized — তাই Python loop-এর চেয়ে ১০-১০০× দ্রুত।
Stage 1: Vectorized comparison
arr > 5— প্রতিটি element-এ comparison।- Python loop নয় — NumPy-র C-level
ufunc(universal function)। - SIMD instruction (AVX, SSE) — একসাথে ৪-৮ element compare।
- Output: same shape boolean array (dtype
bool, ১ byte/element)। - একটি ১M-element float64 array → 8MB; mask → 1MB। ৮× smaller।
Stage 2: Mask array — কী store করে?
- Position-wise True/False — original array-এর সমান shape।
- Memory: ১ byte/element (NumPy bool, না bit-packed)।
np.packbits()দিয়ে bit-packed করা যায় — তবে indexing-এ unpack লাগে।- Mask intermediate object — combine করতে free (
(a>5)&(b<3)— temporary mask)।
Stage 3: Gather operation
arr[mask]— দু'pass operation।- Pass 1: True count → output size determine।
- Pass 2: True position থেকে value copy → contiguous output।
- Output সবসময় 1D (flattened) — কারণ True positions arbitrary।
- Output সবসময় copy — view সম্ভব না (positions non-contiguous)।
Trade-offs (Python vs C):
- Python loop:
[x for x in arr if x > 5]— readable কিন্তু slow। প্রতি element-এ Python object creation, comparison, list append। - NumPy boolean: ১০-১০০× দ্রুত। কিন্তু intermediate mask memory খায়।
- Numba/Cython: No intermediate mask, fused loop। সবচেয়ে দ্রুত যদি condition complex।
Performance benchmark (১M elements):
- Python list comprehension: ~১০০ms।
arr[arr > 5]: ~২ms (50× দ্রুত)।np.compress(arr > 5, arr): same as above, alternative API।- Numba JIT: ~০.৫ms (200× Python)।
Memory cost:
- Original: 8MB (float64 × 1M)।
- Mask: 1MB (bool × 1M)।
- Output: variable (depends on True count)।
- Peak: 8MB + 1MB + output। প্রায় ২× original।
Combine masks — operator precedence trap:
# ভুল!
arr[arr > 3 & arr < 8] # & আগে চলে — TypeError
# সঠিক
arr[(arr > 3) & (arr < 8)]
# কেন? — Python operator precedence
# & is bitwise, higher precedence than >
# & evaluates first → "3 & arr" — শুরুতেই type mismatch
Vectorized vs imperative — design philosophy:
- NumPy = "what you want", not "how to do it"।
- "
arr[arr > 5]" intent-revealing — SQLWHERE-এর মতো। - C internals JIT compile/SIMD optimize করতে পারে।
- Pandas, PyTorch, JAX — সবই এই philosophy carry করেছে।
মূল উপলব্ধি: Boolean indexing ৩-stage pipeline: compare → mask → gather। প্রতিটি C-optimized। Memory cost intermediate mask। Performance ১০-১০০× Python loop। Vectorization NumPy-র "ম্যাজিক" নয় — well-engineered SIMD + cache-friendly layout-এর ফল।
প্র ০৩ Fancy indexing বনাম boolean masking — দু'টোই subset select করে। কোনটা কখন বাছবেন? Performance, readability, ও memory trade-off-এর light-এ practical guideline কী?
দু'টি technique প্রায় interchangeable মনে হলেও — semantics, performance, ও use case ভিন্ন। ভুল choice ১০× slowdown বা bug দিতে পারে।
Semantic difference:
- Boolean mask: "প্রতিটি position-এ যাব কি যাব না।" শর্তভিত্তিক — order automatic (original order preserved)।
- Fancy indexing: "এই specific position-গুলো — এই order-এ।" Position list-এর order respected, repetition allowed।
কখন boolean mask:
- Condition-based filter:
arr[arr > threshold]। - Multi-condition:
arr[(arr > a) & (arr < b)]। - Element-wise condition থেকে subset।
- Order matter না — কোন position match জানা নেই।
- Pandas-এ
df[df.age > 18]— same idea।
কখন fancy indexing:
- Specific known indices:
arr[[0, 5, 10]]। - Random sampling:
arr[np.random.choice(n, k)]। - Train/test split: shuffled indices।
- Reorder/permute:
arr[np.argsort(scores)]। - Repetition needed:
arr[[0, 0, 1]]— boolean পারে না। - Custom order:
arr[[3, 1, 2]]— boolean original order ফেরায়।
Performance comparison:
- Boolean — small selection (1%): overhead high (full mask traverse)।
- Boolean — large selection (50%): efficient (single pass)।
- Fancy — small selection: very efficient (only k lookups)।
- Fancy — large selection: slower than boolean (random access pattern)।
- Rule: < ১০% select → fancy; > ১০% select → boolean।
Memory comparison:
- Boolean mask: O(n) memory always (full-size mask)।
- Fancy indices: O(k) memory (k = selected count)।
- Tiny selection from huge array → fancy clearly winner।
Cache behavior:
- Boolean: sequential scan — cache-friendly।
- Fancy: random access — cache miss potential বেশি।
- Sorted indices fancy-এও sequential — দ্রুত।
Readability:
# Intent: "৬৫+ score-এর student"
# Boolean — clear
high_scorers = students[scores > 65]
# Fancy — extra step, less clear
high_idx = np.where(scores > 65)[0]
high_scorers = students[high_idx]
# Intent: "specific 3 students by ID"
# Fancy — natural
selected = students[[101, 205, 309]]
# Boolean — awkward
selected = students[np.isin(ids, [101, 205, 309])]
2D context:
- Boolean 2D: result flattened (loses shape)।
- Fancy 2D: shape preserved more cleanly।
- Row select:
mat[[0, 2]](fancy) vsmat[mask](flat)।
Combining — best of both:
# Step 1: boolean → indices
indices = np.where(scores > 65)[0]
# Step 2: random sample
sample = np.random.choice(indices, size=10, replace=False)
# Step 3: fancy index
selected = students[sample]
Mutable assignment — both work, with differences:
- Boolean:
arr[arr<0] = 0— clip negative to 0। - Fancy:
arr[[1,3,5]] = 99— specific positions। - Fancy duplicate:
arr[[0,0,0]] = [1,2,3]— undefined order, last wins (typically 3)।
মূল উপলব্ধি: Boolean = "condition-based, full scan, large subset"। Fancy = "specific positions, small subset, custom order"। Right tool choosing comes with practice — শুরুতে readability prefer করুন, hot path-এ profile করে optimize।
প্র ০৪ Image processing-এ slicing-এর বাস্তব ব্যবহার — crop, channel split, downsample, padding, augmentation। Computer vision pipeline-এ slicing কেন এত central, এবং কোন pitfall এড়াতে হয়?
Computer vision = NumPy slicing-এর masterclass। প্রতিটি image transform — slicing-এর creative ব্যবহার। PIL/OpenCV-র অনেক operation আসলে এই pattern-গুলোর wrapper।
Image = 3D ndarray:
- Grayscale:
(H, W)— height × width। - RGB:
(H, W, 3)— শেষ axis = R, G, B channel। - RGBA:
(H, W, 4)— alpha channel যোগ। - Batch:
(N, H, W, C)— N ছবি একসাথে (TF order)। - PyTorch:
(N, C, H, W)— channel আগে।
(১) Crop — slice on H, W:
# Center crop 224x224 from larger image
h, w = img.shape[:2]
top = (h - 224) // 2
left = (w - 224) // 2
crop = img[top:top+224, left:left+224]
# Random crop — data augmentation
top = np.random.randint(0, h - 224)
left = np.random.randint(0, w - 224)
crop = img[top:top+224, left:left+224]
(২) Channel split:
r = img[:, :, 0] # Red channel
g = img[:, :, 1] # Green
b = img[:, :, 2] # Blue
# Grayscale conversion
gray = 0.299*r + 0.587*g + 0.114*b
# Drop alpha
rgb = rgba[:, :, :3]
# Reorder BGR → RGB (OpenCV convention)
rgb = bgr[:, :, ::-1] # last axis reverse
(৩) Downsample — step slicing:
# 2x downsample (nearest)
small = img[::2, ::2]
# 4x downsample
tiny = img[::4, ::4]
# Quick thumbnail — quality poor কিন্তু fast
thumb = img[::8, ::8]
# Production: cv2.resize / PIL.thumbnail
# (anti-aliasing, interpolation)
(৪) Flip — reverse slice:
flipped_h = img[:, ::-1] # horizontal flip
flipped_v = img[::-1, :] # vertical flip
both = img[::-1, ::-1] # 180° rotate
(৫) Padding — assignment slicing:
# Black border 10px
h, w, c = img.shape
padded = np.zeros((h+20, w+20, c), dtype=img.dtype)
padded[10:10+h, 10:10+w] = img
# Better: np.pad — fewer error
padded = np.pad(img, ((10,10), (10,10), (0,0)))
(৬) Patch extraction (CNN, ViT input):
# Non-overlapping 16x16 patches
patches = []
for i in range(0, h, 16):
for j in range(0, w, 16):
patches.append(img[i:i+16, j:j+16])
# Vectorized — reshape trick
patches = img.reshape(h//16, 16, w//16, 16, 3)
patches = patches.transpose(0, 2, 1, 3, 4)
# shape: (n_h, n_w, 16, 16, 3)
(৭) Mask-based segmentation:
# Foreground mask (boolean H×W)
foreground = mask > 0.5
# Apply to image — broadcast
result = img.copy()
result[~foreground] = 0 # background black
Pitfalls:
- View vs copy:
crop = img[100:200, 100:200]— view! crop modify করলে img-ও বদলায়। Augmentation pipeline-এ disaster। - Bound check:
img[1000:1200]— image যদি ৮০০ row, no error, just empty array। Silent bug। - Channel order confusion: OpenCV BGR, PIL/Matplotlib RGB। Color wrong হলে — debug nightmare।
- HWC vs CHW: NumPy/TF HWC, PyTorch CHW। Wrong shape model crash।
- dtype issue: uint8 (০-২৫৫) vs float (০-১)। Slice keep dtype, কিন্তু arithmetic-এ overflow।
- Negative stride performance:
img[::-1]view but reverse stride — কিছু operation slow।
Vectorized augmentation pipeline:
def random_augment(img):
# Random crop
h, w = img.shape[:2]
new_h, new_w = 224, 224
top = np.random.randint(0, h - new_h)
left = np.random.randint(0, w - new_w)
img = img[top:top+new_h, left:left+new_w].copy() # ← copy দরকার!
# Random horizontal flip
if np.random.rand() < 0.5:
img = img[:, ::-1].copy()
# Random channel shuffle (rare)
if np.random.rand() < 0.1:
perm = np.random.permutation(3)
img = img[:, :, perm]
return img
Production tools:
- OpenCV — C++ optimized, geometric ops fast।
- PIL/Pillow — convenient, slower।
- Albumentations — augmentation library, NumPy-based।
- Kornia — GPU augmentation (PyTorch tensor)।
- tf.image — TensorFlow native।
মূল উপলব্ধি: Image processing = "ndarray gymnastics"। Slicing প্রায় সব transform-এর foundation। View/copy gotcha augmentation pipeline-এ disaster — সবসময় .copy() at boundary। Vectorized slicing GPU-এর age-এও relevant — কারণ PyTorch/JAX-এর tensor indexing identical syntax follow করে। NumPy ভাল বুঝলে — সব deep learning framework-এর data manipulation আপনার কাছে natural।
অনুশীলন
-
Slice practice: একটি 5×5 matrix বানান (১ থেকে ২৫)। (ক) মাঝের 3×3 sub-matrix বের করুন। (খ) দ্বিতীয় column বের করুন। (গ) প্রতি ২য় row নিয়ে নতুন matrix বানান।
import numpy as np mat = np.arange(1, 26).reshape(5, 5) print(mat) # [[ 1 2 3 4 5] # [ 6 7 8 9 10] # [11 12 13 14 15] # [16 17 18 19 20] # [21 22 23 24 25]] # (ক) মাঝের 3x3 print(mat[1:4, 1:4]) # [[ 7 8 9] # [12 13 14] # [17 18 19]] # (খ) দ্বিতীয় column (index 1) print(mat[:, 1]) # [ 2 7 12 17 22] # (গ) প্রতি ২য় row print(mat[::2]) # [[ 1 2 3 4 5] # [11 12 13 14 15] # [21 22 23 24 25]] -
Boolean & fancy:
arr = np.array([3, 7, 1, 9, 4, 6, 2, 8, 5])। (ক) ৪-৭ range-এ যেগুলো — boolean দিয়ে। (খ) সবচেয়ে বড় ৩টি element-এর index —argsortদিয়ে। (গ) ০, ৩, ৬ index-এর element — fancy দিয়ে।import numpy as np arr = np.array([3, 7, 1, 9, 4, 6, 2, 8, 5]) # (ক) ৪-৭ range print(arr[(arr >= 4) & (arr <= 7)]) # [7 4 6 5] # (খ) top-3 index top3 = np.argsort(arr)[-3:][::-1] print(top3, arr[top3]) # [3 7 1] [9 8 7] # (গ) fancy print(arr[[0, 3, 6]]) # [3 9 2] -
View vs copy bug: নিচের code-এ কী bug? কীভাবে fix করবেন?
def zero_first_half(a): half = a[:len(a)//2] half[:] = 0 return halfBug:
half = a[:len(a)//2]view ফেরায় — তাইhalf[:] = 0লিখলে original arraya-ও modify হয়। Caller-এর data corrupt!# প্রমাণ import numpy as np arr = np.array([1, 2, 3, 4, 5, 6]) result = zero_first_half(arr) print(arr) # [0 0 0 4 5 6] ← original বদলে গেছে! # Fix 1 — copy at start def zero_first_half_fixed(a): half = a[:len(a)//2].copy() # ← copy half[:] = 0 return half # Fix 2 — explicit intent (caller আগে copy দেয়) def zero_first_half_v2(a): """Note: returns view; modifies input.""" a = a.copy() a[:len(a)//2] = 0 return a # Lesson: function-এর শুরুতে input mutate করা avoid। # Library-quality code-এ defensive copy বা document mutation।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পাঠ ১১ · Broadcasting — অ্যারের জাদু পরবর্তী পাঠ ভিন্ন shape-এর array কীভাবে আপনাআপনি align হয় — vectorization-এর সবচেয়ে শক্তিশালী idea।
- পাঠ ০৯ · NumPy পরিচিতি — ndarray আগের পাঠ ndarray, dtype, shape, axis — basics-এ ফিরে যান।
-
পাঠ ১৩ · Pandas পরিচিতি এই পাঠের সাথে সম্পর্কিত
Pandas-এর
.loc,.iloc, boolean filter — সবই NumPy indexing-এর extension। - সব AI Courses দেখুন ABCL TECH Python, ML, DL, NLP, CV, GenAI, RL, MLOps — সব AI কোর্স একসাথে।