NumPy چیست؟ آموزش کامل آرایه، ماتریس و محاسبات برداری با پایتون
آموزش کامل NumPy از ساخت ndarray، Indexing، Reshape و Broadcasting تا آمار، جبر خطی، بهینهسازی حافظه و ساخت موتور جستوجوی برداری با Cosine Similarity؛ همراه با کدهای عملی.
NumPy یکی از بنیادیترین کتابخانههای پایتون برای محاسبات عددی، تحلیل داده، یادگیری ماشین و هوش مصنوعی است. بسیاری از کتابخانههای شناختهشده مانند Pandas، Scikit-learn، SciPy، OpenCV و ابزارهای یادگیری عمیق، بهصورت مستقیم یا غیرمستقیم با آرایههای NumPy کار میکنند.
اگر میخواهید پردازش داده، ساخت مدل یادگیری ماشین، محاسبات ماتریسی، کار با تصویر، تحلیل سیگنال، Embedding یا Semantic Search را بهصورت اصولی یاد بگیرید، درک NumPy اهمیت زیادی دارد.
در این مقاله، NumPy را از پایه تا سطح کاربردی بررسی میکنیم و در پایان یک موتور جستوجوی برداری کوچک با Cosine Similarity میسازیم. این پروژه نشان میدهد مفاهیمی مانند Vector، Matrix، Normalization و Matrix Multiplication چگونه در سیستمهای هوش مصنوعی استفاده میشوند.
NumPy چیست؟
NumPy مخفف Numerical Python است. مهمترین ساختار آن ndarray نام دارد؛ یک آرایه چندبعدی که برای نگهداری و پردازش دادههای همنوع طراحی شده است.
یک آرایه NumPy میتواند چنین شکلهایی داشته باشد:
Scalar:
42
Vector:
[10, 20, 30]
Matrix:
[[1, 2, 3],
[4, 5, 6]]
Tensor سهبعدی:
[
[[1, 2], [3, 4]],
[[5, 6], [7, 8]]
]
طبق مستندات رسمی NumPy، این کتابخانه علاوه بر آرایههای چندبعدی، امکاناتی برای عملیات ریاضی، آمار، جبر خطی، تبدیل فوریه، تولید اعداد تصادفی و ورودی و خروجی داده ارائه میدهد.
NumPy چه کاربردهایی دارد؟
NumPy در حوزههای مختلف استفاده میشود:
- محاسبات برداری و ماتریسی
- تحلیل دادههای عددی
- آمادهسازی داده برای Machine Learning
- پردازش تصویر
- پردازش سیگنال
- محاسبات آماری
- شبیهسازی و نمونهگیری تصادفی
- کار با Embedding
- محاسبه Cosine Similarity
- پیادهسازی الگوریتمهای علمی
- بهینهسازی عملیات عددی
- تبدیل داده بین کتابخانههای مختلف
- ساخت Batch برای مدلهای هوش مصنوعی
- پیادهسازی بخشهایی از شبکه عصبی
تفاوت NumPy Array و List پایتون
List در Python میتواند مقادیر مختلفی را نگهداری کند:
python_list = [
10,
"text",
True,
3.14,
]
آرایه NumPy معمولاً یک نوع داده مشخص دارد:
import numpy as np
numbers = np.array(
[
10,
20,
30,
40,
],
dtype=np.int64,
)
مقایسه کلی:
| ویژگی | Python List | NumPy Array |
|---|---|---|
| نوع داده | میتواند ترکیبی باشد | معمولاً یکسان |
| محاسبات برداری | مستقیم ندارد | دارد |
| مصرف حافظه عددی | معمولاً بیشتر | معمولاً بهینهتر |
| عملیات ماتریسی | محدود | قدرتمند |
| Broadcasting | ندارد | دارد |
| مناسب محاسبات علمی | محدود | بسیار مناسب |
| استفاده در یادگیری ماشین | با تبدیل | مستقیمتر |
NumPy برای محاسبات عددی بزرگ معمولاً مناسبتر است؛ اما برای مجموعههای کوچک و ناهمگون، List همچنان انتخاب ساده و مفیدی است.
نصب NumPy
pip install numpy
بررسی نسخه نصبشده:
python -c "import numpy; print(numpy.__version__)"
در زمان نگارش این مقاله، NumPy 2.5 نسخه اصلی منتشرشده است. برای مشاهده نسخه فعلی میتوانید به وبسایت رسمی NumPy یا Release Notes مراجعه کنید.
ساخت Virtual Environment
ایجاد محیط مجازی:
python -m venv .venv
فعالسازی در Windows PowerShell:
.venv\Scripts\Activate.ps1
فعالسازی در Linux و macOS:
source .venv/bin/activate
نصب وابستگیها:
python -m pip install --upgrade pip
python -m pip install numpy matplotlib requests
Import کردن NumPy
روش استاندارد:
import numpy as np
نام اختصاری np در اکثر پروژههای Python استفاده میشود.
ساخت آرایه یکبعدی
numbers = np.array(
[
10,
20,
30,
40,
]
)
print(numbers)
خروجی:
[10 20 30 40]
بررسی نوع Object:
print(
type(numbers)
)
خروجی:
<class 'numpy.ndarray'>
ساخت آرایه دوبعدی
matrix = np.array(
[
[1, 2, 3],
[4, 5, 6],
]
)
print(matrix)
خروجی:
[[1 2 3]
[4 5 6]]
ویژگیهای مهم ndarray
print(
matrix.ndim
)
print(
matrix.shape
)
print(
matrix.size
)
print(
matrix.dtype
)
print(
matrix.itemsize
)
print(
matrix.nbytes
)
معنی ویژگیها:
| ویژگی | کاربرد |
|---|---|
ndim | تعداد Dimensionها |
shape | اندازه هر Dimension |
size | تعداد کل Elementها |
dtype | نوع داده |
itemsize | حافظه هر Element بر حسب Byte |
nbytes | حافظه کل داده آرایه |
برای ماتریس قبلی:
ndim = 2
shape = (2, 3)
size = 6
انتخاب dtype مناسب
integers = np.array(
[1, 2, 3],
dtype=np.int32,
)
floats = np.array(
[1.0, 2.0, 3.0],
dtype=np.float32,
)
انواع رایج:
| dtype | کاربرد |
|---|---|
int8 | عدد صحیح ۸ بیتی |
int16 | عدد صحیح ۱۶ بیتی |
int32 | عدد صحیح ۳۲ بیتی |
int64 | عدد صحیح ۶۴ بیتی |
float16 | اعشاری کمدقت |
float32 | اعشاری رایج در یادگیری ماشین |
float64 | اعشاری با دقت بیشتر |
bool | مقدار منطقی |
complex64 | عدد مختلط |
str_ | رشته |
نوع داده کوچکتر میتواند حافظه کمتری مصرف کند؛ اما باید محدوده مقدار و دقت موردنیاز را بررسی کنید.
برای نمونه، int8 فقط محدوده کوچکی از اعداد را نگهداری میکند و استفاده اشتباه از آن ممکن است باعث Overflow شود.
تبدیل dtype
numbers = np.array(
[
1.2,
2.7,
3.9,
]
)
integer_numbers = numbers.astype(
np.int32
)
print(integer_numbers)
خروجی:
[1 2 3]
تبدیل Float به Integer بخش اعشاری را حذف میکند و Round انجام نمیدهد.
برای Round:
rounded = np.rint(
numbers
).astype(
np.int32
)
روشهای ساخت آرایه
آرایه صفر
zeros = np.zeros(
shape=(3, 4),
dtype=np.float32,
)
آرایه یک
ones = np.ones(
shape=(2, 3),
dtype=np.float32,
)
آرایه با مقدار ثابت
filled = np.full(
shape=(2, 3),
fill_value=7,
)
ماتریس همانی
identity = np.eye(
4,
dtype=np.float32,
)
دنباله عددی با arange
values = np.arange(
start=0,
stop=10,
step=2,
)
خروجی:
[0 2 4 6 8]
مقدار stop در خروجی قرار نمیگیرد.
دنباله با linspace
values = np.linspace(
start=0,
stop=1,
num=6,
)
خروجی:
[0. 0.2 0.4 0.6 0.8 1. ]
تفاوت:
arangeبر اساس Step کار میکند.linspaceتعداد نقاط را مشخص میکند.
برای اعداد اعشاری، linspace در بسیاری از موارد قابلپیشبینیتر است.
روشهای رسمی ایجاد آرایه در راهنمای Array Creation توضیح داده شدهاند.
Indexing در NumPy
آرایه یکبعدی:
values = np.array(
[
10,
20,
30,
40,
50,
]
)
print(
values[0]
)
print(
values[-1]
)
ماتریس:
matrix = np.array(
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
)
print(
matrix[1, 2]
)
خروجی:
6
انتخاب یک ردیف:
second_row = matrix[1, :]
انتخاب یک ستون:
second_column = matrix[:, 1]
انتخاب بخشی از ماتریس:
subset = matrix[
0:2,
1:3,
]
Slicing
values = np.array(
[
10,
20,
30,
40,
50,
60,
]
)
print(
values[1:5]
)
print(
values[::2]
)
print(
values[::-1]
)
خروجی:
[20 30 40 50]
[10 30 50]
[60 50 40 30 20 10]
Boolean Indexing
scores = np.array(
[
42,
78,
91,
65,
88,
]
)
mask = scores >= 70
print(mask)
print(
scores[mask]
)
میتوان شرط را مستقیم نوشت:
selected_scores = scores[
scores >= 70
]
ترکیب شرطها:
selected_scores = scores[
(scores >= 70)
& (scores <= 90)
]
استفاده از and و or برای آرایه NumPy صحیح نیست. باید از &، | و پرانتز استفاده کنید.
Advanced Indexing
values = np.array(
[
10,
20,
30,
40,
50,
]
)
indexes = np.array(
[
0,
2,
4,
]
)
selected = values[
indexes
]
انتخاب چند ردیف و ستون:
matrix = np.arange(
20
).reshape(
4,
5,
)
rows = np.array(
[
0,
2,
]
)
columns = np.array(
[
1,
3,
]
)
selected = matrix[
np.ix_(
rows,
columns,
)
]
راهنمای کامل Indexing در مستندات Indexing آرایههای NumPy موجود است.
تفاوت View و Copy
یکی از مهمترین نکات NumPy این است که Slicing معمولاً یک View از آرایه اصلی ایجاد میکند.
original = np.array(
[
10,
20,
30,
40,
]
)
view = original[1:3]
view[0] = 999
print(original)
خروجی:
[ 10 999 30 40]
با تغییر view، آرایه اصلی نیز تغییر کرده است.
اگر Copy مستقل نیاز دارید:
copied = original[
1:3
].copy()
copied[0] = 500
print(original)
print(copied)
مستندات NumPy توضیح میدهد که Basic Indexing معمولاً View ایجاد میکند، درحالیکه Advanced Indexing معمولاً Copy برمیگرداند. جزئیات در راهنمای Copies and Views آمده است.
تغییر Shape آرایه
values = np.arange(
12
)
matrix = values.reshape(
3,
4,
)
print(matrix)
خروجی:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
استفاده از -1 برای محاسبه خودکار یک Dimension:
matrix = values.reshape(
2,
-1,
)
NumPy Dimension دوم را برابر ۶ قرار میدهد.
تعداد Elementهای Shape جدید باید با تعداد Elementهای آرایه برابر باشد.
تبدیل ماتریس به Vector
با reshape:
vector = matrix.reshape(
-1
)
با ravel:
vector = matrix.ravel()
با flatten:
vector = matrix.flatten()
تفاوت مهم:
flattenهمیشه Copy ایجاد میکند.ravelدر صورت امکان View برمیگرداند.reshape(-1)نیز ممکن است View ایجاد کند.
اگر قرار است خروجی را تغییر دهید و آرایه اصلی نباید تغییر کند، از .copy() یا flatten() استفاده کنید.
اضافه یا حذف Dimension
vector = np.array(
[
1,
2,
3,
]
)
row_vector = vector[
np.newaxis,
:
]
column_vector = vector[
:,
np.newaxis
]
Shapeها:
vector: (3,)
row_vector: (1, 3)
column_vector: (3, 1)
روش دیگر:
expanded = np.expand_dims(
vector,
axis=0,
)
حذف Dimensionهای دارای اندازه یک:
squeezed = np.squeeze(
expanded
)
Transpose
matrix = np.array(
[
[1, 2, 3],
[4, 5, 6],
]
)
transposed = matrix.T
Shape اولیه:
(2, 3)
Shape جدید:
(3, 2)
برای Tensor چندبعدی:
tensor = np.zeros(
(
2,
3,
4,
)
)
reordered = np.transpose(
tensor,
axes=(
2,
0,
1,
),
)
عملیات برداری
first = np.array(
[
1,
2,
3,
]
)
second = np.array(
[
4,
5,
6,
]
)
جمع:
result = first + second
تفریق:
result = first - second
ضرب Element-wise:
result = first * second
تقسیم:
result = first / second
توان:
result = first ** 2
این عملیات بدون Loop صریح Python روی تمام Elementها اجرا میشوند.
Vectorization چیست؟
روش معمول با Loop:
values = [
1,
2,
3,
4,
]
squared = []
for value in values:
squared.append(
value ** 2
)
نسخه NumPy:
values = np.array(
[
1,
2,
3,
4,
]
)
squared = values ** 2
Vectorization کد را کوتاهتر میکند و معمولاً عملکرد بهتری دارد؛ زیرا بخش اصلی محاسبه در کد بهینهشده کتابخانه اجرا میشود.
مقایسه سرعت Loop و NumPy
import time
import numpy as np
number_of_items = 1_000_000
python_values = list(
range(
number_of_items
)
)
start = time.perf_counter()
python_result = [
value * 2
for value in python_values
]
python_time = (
time.perf_counter()
- start
)
numpy_values = np.arange(
number_of_items
)
start = time.perf_counter()
numpy_result = (
numpy_values * 2
)
numpy_time = (
time.perf_counter()
- start
)
print(
"Python time:",
python_time,
)
print(
"NumPy time:",
numpy_time,
)
نتیجه دقیق به سیستم، نسخه Python، dtype و نوع عملیات بستگی دارد؛ بنابراین بهتر است بهجای تکرار ادعاهای کلی، Benchmark را روی Workflow واقعی خود اجرا کنید.
Broadcasting چیست؟
Broadcasting به NumPy اجازه میدهد عملیات را بین آرایههایی با Shape متفاوت اما سازگار انجام دهد.
مثال ساده:
values = np.array(
[
10,
20,
30,
]
)
result = values + 5
عدد ۵ بهصورت منطقی برای تمام Elementها استفاده میشود:
[15 25 35]
مثال ماتریس و Vector:
matrix = np.array(
[
[1, 2, 3],
[4, 5, 6],
]
)
offset = np.array(
[
10,
20,
30,
]
)
result = matrix + offset
خروجی:
[[11 22 33]
[14 25 36]]
قوانین Broadcasting
NumPy Shapeها را از سمت راست مقایسه میکند. دو Dimension زمانی سازگار هستند که:
- اندازه آنها برابر باشد؛ یا
- اندازه یکی از آنها برابر یک باشد.
مثال سازگار:
Matrix shape: (4, 3)
Vector shape: (3,)
مثال ناسازگار:
Matrix shape: (4, 3)
Vector shape: (4,)
برای سازگارکردن مثال دوم:
vector = np.array(
[
10,
20,
30,
40,
]
).reshape(
4,
1,
)
result = matrix + vector
Broadcasting امکان Vectorization را فراهم میکند و معمولاً بدون ساخت Copyهای غیرضروری کار میکند. جزئیات در راهنمای رسمی Broadcasting توضیح داده شده است.
توابع آماری NumPy
values = np.array(
[
10,
20,
30,
40,
50,
],
dtype=np.float64,
)
مجموع:
total = np.sum(
values
)
میانگین:
average = np.mean(
values
)
میانه:
median = np.median(
values
)
انحراف معیار:
standard_deviation = np.std(
values
)
واریانس:
variance = np.var(
values
)
کمینه و بیشینه:
minimum = np.min(
values
)
maximum = np.max(
values
)
صدک:
percentile_90 = np.percentile(
values,
90,
)
مفهوم axis
matrix = np.array(
[
[1, 2, 3],
[4, 5, 6],
]
)
مجموع تمام Elementها:
print(
matrix.sum()
)
مجموع هر ستون:
print(
matrix.sum(
axis=0
)
)
خروجی:
[5 7 9]
مجموع هر ردیف:
print(
matrix.sum(
axis=1
)
)
خروجی:
[ 6 15]
قاعده کاربردی:
axis=0یعنی Dimension ردیفها حذف و نتیجه برای ستونها محاسبه شود.axis=1یعنی Dimension ستونها حذف و نتیجه برای ردیفها محاسبه شود.
برای Tensorهای چندبعدی بهتر است همیشه Shape ورودی و خروجی را بررسی کنید.
حفظ Dimension با keepdims
row_means = matrix.mean(
axis=1,
keepdims=True,
)
Shape نتیجه:
(2, 1)
این ویژگی در Broadcasting مفید است:
centered = (
matrix
- row_means
)
مدیریت NaN
values = np.array(
[
10.0,
np.nan,
30.0,
40.0,
]
)
میانگین معمولی:
print(
np.mean(values)
)
خروجی nan است.
میانگین با نادیدهگرفتن NaN:
print(
np.nanmean(values)
)
سایر توابع:
np.nansum(values)
np.nanmin(values)
np.nanmax(values)
np.nanmedian(values)
np.nanstd(values)
شناسایی NaN:
missing_mask = np.isnan(
values
)
جایگزینی:
clean_values = np.nan_to_num(
values,
nan=0.0,
posinf=1_000.0,
neginf=-1_000.0,
)
جایگزینی خودکار NaN با صفر همیشه انتخاب مناسبی نیست. روش جایگزینی باید بر اساس معنی داده تعیین شود.
استفاده از np.where
scores = np.array(
[
45,
72,
88,
61,
]
)
labels = np.where(
scores >= 70,
"accepted",
"review",
)
جایگزینی مقادیر منفی:
values = np.array(
[
10,
-5,
20,
-3,
]
)
clean_values = np.where(
values < 0,
0,
values,
)
محدودکردن مقدار با clip
probabilities = np.array(
[
-0.2,
0.3,
0.8,
1.4,
]
)
clipped = np.clip(
probabilities,
0.0,
1.0,
)
خروجی:
[0. 0.3 0.8 1. ]
مرتبسازی
values = np.array(
[
30,
10,
50,
20,
]
)
sorted_values = np.sort(
values
)
Indexهای مرتبسازی:
sorted_indexes = np.argsort(
values
)
مرتبسازی نزولی:
descending_indexes = np.argsort(
values
)[::-1]
descending_values = values[
descending_indexes
]
این روش در رتبهبندی نتایج جستوجوی برداری نیز استفاده میشود.
مقادیر یکتا
labels = np.array(
[
"a",
"b",
"a",
"c",
"b",
"a",
]
)
unique_values = np.unique(
labels
)
همراه تعداد تکرار:
values, counts = np.unique(
labels,
return_counts=True,
)
ساخت Dictionary:
frequency = dict(
zip(
values.tolist(),
counts.tolist(),
)
)
اتصال آرایهها
first = np.array(
[
1,
2,
3,
]
)
second = np.array(
[
4,
5,
6,
]
)
با concatenate:
combined = np.concatenate(
[
first,
second,
]
)
ساخت ماتریس ردیفی:
stacked_rows = np.vstack(
[
first,
second,
]
)
ساخت ماتریس ستونی:
stacked_columns = np.column_stack(
[
first,
second,
]
)
ساخت Dimension جدید:
stacked = np.stack(
[
first,
second,
],
axis=0,
)
تفاوت مهم این است که stack یک Dimension جدید ایجاد میکند، اما concatenate روی Dimension موجود اتصال انجام میدهد.
تقسیم آرایه
values = np.arange(
12
)
تقسیم مساوی:
parts = np.split(
values,
3,
)
تقسیم نامساوی:
parts = np.array_split(
values,
5,
)
تقسیم ماتریس:
matrix = np.arange(
24
).reshape(
6,
4,
)
row_parts = np.vsplit(
matrix,
3,
)
column_parts = np.hsplit(
matrix,
2,
)
تولید اعداد تصادفی
روش پیشنهادی استفاده از Generator است:
rng = np.random.default_rng(
seed=42
)
عدد تصادفی بین صفر و یک:
values = rng.random(
size=5
)
عدد صحیح:
integers = rng.integers(
low=0,
high=100,
size=10,
)
توزیع نرمال:
normal_values = rng.normal(
loc=0.0,
scale=1.0,
size=1000,
)
انتخاب تصادفی:
selected = rng.choice(
[
"basic",
"pro",
"team",
],
size=20,
p=[
0.60,
0.30,
0.10,
],
)
Shuffle:
values = np.arange(
10
)
rng.shuffle(
values
)
مستندات مقدماتی NumPy نیز استفاده از np.random.default_rng() را برای ساخت Generator نشان میدهد. برای مطالعه بیشتر میتوانید راهنمای NumPy برای مبتدیان را ببینید.
ضرب ماتریسی
ضرب Element-wise:
first = np.array(
[
[1, 2],
[3, 4],
]
)
second = np.array(
[
[5, 6],
[7, 8],
]
)
elementwise = (
first * second
)
ضرب ماتریسی:
matrix_product = (
first @ second
)
یا:
matrix_product = np.matmul(
first,
second,
)
این دو عملیات یکسان نیستند.
Dot Product
first = np.array(
[
1.0,
2.0,
3.0,
]
)
second = np.array(
[
4.0,
5.0,
6.0,
]
)
dot_product = np.dot(
first,
second,
)
یا:
dot_product = (
first @ second
)
فرمول Dot Product:
a · b =
a1 × b1
+ a2 × b2
+ ...
+ an × bn
جبر خطی با NumPy
matrix = np.array(
[
[2.0, 1.0],
[1.0, 3.0],
]
)
دترمینان:
determinant = np.linalg.det(
matrix
)
معکوس:
inverse = np.linalg.inv(
matrix
)
مقادیر و بردارهای ویژه:
eigenvalues, eigenvectors = (
np.linalg.eig(
matrix
)
)
Norm:
norm = np.linalg.norm(
matrix
)
حل دستگاه معادلات:
coefficients = np.array(
[
[2.0, 1.0],
[1.0, 3.0],
]
)
targets = np.array(
[
8.0,
13.0,
]
)
solution = np.linalg.solve(
coefficients,
targets,
)
برای حل دستگاه، استفاده از solve معمولاً از محاسبه مستقیم معکوس مناسبتر است.
Normalization بردار
vector = np.array(
[
3.0,
4.0,
],
dtype=np.float32,
)
norm = np.linalg.norm(
vector
)
normalized_vector = (
vector / norm
)
خروجی:
[0.6 0.8]
برای جلوگیری از تقسیم بر صفر:
epsilon = 1e-12
normalized_vector = (
vector
/ max(
norm,
epsilon,
)
)
Normalization چند بردار
vectors = np.array(
[
[3.0, 4.0],
[1.0, 0.0],
[0.0, 2.0],
],
dtype=np.float32,
)
norms = np.linalg.norm(
vectors,
axis=1,
keepdims=True,
)
normalized_vectors = (
vectors
/ np.maximum(
norms,
1e-12,
)
)
استفاده از keepdims=True باعث میشود Shape برای Broadcasting مناسب باقی بماند.
Cosine Similarity چیست؟
Cosine Similarity میزان همجهتی دو بردار را اندازهگیری میکند:
cosine_similarity(a, b) =
dot(a, b) / (norm(a) × norm(b))
کد NumPy:
def cosine_similarity(
first: np.ndarray,
second: np.ndarray,
) -> float:
first = np.asarray(
first,
dtype=np.float32,
)
second = np.asarray(
second,
dtype=np.float32,
)
denominator = (
np.linalg.norm(first)
* np.linalg.norm(second)
)
if denominator == 0:
return 0.0
return float(
np.dot(
first,
second,
)
/ denominator
)
اگر بردارها از قبل L2-normalized باشند:
Cosine Similarity = Dot Product
این نکته امکان محاسبه سریع شباهت تعداد زیادی بردار را فراهم میکند.
پروژه عملی: ساخت موتور جستوجوی برداری با NumPy
فرض کنید برای چند سند، Embedding ذخیره کردهایم. برای قابلاجرا بودن مثال، بردارها داخل کد قرار گرفتهاند.
در پروژه واقعی، این بردارها توسط یک Embedding Model تولید میشوند و باید تمام آنها با یک مدل و تنظیمات یکسان ساخته شوند.
import numpy as np
documents = [
{
"id": "doc_001",
"title": (
"آموزش ساخت چتبات "
"با پایتون"
),
},
{
"id": "doc_002",
"title": (
"تحلیل فایل CSV "
"با Pandas"
),
},
{
"id": "doc_003",
"title": (
"ساخت جستوجوی معنایی "
"با Embedding"
),
},
{
"id": "doc_004",
"title": (
"آموزش تولید تصویر "
"با هوش مصنوعی"
),
},
{
"id": "doc_005",
"title": (
"مدیریت هزینه API "
"هوش مصنوعی"
),
},
]
بردارهای آزمایشی:
document_embeddings = np.array(
[
[
0.82,
0.71,
0.15,
0.10,
0.22,
0.18,
],
[
0.20,
0.35,
0.88,
0.74,
0.12,
0.16,
],
[
0.74,
0.68,
0.30,
0.25,
0.80,
0.72,
],
[
0.22,
0.19,
0.14,
0.18,
0.84,
0.76,
],
[
0.41,
0.38,
0.44,
0.35,
0.28,
0.90,
],
],
dtype=np.float32,
)
بردار Query آزمایشی:
query_embedding = np.array(
[
0.78,
0.70,
0.26,
0.20,
0.83,
0.69,
],
dtype=np.float32,
)
این بردارها فقط برای نمایش محاسبات هستند و کیفیت Semantic Search واقعی را نشان نمیدهند.
اعتبارسنجی Shapeها
if document_embeddings.ndim != 2:
raise ValueError(
"Document embeddings "
"must be a 2D array."
)
if query_embedding.ndim != 1:
raise ValueError(
"Query embedding "
"must be a 1D array."
)
if (
document_embeddings.shape[1]
!= query_embedding.shape[0]
):
raise ValueError(
"Embedding dimensions "
"do not match."
)
if (
document_embeddings.shape[0]
!= len(documents)
):
raise ValueError(
"Number of documents "
"and embeddings must match."
)
Normalization اسناد
document_norms = np.linalg.norm(
document_embeddings,
axis=1,
keepdims=True,
)
safe_document_norms = np.maximum(
document_norms,
1e-12,
)
normalized_documents = (
document_embeddings
/ safe_document_norms
)
Normalization کوئری:
query_norm = np.linalg.norm(
query_embedding
)
if query_norm < 1e-12:
raise ValueError(
"Query embedding "
"cannot be a zero vector."
)
normalized_query = (
query_embedding
/ query_norm
)
محاسبه شباهت تمام اسناد
similarities = (
normalized_documents
@ normalized_query
)
Shapeها:
normalized_documents: (5, 6)
normalized_query: (6,)
similarities: (5,)
NumPy با ضرب ماتریسی، شباهت Query را با تمام اسناد در یک عملیات محاسبه میکند.
انتخاب Top K
top_k = 3
top_indexes = np.argsort(
similarities
)[::-1][:top_k]
ساخت نتیجه:
results = []
for index in top_indexes:
results.append(
{
"id": (
documents[index]["id"]
),
"title": (
documents[index]["title"]
),
"score": round(
float(
similarities[index]
),
6,
),
}
)
for result in results:
print(result)
انتخاب Top K با argpartition
برای تعداد بسیار زیاد بردار، مرتبسازی کامل ضروری نیست:
top_k = 3
candidate_indexes = np.argpartition(
similarities,
-top_k,
)[-top_k:]
top_indexes = candidate_indexes[
np.argsort(
similarities[
candidate_indexes
]
)[::-1]
]
argpartition ابتدا کاندیداهای Top K را پیدا میکند و سپس فقط همان بخش مرتب میشود.
ساخت کلاس VectorSearchEngine
import numpy as np
class VectorSearchEngine:
def __init__(
self,
documents: list[dict],
embeddings: np.ndarray,
):
embeddings = np.asarray(
embeddings,
dtype=np.float32,
)
if embeddings.ndim != 2:
raise ValueError(
"Embeddings must "
"be a 2D array."
)
if (
embeddings.shape[0]
!= len(documents)
):
raise ValueError(
"Number of documents "
"and vectors must match."
)
norms = np.linalg.norm(
embeddings,
axis=1,
keepdims=True,
)
zero_vectors = (
norms[:, 0] < 1e-12
)
if np.any(
zero_vectors
):
invalid_indexes = np.flatnonzero(
zero_vectors
).tolist()
raise ValueError(
"Zero document vectors "
"at indexes: "
f"{invalid_indexes}"
)
self.documents = documents
self.embeddings = (
embeddings
/ norms
)
self.dimension = int(
embeddings.shape[1]
)
def search(
self,
query_embedding: np.ndarray,
top_k: int = 5,
minimum_score: float | None = None,
) -> list[dict]:
query = np.asarray(
query_embedding,
dtype=np.float32,
)
if query.ndim != 1:
raise ValueError(
"Query must be "
"a 1D vector."
)
if (
query.shape[0]
!= self.dimension
):
raise ValueError(
"Query dimension does "
"not match index dimension."
)
query_norm = np.linalg.norm(
query
)
if query_norm < 1e-12:
raise ValueError(
"Query cannot be "
"a zero vector."
)
query = (
query / query_norm
)
scores = (
self.embeddings
@ query
)
top_k = min(
max(
int(top_k),
1,
),
len(self.documents),
)
candidate_indexes = (
np.argpartition(
scores,
-top_k,
)[-top_k:]
)
sorted_indexes = (
candidate_indexes[
np.argsort(
scores[
candidate_indexes
]
)[::-1]
]
)
results = []
for index in sorted_indexes:
score = float(
scores[index]
)
if (
minimum_score
is not None
and score
< minimum_score
):
continue
results.append(
{
**self.documents[
index
],
"score": round(
score,
6,
),
}
)
return results
استفاده:
engine = VectorSearchEngine(
documents=documents,
embeddings=(
document_embeddings
),
)
results = engine.search(
query_embedding=(
query_embedding
),
top_k=3,
)
print(results)
جستوجوی Batch
اگر چند Query داشته باشیم:
query_embeddings = np.array(
[
[
0.78,
0.70,
0.26,
0.20,
0.83,
0.69,
],
[
0.18,
0.32,
0.90,
0.72,
0.10,
0.20,
],
],
dtype=np.float32,
)
Normalization:
query_norms = np.linalg.norm(
query_embeddings,
axis=1,
keepdims=True,
)
normalized_queries = (
query_embeddings
/ np.maximum(
query_norms,
1e-12,
)
)
محاسبه ماتریس شباهت:
similarity_matrix = (
normalized_queries
@ normalized_documents.T
)
Shape خروجی:
(number_of_queries, number_of_documents)
هر ردیف شامل Scoreهای یک Query برای تمام اسناد است.
ذخیره Embeddingها
ذخیره یک آرایه:
np.save(
"document_embeddings.npy",
document_embeddings,
)
بارگذاری:
loaded_embeddings = np.load(
"document_embeddings.npy"
)
ذخیره چند آرایه:
np.savez_compressed(
"vector_index.npz",
embeddings=(
document_embeddings
),
document_ids=np.array(
[
item["id"]
for item in documents
]
),
)
بارگذاری:
index_data = np.load(
"vector_index.npz",
allow_pickle=False,
)
loaded_embeddings = (
index_data["embeddings"]
)
loaded_ids = (
index_data["document_ids"]
)
تا زمانی که نیازی وجود ندارد، allow_pickle=True را فعال نکنید. آرایههای عددی و رشتهای استاندارد را میتوان بدون Pickle ذخیره کرد.
ذخیره Metadata اسناد
import json
with open(
"documents.json",
"w",
encoding="utf-8",
) as file:
json.dump(
documents,
file,
ensure_ascii=False,
indent=2,
)
بارگذاری:
with open(
"documents.json",
"r",
encoding="utf-8",
) as file:
loaded_documents = (
json.load(file)
)
همراه Index بهتر است موارد زیر ذخیره شوند:
- Model ID تولیدکننده Embedding
- Dimension بردار
- وضعیت Normalization
- تاریخ ساخت Index
- تعداد اسناد
- نسخه داده
- شناسه هر سند
- الگوریتم Similarity
نباید Embeddingهای تولیدشده توسط مدلهای مختلف را بدون بررسی داخل یک Index قرار دهید.
محاسبه شباهت در Batchهای کوچک
اگر تمام بردارها همزمان در حافظه جا نمیشوند:
def batched_vector_search(
document_embeddings: np.ndarray,
query_embedding: np.ndarray,
batch_size: int = 100_000,
top_k: int = 10,
) -> list[tuple[int, float]]:
query = np.asarray(
query_embedding,
dtype=np.float32,
)
query_norm = np.linalg.norm(
query
)
if query_norm < 1e-12:
raise ValueError(
"Query vector is zero."
)
query = (
query / query_norm
)
candidates = []
for start in range(
0,
len(document_embeddings),
batch_size,
):
end = min(
start + batch_size,
len(document_embeddings),
)
batch = np.asarray(
document_embeddings[
start:end
],
dtype=np.float32,
)
batch_norms = np.linalg.norm(
batch,
axis=1,
keepdims=True,
)
normalized_batch = (
batch
/ np.maximum(
batch_norms,
1e-12,
)
)
scores = (
normalized_batch
@ query
)
local_k = min(
top_k,
len(scores),
)
local_indexes = np.argpartition(
scores,
-local_k,
)[-local_k:]
for local_index in local_indexes:
candidates.append(
(
start
+ int(local_index),
float(
scores[
local_index
]
),
)
)
candidates.sort(
key=lambda item: item[1],
reverse=True,
)
return candidates[
:top_k
]
این روش آموزشی است. برای میلیونها بردار، جستوجوی Approximate Nearest Neighbor و Vector Database معمولاً مقیاسپذیرتر خواهد بود.
تفاوت NumPy و Vector Database
| ویژگی | NumPy | Vector Database |
|---|---|---|
| راهاندازی | بسیار ساده | نیازمند سرویس یا نرمافزار |
| جستوجوی Exact | مناسب | بسته به تنظیمات |
| مقیاس کوچک | بسیار مناسب | ممکن است اضافه باشد |
| Metadata Filter | باید دستی ساخته شود | معمولاً داخلی |
| Index تقریبی | بهصورت پیشفرض ندارد | معمولاً دارد |
| Persistence | فایل .npy یا .npz | داخلی |
| آپدیت آنلاین | نیازمند طراحی دستی | معمولاً پشتیبانی میشود |
| مقیاس میلیونها Vector | محدودتر | مناسبتر |
NumPy برای Prototype، تست الگوریتم، دیتاست کوچک و درک محاسبات بسیار مناسب است.
توضیح نتایج با API درواره
میتوان نتایج جستوجوی برداری را برای تولید پاسخ یا خلاصه به یک مدل زبانی ارسال کرد. برای دسترسی به مدلهای مختلف از طریق API میتوانید از درواره استفاده کنید.
تنظیم متغیرهای محیطی:
export DARVAREH_API_KEY="YOUR_DARVAREH_API_KEY"
export DARVAREH_MODEL_ID="YOUR_MODEL_ID"
در Windows PowerShell:
$env:DARVAREH_API_KEY="YOUR_DARVAREH_API_KEY"
$env:DARVAREH_MODEL_ID="YOUR_MODEL_ID"
ارسال نتایج:
import json
import os
import requests
api_key = os.environ[
"DARVAREH_API_KEY"
]
model_id = os.environ[
"DARVAREH_MODEL_ID"
]
retrieved_results = (
engine.search(
query_embedding,
top_k=3,
)
)
context_json = json.dumps(
retrieved_results,
ensure_ascii=False,
indent=2,
)
response = requests.post(
"https://api.darvareh.ir/v1/chat/completions",
headers={
"Authorization": (
f"Bearer {api_key}"
),
"Content-Type": (
"application/json"
),
},
json={
"model": model_id,
"messages": [
{
"role": "system",
"content": (
"فقط بر اساس نتایج "
"بازیابیشده پاسخ بده. "
"اگر اطلاعات کافی نیست، "
"شفاف اعلام کن."
),
},
{
"role": "user",
"content": f"""
نتایج بازیابیشده:
{context_json}
این نتایج را به فارسی و به ترتیب ارتباط خلاصه کن.
Scoreها را تغییر نده و اطلاعات جدیدی اختراع نکن.
""",
},
],
"temperature": 0.1,
},
timeout=60,
)
response.raise_for_status()
data = response.json()
answer = data[
"choices"
][0]["message"]["content"]
print(answer)
برای انتخاب Model ID و مشاهده قیمت بهروز، صفحه مدلهای درواره را بررسی کنید.
در سیستم واقعی بهتر است بهجای عنوان، متن Chunkهای بازیابیشده همراه شناسه منبع در اختیار مدل قرار گیرد.
ارتباط NumPy با شبکه عصبی
ورودی شبکه عصبی معمولاً یک Tensor عددی است. NumPy میتواند برای آمادهسازی این داده استفاده شود.
مثال یک Batch:
batch = np.array(
[
[
0.2,
0.5,
0.8,
],
[
0.1,
0.9,
0.3,
],
],
dtype=np.float32,
)
وزنها:
weights = np.array(
[
[
0.4,
0.2,
],
[
0.7,
0.1,
],
[
0.3,
0.8,
],
],
dtype=np.float32,
)
محاسبه لایه خطی:
bias = np.array(
[
0.1,
-0.2,
],
dtype=np.float32,
)
output = (
batch @ weights
+ bias
)
این محاسبه شکل سادهای از عملیات لایه Linear در شبکه عصبی است.
پیادهسازی Softmax با NumPy
def softmax(
logits: np.ndarray,
) -> np.ndarray:
logits = np.asarray(
logits,
dtype=np.float64,
)
shifted = (
logits
- np.max(
logits,
axis=-1,
keepdims=True,
)
)
exponentials = np.exp(
shifted
)
return (
exponentials
/ exponentials.sum(
axis=-1,
keepdims=True,
)
)
استفاده:
logits = np.array(
[
[2.0, 1.0, 0.1],
[0.2, 0.3, 1.8],
]
)
probabilities = softmax(
logits
)
print(probabilities)
کمکردن مقدار بیشینه پیش از exp به پایداری عددی کمک میکند.
پیادهسازی Sigmoid
def sigmoid(
values: np.ndarray,
) -> np.ndarray:
values = np.asarray(
values,
dtype=np.float64,
)
return 1.0 / (
1.0
+ np.exp(-values)
)
استفاده:
logits = np.array(
[
-2.0,
0.0,
2.0,
]
)
probabilities = sigmoid(
logits
)
بهینهسازی حافظه
بررسی حافظه:
array = np.zeros(
(
1_000_000,
128,
),
dtype=np.float32,
)
print(
array.nbytes
/ 1024**2
)
اگر float64 استفاده شود، حافظه تقریباً دو برابر خواهد شد:
float64_array = np.zeros(
(
1_000_000,
128,
),
dtype=np.float64,
)
برای Embedding معمولاً float32 نقطه شروع مناسبی است، اما تغییر dtype باید با اندازهگیری اثر آن بر دقت انجام شود.
استفاده از Memory Mapping
برای آرایههایی که کامل در RAM جا نمیشوند:
shape = (
1_000_000,
128,
)
mapped = np.memmap(
"embeddings.dat",
dtype=np.float32,
mode="w+",
shape=shape,
)
نوشتن یک Batch:
mapped[
0:1000
] = np.zeros(
(
1000,
128,
),
dtype=np.float32,
)
mapped.flush()
بازکردن مجدد:
mapped = np.memmap(
"embeddings.dat",
dtype=np.float32,
mode="r",
shape=shape,
)
Shape، dtype و Metadata فایل باید جداگانه ذخیره شوند؛ زیرا فایل خام Memmap بهتنهایی آنها را توضیح نمیدهد.
خطاهای رایج NumPy
خطای No module named 'numpy'
python -m pip install numpy
بررسی:
python -m pip show numpy
خطای ناسازگاری Shape
نمونه:
ValueError:
operands could not be broadcast together
Shapeها را چاپ کنید:
print(
first.shape
)
print(
second.shape
)
سپس بررسی کنید Dimensionهای آنها از سمت راست با قوانین Broadcasting سازگار باشند.
خطای Reshape
ValueError:
cannot reshape array
تعداد Elementهای Shape جدید باید با array.size برابر باشد:
print(
array.size
)
تغییر ناخواسته آرایه اصلی
احتمالاً یک View را تغییر دادهاید:
subset = array[
1:5
].copy()
خروجی nan
ورودی را بررسی کنید:
print(
np.isnan(array).sum()
)
print(
np.isinf(array).sum()
)
خطای Overflow
نوع داده ظرفیت کافی ندارد:
small = np.array(
[
120,
],
dtype=np.int8,
)
پیش از محاسبه به نوع بزرگتر تبدیل کنید:
safe = small.astype(
np.int32
)
خطای نصب پس از ارتقای NumPy
بعضی کتابخانههای دارای بخش باینری ممکن است با نسخه جدید NumPy سازگار نباشند. ابتدا نسخههای کتابخانهها و Dependencyها را بررسی و در یک Virtual Environment تمیز نصب کنید. راهنمای رسمی نیز در صفحه Troubleshooting نصب NumPy ارائه شده است.
اشتباهات متداول
استفاده از Loop برای تمام عملیاتها
ابتدا Broadcasting، Ufunc و عملیات Vectorized را بررسی کنید.
نادیدهگرفتن dtype
انتخاب نامناسب dtype میتواند مصرف حافظه، دقت یا محدوده عددی را تغییر دهد.
اشتباهگرفتن ضرب Element-wise و Matrix Multiplication
a * b
با:
a @ b
متفاوت است.
تغییر View بدون اطلاع
Slicing معمولاً View ایجاد میکند. در صورت نیاز از .copy() استفاده کنید.
استفاده مستقیم از np.linalg.inv
برای حل دستگاه معادلات معمولاً np.linalg.solve مناسبتر است.
مقایسه Float با برابری دقیق
کد شکننده:
first == second
روش مناسبتر:
np.isclose(
first,
second,
)
برای آرایه:
np.allclose(
first_array,
second_array,
)
تولید عدد تصادفی بدون Generator
روش پیشنهادی:
rng = np.random.default_rng(
42
)
نگهداری تمام داده در RAM
برای آرایه بزرگ، Memmap، Batch Processing، dtype کوچکتر یا ابزارهای مقیاسپذیرتر را بررسی کنید.
چکلیست استفاده از NumPy
- Shape ورودیها بررسی شده است.
- dtype آگاهانه انتخاب شده است.
- تفاوت View و Copy مشخص است.
- عملیات تا حد ممکن Vectorized است.
- Broadcasting قبل از اجرا بررسی شده است.
- معنی
axisدر هر عملیات مشخص است. - NaN و Infinity کنترل میشوند.
- تقسیم بر صفر مدیریت شده است.
- Seed برای آزمایشهای قابلبازتولید تنظیم شده است.
- ضرب Element-wise و ماتریسی اشتباه نشدهاند.
- مصرف حافظه با
nbytesبررسی شده است. - Model ID و Dimension بردارها ذخیره شدهاند.
- Embeddingهای مدلهای مختلف مخلوط نشدهاند.
- نتیجه Similarity بهعنوان اثبات قطعی تفسیر نمیشود.
سؤالات متداول
NumPy چیست؟
NumPy کتابخانه اصلی محاسبات عددی در Python است و ساختار ndarray را برای پردازش آرایههای چندبعدی ارائه میدهد.
ndarray چیست؟
ndarray آرایه چندبعدی NumPy است که معمولاً Elementهای همنوع دارد و از عملیات Vectorized، Indexing و Broadcasting پشتیبانی میکند.
تفاوت NumPy و Pandas چیست؟
NumPy برای محاسبات آرایهای و عددی طراحی شده است. Pandas ساختارهای دارای Label مانند DataFrame را برای تحلیل داده جدولی ارائه میدهد و در بسیاری از بخشها از NumPy استفاده میکند.
آیا NumPy از GPU استفاده میکند؟
NumPy استاندارد معمولاً روی CPU اجرا میشود. برای محاسبات مشابه روی GPU میتوان ابزارهایی مانند CuPy، PyTorch یا JAX را بررسی کرد.
آیا NumPy برای هوش مصنوعی ضروری است؟
اجباری نیست، اما درک NumPy یادگیری ساختار Tensor، عملیات ماتریسی، Normalization، Batch Processing و Embedding را بسیار سادهتر میکند.
Broadcasting در NumPy چیست؟
Broadcasting روشی است که NumPy برای اجرای عملیات روی آرایههایی با Shape متفاوت اما سازگار استفاده میکند.
تفاوت reshape و resize چیست؟
reshape نمایی با Shape جدید از همان داده ایجاد میکند یا در صورت نیاز Copy میسازد. resize میتواند اندازه آرایه را تغییر دهد و رفتار متفاوتی دارد. برای تغییر Shape معمولاً reshape انتخاب واضحتری است.
تفاوت ravel و flatten چیست؟
flatten همیشه Copy ایجاد میکند. ravel در صورت امکان View برمیگرداند.
تفاوت np.dot و @ چیست؟
برای Vector و Matrix ساده، نتایج آنها اغلب مشابه است؛ اما در آرایههای چندبعدی Semantics میتواند متفاوت باشد. برای Matrix Multiplication معمولاً @ یا np.matmul خواناتر است.
Cosine Similarity چیست؟
Cosine Similarity زاویه و همجهتی دو بردار را اندازه میگیرد و در جستوجوی برداری و مقایسه Embeddingها استفاده میشود.
آیا NumPy جای Vector Database را میگیرد؟
برای Prototype و دیتاست کوچک میتواند کافی باشد. برای حجم بالا، فیلتر Metadata، آپدیت آنلاین و جستوجوی تقریبی، Vector Database گزینه مناسبتری است.
آیا میتوان NumPy را با API درواره ترکیب کرد؟
بله. میتوانید بردارها و نتایج عددی را با NumPy پردازش و خروجی بازیابیشده را برای تولید پاسخ یا گزارش به مدلهای زبانی از طریق API درواره ارسال کنید.
جمعبندی
NumPy پایه محاسبات عددی در اکوسیستم علمی Python است. مفاهیمی مانند ndarray، Shape، dtype، Indexing، Broadcasting، Vectorization، View، Copy و Matrix Multiplication فقط امکانات یک کتابخانه نیستند؛ بلکه پایه درک بسیاری از عملیات یادگیری ماشین و هوش مصنوعی محسوب میشوند.
برای استفاده حرفهای از NumPy، صرفاً یادگیری چند تابع کافی نیست. باید بتوانید Shape داده را در هر مرحله دنبال کنید، dtype مناسب انتخاب کنید، مصرف حافظه را اندازه بگیرید و تفاوت عملیات Element-wise با عملیات ماتریسی را تشخیص دهید.
پروژه جستوجوی برداری این مقاله نشان داد که با چند عملیات اصلی NumPy میتوان بردارها را Normalize کرد، Cosine Similarity را برای تمام اسناد محاسبه کرد و مرتبطترین نتایج را به دست آورد. همین مفاهیم در مقیاس بزرگتر، بخش مهمی از Semantic Search و سیستمهای RAG را تشکیل میدهند.
برای افزودن مدلهای زبانی به پروژههای Python و هوش مصنوعی میتوانید از API هوش مصنوعی درواره استفاده کنید. برای انتخاب Model ID و مشاهده قیمتهای بهروز نیز صفحه مدلها و قیمتهای درواره را ببینید.
مقالات مرتبط
- Embedding چیست؟ راهنمای کامل بردارسازی متن
- Semantic Search چیست؟ آموزش جستوجوی معنایی
- Vector Database چیست؟ راهنمای پایگاه داده برداری
- RAG چیست؟ آموزش Retrieval-Augmented Generation
- تحلیل فایل CSV با هوش مصنوعی
- آموزش هوش مصنوعی با پایتون و API درواره
- آموزش دریافت API Key هوش مصنوعی
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.