From 30742a5403bd41bbef64716cb0b4fe1d7c4c5191 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 13 Apr 2026 21:53:05 +1000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=8C=90=20[translation-sync]=20Improve?= =?UTF-8?q?=20jax=5Fintro=20lecture=20(replaces=20#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .translate/state/jax_intro.md.yml | 4 +- lectures/jax_intro.md | 187 ++++++++++++++---------------- 2 files changed, 90 insertions(+), 101 deletions(-) diff --git a/.translate/state/jax_intro.md.yml b/.translate/state/jax_intro.md.yml index 34786c5..663bfe6 100644 --- a/.translate/state/jax_intro.md.yml +++ b/.translate/state/jax_intro.md.yml @@ -1,5 +1,5 @@ -source-sha: 4c025df0d52a5b6546938952294776d4bd4908ce -synced-at: "2026-04-10" +source-sha: 8d73de367a7f160dac777aa557f1c26069f84ea5 +synced-at: "2026-04-12" model: claude-sonnet-4-6 mode: UPDATE section-count: 7 diff --git a/lectures/jax_intro.md b/lectures/jax_intro.md index df5fa61..d3a5d00 100644 --- a/lectures/jax_intro.md +++ b/lectures/jax_intro.md @@ -37,7 +37,6 @@ translation: JIT Compilation::Compiling the whole function: کامپایل کل تابع JIT Compilation::How JIT compilation works: نحوه کار کامپایل JIT JIT Compilation::Compiling non-pure functions: کامپایل توابع غیرخالص - JIT Compilation::Summary: خلاصه Vectorization with `vmap`: برداری‌سازی با `vmap` Vectorization with `vmap`::A simple example: یک مثال ساده Vectorization with `vmap`::Combining transformations: ترکیب تبدیل‌ها @@ -49,6 +48,9 @@ translation: این سخنرانی مقدمه‌ای کوتاه بر [Google JAX](https://github.com/jax-ml/jax) ارائه می‌دهد. +```{include} _admonition/gpu.md +``` + JAX یک کتابخانه محاسبات علمی با کارایی بالا است که موارد زیر را فراهم می‌کند: * یک رابط شبیه [NumPy](https://en.wikipedia.org/wiki/NumPy) که می‌تواند به صورت خودکار در CPUها و GPUها موازی‌سازی شود، @@ -65,9 +67,18 @@ JAX یک کتابخانه محاسبات علمی با کارایی بالا ا !pip install jax quantecon ``` -```{include} _admonition/gpu.md +از import‌های زیر استفاده خواهیم کرد: + +```{code-cell} ipython3 +import jax +import jax.numpy as jnp +import matplotlib.pyplot as plt +import numpy as np +import quantecon as qe ``` +توجه کنید که `jax.numpy as jnp` را import می‌کنیم که یک رابط شبیه NumPy فراهم می‌کند. + ## JAX به عنوان جایگزین NumPy یکی از ویژگی‌های جذاب JAX این است که، هر زمان که امکان‌پذیر باشد، عملیات پردازش آرایه‌های آن با API NumPy مطابقت دارد. @@ -78,18 +89,6 @@ JAX یک کتابخانه محاسبات علمی با کارایی بالا ا ### شباهت‌ها -ما از import‌های زیر استفاده خواهیم کرد - -```{code-cell} ipython3 -import jax -import jax.numpy as jnp -import matplotlib.pyplot as plt -import numpy as np -import quantecon as qe -``` - -توجه کنید که ما `jax.numpy as jnp` را import می‌کنیم که یک رابط مشابه NumPy فراهم می‌کند. - در اینجا برخی عملیات استاندارد آرایه با استفاده از `jnp` آمده است: ```{code-cell} ipython3 @@ -104,10 +103,6 @@ print(a) print(jnp.sum(a)) ``` -```{code-cell} ipython3 -print(jnp.mean(a)) -``` - ```{code-cell} ipython3 print(jnp.dot(a, a)) ``` @@ -122,31 +117,12 @@ a type(a) ``` -حتی نگاشت‌های با مقدار اسکالر روی آرایه‌ها، آرایه‌های JAX را برمی‌گردانند. +حتی نگاشت‌های با مقدار اسکالر روی آرایه‌ها، آرایه‌های JAX را برمی‌گردانند نه اسکالرها! ```{code-cell} ipython3 jnp.sum(a) ``` -عملیات روی آرایه‌های با ابعاد بالاتر نیز مشابه NumPy هستند: - -```{code-cell} ipython3 -A = jnp.ones((2, 2)) -B = jnp.identity(2) -A @ B -``` - -رابط آرایه JAX همچنین زیربسته `linalg` را فراهم می‌کند: - -```{code-cell} ipython3 -jnp.linalg.inv(B) # Inverse of identity is identity -``` - -```{code-cell} ipython3 -eigvals, eigvecs = jnp.linalg.eigh(B) # Computes eigenvalues and eigenvectors -eigvals -``` - ### تفاوت‌ها اکنون به برخی از تفاوت‌های بین عملیات آرایه JAX و NumPy نگاه کنیم. @@ -154,7 +130,7 @@ eigvals (jax_speed)= #### سرعت! -فرض کنید می‌خواهیم تابع کسینوس را در نقاط بسیاری ارزیابی کنیم. +فرض کنیم می‌خواهیم تابع کسینوس را در نقاط بسیاری ارزیابی کنیم. ```{code-cell} n = 50_000_000 @@ -167,6 +143,7 @@ x = np.linspace(0, 10, n) ```{code-cell} with qe.Timer(): + # First NumPy timing y = np.cos(x) ``` @@ -174,27 +151,30 @@ with qe.Timer(): ```{code-cell} with qe.Timer(): + # Second NumPy timing y = np.cos(x) ``` در اینجا -* NumPy از یک باینری از پیش‌ساخته برای اعمال کسینوس روی آرایه‌ای از اعداد اعشاری استفاده می‌کند -* این باینری روی CPU دستگاه محلی اجرا می‌شود +* NumPy از یک باینری از پیش ساخته شده برای اعمال کسینوس بر یک آرایه از اعداد اعشاری استفاده می‌کند +* باینری روی CPU ماشین محلی اجرا می‌شود ##### با JAX -حالا بیایید با JAX امتحان کنیم. +اکنون بیایید با JAX امتحان کنیم. ```{code-cell} x = jnp.linspace(0, 10, n) ``` -بیایید همین رویه را زمان‌بندی کنیم. +بیایید همان رویه را زمان‌بندی کنیم. ```{code-cell} with qe.Timer(): + # First run y = jnp.cos(x) + # Hold the interpreter until the array operation finishes jax.block_until_ready(y); ``` @@ -203,33 +183,34 @@ with qe.Timer(): تا مفسر را تا زمانی که نتایج محاسبات بازگردانده شوند نگه داریم. این ضروری است زیرا JAX از ارسال ناهمزمان استفاده می‌کند که -به مفسر Python اجازه می‌دهد جلوتر از محاسبات عددی اجرا شود. +به مفسر Python اجازه می‌دهد جلوتر از محاسبات عددی حرکت کند. برای کدهایی که زمان‌بندی نمی‌شوند، می‌توانید خط حاوی `block_until_ready` را حذف کنید. ``` -و دوباره زمان‌بندی کنیم. +و بیایید دوباره زمان‌بندی کنیم. ```{code-cell} with qe.Timer(): + # Second run y = jnp.cos(x) + # Hold interpreter jax.block_until_ready(y); ``` روی GPU، این کد بسیار سریع‌تر از معادل NumPy خود اجرا می‌شود. -همچنین، معمولاً اجرای دوم به دلیل کامپایل JIT سریع‌تر از اولی است. +همچنین، معمولاً اجرای دوم به دلیل کامپایل JIT سریع‌تر از اجرای اول است. -این به این دلیل است که حتی توابع داخلی مانند `jnp.cos` نیز JIT-کامپایل می‌شوند --- و -اجرای اول شامل زمان کامپایل است. +این به این دلیل است که حتی توابع داخلی مانند `jnp.cos` نیز با JIT کامپایل می‌شوند --- و اجرای اول شامل زمان کامپایل است. -چرا JAX می‌خواهد توابع داخلی مانند `jnp.cos` را به جای ارائه نسخه‌های از پیش‌کامپایل‌شده مانند NumPy، JIT-کامپایل کند؟ +چرا JAX می‌خواهد توابع داخلی مانند `jnp.cos` را با JIT کامپایل کند به جای اینکه نسخه‌های از پیش کامپایل‌شده مانند NumPy ارائه دهد؟ -دلیل این است که کامپایلر JIT می‌خواهد روی *اندازه* آرایه مورد استفاده (و همچنین نوع داده) تخصص پیدا کند. +دلیل این است که کامپایلر JIT می‌خواهد بر *اندازه* آرایه مورد استفاده (و همچنین نوع داده) تخصص پیدا کند. -اندازه برای تولید کد بهینه‌شده اهمیت دارد زیرا موازی‌سازی کارآمد نیاز به تطابق اندازه وظیفه با سخت‌افزار موجود دارد. +اندازه برای تولید کد بهینه اهمیت دارد زیرا موازی‌سازی کارآمد نیازمند تطابق اندازه کار با سخت‌افزار موجود است. -می‌توانیم ادعای تخصص JAX روی اندازه آرایه را با تغییر اندازه ورودی و مشاهده زمان‌های اجرا تأیید کنیم. +می‌توانیم ادعا که JAX بر اندازه آرایه تخصص پیدا می‌کند را با تغییر اندازه ورودی و مشاهده زمان‌های اجرا تأیید کنیم. ```{code-cell} x = jnp.linspace(0, 10, n + 1) @@ -237,25 +218,29 @@ x = jnp.linspace(0, 10, n + 1) ```{code-cell} with qe.Timer(): + # First run y = jnp.cos(x) + # Hold interpreter jax.block_until_ready(y); ``` ```{code-cell} with qe.Timer(): + # Second run y = jnp.cos(x) + # Hold interpreter jax.block_until_ready(y); ``` زمان اجرا افزایش می‌یابد و سپس دوباره کاهش می‌یابد (این روی GPU واضح‌تر خواهد بود). -این با بحث بالا همخوانی دارد -- اولین اجرا پس از تغییر اندازه آرایه، سربار کامپایل را نشان می‌دهد. +این با بحث بالا همخوانی دارد -- اولین اجرا پس از تغییر اندازه آرایه سربار کامپایل را نشان می‌دهد. بحث بیشتر درباره کامپایل JIT در ادامه ارائه شده است. #### دقت -تفاوت دیگری بین NumPy و JAX این است که JAX به طور پیش‌فرض از اعداد اعشاری 32 بیتی استفاده می‌کند. +یکی دیگر از تفاوت‌های بین NumPy و JAX این است که JAX به طور پیش‌فرض از اعداد اعشاری 32 بیتی استفاده می‌کند. این به این دلیل است که JAX اغلب برای محاسبات GPU استفاده می‌شود و بیشتر محاسبات GPU از اعداد اعشاری 32 بیتی استفاده می‌کنند. @@ -308,11 +293,11 @@ except Exception as e: ``` -طراحان JAX تصمیم گرفتند آرایه‌ها را تغییرناپذیر کنند زیرا JAX از سبک برنامه‌نویسی تابعی استفاده می‌کند که در ادامه درباره آن بحث می‌کنیم. +طراحان JAX تصمیم گرفتند آرایه‌ها را تغییرناپذیر کنند زیرا JAX از سبک برنامه‌نویسی تابعی استفاده می‌کند که در ادامه آن را بررسی می‌کنیم. #### راه‌حل جایگزین -توجه می‌کنیم که JAX نسخه‌ای از تغییر درجای آرایه را با استفاده از [متد `at`](https://docs.jax.dev/en/latest/_autosummary/jax.numpy.ndarray.at.html) فراهم می‌کند. +توجه می‌کنیم که JAX یک جایگزین برای تغییر درجای آرایه با استفاده از [متد `at`](https://docs.jax.dev/en/latest/_autosummary/jax.numpy.ndarray.at.html) فراهم می‌کند. ```{code-cell} ipython3 a = jnp.linspace(0, 1, 3) @@ -340,9 +325,7 @@ a *هنگام پیاده‌روی در حومه ایتالیا، مردم از گفتن این که JAX دارای "una anima di pura programmazione funzionale" است، تردید نخواهند کرد.* -به عبارت دیگر، JAX یک سبک -[برنامه‌نویسی تابعی](https://en.wikipedia.org/wiki/Functional_programming) -را فرض می‌کند. +به عبارت دیگر، JAX یک سبک برنامه‌نویسی تابعی را فرض می‌کند. ### توابع خالص @@ -400,21 +383,35 @@ def add_tax_pure(prices, tax_rate): ### چرا برنامه‌نویسی تابعی؟ +در QuantEcon ما توابع خالص را دوست داریم زیرا + +* به آزمایش کمک می‌کنند: هر تابع می‌تواند به صورت مستقل عمل کند +* رفتار قطعی و در نتیجه بازتولیدپذیری را ترویج می‌دهند +* از بروز اشکالاتی که از تغییر وضعیت مشترک ناشی می‌شود، جلوگیری می‌کنند + +کامپایلر JAX توابع خالص و برنامه‌نویسی تابعی را دوست دارد زیرا + +* وابستگی‌های داده صریح هستند، که به بهینه‌سازی محاسبات پیچیده کمک می‌کند +* توابع خالص راحت‌تر مشتق‌گیری می‌شوند (autodiff) +* توابع خالص راحت‌تر موازی‌سازی و بهینه‌سازی می‌شوند (به وضعیت تغییرپذیر مشترک وابسته نیستند) + +راه دیگری برای تفکر در این مورد به شرح زیر است: + JAX توابع را به صورت گراف‌های محاسباتی نمایش می‌دهد که سپس کامپایل یا تبدیل می‌شوند (مثلاً مشتق‌گیری می‌شوند). -این گراف‌های محاسباتی توصیف می‌کنند که چگونه یک مجموعه داده ورودی به خروجی تبدیل می‌شود. +این گراف‌های محاسباتی توصیف می‌کنند که چگونه یک مجموعه ورودی مشخص به یک خروجی تبدیل می‌شود. -آن‌ها ذاتاً خالص هستند. +گراف‌های محاسباتی JAX ذاتاً خالص هستند. JAX از سبک برنامه‌نویسی تابعی استفاده می‌کند تا توابع ساخته‌شده توسط کاربر مستقیماً به نمایش‌های گراف-نظری پشتیبانی‌شده توسط JAX نگاشت شوند. ## اعداد تصادفی -تولید اعداد تصادفی در JAX به طور قابل توجهی با الگوهای موجود در NumPy یا MATLAB تفاوت دارد. +اعداد تصادفی در JAX نسبت به آنچه در NumPy یا Matlab می‌یابید بسیار متفاوت هستند. در ابتدا ممکن است نحو را نسبتاً پرمخاطب بیابید. -اما نحو و معناشناسی برای حفظ سبک برنامه‌نویسی تابعی که به تازگی مورد بحث قرار دادیم، ضروری است. +اما به زودی متوجه خواهید شد که نحو و معناشناسی برای حفظ سبک برنامه‌نویسی تابعی که به تازگی مورد بحث قرار دادیم، ضروری است. علاوه بر این، کنترل کامل وضعیت تصادفی برای برنامه‌نویسی موازی، مانند زمانی که می‌خواهیم آزمایش‌های مستقل را در چندین رشته اجرا کنیم، ضروری است. @@ -528,7 +525,7 @@ plt.tight_layout() plt.show() ``` -این نحو برای کاربر NumPy یا Matlab غیرعادی به نظر می‌رسد --- اما وقتی به برنامه‌نویسی موازی پیش می‌رویم، منطقی خواهد بود. +این نحو برای کاربر NumPy یا Matlab غیرعادی به نظر می‌رسد --- اما وقتی به برنامه‌نویسی موازی می‌رسیم، منطقی‌تر خواهد بود. تابع زیر `k` ماتریس تصادفی `n x n` (شبه) مستقل را با استفاده از `split` تولید می‌کند. @@ -577,7 +574,7 @@ matrices = gen_random_matrices(key) #### رویکرد NumPy -در رابط برنامه‌نویسی قدیمی تولید اعداد تصادفی NumPy (که از MATLAB تقلید می‌کند)، تولید با حفظ وضعیت سراسری پنهان کار می‌کند. +در NumPy، تولید اعداد تصادفی با حفظ وضعیت سراسری پنهان کار می‌کند. هر بار که یک تابع تصادفی را فراخوانی می‌کنیم، این وضعیت به‌روزرسانی می‌شود: @@ -639,13 +636,13 @@ random_sum_jax(key) کامپایلر just-in-time (JIT) JAX اجرا را با تولید کد ماشین کارآمد که با هم اندازه وظیفه و هم سخت‌افزار متفاوت است، تسریع می‌کند. -قدرت کامپایلر JIT JAX را در ترکیب با سخت‌افزار موازی {ref}`در بالا ` دیدیم، هنگامی که `cos` را روی یک آرایه بزرگ اعمال کردیم. +ما قدرت کامپایلر JIT JAX را در ترکیب با سخت‌افزار موازی {ref}`در بالا ` مشاهده کردیم، هنگامی که `cos` را روی یک آرایه بزرگ اعمال کردیم. بیایید همان کار را با یک تابع پیچیده‌تر امتحان کنیم. ### ارزیابی یک تابع پیچیده‌تر -تابع زیر را در نظر بگیرید: +تابع زیر را در نظر بگیرید ```{code-cell} def f(x): @@ -664,6 +661,7 @@ x = np.linspace(0, 10, n) ```{code-cell} with qe.Timer(): + # Time NumPy code y = f(x) ``` @@ -677,29 +675,33 @@ with qe.Timer(): def f(x): y = jnp.cos(2 * x**2) + jnp.sqrt(jnp.abs(x)) + 2 * jnp.sin(x**4) - x**2 return y -``` -اکنون بیایید آن را زمان‌بندی کنیم. -```{code-cell} x = jnp.linspace(0, 10, n) ``` +اکنون بیایید آن را زمان‌بندی کنیم. + ```{code-cell} with qe.Timer(): + # First call y = f(x) + # Hold interpreter jax.block_until_ready(y); ``` ```{code-cell} with qe.Timer(): + # Second call y = f(x) + # Hold interpreter jax.block_until_ready(y); ``` -نتیجه مشابه مثال `cos` است --- JAX سریع‌تر است، به ویژه در اجرای دوم پس از کامپایل JIT. +نتیجه مشابه مثال `cos` است --- JAX سریع‌تر است، به ویژه در +اجرای دوم پس از کامپایل JIT. -با این حال، با JAX، ترفند دیگری در آستین داریم --- می‌توانیم *کل* تابع را JIT-کامپایل کنیم، نه فقط عملیات‌های منفرد. +علاوه بر این، با JAX، ترفند دیگری در آستین داریم --- می‌توانیم *کل* تابع را JIT-کامپایل کنیم، نه فقط عملیات‌های منفرد. ### کامپایل کل تابع @@ -713,13 +715,17 @@ f_jax = jax.jit(f) ```{code-cell} with qe.Timer(): + # First run y = f_jax(x) + # Hold interpreter jax.block_until_ready(y); ``` ```{code-cell} with qe.Timer(): + # Second run y = f_jax(x) + # Hold interpreter jax.block_until_ready(y); ``` @@ -727,7 +733,6 @@ with qe.Timer(): برای مثال، کامپایلر می‌تواند چندین فراخوانی به شتاب‌دهنده سخت‌افزاری و ایجاد تعدادی آرایه میانی را حذف کند. - اتفاقاً، نحو رایج‌تر هنگام هدف قرار دادن یک تابع برای کامپایلر JIT این است ```{code-cell} ipython3 @@ -792,22 +797,6 @@ f(x) درس اخلاقی داستان: هنگام استفاده از JAX، توابع خالص بنویسید! -### خلاصه - -اکنون می‌توانیم ببینیم که چرا هم توسعه‌دهندگان و هم کامپایلرها از توابع خالص بهره می‌برند. - -ما توابع خالص را دوست داریم زیرا آنها - -* به تست کمک می‌کنند: هر تابع می‌تواند به صورت جداگانه عمل کند -* رفتار قطعی و از این رو تکرارپذیری را ترویج می‌کنند -* از باگ‌هایی که از تغییر وضعیت مشترک ناشی می‌شوند، جلوگیری می‌کنند - -کامپایلر توابع خالص و برنامه‌نویسی تابعی را دوست دارد زیرا - -* وابستگی‌های داده صریح هستند، که به بهینه‌سازی محاسبات پیچیده کمک می‌کند -* توابع خالص راحت‌تر قابل تمایز هستند (autodiff) -* توابع خالص راحت‌تر موازی‌سازی و بهینه‌سازی می‌شوند (به وضعیت قابل تغییر مشترک وابسته نیستند) - ## برداری‌سازی با `vmap` یکی دیگر از تبدیل‌های قدرتمند JAX، `jax.vmap` است که به‌طور خودکار @@ -817,18 +806,18 @@ f(x) ### یک مثال ساده -فرض کنید تابعی داریم که آمارهای خلاصه را برای یک آرایه منفرد محاسبه می‌کند: +فرض کنید تابعی داریم که تفاوت بین میانگین و میانه را برای یک آرایه از اعداد محاسبه می‌کند. ```{code-cell} ipython3 -def summary(x): - return jnp.mean(x), jnp.median(x) +def mm_diff(x): + return jnp.mean(x) - jnp.median(x) ``` می‌توانیم آن را روی یک بردار منفرد اعمال کنیم: ```{code-cell} ipython3 x = jnp.array([1.0, 2.0, 5.0]) -summary(x) +mm_diff(x) ``` حال فرض کنید یک ماتریس داریم و می‌خواهیم این آمارها را برای هر سطر محاسبه کنیم. @@ -841,7 +830,7 @@ X = jnp.array([[1.0, 2.0, 5.0], [1.0, 8.0, 9.0]]) for row in X: - print(summary(row)) + print(mm_diff(row)) ``` با این حال، حلقه‌های Python کُند هستند و نمی‌توانند به‌طور کارآمد توسط JAX کامپایل یا موازی‌سازی شوند. @@ -850,11 +839,11 @@ for row in X: تبدیل‌های JAX مانند `jit` و `grad` ترکیب می‌شود: ```{code-cell} ipython3 -batch_summary = jax.vmap(summary) -batch_summary(X) +batch_mm_diff = jax.vmap(mm_diff) +batch_mm_diff(X) ``` -تابع `summary` برای یک آرایه منفرد نوشته شده بود، و `vmap` به‌طور خودکار +تابع `mm_diff` برای یک آرایه منفرد نوشته شده بود، و `vmap` به‌طور خودکار آن را برای عمل سطربه‌سطر روی یک ماتریس ارتقا داد --- بدون حلقه، بدون تغییر شکل. ### ترکیب تبدیل‌ها @@ -864,8 +853,8 @@ batch_summary(X) برای مثال، می‌توانیم یک تابع برداری‌شده را با JIT کامپایل کنیم: ```{code-cell} ipython3 -fast_batch_summary = jax.jit(jax.vmap(summary)) -fast_batch_summary(X) +fast_batch_mm_diff = jax.jit(jax.vmap(mm_diff)) +fast_batch_mm_diff(X) ``` این ترکیب `jit`، `vmap`، و (همان‌طور که در ادامه خواهیم دید) `grad` در قلب From 513b3a0205167882cc52930409b60516b273e8bd Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 13 Apr 2026 21:57:21 +1000 Subject: [PATCH 2/2] fix: correct translation of 'verbose' and 'reproducibility' --- lectures/jax_intro.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lectures/jax_intro.md b/lectures/jax_intro.md index d3a5d00..96b45e2 100644 --- a/lectures/jax_intro.md +++ b/lectures/jax_intro.md @@ -386,7 +386,7 @@ def add_tax_pure(prices, tax_rate): در QuantEcon ما توابع خالص را دوست داریم زیرا * به آزمایش کمک می‌کنند: هر تابع می‌تواند به صورت مستقل عمل کند -* رفتار قطعی و در نتیجه بازتولیدپذیری را ترویج می‌دهند +* رفتار قطعی و در نتیجه تکرارپذیری را ترویج می‌دهند * از بروز اشکالاتی که از تغییر وضعیت مشترک ناشی می‌شود، جلوگیری می‌کنند کامپایلر JAX توابع خالص و برنامه‌نویسی تابعی را دوست دارد زیرا @@ -409,7 +409,7 @@ JAX از سبک برنامه‌نویسی تابعی استفاده می‌کن اعداد تصادفی در JAX نسبت به آنچه در NumPy یا Matlab می‌یابید بسیار متفاوت هستند. -در ابتدا ممکن است نحو را نسبتاً پرمخاطب بیابید. +در ابتدا ممکن است نحو را نسبتاً مفصل بیابید. اما به زودی متوجه خواهید شد که نحو و معناشناسی برای حفظ سبک برنامه‌نویسی تابعی که به تازگی مورد بحث قرار دادیم، ضروری است. @@ -566,7 +566,7 @@ matrices = gen_random_matrices(key) ### چرا وضعیت تصادفی صریح؟ -چرا JAX به این رویکرد نسبتاً پرمخاطب برای تولید اعداد تصادفی نیاز دارد؟ +چرا JAX به این رویکرد نسبتاً مفصل برای تولید اعداد تصادفی نیاز دارد؟ یکی از دلایل حفظ توابع خالص است.