├── .gitignore ├── 00 - مقدمات Deep Learning و PyTorch.pdf ├── 01 - نصب PyTorch.ipynb ├── 02 - مبانی PyTorch & Tensor.ipynb ├── 03 - محاسبات در تنسور ها.ipynb ├── 04 - توابع تجمیعی aggregation.ipynb ├── 05 - PyTorch & NumPy.ipynb ├── Ex 01 - تمرینات.ipynb ├── README.md ├── SETUP.md └── images ├── 03-matrix-multiply.gif └── pytorch.png /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /00 - مقدمات Deep Learning و PyTorch.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidinism/deep-learning/d943a71dbbff72d0c69eaf675b55a51066083301/00 - مقدمات Deep Learning و PyTorch.pdf -------------------------------------------------------------------------------- /01 - نصب PyTorch.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "b8668ded", 6 | "metadata": {}, 7 | "source": [ 8 | "## 01. نصب PyTorch\n", 9 | "\n", 10 | "سوالات در مورد این جلسه رو در اینجا مطرح کنید: https://github.com/aidinism/deep-learning/discussions/7 " 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "1f963bd3", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import torch\n", 21 | "from torch import nn # nn contains all of PyTorch's building blocks for neural networks\n", 22 | "import matplotlib.pyplot as plt\n", 23 | "\n", 24 | "# Check PyTorch version\n", 25 | "torch.__version__" 26 | ] 27 | } 28 | ], 29 | "metadata": { 30 | "kernelspec": { 31 | "display_name": "Python 3 (ipykernel)", 32 | "language": "python", 33 | "name": "python3" 34 | }, 35 | "language_info": { 36 | "codemirror_mode": { 37 | "name": "ipython", 38 | "version": 3 39 | }, 40 | "file_extension": ".py", 41 | "mimetype": "text/x-python", 42 | "name": "python", 43 | "nbconvert_exporter": "python", 44 | "pygments_lexer": "ipython3", 45 | "version": "3.9.7" 46 | } 47 | }, 48 | "nbformat": 4, 49 | "nbformat_minor": 5 50 | } 51 | -------------------------------------------------------------------------------- /03 - محاسبات در تنسور ها.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "edf3fded", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "# pytorch\n", 11 | "import torch" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "id": "e9f3c4f5", 17 | "metadata": {}, 18 | "source": [ 19 | "## Getting information about Tensor اطلاعات در مورد تنسور\n", 20 | "\n", 21 | "- **shape** - چه شکل / ابعادی داره تنسور\n", 22 | "- **dtype** - نوع اطلاعات در تنسور چی هست\n", 23 | "- **device** - اطلاعات روی چه دستگاهی ذخیره شده GPU / " 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 2, 29 | "id": "331c52fa", 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "name": "stdout", 34 | "output_type": "stream", 35 | "text": [ 36 | "tensor([[0.1570, 0.6094, 0.6433, 0.2293],\n", 37 | " [0.3329, 0.2511, 0.3633, 0.1050],\n", 38 | " [0.4467, 0.0511, 0.2984, 0.4803]])\n", 39 | "Shape of tensor: torch.Size([3, 4])\n", 40 | "Datatype of tensor: torch.float32\n", 41 | "Device tensor is stored on: cpu\n" 42 | ] 43 | } 44 | ], 45 | "source": [ 46 | "# Create a tensor\n", 47 | "some_tensor = torch.rand(3, 4)\n", 48 | "\n", 49 | "# Find out details about it\n", 50 | "print(some_tensor)\n", 51 | "print(f\"Shape of tensor: {some_tensor.shape}\")\n", 52 | "print(f\"Datatype of tensor: {some_tensor.dtype}\")\n", 53 | "print(f\"Device tensor is stored on: {some_tensor.device}\") # will default to CPU" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "id": "de328ef0", 59 | "metadata": {}, 60 | "source": [ 61 | "**Debuging note:**\n", 62 | "\n", 63 | "- what shape are my tensors?\n", 64 | "- what datatype are they?\n", 65 | "- where are they stored? " 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "05c0919d", 71 | "metadata": {}, 72 | "source": [ 73 | "## Manipulating tensors (tensor operations)\n", 74 | "## تغییر تنسورها" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "id": "89b9c44a", 80 | "metadata": {}, 81 | "source": [ 82 | "In deep learning, data (images, text, video, audio, protein structures, etc) gets represented as tensors.\n", 83 | "\n", 84 | "A model learns by investigating those tensors and performing a series of operations (could be 1,000,000s+) on tensors to create a representation of the patterns in the input data.\n", 85 | "\n", 86 | "These operations are often a wonderful dance between:\n", 87 | "\n", 88 | "- Addition\n", 89 | "- Substraction\n", 90 | "- Multiplication (element-wise)\n", 91 | "- Division\n", 92 | "- Matrix multiplication" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "id": "789b224b", 98 | "metadata": {}, 99 | "source": [ 100 | "## Basic operations" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "id": "b0b865cc", 106 | "metadata": {}, 107 | "source": [ 108 | "### 1.Addition" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 3, 114 | "id": "b14f507a", 115 | "metadata": {}, 116 | "outputs": [ 117 | { 118 | "data": { 119 | "text/plain": [ 120 | "tensor([11, 12, 13])" 121 | ] 122 | }, 123 | "execution_count": 3, 124 | "metadata": {}, 125 | "output_type": "execute_result" 126 | } 127 | ], 128 | "source": [ 129 | "# Create a tensor of values and add a number to it\n", 130 | "tensor = torch.tensor([1, 2, 3])\n", 131 | "tensor + 10" 132 | ] 133 | }, 134 | { 135 | "cell_type": "markdown", 136 | "id": "88e27b3e", 137 | "metadata": {}, 138 | "source": [ 139 | "### 2.Substraction" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 4, 145 | "id": "8370efbf", 146 | "metadata": {}, 147 | "outputs": [ 148 | { 149 | "data": { 150 | "text/plain": [ 151 | "tensor([-2, -1, 0])" 152 | ] 153 | }, 154 | "execution_count": 4, 155 | "metadata": {}, 156 | "output_type": "execute_result" 157 | } 158 | ], 159 | "source": [ 160 | "# Subtract and reassign\n", 161 | "tensor = tensor - 3\n", 162 | "tensor" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "id": "9102f329", 168 | "metadata": {}, 169 | "source": [ 170 | "### 3.Multiplication" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": 5, 176 | "id": "423c66f3", 177 | "metadata": {}, 178 | "outputs": [ 179 | { 180 | "data": { 181 | "text/plain": [ 182 | "tensor([-20, -10, 0])" 183 | ] 184 | }, 185 | "execution_count": 5, 186 | "metadata": {}, 187 | "output_type": "execute_result" 188 | } 189 | ], 190 | "source": [ 191 | "# Multiply it by 10\n", 192 | "tensor * 10" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 6, 198 | "id": "173b73ff", 199 | "metadata": {}, 200 | "outputs": [ 201 | { 202 | "data": { 203 | "text/plain": [ 204 | "tensor([-2, -1, 0])" 205 | ] 206 | }, 207 | "execution_count": 6, 208 | "metadata": {}, 209 | "output_type": "execute_result" 210 | } 211 | ], 212 | "source": [ 213 | "# Tensors don't change unless reassigned\n", 214 | "tensor" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": 7, 220 | "id": "d2f2f639", 221 | "metadata": {}, 222 | "outputs": [ 223 | { 224 | "data": { 225 | "text/plain": [ 226 | "tensor([-20, -10, 0])" 227 | ] 228 | }, 229 | "execution_count": 7, 230 | "metadata": {}, 231 | "output_type": "execute_result" 232 | } 233 | ], 234 | "source": [ 235 | "# Can also use torch functions\n", 236 | "torch.multiply(tensor, 10)" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": 8, 242 | "id": "20e84fe6", 243 | "metadata": {}, 244 | "outputs": [ 245 | { 246 | "data": { 247 | "text/plain": [ 248 | "tensor([ 8, 9, 10])" 249 | ] 250 | }, 251 | "execution_count": 8, 252 | "metadata": {}, 253 | "output_type": "execute_result" 254 | } 255 | ], 256 | "source": [ 257 | "# Can also use torch functions\n", 258 | "torch.add(tensor, 10)" 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "id": "ba250cf8", 264 | "metadata": {}, 265 | "source": [ 266 | "## Matrix multiplication ضرب ماتریس ها، اصول پایه هوش مصنوعی" 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "id": "7f232947", 272 | "metadata": {}, 273 | "source": [ 274 | "One of the most common operations in machine learning and deep learning algorithms (like neural networks) is [matrix multiplication](https://www.mathsisfun.com/algebra/matrix-multiplying.html).\n", 275 | "\n", 276 | "![visual demo of matrix multiplication](https://github.com/aidinism/deep-learning/raw/main/images/03-matrix-multiply.gif)\n", 277 | "\n", 278 | "[matrixmultiplication](http://matrixmultiplication.xyz/)\n", 279 | "\n", 280 | "PyTorch implements matrix multiplication functionality in the [`torch.matmul()`](https://pytorch.org/docs/stable/generated/torch.matmul.html) method.\n", 281 | "\n", 282 | "The main two rules for matrix multiplication to remember are:\n", 283 | "1. The **inner dimensions** must match:\n", 284 | " * `(3, 2) @ (3, 2)` won't work\n", 285 | " * `(2, 3) @ (3, 2)` will work\n", 286 | " * `(3, 2) @ (2, 3)` will work\n", 287 | "2. The resulting matrix has the shape of the **outer dimensions**:\n", 288 | " * `(2, 3) @ (3, 2)` -> `(2, 2)`\n", 289 | " * `(3, 2) @ (2, 3)` -> `(3, 3)`\n", 290 | "\n", 291 | "> **Note:** \"`@`\" in Python is the symbol for matrix multiplication.\n", 292 | "\n", 293 | "> **Resource:** You can see all of the rules for matrix multiplication using `torch.matmul()` [in the PyTorch documentation](https://pytorch.org/docs/stable/generated/torch.matmul.html).\n" 294 | ] 295 | }, 296 | { 297 | "cell_type": "code", 298 | "execution_count": 9, 299 | "id": "2c3449ed", 300 | "metadata": {}, 301 | "outputs": [ 302 | { 303 | "data": { 304 | "text/plain": [ 305 | "torch.Size([3])" 306 | ] 307 | }, 308 | "execution_count": 9, 309 | "metadata": {}, 310 | "output_type": "execute_result" 311 | } 312 | ], 313 | "source": [ 314 | "import torch\n", 315 | "tensor = torch.tensor([1, 2, 3])\n", 316 | "tensor.shape\n" 317 | ] 318 | }, 319 | { 320 | "cell_type": "markdown", 321 | "id": "dcfaea6b", 322 | "metadata": {}, 323 | "source": [ 324 | "The difference between element-wise multiplication and matrix multiplication is the addition of values.\n", 325 | "\n", 326 | "For our `tensor` variable with values `[1, 2, 3]`:\n", 327 | "\n", 328 | "| Operation | Calculation | Code |\n", 329 | "| ----- | ----- | ----- |\n", 330 | "| **Element-wise multiplication** | `[1*1, 2*2, 3*3]` = `[1, 4, 9]` | `tensor * tensor` |\n", 331 | "| **Matrix multiplication** | `[1*1 + 2*2 + 3*3]` = `[14]` | `tensor.matmul(tensor)` |\n" 332 | ] 333 | }, 334 | { 335 | "cell_type": "code", 336 | "execution_count": 10, 337 | "id": "e8802a6f", 338 | "metadata": {}, 339 | "outputs": [ 340 | { 341 | "data": { 342 | "text/plain": [ 343 | "tensor([1, 2, 3])" 344 | ] 345 | }, 346 | "execution_count": 10, 347 | "metadata": {}, 348 | "output_type": "execute_result" 349 | } 350 | ], 351 | "source": [ 352 | "tensor" 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": 11, 358 | "id": "fb13954f", 359 | "metadata": {}, 360 | "outputs": [ 361 | { 362 | "data": { 363 | "text/plain": [ 364 | "tensor([1, 4, 9])" 365 | ] 366 | }, 367 | "execution_count": 11, 368 | "metadata": {}, 369 | "output_type": "execute_result" 370 | } 371 | ], 372 | "source": [ 373 | "# Element-wise matrix multiplication\n", 374 | "tensor * tensor" 375 | ] 376 | }, 377 | { 378 | "cell_type": "code", 379 | "execution_count": 12, 380 | "id": "5213ce5d", 381 | "metadata": {}, 382 | "outputs": [ 383 | { 384 | "data": { 385 | "text/plain": [ 386 | "tensor(14)" 387 | ] 388 | }, 389 | "execution_count": 12, 390 | "metadata": {}, 391 | "output_type": "execute_result" 392 | } 393 | ], 394 | "source": [ 395 | "# Matrix multiplication\n", 396 | "torch.matmul(tensor, tensor)" 397 | ] 398 | }, 399 | { 400 | "cell_type": "code", 401 | "execution_count": 13, 402 | "id": "70369720", 403 | "metadata": {}, 404 | "outputs": [ 405 | { 406 | "data": { 407 | "text/plain": [ 408 | "tensor(14)" 409 | ] 410 | }, 411 | "execution_count": 13, 412 | "metadata": {}, 413 | "output_type": "execute_result" 414 | } 415 | ], 416 | "source": [ 417 | "# Can also use the \"@\" symbol for matrix multiplication, (not recommended)\n", 418 | "tensor @ tensor" 419 | ] 420 | }, 421 | { 422 | "cell_type": "markdown", 423 | "id": "99503ed6", 424 | "metadata": {}, 425 | "source": [ 426 | "You can do matrix multiplication by hand but it's not recommended.\n", 427 | "\n", 428 | "The in-built `torch.matmul()` method is faster." 429 | ] 430 | }, 431 | { 432 | "cell_type": "code", 433 | "execution_count": 14, 434 | "id": "78262ddd", 435 | "metadata": {}, 436 | "outputs": [ 437 | { 438 | "name": "stdout", 439 | "output_type": "stream", 440 | "text": [ 441 | "CPU times: user 505 µs, sys: 234 µs, total: 739 µs\n", 442 | "Wall time: 1.06 ms\n" 443 | ] 444 | }, 445 | { 446 | "data": { 447 | "text/plain": [ 448 | "tensor(14)" 449 | ] 450 | }, 451 | "execution_count": 14, 452 | "metadata": {}, 453 | "output_type": "execute_result" 454 | } 455 | ], 456 | "source": [ 457 | "%%time\n", 458 | "# Matrix multiplication by hand \n", 459 | "# (avoid doing operations with for loops at all cost, they are computationally expensive)\n", 460 | "value = 0\n", 461 | "for i in range(len(tensor)):\n", 462 | " value += tensor[i] * tensor[i]\n", 463 | "value" 464 | ] 465 | }, 466 | { 467 | "cell_type": "code", 468 | "execution_count": 17, 469 | "id": "29709ddc", 470 | "metadata": {}, 471 | "outputs": [ 472 | { 473 | "name": "stdout", 474 | "output_type": "stream", 475 | "text": [ 476 | "CPU times: user 53 µs, sys: 22 µs, total: 75 µs\n", 477 | "Wall time: 76.3 µs\n" 478 | ] 479 | }, 480 | { 481 | "data": { 482 | "text/plain": [ 483 | "tensor(14)" 484 | ] 485 | }, 486 | "execution_count": 17, 487 | "metadata": {}, 488 | "output_type": "execute_result" 489 | } 490 | ], 491 | "source": [ 492 | "%%time\n", 493 | "torch.matmul(tensor, tensor)" 494 | ] 495 | }, 496 | { 497 | "cell_type": "markdown", 498 | "id": "effcba9a", 499 | "metadata": {}, 500 | "source": [ 501 | "## One of the most common errors in deep learning (shape errors)\n", 502 | "\n", 503 | "Because much of deep learning is multiplying and performing operations on matrices and matrices have a strict rule about what shapes and sizes can be combined, one of the most common errors you'll run into in deep learning is shape mismatches." 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": 18, 509 | "id": "441ac6e6", 510 | "metadata": {}, 511 | "outputs": [ 512 | { 513 | "ename": "RuntimeError", 514 | "evalue": "mat1 and mat2 shapes cannot be multiplied (3x2 and 3x2)", 515 | "output_type": "error", 516 | "traceback": [ 517 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 518 | "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", 519 | "Cell \u001b[0;32mIn[18], line 10\u001b[0m\n\u001b[1;32m 2\u001b[0m tensor_A \u001b[38;5;241m=\u001b[39m torch\u001b[38;5;241m.\u001b[39mtensor([[\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m2\u001b[39m],\n\u001b[1;32m 3\u001b[0m [\u001b[38;5;241m3\u001b[39m, \u001b[38;5;241m4\u001b[39m],\n\u001b[1;32m 4\u001b[0m [\u001b[38;5;241m5\u001b[39m, \u001b[38;5;241m6\u001b[39m]], dtype\u001b[38;5;241m=\u001b[39mtorch\u001b[38;5;241m.\u001b[39mfloat32)\n\u001b[1;32m 6\u001b[0m tensor_B \u001b[38;5;241m=\u001b[39m torch\u001b[38;5;241m.\u001b[39mtensor([[\u001b[38;5;241m7\u001b[39m, \u001b[38;5;241m10\u001b[39m],\n\u001b[1;32m 7\u001b[0m [\u001b[38;5;241m8\u001b[39m, \u001b[38;5;241m11\u001b[39m], \n\u001b[1;32m 8\u001b[0m [\u001b[38;5;241m9\u001b[39m, \u001b[38;5;241m12\u001b[39m]], dtype\u001b[38;5;241m=\u001b[39mtorch\u001b[38;5;241m.\u001b[39mfloat32)\n\u001b[0;32m---> 10\u001b[0m \u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmatmul\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtensor_A\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtensor_B\u001b[49m\u001b[43m)\u001b[49m\n", 520 | "\u001b[0;31mRuntimeError\u001b[0m: mat1 and mat2 shapes cannot be multiplied (3x2 and 3x2)" 521 | ] 522 | } 523 | ], 524 | "source": [ 525 | "# Shapes need to be in the right way \n", 526 | "tensor_A = torch.tensor([[1, 2],\n", 527 | " [3, 4],\n", 528 | " [5, 6]], dtype=torch.float32)\n", 529 | "\n", 530 | "tensor_B = torch.tensor([[7, 10],\n", 531 | " [8, 11], \n", 532 | " [9, 12]], dtype=torch.float32)\n", 533 | "\n", 534 | "torch.matmul(tensor_A, tensor_B) # (this will error)" 535 | ] 536 | }, 537 | { 538 | "attachments": { 539 | "image.png": { 540 | "image/png": "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" 541 | } 542 | }, 543 | "cell_type": "markdown", 544 | "id": "50d2cb87", 545 | "metadata": {}, 546 | "source": [ 547 | "## torch.transpose ترانهاده ماتریس\n", 548 | "\n", 549 | "![image.png](attachment:image.png)\n", 550 | "\n", 551 | "- torch.transpose(input, dim0, dim1) → Tensor\n", 552 | "\n", 553 | "- torch.t\n", 554 | "\n", 555 | "- tensor.T" 556 | ] 557 | }, 558 | { 559 | "cell_type": "code", 560 | "execution_count": 19, 561 | "id": "be5a102a", 562 | "metadata": {}, 563 | "outputs": [ 564 | { 565 | "data": { 566 | "text/plain": [ 567 | "tensor([[-0.5551, -1.4631, 0.3651],\n", 568 | " [ 1.3306, -0.7047, -0.0802]])" 569 | ] 570 | }, 571 | "execution_count": 19, 572 | "metadata": {}, 573 | "output_type": "execute_result" 574 | } 575 | ], 576 | "source": [ 577 | "x = torch.randn(2, 3)\n", 578 | "x" 579 | ] 580 | }, 581 | { 582 | "cell_type": "markdown", 583 | "id": "d609e9d4", 584 | "metadata": {}, 585 | "source": [ 586 | "### torch.transpose\n", 587 | "\n", 588 | "- torch.transpose(input, dim0, dim1) → Tensor\n", 589 | "> - input (Tensor) – the input tensor.\n", 590 | "> - dim0 (int) – the first dimension to be transposed\n", 591 | "> - dim1 (int) – the second dimension to be transposed" 592 | ] 593 | }, 594 | { 595 | "cell_type": "code", 596 | "execution_count": 20, 597 | "id": "b9ca78ec", 598 | "metadata": {}, 599 | "outputs": [ 600 | { 601 | "data": { 602 | "text/plain": [ 603 | "tensor([[-0.5551, 1.3306],\n", 604 | " [-1.4631, -0.7047],\n", 605 | " [ 0.3651, -0.0802]])" 606 | ] 607 | }, 608 | "execution_count": 20, 609 | "metadata": {}, 610 | "output_type": "execute_result" 611 | } 612 | ], 613 | "source": [ 614 | "torch.transpose(x, 0, 1)" 615 | ] 616 | }, 617 | { 618 | "cell_type": "markdown", 619 | "id": "fe167643", 620 | "metadata": {}, 621 | "source": [ 622 | "### torch.t" 623 | ] 624 | }, 625 | { 626 | "cell_type": "code", 627 | "execution_count": 21, 628 | "id": "8512e67e", 629 | "metadata": {}, 630 | "outputs": [ 631 | { 632 | "data": { 633 | "text/plain": [ 634 | "tensor([[-0.5551, 1.3306],\n", 635 | " [-1.4631, -0.7047],\n", 636 | " [ 0.3651, -0.0802]])" 637 | ] 638 | }, 639 | "execution_count": 21, 640 | "metadata": {}, 641 | "output_type": "execute_result" 642 | } 643 | ], 644 | "source": [ 645 | "torch.t(x)" 646 | ] 647 | }, 648 | { 649 | "cell_type": "markdown", 650 | "id": "e7c30d6a", 651 | "metadata": {}, 652 | "source": [ 653 | "### tensor.T" 654 | ] 655 | }, 656 | { 657 | "cell_type": "code", 658 | "execution_count": 22, 659 | "id": "c24717c6", 660 | "metadata": {}, 661 | "outputs": [ 662 | { 663 | "data": { 664 | "text/plain": [ 665 | "tensor([[-0.5551, 1.3306],\n", 666 | " [-1.4631, -0.7047],\n", 667 | " [ 0.3651, -0.0802]])" 668 | ] 669 | }, 670 | "execution_count": 22, 671 | "metadata": {}, 672 | "output_type": "execute_result" 673 | } 674 | ], 675 | "source": [ 676 | "x.T" 677 | ] 678 | }, 679 | { 680 | "cell_type": "markdown", 681 | "id": "56757103", 682 | "metadata": {}, 683 | "source": [ 684 | "### Back to fixing the error" 685 | ] 686 | }, 687 | { 688 | "cell_type": "code", 689 | "execution_count": 23, 690 | "id": "a4411303", 691 | "metadata": {}, 692 | "outputs": [ 693 | { 694 | "name": "stdout", 695 | "output_type": "stream", 696 | "text": [ 697 | "tensor([[1., 2.],\n", 698 | " [3., 4.],\n", 699 | " [5., 6.]])\n", 700 | "tensor([[ 7., 10.],\n", 701 | " [ 8., 11.],\n", 702 | " [ 9., 12.]])\n" 703 | ] 704 | } 705 | ], 706 | "source": [ 707 | "print(tensor_A)\n", 708 | "print(tensor_B)" 709 | ] 710 | }, 711 | { 712 | "cell_type": "code", 713 | "execution_count": 24, 714 | "id": "dfa17088", 715 | "metadata": {}, 716 | "outputs": [ 717 | { 718 | "name": "stdout", 719 | "output_type": "stream", 720 | "text": [ 721 | "tensor([[1., 2.],\n", 722 | " [3., 4.],\n", 723 | " [5., 6.]])\n", 724 | "tensor([[ 7., 8., 9.],\n", 725 | " [10., 11., 12.]])\n" 726 | ] 727 | } 728 | ], 729 | "source": [ 730 | "# View tensor_A and tensor_B.T\n", 731 | "print(tensor_A)\n", 732 | "print(tensor_B.T)" 733 | ] 734 | }, 735 | { 736 | "cell_type": "code", 737 | "execution_count": 25, 738 | "id": "151c973a", 739 | "metadata": {}, 740 | "outputs": [ 741 | { 742 | "name": "stdout", 743 | "output_type": "stream", 744 | "text": [ 745 | "Original shapes: tensor_A = torch.Size([3, 2]), tensor_B = torch.Size([3, 2])\n", 746 | "\n", 747 | "New shapes: tensor_A = torch.Size([3, 2]) (same as above), tensor_B.T = torch.Size([2, 3])\n", 748 | "\n", 749 | "Multiplying: torch.Size([3, 2]) * torch.Size([2, 3]) <- inner dimensions match\n", 750 | "\n", 751 | "Output:\n", 752 | "\n", 753 | "tensor([[ 27., 30., 33.],\n", 754 | " [ 61., 68., 75.],\n", 755 | " [ 95., 106., 117.]])\n", 756 | "\n", 757 | "Output shape: torch.Size([3, 3])\n" 758 | ] 759 | } 760 | ], 761 | "source": [ 762 | "# The operation works when tensor_B is transposed\n", 763 | "print(f\"Original shapes: tensor_A = {tensor_A.shape}, tensor_B = {tensor_B.shape}\\n\")\n", 764 | "print(f\"New shapes: tensor_A = {tensor_A.shape} (same as above), tensor_B.T = {tensor_B.T.shape}\\n\")\n", 765 | "print(f\"Multiplying: {tensor_A.shape} * {tensor_B.T.shape} <- inner dimensions match\\n\")\n", 766 | "print(\"Output:\\n\")\n", 767 | "output = torch.matmul(tensor_A, tensor_B.T)\n", 768 | "print(output) \n", 769 | "print(f\"\\nOutput shape: {output.shape}\")" 770 | ] 771 | }, 772 | { 773 | "cell_type": "code", 774 | "execution_count": 26, 775 | "id": "17c29a1a", 776 | "metadata": {}, 777 | "outputs": [ 778 | { 779 | "data": { 780 | "text/plain": [ 781 | "tensor([[ 27., 30., 33.],\n", 782 | " [ 61., 68., 75.],\n", 783 | " [ 95., 106., 117.]])" 784 | ] 785 | }, 786 | "execution_count": 26, 787 | "metadata": {}, 788 | "output_type": "execute_result" 789 | } 790 | ], 791 | "source": [ 792 | "# torch.mm is a shortcut for matmul\n", 793 | "torch.mm(tensor_A, tensor_B.T)" 794 | ] 795 | }, 796 | { 797 | "cell_type": "markdown", 798 | "id": "d921ce08", 799 | "metadata": {}, 800 | "source": [ 801 | "salam\n", 802 | "\n", 803 | "![visual demo of matrix multiplication](https://github.com/aidinism/deep-learning/raw/main/images/03-matrix-multiply.gif)\n", 804 | "\n", 805 | "You can create your own matrix multiplication visuals like this at http://matrixmultiplication.xyz/.\n", 806 | "\n", 807 | "***Note: A matrix multiplication like this is also referred to as the dot product of two matrices.***" 808 | ] 809 | }, 810 | { 811 | "cell_type": "code", 812 | "execution_count": null, 813 | "id": "f6020d4f", 814 | "metadata": {}, 815 | "outputs": [], 816 | "source": [] 817 | } 818 | ], 819 | "metadata": { 820 | "kernelspec": { 821 | "display_name": "Python 3 (ipykernel)", 822 | "language": "python", 823 | "name": "python3" 824 | }, 825 | "language_info": { 826 | "codemirror_mode": { 827 | "name": "ipython", 828 | "version": 3 829 | }, 830 | "file_extension": ".py", 831 | "mimetype": "text/x-python", 832 | "name": "python", 833 | "nbconvert_exporter": "python", 834 | "pygments_lexer": "ipython3", 835 | "version": "3.9.7" 836 | } 837 | }, 838 | "nbformat": 4, 839 | "nbformat_minor": 5 840 | } 841 | -------------------------------------------------------------------------------- /04 - توابع تجمیعی aggregation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "9adda07a", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "# pytorch\n", 11 | "import torch" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "id": "399e1f60", 17 | "metadata": {}, 18 | "source": [ 19 | "## Finding the min, max, mean, sum, etc (aggregation)\n", 20 | "\n", 21 | "حالا که چند روش برای دستکاری دیتا ها یاد گرفتیم ، میتونیم محاسبات تجمیعی روی دیتا انجام بدیم\n" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 2, 27 | "id": "d9744129", 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "data": { 32 | "text/plain": [ 33 | "tensor([ 0., 10., 20., 30., 40., 50., 60., 70., 80., 90.])" 34 | ] 35 | }, 36 | "execution_count": 2, 37 | "metadata": {}, 38 | "output_type": "execute_result" 39 | } 40 | ], 41 | "source": [ 42 | "# Create a tensor\n", 43 | "x = torch.arange(0, 100, 10.)\n", 44 | "x" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 3, 50 | "id": "cd5bf1e8", 51 | "metadata": {}, 52 | "outputs": [ 53 | { 54 | "name": "stdout", 55 | "output_type": "stream", 56 | "text": [ 57 | "Minimum: 0.0\n", 58 | "Maximum: 90.0\n", 59 | "Mean: 45.0\n", 60 | "Sum: 450.0\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "print(f\"Minimum: {x.min()}\")\n", 66 | "print(f\"Maximum: {x.max()}\")\n", 67 | "print(f\"Mean: {x.mean()}\")\n", 68 | "print(f\"Sum: {x.sum()}\")" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "id": "ba96ee7a", 74 | "metadata": {}, 75 | "source": [ 76 | "
\n", 77 | " \n", 78 | "> **نکته:** این دستور نیاز به `torch.float32` داره، انواع دیگه دیتا باعث خطا میشه \n", 79 | " \n", 80 | "
" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 4, 86 | "id": "9682df9a", 87 | "metadata": {}, 88 | "outputs": [ 89 | { 90 | "data": { 91 | "text/plain": [ 92 | "tensor([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90], dtype=torch.int32)" 93 | ] 94 | }, 95 | "execution_count": 4, 96 | "metadata": {}, 97 | "output_type": "execute_result" 98 | } 99 | ], 100 | "source": [ 101 | "# Create a tensor\n", 102 | "y = torch.arange(0, 100, 10, dtype=torch.int32)\n", 103 | "y" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 5, 109 | "id": "0d264e95", 110 | "metadata": {}, 111 | "outputs": [ 112 | { 113 | "name": "stdout", 114 | "output_type": "stream", 115 | "text": [ 116 | "Minimum: 0\n", 117 | "Maximum: 90\n", 118 | "Mean: 45.0\n", 119 | "Sum: 450\n" 120 | ] 121 | } 122 | ], 123 | "source": [ 124 | "print(f\"Minimum: {y.min()}\")\n", 125 | "print(f\"Maximum: {y.max()}\")\n", 126 | "# print(f\"Mean: {y.mean()}\") # this will error\n", 127 | "print(f\"Mean: {y.type(torch.float32).mean()}\") # won't work without float datatype\n", 128 | "print(f\"Sum: {y.sum()}\")" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "id": "7efdc606", 134 | "metadata": {}, 135 | "source": [ 136 | "## Positional min/max\n", 137 | "\n", 138 | "با دستورات زیر میتونیم موقعیت کمترین و بیشترین مقدار رو در یک تنسور پیدا کنیم:\n", 139 | "\n", 140 | "[`torch.argmax()`](https://pytorch.org/docs/stable/generated/torch.argmax.html) and [`torch.argmin()`](https://pytorch.org/docs/stable/generated/torch.argmin.html)" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 6, 146 | "id": "bca0fd59", 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "name": "stdout", 151 | "output_type": "stream", 152 | "text": [ 153 | "Tensor: tensor([10, 20, 30, 40, 50, 60, 70, 80, 90])\n", 154 | "Index where max value occurs: 8\n", 155 | "Index where min value occurs: 0\n" 156 | ] 157 | } 158 | ], 159 | "source": [ 160 | "# Create a tensor\n", 161 | "tensor = torch.arange(10, 100, 10)\n", 162 | "print(f\"Tensor: {tensor}\")\n", 163 | "\n", 164 | "# Returns index of max and min values\n", 165 | "print(f\"Index where max value occurs: {tensor.argmax()}\")\n", 166 | "print(f\"Index where min value occurs: {tensor.argmin()}\")" 167 | ] 168 | }, 169 | { 170 | "cell_type": "markdown", 171 | "id": "99d650f2", 172 | "metadata": {}, 173 | "source": [ 174 | "## Change tensor datatype\n", 175 | "\n", 176 | "همونطور که قبلا اشاره کردیم، یکی از خطاهای متداول در پروسه یادگیری عمیق ، داشتن دیتا تایپ های متفاوت هست.\n", 177 | "\n", 178 | "که به راحتی قابل حل کردن هست، میتونیم با دستورات زیر نوع دیتا های درون تنسور رو عوض کنیم." 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 7, 184 | "id": "38fa3fcf", 185 | "metadata": {}, 186 | "outputs": [ 187 | { 188 | "data": { 189 | "text/plain": [ 190 | "torch.int64" 191 | ] 192 | }, 193 | "execution_count": 7, 194 | "metadata": {}, 195 | "output_type": "execute_result" 196 | } 197 | ], 198 | "source": [ 199 | "# Create a tensor and check its datatype\n", 200 | "tensor = torch.arange(10, 100, 10)\n", 201 | "tensor.dtype" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 8, 207 | "id": "02eba589", 208 | "metadata": {}, 209 | "outputs": [ 210 | { 211 | "data": { 212 | "text/plain": [ 213 | "torch.float32" 214 | ] 215 | }, 216 | "execution_count": 8, 217 | "metadata": {}, 218 | "output_type": "execute_result" 219 | } 220 | ], 221 | "source": [ 222 | "# Create a tensor and check its datatype\n", 223 | "tensor = torch.arange(10., 100., 10.)\n", 224 | "tensor.dtype" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": 9, 230 | "id": "f6b9a91f", 231 | "metadata": {}, 232 | "outputs": [ 233 | { 234 | "data": { 235 | "text/plain": [ 236 | "tensor([10., 20., 30., 40., 50., 60., 70., 80., 90.], dtype=torch.float16)" 237 | ] 238 | }, 239 | "execution_count": 9, 240 | "metadata": {}, 241 | "output_type": "execute_result" 242 | } 243 | ], 244 | "source": [ 245 | "# Create a float16 tensor\n", 246 | "tensor_float16 = tensor.type(torch.float16)\n", 247 | "tensor_float16" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": 10, 253 | "id": "32039d7c", 254 | "metadata": {}, 255 | "outputs": [ 256 | { 257 | "data": { 258 | "text/plain": [ 259 | "tensor([10, 20, 30, 40, 50, 60, 70, 80, 90], dtype=torch.int8)" 260 | ] 261 | }, 262 | "execution_count": 10, 263 | "metadata": {}, 264 | "output_type": "execute_result" 265 | } 266 | ], 267 | "source": [ 268 | "# Create a int8 tensor\n", 269 | "tensor_int8 = tensor.type(torch.int8)\n", 270 | "tensor_int8" 271 | ] 272 | }, 273 | { 274 | "cell_type": "markdown", 275 | "id": "0c2a99c6", 276 | "metadata": {}, 277 | "source": [ 278 | "> **Note:** Different datatypes can be confusing to begin with. But think of it like this, the lower the number (e.g. 32, 16, 8), the less precise a computer stores the value. And with a lower amount of storage, this generally results in faster computation and a smaller overall model. Mobile-based neural networks often operate with 8-bit integers, smaller and faster to run but less accurate than their float32 counterparts. For more on this, I'd read up about [precision in computing](https://en.wikipedia.org/wiki/Precision_(computer_science)).\n", 279 | "\n", 280 | "> **Exercise:** So far we've covered a fair few tensor methods but there's a bunch more in the [`torch.Tensor` documentation](https://pytorch.org/docs/stable/tensors.html), I'd recommend spending 10-minutes scrolling through and looking into any that catch your eye. Click on them and then write them out in code yourself to see what happens." 281 | ] 282 | }, 283 | { 284 | "cell_type": "markdown", 285 | "id": "d7878613", 286 | "metadata": {}, 287 | "source": [ 288 | "## Reshaping, stacking, squeezing and unsqueezing\n", 289 | "\n", 290 | "خیلی از اوقات ما نیاز داریم ماتریسها / تنسورها رو تغییر شکل/ابعاد بدیم بدون اینکه دیتا اصلی تغییر کنه.\n", 291 | "\n", 292 | "اینها روشهای اصلی این تغییرها هستند:\n", 293 | "\n", 294 | "\n", 295 | "| روش | توضیحات |\n", 296 | "| ----- | ----- |\n", 297 | "| [`torch.reshape(input, shape)`](https://pytorch.org/docs/stable/generated/torch.reshape.html#torch.reshape) | Reshapes `input` to `shape` (if compatible), can also use `torch.Tensor.reshape()`. |\n", 298 | "| [`torch.Tensor.view(shape)`](https://pytorch.org/docs/stable/generated/torch.Tensor.view.html) | Returns a view of the original tensor in a different `shape` but shares the same data as the original tensor. |\n", 299 | "| [`torch.stack(tensors, dim=0)`](https://pytorch.org/docs/1.9.1/generated/torch.stack.html) | Concatenates a sequence of `tensors` along a new dimension (`dim`), all `tensors` must be same size. |\n", 300 | "| [`torch.squeeze(input)`](https://pytorch.org/docs/stable/generated/torch.squeeze.html) | Squeezes `input` to remove all the dimenions with value `1`. |\n", 301 | "| [`torch.unsqueeze(input, dim)`](https://pytorch.org/docs/1.9.1/generated/torch.unsqueeze.html) | Returns `input` with a dimension value of `1` added at `dim`. | \n", 302 | "| [`torch.permute(input, dims)`](https://pytorch.org/docs/stable/generated/torch.permute.html) | Returns a *view* of the original `input` with its dimensions permuted (rearranged) to `dims`. | \n", 303 | "\n", 304 | "چرا مهمه?\n", 305 | "\n", 306 | "چون در یادگیری عمیق همیشه ما نیاز به اعمال تغییرات و ضرب ماتریس ها (تنسور) داریم. و چون این محاسبات قوانین مشخصی برای شکل و ابعاد ماتریس ها دارند ، نیاز داریم این ابعاد رو با توجه به کاری که میخواهیم بکنیم ، تغییر بدیم. این روش ها به ما کمک میکنند بتونیم این تغییرات رو انجام بدیم." 307 | ] 308 | }, 309 | { 310 | "cell_type": "code", 311 | "execution_count": 11, 312 | "id": "b4b55882", 313 | "metadata": {}, 314 | "outputs": [ 315 | { 316 | "data": { 317 | "text/plain": [ 318 | "(tensor([1., 2., 3., 4., 5., 6., 7.]), torch.Size([7]))" 319 | ] 320 | }, 321 | "execution_count": 11, 322 | "metadata": {}, 323 | "output_type": "execute_result" 324 | } 325 | ], 326 | "source": [ 327 | "# Create a tensor\n", 328 | "x = torch.arange(1., 8.)\n", 329 | "x, x.shape" 330 | ] 331 | }, 332 | { 333 | "cell_type": "markdown", 334 | "id": "56a1c48d", 335 | "metadata": {}, 336 | "source": [ 337 | "### reshape()\n", 338 | "حالا یک بعد به ماتریس/تنسور اضافه میکنیم \n", 339 | "\n", 340 | "`torch.reshape()`. " 341 | ] 342 | }, 343 | { 344 | "cell_type": "code", 345 | "execution_count": 12, 346 | "id": "57a00e97", 347 | "metadata": {}, 348 | "outputs": [ 349 | { 350 | "data": { 351 | "text/plain": [ 352 | "(tensor([[1., 2., 3., 4., 5., 6., 7.]]), torch.Size([1, 7]))" 353 | ] 354 | }, 355 | "execution_count": 12, 356 | "metadata": {}, 357 | "output_type": "execute_result" 358 | } 359 | ], 360 | "source": [ 361 | "# Add an extra dimension\n", 362 | "x_reshaped = x.reshape(1, 7)\n", 363 | "x_reshaped, x_reshaped.shape" 364 | ] 365 | }, 366 | { 367 | "cell_type": "markdown", 368 | "id": "9efa62b6", 369 | "metadata": {}, 370 | "source": [ 371 | "### view()\n", 372 | "میتونیم نما یا شکل نمایش تنسور رو عوض کنیم\n", 373 | "\n", 374 | "`torch.view()`." 375 | ] 376 | }, 377 | { 378 | "cell_type": "code", 379 | "execution_count": 13, 380 | "id": "2e0fd154", 381 | "metadata": {}, 382 | "outputs": [ 383 | { 384 | "data": { 385 | "text/plain": [ 386 | "(tensor([[1., 2., 3., 4., 5., 6., 7.]]), torch.Size([1, 7]))" 387 | ] 388 | }, 389 | "execution_count": 13, 390 | "metadata": {}, 391 | "output_type": "execute_result" 392 | } 393 | ], 394 | "source": [ 395 | "# Change view (keeps same data as original but changes view)\n", 396 | "# See more: https://stackoverflow.com/a/54507446/7900723\n", 397 | "z = x.view(1, 7)\n", 398 | "z, z.shape" 399 | ] 400 | }, 401 | { 402 | "cell_type": "markdown", 403 | "id": "254b6909", 404 | "metadata": {}, 405 | "source": [ 406 | "
\n", 407 | " \n", 408 | "عوض کردن نحوه نمایش تنسور با دستور torch.view(tensor) فقط نحوه نمایش رو عوض میکنه و هنوز به دیتا اصلی اشاره میکنه\n", 409 | "\n", 410 | "بنابر این اگر مقدار نما رو عوض کنید ، دیتا اصلی هم تغییر میکنه.\n", 411 | " \n", 412 | "
" 413 | ] 414 | }, 415 | { 416 | "cell_type": "code", 417 | "execution_count": 14, 418 | "id": "d3c3f325", 419 | "metadata": {}, 420 | "outputs": [ 421 | { 422 | "data": { 423 | "text/plain": [ 424 | "(tensor([[5., 2., 3., 4., 5., 6., 7.]]), tensor([5., 2., 3., 4., 5., 6., 7.]))" 425 | ] 426 | }, 427 | "execution_count": 14, 428 | "metadata": {}, 429 | "output_type": "execute_result" 430 | } 431 | ], 432 | "source": [ 433 | "# Changing z changes x\n", 434 | "z[:, 0] = 5\n", 435 | "z, x" 436 | ] 437 | }, 438 | { 439 | "cell_type": "markdown", 440 | "id": "dac4c3de", 441 | "metadata": {}, 442 | "source": [ 443 | "### stack()\n", 444 | "از دستور زیر برای روی هم گذاشتن تنسورها میتونیم استفاده کنیم:\n", 445 | "\n", 446 | "`torch.stack()`." 447 | ] 448 | }, 449 | { 450 | "cell_type": "code", 451 | "execution_count": 25, 452 | "id": "ad144a2c", 453 | "metadata": {}, 454 | "outputs": [ 455 | { 456 | "data": { 457 | "text/plain": [ 458 | "tensor([[5., 2., 3., 4., 5., 6., 7.],\n", 459 | " [5., 2., 3., 4., 5., 6., 7.],\n", 460 | " [5., 2., 3., 4., 5., 6., 7.],\n", 461 | " [5., 2., 3., 4., 5., 6., 7.]])" 462 | ] 463 | }, 464 | "execution_count": 25, 465 | "metadata": {}, 466 | "output_type": "execute_result" 467 | } 468 | ], 469 | "source": [ 470 | "# Stack tensors on top of each other\n", 471 | "x_stacked = torch.stack([x, x, x, x], dim=0) # try changing dim to dim=1 and see what happens\n", 472 | "x_stacked" 473 | ] 474 | }, 475 | { 476 | "cell_type": "markdown", 477 | "id": "eefc6872", 478 | "metadata": {}, 479 | "source": [ 480 | "### squeeze()\n", 481 | "\n", 482 | "حذف کردن یک بعد از تنسور" 483 | ] 484 | }, 485 | { 486 | "cell_type": "code", 487 | "execution_count": 26, 488 | "id": "0df0865f", 489 | "metadata": {}, 490 | "outputs": [ 491 | { 492 | "name": "stdout", 493 | "output_type": "stream", 494 | "text": [ 495 | "Previous tensor: tensor([[5., 2., 3., 4., 5., 6., 7.]])\n", 496 | "Previous shape: torch.Size([1, 7])\n", 497 | "\n", 498 | "New tensor: tensor([5., 2., 3., 4., 5., 6., 7.])\n", 499 | "New shape: torch.Size([7])\n" 500 | ] 501 | } 502 | ], 503 | "source": [ 504 | "print(f\"Previous tensor: {x_reshaped}\")\n", 505 | "print(f\"Previous shape: {x_reshaped.shape}\")\n", 506 | "\n", 507 | "# Remove extra dimension from x_reshaped\n", 508 | "x_squeezed = x_reshaped.squeeze()\n", 509 | "print(f\"\\nNew tensor: {x_squeezed}\")\n", 510 | "print(f\"New shape: {x_squeezed.shape}\")" 511 | ] 512 | }, 513 | { 514 | "cell_type": "markdown", 515 | "id": "28d6c5d2", 516 | "metadata": {}, 517 | "source": [ 518 | "### unsqueeze()\n", 519 | "\n", 520 | "اضافه کردن یک بعد به تنسور (برعکس دستور قبلی)" 521 | ] 522 | }, 523 | { 524 | "cell_type": "code", 525 | "execution_count": 27, 526 | "id": "5c68835c", 527 | "metadata": {}, 528 | "outputs": [ 529 | { 530 | "name": "stdout", 531 | "output_type": "stream", 532 | "text": [ 533 | "Previous tensor: tensor([5., 2., 3., 4., 5., 6., 7.])\n", 534 | "Previous shape: torch.Size([7])\n", 535 | "\n", 536 | "New tensor: tensor([[5., 2., 3., 4., 5., 6., 7.]])\n", 537 | "New shape: torch.Size([1, 7])\n" 538 | ] 539 | } 540 | ], 541 | "source": [ 542 | "print(f\"Previous tensor: {x_squeezed}\")\n", 543 | "print(f\"Previous shape: {x_squeezed.shape}\")\n", 544 | "\n", 545 | "## Add an extra dimension with unsqueeze\n", 546 | "x_unsqueezed = x_squeezed.unsqueeze(dim=0)\n", 547 | "print(f\"\\nNew tensor: {x_unsqueezed}\")\n", 548 | "print(f\"New shape: {x_unsqueezed.shape}\")" 549 | ] 550 | }, 551 | { 552 | "cell_type": "markdown", 553 | "id": "5110b88c", 554 | "metadata": {}, 555 | "source": [ 556 | "### permute()\n", 557 | "\n", 558 | "تغییر دادن جای ابعاد مختلف یک تنسور\n", 559 | "\n", 560 | ">نکته: چون خروجی این دستور یک ویو هست ، اگر دیتا تنسور خروجی تغییر کند ، دیتا تنسور اصلی هم تغییر میکند" 561 | ] 562 | }, 563 | { 564 | "cell_type": "code", 565 | "execution_count": 28, 566 | "id": "183c64e2", 567 | "metadata": {}, 568 | "outputs": [ 569 | { 570 | "name": "stdout", 571 | "output_type": "stream", 572 | "text": [ 573 | "Previous shape: torch.Size([224, 224, 3])\n", 574 | "New shape: torch.Size([3, 224, 224])\n" 575 | ] 576 | } 577 | ], 578 | "source": [ 579 | "# Create tensor with specific shape\n", 580 | "x_original = torch.rand(size=(224, 224, 3))\n", 581 | "\n", 582 | "# Permute the original tensor to rearrange the axis order\n", 583 | "x_permuted = x_original.permute(2, 0, 1) # shifts axis 0->1, 1->2, 2->0\n", 584 | "\n", 585 | "print(f\"Previous shape: {x_original.shape}\")\n", 586 | "print(f\"New shape: {x_permuted.shape}\")" 587 | ] 588 | }, 589 | { 590 | "cell_type": "markdown", 591 | "id": "7189d17b", 592 | "metadata": {}, 593 | "source": [ 594 | "### Indexing (selecting data from tensors)\n", 595 | "\n", 596 | "انتخاب یک عضو (مثلا یک دیتا یا یک سطر یا یک ستون)" 597 | ] 598 | }, 599 | { 600 | "cell_type": "code", 601 | "execution_count": 29, 602 | "id": "dcbf2ba5", 603 | "metadata": {}, 604 | "outputs": [ 605 | { 606 | "data": { 607 | "text/plain": [ 608 | "(tensor([[[1, 2, 3],\n", 609 | " [4, 5, 6],\n", 610 | " [7, 8, 9]]]),\n", 611 | " torch.Size([1, 3, 3]))" 612 | ] 613 | }, 614 | "execution_count": 29, 615 | "metadata": {}, 616 | "output_type": "execute_result" 617 | } 618 | ], 619 | "source": [ 620 | "x = torch.arange(1, 10).reshape(1, 3, 3)\n", 621 | "x, x.shape" 622 | ] 623 | }, 624 | { 625 | "cell_type": "code", 626 | "execution_count": 30, 627 | "id": "59c179c1", 628 | "metadata": {}, 629 | "outputs": [ 630 | { 631 | "name": "stdout", 632 | "output_type": "stream", 633 | "text": [ 634 | "First square bracket:\n", 635 | "tensor([[1, 2, 3],\n", 636 | " [4, 5, 6],\n", 637 | " [7, 8, 9]])\n", 638 | "Second square bracket: tensor([1, 2, 3])\n", 639 | "Third square bracket: 1\n" 640 | ] 641 | } 642 | ], 643 | "source": [ 644 | "# Let's index bracket by bracket\n", 645 | "print(f\"First square bracket:\\n{x[0]}\") \n", 646 | "print(f\"Second square bracket: {x[0][0]}\") \n", 647 | "print(f\"Third square bracket: {x[0][0][0]}\")" 648 | ] 649 | }, 650 | { 651 | "cell_type": "markdown", 652 | "id": "e883304b", 653 | "metadata": {}, 654 | "source": [ 655 | "**عملگر :**\n", 656 | "\n", 657 | "میتونید از : برای گرفتن همه مقادیر در اون بعد استفاده کنید\n" 658 | ] 659 | }, 660 | { 661 | "cell_type": "code", 662 | "execution_count": 34, 663 | "id": "37f7a757", 664 | "metadata": {}, 665 | "outputs": [ 666 | { 667 | "data": { 668 | "text/plain": [ 669 | "tensor([[1, 2, 3]])" 670 | ] 671 | }, 672 | "execution_count": 34, 673 | "metadata": {}, 674 | "output_type": "execute_result" 675 | } 676 | ], 677 | "source": [ 678 | "# Get all values of 0th dimension and the 0 index of 1st dimension\n", 679 | "x[:, 0]" 680 | ] 681 | }, 682 | { 683 | "cell_type": "code", 684 | "execution_count": 32, 685 | "id": "5759574c", 686 | "metadata": {}, 687 | "outputs": [ 688 | { 689 | "data": { 690 | "text/plain": [ 691 | "tensor([[2, 5, 8]])" 692 | ] 693 | }, 694 | "execution_count": 32, 695 | "metadata": {}, 696 | "output_type": "execute_result" 697 | } 698 | ], 699 | "source": [ 700 | "# Get all values of 0th & 1st dimensions but only index 1 of 2nd dimension\n", 701 | "x[:, :, 1]" 702 | ] 703 | }, 704 | { 705 | "cell_type": "code", 706 | "execution_count": 35, 707 | "id": "77259c57", 708 | "metadata": {}, 709 | "outputs": [ 710 | { 711 | "data": { 712 | "text/plain": [ 713 | "tensor([5])" 714 | ] 715 | }, 716 | "execution_count": 35, 717 | "metadata": {}, 718 | "output_type": "execute_result" 719 | } 720 | ], 721 | "source": [ 722 | "# Get all values of the 0 dimension but only the 1 index value of the 1st and 2nd dimension\n", 723 | "x[:, 1, 1]\n" 724 | ] 725 | }, 726 | { 727 | "cell_type": "code", 728 | "execution_count": 36, 729 | "id": "6d3d2558", 730 | "metadata": {}, 731 | "outputs": [ 732 | { 733 | "data": { 734 | "text/plain": [ 735 | "tensor([1, 2, 3])" 736 | ] 737 | }, 738 | "execution_count": 36, 739 | "metadata": {}, 740 | "output_type": "execute_result" 741 | } 742 | ], 743 | "source": [ 744 | "# Get index 0 of 0th and 1st dimension and all values of 2nd dimension \n", 745 | "x[0, 0, :] # same as x[0][0]" 746 | ] 747 | }, 748 | { 749 | "cell_type": "code", 750 | "execution_count": null, 751 | "id": "9b6d023e", 752 | "metadata": {}, 753 | "outputs": [], 754 | "source": [] 755 | } 756 | ], 757 | "metadata": { 758 | "kernelspec": { 759 | "display_name": "Python 3 (ipykernel)", 760 | "language": "python", 761 | "name": "python3" 762 | }, 763 | "language_info": { 764 | "codemirror_mode": { 765 | "name": "ipython", 766 | "version": 3 767 | }, 768 | "file_extension": ".py", 769 | "mimetype": "text/x-python", 770 | "name": "python", 771 | "nbconvert_exporter": "python", 772 | "pygments_lexer": "ipython3", 773 | "version": "3.9.7" 774 | } 775 | }, 776 | "nbformat": 4, 777 | "nbformat_minor": 5 778 | } 779 | -------------------------------------------------------------------------------- /05 - PyTorch & NumPy.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2df0d9a0", 6 | "metadata": {}, 7 | "source": [ 8 | "## PyTorch tensors & NumPy" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "bdeff4f3", 14 | "metadata": {}, 15 | "source": [ 16 | "- **torch.from_numpy(ndarray)** - NumPy array --> PyTorch tensor.\n", 17 | "- **torch.Tensor.numpy()** - PyTorch tensor --> NumPy array." 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "id": "d6583912", 24 | "metadata": {}, 25 | "outputs": [ 26 | { 27 | "data": { 28 | "text/plain": [ 29 | "(array([1., 2., 3., 4., 5., 6., 7., 8., 9.]),\n", 30 | " tensor([1., 2., 3., 4., 5., 6., 7., 8., 9.], dtype=torch.float64))" 31 | ] 32 | }, 33 | "execution_count": 1, 34 | "metadata": {}, 35 | "output_type": "execute_result" 36 | } 37 | ], 38 | "source": [ 39 | "# NumPy array -> PyTorch tensor \n", 40 | "import torch\n", 41 | "import numpy as np\n", 42 | "array = np.arange(1.0, 10.0)\n", 43 | "tensor = torch.from_numpy(array)\n", 44 | "array, tensor" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 2, 50 | "id": "99cdec06", 51 | "metadata": {}, 52 | "outputs": [ 53 | { 54 | "data": { 55 | "text/plain": [ 56 | "(dtype('float64'), torch.float64)" 57 | ] 58 | }, 59 | "execution_count": 2, 60 | "metadata": {}, 61 | "output_type": "execute_result" 62 | } 63 | ], 64 | "source": [ 65 | "array.dtype , tensor.dtype" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "5ef2d67b", 71 | "metadata": {}, 72 | "source": [ 73 | "
\n", 74 | "\n", 75 | "نوع داده پیش فرض در NumPy از نوع float43 هست\n", 76 | "\n", 77 | "وقتی از NumPy به PyTorch تبدیل میکنیم ، نوع داده از متغیر مبدا کپی میشه\n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | "
" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 3, 87 | "id": "aa7ccd8f", 88 | "metadata": {}, 89 | "outputs": [], 90 | "source": [ 91 | "tensor = torch.from_numpy(array).type(torch.float32)" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 4, 97 | "id": "6b4aea4a", 98 | "metadata": {}, 99 | "outputs": [ 100 | { 101 | "data": { 102 | "text/plain": [ 103 | "torch.float32" 104 | ] 105 | }, 106 | "execution_count": 4, 107 | "metadata": {}, 108 | "output_type": "execute_result" 109 | } 110 | ], 111 | "source": [ 112 | "tensor.dtype" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 5, 118 | "id": "c9be5cb1", 119 | "metadata": {}, 120 | "outputs": [ 121 | { 122 | "data": { 123 | "text/plain": [ 124 | "(array([ 3., 4., 5., 6., 7., 8., 9., 10., 11.]),\n", 125 | " tensor([1., 2., 3., 4., 5., 6., 7., 8., 9.]))" 126 | ] 127 | }, 128 | "execution_count": 5, 129 | "metadata": {}, 130 | "output_type": "execute_result" 131 | } 132 | ], 133 | "source": [ 134 | "array = array + 2\n", 135 | "array , tensor" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": 7, 141 | "id": "1659e114", 142 | "metadata": {}, 143 | "outputs": [ 144 | { 145 | "data": { 146 | "text/plain": [ 147 | "(tensor([0., 0., 0., 0., 0., 0., 0., 0.]),\n", 148 | " array([0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32))" 149 | ] 150 | }, 151 | "execution_count": 7, 152 | "metadata": {}, 153 | "output_type": "execute_result" 154 | } 155 | ], 156 | "source": [ 157 | "# NumPy array -> PyTorch tensor\n", 158 | "tensor = torch.zeros(8)\n", 159 | "numpy_tensor = tensor.numpy()\n", 160 | "tensor , numpy_tensor" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 8, 166 | "id": "b727e7b2", 167 | "metadata": {}, 168 | "outputs": [ 169 | { 170 | "data": { 171 | "text/plain": [ 172 | "(torch.float32, dtype('float32'))" 173 | ] 174 | }, 175 | "execution_count": 8, 176 | "metadata": {}, 177 | "output_type": "execute_result" 178 | } 179 | ], 180 | "source": [ 181 | "tensor.dtype , numpy_tensor.dtype" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": 9, 187 | "id": "2a54ef09", 188 | "metadata": {}, 189 | "outputs": [ 190 | { 191 | "data": { 192 | "text/plain": [ 193 | "(tensor([1., 1., 1., 1., 1., 1., 1., 1.]),\n", 194 | " array([0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32))" 195 | ] 196 | }, 197 | "execution_count": 9, 198 | "metadata": {}, 199 | "output_type": "execute_result" 200 | } 201 | ], 202 | "source": [ 203 | "tensor = tensor + 1\n", 204 | "tensor , numpy_tensor" 205 | ] 206 | }, 207 | { 208 | "cell_type": "markdown", 209 | "id": "7311d65e", 210 | "metadata": {}, 211 | "source": [ 212 | "### Reproducibility \n", 213 | "\n", 214 | "تکرار الگوهای تصادفی\n", 215 | "\n", 216 | "در طول این دوره به اهمیت تولید اعداد تصادفی در ساخت و استفاده از شبکه های عصبی و یادگیری ماشین بیشتر پی میبریم\n", 217 | "\n", 218 | "بیشتر مدلهای شبکه های عصبی و هوش مصنوعی به طور کلی از روند زیر پیروی میکنند:\n", 219 | "\n", 220 | "1. با اعداد تصادفی شروع کن\n", 221 | "2. محاسبات مورد نظر رو انجام بده\n", 222 | "3. نتایج رو بهتر کن\n", 223 | "4. همین روند رو تکرار کن\n", 224 | "\n", 225 | "\n", 226 | "با اینکه تولید اعداد تصادفی مهم و حیاتی هستند برای این پروسه ، گاهی ما نیاز داریم (برای آزمایش یا تکرار نتایج و بررسی) همون اعداد رو تولید کنیم" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": 21, 232 | "id": "023200ff", 233 | "metadata": {}, 234 | "outputs": [ 235 | { 236 | "name": "stdout", 237 | "output_type": "stream", 238 | "text": [ 239 | "Tensor A:\n", 240 | "tensor([[0.3906, 0.6538, 0.6440, 0.8795],\n", 241 | " [0.6813, 0.9011, 0.4105, 0.3022],\n", 242 | " [0.3226, 0.4308, 0.6492, 0.8568]])\n", 243 | "\n", 244 | "Tensor B:\n", 245 | "tensor([[0.4829, 0.5384, 0.8557, 0.2473],\n", 246 | " [0.2483, 0.4148, 0.4512, 0.1732],\n", 247 | " [0.0494, 0.2218, 0.4972, 0.8161]])\n", 248 | "\n", 249 | "Does Tensor A equal Tensor B? (anywhere)\n" 250 | ] 251 | }, 252 | { 253 | "data": { 254 | "text/plain": [ 255 | "tensor([[False, False, False, False],\n", 256 | " [False, False, False, False],\n", 257 | " [False, False, False, False]])" 258 | ] 259 | }, 260 | "execution_count": 21, 261 | "metadata": {}, 262 | "output_type": "execute_result" 263 | } 264 | ], 265 | "source": [ 266 | "# Create two random tensors\n", 267 | "random_tensor_A = torch.rand(3, 4)\n", 268 | "random_tensor_B = torch.rand(3, 4)\n", 269 | "\n", 270 | "print(f\"Tensor A:\\n{random_tensor_A}\\n\")\n", 271 | "print(f\"Tensor B:\\n{random_tensor_B}\\n\")\n", 272 | "print(f\"Does Tensor A equal Tensor B? (anywhere)\")\n", 273 | "random_tensor_A == random_tensor_B" 274 | ] 275 | }, 276 | { 277 | "cell_type": "markdown", 278 | "id": "8fcf93a9", 279 | "metadata": {}, 280 | "source": [ 281 | "### Random Seed\n", 282 | "\n", 283 | "Wikipedia: https://en.wikipedia.org/wiki/Random_seed\n", 284 | "\n", 285 | "PyTorch Docs: https://pytorch.org/docs/stable/notes/randomness.html\n" 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": 24, 291 | "id": "3f940eef", 292 | "metadata": {}, 293 | "outputs": [ 294 | { 295 | "name": "stdout", 296 | "output_type": "stream", 297 | "text": [ 298 | "Tensor C:\n", 299 | "tensor([[0.8823, 0.9150, 0.3829, 0.9593],\n", 300 | " [0.3904, 0.6009, 0.2566, 0.7936],\n", 301 | " [0.9408, 0.1332, 0.9346, 0.5936]])\n", 302 | "\n", 303 | "Tensor D:\n", 304 | "tensor([[0.8823, 0.9150, 0.3829, 0.9593],\n", 305 | " [0.3904, 0.6009, 0.2566, 0.7936],\n", 306 | " [0.9408, 0.1332, 0.9346, 0.5936]])\n", 307 | "\n", 308 | "Does Tensor C equal Tensor D? (anywhere)\n" 309 | ] 310 | }, 311 | { 312 | "data": { 313 | "text/plain": [ 314 | "tensor([[True, True, True, True],\n", 315 | " [True, True, True, True],\n", 316 | " [True, True, True, True]])" 317 | ] 318 | }, 319 | "execution_count": 24, 320 | "metadata": {}, 321 | "output_type": "execute_result" 322 | } 323 | ], 324 | "source": [ 325 | "import random\n", 326 | "\n", 327 | "# # Set the random seed\n", 328 | "RANDOM_SEED=42 # try changing this to different values and see what happens to the numbers below\n", 329 | "torch.manual_seed(seed=RANDOM_SEED) \n", 330 | "random_tensor_C = torch.rand(3, 4)\n", 331 | "\n", 332 | "# Have to reset the seed every time a new rand() is called \n", 333 | "# Without this, tensor_D would be different to tensor_C \n", 334 | "torch.random.manual_seed(seed=RANDOM_SEED) # try commenting this line out and seeing what happens\n", 335 | "random_tensor_D = torch.rand(3, 4)\n", 336 | "\n", 337 | "print(f\"Tensor C:\\n{random_tensor_C}\\n\")\n", 338 | "print(f\"Tensor D:\\n{random_tensor_D}\\n\")\n", 339 | "print(f\"Does Tensor C equal Tensor D? (anywhere)\")\n", 340 | "random_tensor_C == random_tensor_D" 341 | ] 342 | }, 343 | { 344 | "cell_type": "markdown", 345 | "id": "cf9f81ad", 346 | "metadata": {}, 347 | "source": [ 348 | "# Tensors on GPUs\n", 349 | "\n", 350 | "\n", 351 | "\n", 352 | "
\n", 353 | "\n", 354 | " \n", 355 | "CPU یا Central Processing Unit (واحد پردازش مرکزی)، یک قطعه سخت‌افزاری است که مسئول اجرای برنامه‌ها و دستورات سیستمی در یک سیستم کامپیوتری است. CPU در عملیات‌های متناوب، محاسباتی سنگین و کاربردهای گسترده دیگر به کار می‌رود. به طور کلی، CPU مجهز به تعداد کمی هسته پردازشی (معمولاً ۲ تا ۱۶ هسته) است، هر کدام با سرعت بالایی اجرا می‌شوند، با توجه به فرکانس ساعت (clock speed) آن‌ها.\n", 356 | "\n", 357 | "اما GPU یا Graphics Processing Unit (واحد پردازش گرافیکی) برای کاربردهای گرافیکی به کار می‌رود، مثل بازی‌های ویدیویی، ویرایش ویدیو و عکس و محاسبات علمی. GPU با تعداد زیادی هسته پردازشی (معمولاً بیشتر از ۱۰۰۰ هسته) و فرکانس ساعت پایین‌تر از CPU، برای انجام محاسبات گرافیکی و محاسبات ماتریسی بسیار سریع است.\n", 358 | "\n", 359 | "به طور کلی، CPU برای محاسبات عمومی و سنگین، مانند محاسبات شبکه‌های عصبی و پردازش صوتی و متنی مناسب است. از سوی دیگر، GPU برای محاسبات گرافیکی و علمی، مانند محاسبات شبیه‌سازی فیزیکی، یادگیری ژرف و شبکه‌های عصبی عظیم الجثه، مناسب است.\n", 360 | "\n", 361 | " \n", 362 | "
\n", 363 | "\n" 364 | ] 365 | }, 366 | { 367 | "cell_type": "markdown", 368 | "id": "bc448552", 369 | "metadata": {}, 370 | "source": [ 371 | "### Getting a GPU\n", 372 | "\n", 373 | "\n", 374 | "| **روش** | **راه اندازی** | **مزایا** | **معایب** | **راهنما** |\n", 375 | "| ----- | ----- | ----- | ----- | ----- |\n", 376 | "| Google Colab | Easy | Free to use, almost zero setup required, can share work with others as easy as a link | Doesn't save your data outputs, limited compute, subject to timeouts | [Follow the Google Colab Guide](https://colab.research.google.com/notebooks/gpu.ipynb) |\n", 377 | "| Use your own | Medium | Run everything locally on your own machine | GPUs aren't free, require upfront cost | Follow the [PyTorch installation guidelines](https://pytorch.org/get-started/locally/) |\n", 378 | "| Cloud computing (AWS, GCP, Azure) | Medium-Hard | Small upfront cost, access to almost infinite compute | Can get expensive if running continually, takes some time ot setup right | Follow the [PyTorch installation guidelines](https://pytorch.org/get-started/cloud-partners/) |\n", 379 | "\n", 380 | "\n" 381 | ] 382 | }, 383 | { 384 | "cell_type": "markdown", 385 | "id": "cb39370f", 386 | "metadata": {}, 387 | "source": [ 388 | "To check if you've got access to a Nvidia GPU, you can run `!nvidia-smi` where the `!` (also called bang) means \"run this on the command line\"." 389 | ] 390 | }, 391 | { 392 | "cell_type": "code", 393 | "execution_count": 28, 394 | "id": "22d518e2", 395 | "metadata": {}, 396 | "outputs": [ 397 | { 398 | "name": "stdout", 399 | "output_type": "stream", 400 | "text": [ 401 | "Sun Mar 19 23:24:28 2023 \n", 402 | "+---------------------------------------------------------------------------------------+\n", 403 | "| NVIDIA-SMI 530.30.04 Driver Version: 531.29 CUDA Version: 12.1 |\n", 404 | "|-----------------------------------------+----------------------+----------------------+\n", 405 | "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n", 406 | "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n", 407 | "| | | MIG M. |\n", 408 | "|=========================================+======================+======================|\n", 409 | "| 0 NVIDIA GeForce RTX 3080 Ti On | 00000000:01:00.0 On | N/A |\n", 410 | "| 0% 34C P8 32W / 370W| 1368MiB / 12288MiB | 11% Default |\n", 411 | "| | | N/A |\n", 412 | "+-----------------------------------------+----------------------+----------------------+\n", 413 | " \n", 414 | "+---------------------------------------------------------------------------------------+\n", 415 | "| Processes: |\n", 416 | "| GPU GI CI PID Type Process name GPU Memory |\n", 417 | "| ID ID Usage |\n", 418 | "|=======================================================================================|\n", 419 | "| 0 N/A N/A 224 G /Xwayland N/A |\n", 420 | "+---------------------------------------------------------------------------------------+\n" 421 | ] 422 | } 423 | ], 424 | "source": [ 425 | "!nvidia-smi" 426 | ] 427 | }, 428 | { 429 | "cell_type": "markdown", 430 | "id": "bf50a80e", 431 | "metadata": {}, 432 | "source": [ 433 | "
\n", 434 | "\n", 435 | "اگر `GPU` ندارید یا درایور نصب نباشه پیغامی شبیه به این ممکنه بگیرید:\n", 436 | " \n", 437 | "
\n", 438 | "\n", 439 | "`\n", 440 | "NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running.\n", 441 | "`\n" 442 | ] 443 | }, 444 | { 445 | "cell_type": "markdown", 446 | "id": "0e5ba3fd", 447 | "metadata": {}, 448 | "source": [ 449 | "### PyTorch on GPU\n", 450 | "\n", 451 | "#### TORCH.CUDA\n", 452 | "##### This package adds support for CUDA tensor types, that implement the same function as CPU tensors, but they utilize GPUs for computation.\n", 453 | "\n", 454 | "It is lazily initialized, so you can always import it, and use is_available() to determine if your system supports `CUDA`.\n", 455 | "\n", 456 | "`CUDA` semantics has more details about working with `CUDA`.\n", 457 | "\n", 458 | "https://pytorch.org/docs/stable/cuda.html" 459 | ] 460 | }, 461 | { 462 | "cell_type": "markdown", 463 | "id": "2e7dd214", 464 | "metadata": {}, 465 | "source": [ 466 | "#### Check GPU / CUDA" 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "execution_count": 35, 472 | "id": "26937c90", 473 | "metadata": {}, 474 | "outputs": [ 475 | { 476 | "data": { 477 | "text/plain": [ 478 | "True" 479 | ] 480 | }, 481 | "execution_count": 35, 482 | "metadata": {}, 483 | "output_type": "execute_result" 484 | } 485 | ], 486 | "source": [ 487 | "# Check for GPU\n", 488 | "import torch\n", 489 | "torch.cuda.is_available()" 490 | ] 491 | }, 492 | { 493 | "cell_type": "markdown", 494 | "id": "fa4bf277", 495 | "metadata": {}, 496 | "source": [ 497 | "#### Set device to CUDA / GPU" 498 | ] 499 | }, 500 | { 501 | "cell_type": "code", 502 | "execution_count": 33, 503 | "id": "77e8e859", 504 | "metadata": {}, 505 | "outputs": [ 506 | { 507 | "data": { 508 | "text/plain": [ 509 | "'cuda'" 510 | ] 511 | }, 512 | "execution_count": 33, 513 | "metadata": {}, 514 | "output_type": "execute_result" 515 | } 516 | ], 517 | "source": [ 518 | "# Set device type\n", 519 | "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", 520 | "device" 521 | ] 522 | }, 523 | { 524 | "cell_type": "markdown", 525 | "id": "ddaada94", 526 | "metadata": {}, 527 | "source": [ 528 | "#### Multi GPU" 529 | ] 530 | }, 531 | { 532 | "cell_type": "code", 533 | "execution_count": 37, 534 | "id": "57f38400", 535 | "metadata": {}, 536 | "outputs": [ 537 | { 538 | "data": { 539 | "text/plain": [ 540 | "1" 541 | ] 542 | }, 543 | "execution_count": 37, 544 | "metadata": {}, 545 | "output_type": "execute_result" 546 | } 547 | ], 548 | "source": [ 549 | "# Multi GPU\n", 550 | "# Count number of devices\n", 551 | "torch.cuda.device_count()" 552 | ] 553 | }, 554 | { 555 | "cell_type": "markdown", 556 | "id": "ba56856a", 557 | "metadata": {}, 558 | "source": [ 559 | "#### Putting tensors (and models) on the GPU\n", 560 | "\n", 561 | "`.to(device)` return a `COPY` of tensor on the other deive, thus tensor will be on both `CPU` and `GPU`.\n" 562 | ] 563 | }, 564 | { 565 | "cell_type": "code", 566 | "execution_count": 38, 567 | "id": "229f85d3", 568 | "metadata": {}, 569 | "outputs": [ 570 | { 571 | "name": "stdout", 572 | "output_type": "stream", 573 | "text": [ 574 | "tensor([1, 2, 3]) cpu\n" 575 | ] 576 | }, 577 | { 578 | "data": { 579 | "text/plain": [ 580 | "tensor([1, 2, 3], device='cuda:0')" 581 | ] 582 | }, 583 | "execution_count": 38, 584 | "metadata": {}, 585 | "output_type": "execute_result" 586 | } 587 | ], 588 | "source": [ 589 | "# Create tensor (default on CPU)\n", 590 | "tensor = torch.tensor([1, 2, 3])\n", 591 | "\n", 592 | "# Tensor not on GPU\n", 593 | "print(tensor, tensor.device)\n", 594 | "\n", 595 | "# Move tensor to GPU (if available)\n", 596 | "tensor_on_gpu = tensor.to(device)\n", 597 | "tensor_on_gpu" 598 | ] 599 | }, 600 | { 601 | "cell_type": "markdown", 602 | "id": "2267a5ad", 603 | "metadata": {}, 604 | "source": [ 605 | "#### Moving tensors back to the CPU" 606 | ] 607 | }, 608 | { 609 | "cell_type": "code", 610 | "execution_count": 41, 611 | "id": "6f1d450c", 612 | "metadata": {}, 613 | "outputs": [ 614 | { 615 | "ename": "TypeError", 616 | "evalue": "can't convert cuda:0 device type tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first.", 617 | "output_type": "error", 618 | "traceback": [ 619 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 620 | "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", 621 | "Cell \u001b[0;32mIn[41], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# If tensor is on GPU, can't transform it to NumPy (this will error)\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mtensor_on_gpu\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnumpy\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", 622 | "\u001b[0;31mTypeError\u001b[0m: can't convert cuda:0 device type tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first." 623 | ] 624 | } 625 | ], 626 | "source": [ 627 | "# If tensor is on GPU, can't transform it to NumPy (this will error)\n", 628 | "tensor_on_gpu.numpy()" 629 | ] 630 | }, 631 | { 632 | "cell_type": "code", 633 | "execution_count": 42, 634 | "id": "f2b18736", 635 | "metadata": {}, 636 | "outputs": [ 637 | { 638 | "data": { 639 | "text/plain": [ 640 | "array([1, 2, 3])" 641 | ] 642 | }, 643 | "execution_count": 42, 644 | "metadata": {}, 645 | "output_type": "execute_result" 646 | } 647 | ], 648 | "source": [ 649 | "# Instead, copy the tensor back to cpu\n", 650 | "tensor_back_on_cpu = tensor_on_gpu.cpu().numpy()\n", 651 | "tensor_back_on_cpu" 652 | ] 653 | }, 654 | { 655 | "cell_type": "code", 656 | "execution_count": 43, 657 | "id": "f2f7c1e1", 658 | "metadata": {}, 659 | "outputs": [ 660 | { 661 | "data": { 662 | "text/plain": [ 663 | "tensor([1, 2, 3], device='cuda:0')" 664 | ] 665 | }, 666 | "execution_count": 43, 667 | "metadata": {}, 668 | "output_type": "execute_result" 669 | } 670 | ], 671 | "source": [ 672 | "tensor_on_gpu" 673 | ] 674 | }, 675 | { 676 | "cell_type": "code", 677 | "execution_count": null, 678 | "id": "e79befe8", 679 | "metadata": {}, 680 | "outputs": [], 681 | "source": [] 682 | }, 683 | { 684 | "cell_type": "markdown", 685 | "id": "6b512923", 686 | "metadata": {}, 687 | "source": [ 688 | "***تمرین ها: [لینک به فایل تمرین ها](Ex%2001%20-%20تمرینات.ipynb)***" 689 | ] 690 | }, 691 | { 692 | "cell_type": "markdown", 693 | "id": "03c7f6b8", 694 | "metadata": {}, 695 | "source": [ 696 | "\n", 697 | "**Intro:** https://pytorch.org/tutorials/beginner/basics/intro.html\n", 698 | "\n", 699 | "**Quick Start:** https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html\n", 700 | "\n", 701 | "**Tensors:** https://pytorch.org/tutorials/beginner/basics/tensorqs_tutorial.html" 702 | ] 703 | }, 704 | { 705 | "cell_type": "code", 706 | "execution_count": null, 707 | "id": "34b2e486", 708 | "metadata": {}, 709 | "outputs": [], 710 | "source": [] 711 | }, 712 | { 713 | "cell_type": "code", 714 | "execution_count": null, 715 | "id": "6594b372", 716 | "metadata": {}, 717 | "outputs": [], 718 | "source": [] 719 | } 720 | ], 721 | "metadata": { 722 | "kernelspec": { 723 | "display_name": "Python 3 (ipykernel)", 724 | "language": "python", 725 | "name": "python3" 726 | }, 727 | "language_info": { 728 | "codemirror_mode": { 729 | "name": "ipython", 730 | "version": 3 731 | }, 732 | "file_extension": ".py", 733 | "mimetype": "text/x-python", 734 | "name": "python", 735 | "nbconvert_exporter": "python", 736 | "pygments_lexer": "ipython3", 737 | "version": "3.9.7" 738 | } 739 | }, 740 | "nbformat": 4, 741 | "nbformat_minor": 5 742 | } 743 | -------------------------------------------------------------------------------- /Ex 01 - تمرینات.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "33a506b5", 6 | "metadata": {}, 7 | "source": [ 8 | "تمرینات:" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "0a1b45b6", 14 | "metadata": {}, 15 | "source": [ 16 | "### 1. منابع\n", 17 | "\n", 18 | "\n", 19 | "**Intro:** https://pytorch.org/tutorials/beginner/basics/intro.html\n", 20 | "\n", 21 | "**Quick Start:** https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html\n", 22 | "\n", 23 | "**Tensors:** https://pytorch.org/tutorials/beginner/basics/tensorqs_tutorial.html" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "id": "eb3690c2", 29 | "metadata": {}, 30 | "source": [ 31 | "\n", 32 | "### 2. یک تنسور تصادفی 7 در 7 ایجاد کنید\n", 33 | "\n" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 7, 39 | "id": "fccaca70", 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "# Import torch\n", 44 | "\n", 45 | "# Create random tensor" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "id": "faea56fd", 51 | "metadata": {}, 52 | "source": [ 53 | "\n", 54 | "### 3. تنسور قبل را در یک تنسور تصادفی 1 در 7 ضرب کنید\n" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 8, 60 | "id": "1fd009e8", 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "# Create another random tensor\n", 65 | "\n", 66 | "# Perform matrix multiplication " 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "id": "3cc4dc7d", 72 | "metadata": {}, 73 | "source": [ 74 | "### 4. هسته تولید عدد تصادفی را روی 0 ست کنید و مراحل 2 و 3 را تکرار کنید\n", 75 | "\n", 76 | "خروجی باید برابر با مقدار زیر باشد:\n", 77 | "\n", 78 | "(tensor([[1.8542],\n", 79 | " [1.9611],\n", 80 | " [2.2884],\n", 81 | " [3.0481],\n", 82 | " [1.7067],\n", 83 | " [2.5290],\n", 84 | " [1.7989]]), torch.Size([7, 1]))" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 9, 90 | "id": "6a2f2a97", 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "# Set manual seed\n", 95 | "\n", 96 | "# Create two random tensors\n", 97 | "\n", 98 | "# Matrix multiply tensors" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "id": "fdf23059", 104 | "metadata": {}, 105 | "source": [ 106 | "### 5. (اگر جی چی یو دارید) مرحله 4 رو برای جی پی یو تکرار کنید\n", 107 | "\n", 108 | "- هسته تولید عدد تصادفی رو در جی پی یو روی 1234 ست کنید" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 10, 114 | "id": "c5e22d61", 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "# Set random seed on the GPU" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "id": "9cc393ae", 124 | "metadata": {}, 125 | "source": [ 126 | "### 6. دو تنسور تصادفی 2 در 3 ایجاد کنید، هر دو رو به جی چی یو منتقل کنید\n", 127 | "\n", 128 | "- هسته تصادفی رو روی 1234 ست کنید\n", 129 | "- نکته: هسته تصادفی برای سی پی یو ست میشه\n", 130 | "- خروجی باید معادل زیر باشه\n", 131 | "\n", 132 | "Device: cuda\n", 133 | "\n", 134 | "(tensor([[0.0290, 0.4019, 0.2598], \n", 135 | " [0.3666, 0.0583, 0.7006]], device='cuda:0'),\n", 136 | " \n", 137 | " tensor([[0.0518, 0.4681, 0.6738], \n", 138 | " [0.3315, 0.7837, 0.5631]], device='cuda:0'))" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": 11, 144 | "id": "13d6f14b", 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "# Set random seed\n", 149 | "\n", 150 | "# Check for access to GPU\n", 151 | "\n", 152 | "# Create two random tensors on GPU" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "id": "6cc5200f", 158 | "metadata": {}, 159 | "source": [ 160 | "### 7. دو تنسور مرحله قبل رو در هم ضرب کنید\n", 161 | "\n", 162 | "- ممکنه نیاز داشته باشید ابعاد تنسور رو تغییر بدید\n", 163 | "- خروجی باید معادل زیر باشه\n", 164 | "\n", 165 | "(tensor([[0.3647, 0.4709],\n", 166 | " [0.5184, 0.5617]], device='cuda:0'), torch.Size([2, 2]))\n" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 12, 172 | "id": "9b947a4b", 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [ 176 | "# Perform matmul on tensor_A and tensor_B" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "id": "3def6da7", 182 | "metadata": {}, 183 | "source": [ 184 | "### 8. مقدار بیشترین و کمترین رو در تنسور مرحله 7 پیدا کنید" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": null, 190 | "id": "71b0cc21", 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [ 194 | "# Find max\n", 195 | "\n", 196 | "# Find min" 197 | ] 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "id": "d3f053c9", 202 | "metadata": {}, 203 | "source": [ 204 | "### 9. موقعیت مقدار بیشترین و کمترین رو در تنسور مرحله 7 پیدا کنید" 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": 14, 210 | "id": "92f6b663", 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [ 214 | "# Find index max\n", 215 | "\n", 216 | "# Find index min" 217 | ] 218 | }, 219 | { 220 | "cell_type": "markdown", 221 | "id": "5ee0228d", 222 | "metadata": {}, 223 | "source": [ 224 | "### 10.\n", 225 | "- (1, 1, 1, 10) یک تنسور 1 در 1 در 1 در 10 ایجاد کنید\n", 226 | "- همه ابعاد 1 رو از تنسور حذف کنید (فقط بعد 10 باقی بمونه)\n", 227 | "- هسته تصادفی رو روی 7 ست کنید\n", 228 | "- تنسور اصلی و خروجی مرحله قبل رو به همراه شکل تنسور ها چاپ کنید\n", 229 | "- خروجی باید معادل زیر باشد\n", 230 | "\n", 231 | "tensor([[[[0.5349, 0.1988, 0.6592, 0.6569, 0.2328, 0.4251, 0.2071, 0.6297,\n", 232 | " 0.3653, 0.8513]]]]) torch.Size([1, 1, 1, 10])\n", 233 | "\n", 234 | "tensor([0.5349, 0.1988, 0.6592, 0.6569, 0.2328, 0.4251, 0.2071, 0.6297, 0.3653,\n", 235 | " 0.8513]) torch.Size([10])" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 15, 241 | "id": "2d706e53", 242 | "metadata": {}, 243 | "outputs": [], 244 | "source": [ 245 | "# Set seed\n", 246 | "\n", 247 | "\n", 248 | "# Create random tensor\n", 249 | "\n", 250 | "\n", 251 | "# Remove single dimensions\n", 252 | "\n", 253 | "\n", 254 | "# Print out tensors and their shapes" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": null, 260 | "id": "c07e6875", 261 | "metadata": {}, 262 | "outputs": [], 263 | "source": [] 264 | } 265 | ], 266 | "metadata": { 267 | "kernelspec": { 268 | "display_name": "Python 3 (ipykernel)", 269 | "language": "python", 270 | "name": "python3" 271 | }, 272 | "language_info": { 273 | "codemirror_mode": { 274 | "name": "ipython", 275 | "version": 3 276 | }, 277 | "file_extension": ".py", 278 | "mimetype": "text/x-python", 279 | "name": "python", 280 | "nbconvert_exporter": "python", 281 | "pygments_lexer": "ipython3", 282 | "version": "3.9.7" 283 | } 284 | }, 285 | "nbformat": 4, 286 | "nbformat_minor": 5 287 | } 288 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # آموزش Deep Learning با استفاده از PyTorch 2 | 3 | دوره آموزشی رایگان آموزش عمیق [Deep Learning](https://en.wikipedia.org/wiki/Deep_learning) با استفاده از [PyTorch](https://pytorch.org/) 4 | 5 | مستندات رسمی PyTorch رو میتونید در این لینک پیدا کنید: [PyTorch Documentation](https://pytorch.org/docs/stable/index.html) 6 | 7 |

8 | 9 |
10 | 11 | pytorch deep learning 12 | 13 |
14 | 15 |

16 | 17 | ## محتوای دوره 18 | 19 | [1- درسها و ویدیوها](https://github.com/aidinism/deep-learning#1--%D8%AF%D8%B1%D8%B3%D9%87%D8%A7-%D9%88-%D9%88%DB%8C%D8%AF%DB%8C%D9%88%D9%87%D8%A7--%EF%B8%8F) 20 | 21 | [2- درباره این دوره](https://github.com/aidinism/deep-learning#2--%D8%AF%D8%B1%D8%A8%D8%A7%D8%B1%D9%87-%D8%A7%DB%8C%D9%86-%D8%AF%D9%88%D8%B1%D9%87) 22 | 23 | [3- بروزرسانی ها](https://github.com/aidinism/deep-learning#3--%D8%A8%D8%B1%D9%88%D8%B2%D8%B1%D8%B3%D8%A7%D9%86%DB%8C-%D9%87%D8%A7) 24 | 25 |
26 | 27 | 28 | ## 1- درسها و ویدیوها 📚 📽️ 29 | 30 |
31 | 32 | 📖 **کتاب آنلاین:** تمام درس ها در این کتاب آنلاین قابل دسترسی هست [Online Book](). 33 | 34 | 🎥 **ویدیو ها در یوتیوب:** ویدیو های درسها رو در اینجا ببینید [Youtube](https://www.youtube.com/playlist?list=PLF7LEiFs58lo0npB0kGWy41iv0D8ZME3m). 35 | 36 | 🤔 **سوال بپرسید:** اگر سوالی دارید میتونید در [Discussions](https://github.com/aidinism/deep-learning/discussions) بپرسید یا جوابهای قبل رو بخونید. 37 | 38 |


39 | 40 | | **قسمت** | **محتوا** | **ویدیو** | **متن** | 41 | | --- | --- | --- | --- | 42 | | [00 - مبانی هوش مصنوعی](00%20-%20مقدمات%20Deep%20Learning%20و%20PyTorch.pdf) | مبانی اولیه یادگیری ماشین/ شبکه های عصبی | [ویدیو در یوتیوب](https://www.youtube.com/watch?v=5ohQQtpzhrM) | [لینک متن]() | 43 | | [01 - نصب PyTorch](01%20-%20نصب%20PyTorch.ipynb) | نصب و راه اندازی پای تورچ | [ویدیو در یوتیوب](https://www.youtube.com/watch?v=AjF-otsoiAY) | [لینک متن]() | 44 | | [02 - مبانی PyTorch & Tensor](02%20-%20مبانی%20PyTorch%20%26%20Tensor.ipynb) | مبانی PyTorch & Tensor | [ویدیو در یوتیوب](https://www.youtube.com/watch?v=8wb1jwwbaHU) | [لینک متن]() | 45 | | [03 - محاسبات در تنسور ها](03%20-%20محاسبات%20در%20تنسور%20ها.ipynb) | محاسبات در تنسور ها | [ویدیو در یوتیوب](https://www.youtube.com/watch?v=oBGmU3Rktso) | [لینک متن]() | 46 | | [04 - تغییر در تنسور ها](04%20-%20توابع%20تجمیعی%20aggregation.ipynb) | تغییر در تنسور ها | [ویدیو در یوتیوب](https://www.youtube.com/watch?v=Q74J4_JwaBg) | [لینک متن]() | 47 | | [05 - مبانی PyTorch & Tensor](05%20-%20PyTorch%20%26%20NumPy.ipynb) | مبانی PyTorch & Tensor | [ویدیو در یوتیوب](https://www.youtube.com/watch?v=8wb1jwwbaHU) | [لینک متن]() | 48 | 49 | 50 | 51 |


52 | 53 | --- 54 | 55 |
56 | 57 | ## 2- درباره این دوره 58 | 59 |
60 | 61 | ### این دوره برای چه کسانی هست؟ 62 | 63 | >**شما:** در زمینه هوش مصنوعی یا یادگیری عمیق مبتدی هستید و تمایل دارید PyTorch رو یاد بگیرید. 64 | > 65 | >**این دوره:** به شما PyTorch و خیلی از مفاهیم یادگیری ماشین رو با روش عملی و کد نویسی یاد میده. 66 | 67 |
68 | 69 | ### پیش نیاز های این دوره چی هست؟ 70 | 71 | >1- سه تا شش ماه تجربه برنامه نویسی با پایتون 72 | > 73 | >2- درک کلی و سطحی از یادگیری ماشین 74 | > 75 | >3- تجربه کار با Jupyter Notebook یا Google Colab (البه در طول همین دوره هم یاد میگیرید) 76 | > 77 | >4- تمایل و اشتیاق به یادگیری 78 | 79 |
80 | 81 | ### چطور از این دوره استفاده کنم؟ 82 | 83 | >تمام محتوای این دوره به صورت تعاملی در وب سایت []() موجود هست. اگر تمایل به خوندن نسخه متنی دارید میتونید از این منبع استفاده کنید. 84 | > 85 | >اگر تمایل به یادگیری از طریق ویدیو دارید ، میتونید در یوتیوب من این درس ها رو تماشا کنید و همزمان با من کد نویسی رو انجام بدید. 86 | 87 |
88 | 89 | ### چه چیزی در این دوره کد نویسی میکنیم؟ 90 | 91 | >ما با اصول اولیه PyTorch و یادگیری ماشین شروع میکنیم، پس اگه تازه کار هستید میتونید از همینجا شروع کنید. 92 | > 93 | >در ادامه سراغ موضوعات پیشرفته تر مثل شبکه های عصبی ، روندهای PyTorch , بینایی ماشین , دیتاست های شخصی شده ، انتقال یادگیری و ... میریم. 94 | > 95 | >در این دوره یک پروژه عملی هوش مصنوعی برای تشخیص و دسته بندی تصاویر با استفاده از شبکه های عصبی رو با هم انجام میدیم. این پروژه عملی میتونه به شما کمک کنه با انجام دادنش و ایده برداری ازش، هوش مصنوعی دلخواه خودتون رو درست کنید و اولین تجربه واقعی هوش مصنوعی رو در رزومه خودتون ایجاد کنید. 96 | 97 |
98 | 99 | ### چطور و از کجا شروع کنم؟ 100 | 101 | >محتوای این دوره در هر دستگاهی قابل خواندن/دیدن هست اما برای کد نویسی و تجربه بهتر یادگیری نیاز به یک کامپیوتر/لپتاب دارید. 102 | > 103 | >برای شروع: 104 | > 105 | >یک - 106 | > 107 | >دو - 108 | 109 |
110 | 111 | ### راه های تماس 112 | 113 | >اگر سوالی دارید میتونید در [Discussions](https://github.com/aidinism/deep-learning/discussions) بپرسید یا جوابهای قبل رو بخونید.
همینطور میتونید من رو در شبکه های اجتماعی زیر پیدا کنید: 114 | > 115 | >- Twitter: [@Aidinism](https://twitter.com/Aidinism) 116 | >- Instagram: [@Aidinism](https://www.instagram.com/aidinism/) 117 | >- Youtube: [@AidinEslami](https://www.youtube.com/@AidinEslami) 118 | 119 |


120 |


121 | 122 | --- 123 | 124 |


125 | 126 | ## 3- بروزرسانی ها 127 | 128 | - 27 Feb 2023 - 🎥 Episode 01 released 129 | - 23 Feb 2023 - 🎥 Episode 00 released 130 | - 20 Feb 2023 - 🔥 init repo, add fundamentals 131 | -------------------------------------------------------------------------------- /SETUP.md: -------------------------------------------------------------------------------- 1 | # نصب و راه اندازی PyTorch 2 | 3 |
4 | 5 | pytorch deep learning 6 | 7 |
8 | 9 | برای استفاده از PyTorch از 2 روش میتونید استفاده کنید: 10 | 11 | 1.استفاده از [Google Colab](https://colab.research.google.com/): اگر دسترسی دارید گزینه راحت تری هست (ممکنه در ایران قابل استفاده نباشه 😔 ) 12 | 13 | 2.نصب محلی: اگر دسترسی ندارید نگران نباشید 😊 این روش هم به راحتی قابل انجام هست و البته امکانات بیشتری بهتون میده 14 | 15 | 16 | 17 |
18 |
19 | 20 | ## 1. استفاده از Google Colab 21 | 22 | گوگل کولب یک کامپیوتر آنلاین (با رابط ژوپیتر نوت بوک) هست که به طور رایگان در اختیار همه قرار داره. 23 | از این آدرس میتونید دسترسی پیدا کنید: 24 | 25 | https://colab.research.google.com 26 | 27 | --- 28 | --- 29 | --- 30 | 31 | ## 2. نصب و راه اندازی محلی (Linux , Windows WSL) 32 | 33 | این راهنما برای نصب در محیط لینوکس هست (یا WSL در ویندوز). برای نصب در ویندوز یا مکینتاش به [راهنمای نصب رسمی PyTorch](https://pytorch.org/get-started/locally/) مراجعه کنید. 34 | 35 | ### **نکته:** اگر GPU NVIDIA دارید میتوانید 36 | 37 | 38 | اگر از WSL 2 استفاده میکنید از راهنمای زیر میتونید CUDA رو نصب کنید: 39 | 40 | https://learn.microsoft.com/en-us/windows/ai/directml/gpu-cuda-in-wsl 41 | 42 | 43 | ## Setup steps locally for a Linux system with a GPU 44 | 45 | 1. Check CUDA install: [NVIDIA CUDA](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/) 46 | ![CUDA](https://docs.nvidia.com/cuda/_static/Logo_and_CUDA.png) 47 | 48 | 49 | 2. [Install Miniconda](https://docs.conda.io/projects/conda/en/latest/user-guide/install/linux.html) (you can use Anaconda if you already have it), the main thing is you need access to `conda` on the command line. Make sure to follow all the steps in the Miniconda installation guide before moving onto the next step. 50 | 3. Make a directory for the course materials, you can name it what you want and then change into it. For example: 51 | ``` 52 | mkdir ztm-pytorch-course 53 | cd ztm-pytorch-course 54 | ``` 55 | 4. Create a `conda` environment in the directory you just created. The following command will create a `conda` enviroment that lives in the folder called `env` which lives in the folder you just created (e.g. `ztm-pytorch-course/env`). Press `y` when the command below asks `y/n?`. 56 | ``` 57 | conda create --prefix ./env python=3.8 58 | ``` 59 | 5. Activate the environment you just created. 60 | ``` 61 | conda activate ./env 62 | ``` 63 | 6. Install the code dependencies you'll need for the course such as PyTorch and CUDA Toolkit for running PyTorch on your GPU. You can run all of these at the same time (**note:** this is specifically for Linux systems with a NVIDIA GPU, for other options see the [PyTorch setup documentation](https://pytorch.org/get-started/locally/)): 64 | ``` 65 | conda install -c pytorch pytorch=1.10.0 torchvision cudatoolkit=11.3 -y 66 | conda install -c conda-forge jupyterlab torchinfo torchmetrics -y 67 | conda install -c anaconda pip -y 68 | conda install pandas matplotlib scikit-learn -y 69 | ``` 70 | 7. Verify the installation ran correctly by running starting a Jupyter Lab server: 71 | 72 | ```bash 73 | jupyter lab 74 | ``` 75 | 76 | 8. After Jupyter Lab is running, start a Jupyter Notebook and running the following piece of code in a cell. 77 | ```python 78 | import pandas as pd 79 | import numpy as np 80 | import torch 81 | import sklearn 82 | import matplotlib 83 | import torchinfo, torchmetrics 84 | 85 | # Check PyTorch access (should print out a tensor) 86 | print(torch.randn(3, 3)) 87 | 88 | # Check for GPU (should return True) 89 | print(torch.cuda.is_available()) 90 | ``` 91 | 92 | 93 | 94 | اگر در نصب به مشکل برخوردید میتونید در [Learn PyTorch GitHub Discussions page](https://github.com/aidinism/deep-learning/discussions/2) سوال بپرسید و یا از [PyTorch setup documentation page](https://pytorch.org/get-started/locally/) استفاده کنید. -------------------------------------------------------------------------------- /images/03-matrix-multiply.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidinism/deep-learning/d943a71dbbff72d0c69eaf675b55a51066083301/images/03-matrix-multiply.gif -------------------------------------------------------------------------------- /images/pytorch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidinism/deep-learning/d943a71dbbff72d0c69eaf675b55a51066083301/images/pytorch.png --------------------------------------------------------------------------------