{ "cells": [ { "cell_type": "markdown", "id": "d9ceda45", "metadata": { "origin_pos": 0 }, "source": [ "# GPU\n", ":label:`sec_use_gpu`\n", "\n", "Năm :numref:`tab_intro_decade`, chúng tôi đã thảo luận về sự tăng trưởng nhanh chóng của tính toán trong hai thập kỷ qua. Tóm lại, hiệu suất GPU đã tăng hệ số 1000 mỗi thập kỷ kể từ năm 2000. Điều này mang lại những cơ hội tuyệt vời nhưng nó cũng cho thấy một nhu cầu đáng kể để cung cấp hiệu suất như vậy. \n", "\n", "Trong phần này, chúng tôi bắt đầu thảo luận về cách khai thác hiệu suất tính toán này cho nghiên cứu của bạn. Đầu tiên bằng cách sử dụng GPU duy nhất và tại một thời điểm sau đó, làm thế nào để sử dụng nhiều GPU và nhiều máy chủ (với nhiều GPU). \n", "\n", "Cụ thể, chúng tôi sẽ thảo luận về cách sử dụng một GPU NVIDIA duy nhất để tính toán. Trước tiên, hãy chắc chắn rằng bạn đã cài đặt ít nhất một GPU NVIDIA. Sau đó, tải xuống [NVIDIA driver and CUDA](https://developer.nvidia.com/cuda-downloads) và làm theo lời nhắc để đặt đường dẫn thích hợp. Khi các chế phẩm này hoàn tất, lệnh `nvidia-smi` có thể được sử dụng để (**xem thông tin card đồ hoạ**).\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "c2071769", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:19.628055Z", "iopub.status.busy": "2022-04-26T13:13:19.627434Z", "iopub.status.idle": "2022-04-26T13:13:20.575164Z", "shell.execute_reply": "2022-04-26T13:13:20.574321Z" }, "origin_pos": 1, "tab": [ "tensorflow" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Tue Apr 26 13:13:20 2022 \r\n", "+-----------------------------------------------------------------------------+\r\n", "| NVIDIA-SMI 460.27.04 Driver Version: 460.27.04 CUDA Version: 11.2 |\r\n", "|-------------------------------+----------------------+----------------------+\r\n", "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\r\n", "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\r\n", "| | | MIG M. |\r\n", "|===============================+======================+======================|\r\n", "| 0 Tesla V100-SXM2... Off | 00000000:00:1B.0 Off | 0 |\r\n", "| N/A 45C P0 42W / 300W | 0MiB / 16160MiB | 0% Default |\r\n", "| | | N/A |\r\n", "+-------------------------------+----------------------+----------------------+\r\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "| 1 Tesla V100-SXM2... Off | 00000000:00:1C.0 Off | 0 |\r\n", "| N/A 40C P0 49W / 300W | 3442MiB / 16160MiB | 0% Default |\r\n", "| | | N/A |\r\n", "+-------------------------------+----------------------+----------------------+\r\n", "| 2 Tesla V100-SXM2... Off | 00000000:00:1D.0 Off | 0 |\r\n", "| N/A 41C P0 57W / 300W | 0MiB / 16160MiB | 0% Default |\r\n", "| | | N/A |\r\n", "+-------------------------------+----------------------+----------------------+\r\n", "| 3 Tesla V100-SXM2... Off | 00000000:00:1E.0 Off | 0 |\r\n", "| N/A 38C P0 52W / 300W | 3350MiB / 16160MiB | 0% Default |\r\n", "| | | N/A |\r\n", "+-------------------------------+----------------------+----------------------+\r\n", " \r\n", "+-----------------------------------------------------------------------------+\r\n", "| Processes: |\r\n", "| GPU GI CI PID Type Process name GPU Memory |\r\n", "| ID ID Usage |\r\n", "|=============================================================================|\r\n", "| 1 N/A N/A 14816 C ...l-vi-release-0/bin/python 3439MiB |\r\n", "| 3 N/A N/A 14816 C ...l-vi-release-0/bin/python 3347MiB |\r\n", "+-----------------------------------------------------------------------------+\r\n" ] } ], "source": [ "!nvidia-smi" ] }, { "cell_type": "markdown", "id": "70fec9f5", "metadata": { "origin_pos": 4 }, "source": [ "Để chạy các chương trình trong phần này, bạn cần ít nhất hai GPU. Lưu ý rằng điều này có thể là xa hoa đối với hầu hết các máy tính để bàn nhưng nó có thể dễ dàng có sẵn trên đám mây, ví dụ, bằng cách sử dụng các phiên bản đa GPU AWS EC2. Hầu như tất cả các phần khác làm * không* yêu cầu nhiều GPU. Thay vào đó, điều này chỉ đơn giản là để minh họa cách dữ liệu chảy giữa các thiết bị khác nhau. \n", "\n", "## [**Thiết bị vi tính**]\n", "\n", "Chúng tôi có thể chỉ định các thiết bị, chẳng hạn như CPU và GPU, để lưu trữ và tính toán. Theo mặc định, hàng chục được tạo trong bộ nhớ chính và sau đó sử dụng CPU để tính toán nó.\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "fff66b08", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:20.579233Z", "iopub.status.busy": "2022-04-26T13:13:20.578707Z", "iopub.status.idle": "2022-04-26T13:13:24.951437Z", "shell.execute_reply": "2022-04-26T13:13:24.950788Z" }, "origin_pos": 9, "tab": [ "tensorflow" ] }, "outputs": [ { "data": { "text/plain": [ "(,\n", " ,\n", " )" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import tensorflow as tf\n", "\n", "tf.device('/CPU:0'), tf.device('/GPU:0'), tf.device('/GPU:1')" ] }, { "cell_type": "markdown", "id": "692cd194", "metadata": { "origin_pos": 10 }, "source": [ "Chúng tôi có thể (**truy vấn số lượng GPU có sẵn.**)\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "f152432d", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:24.954814Z", "iopub.status.busy": "2022-04-26T13:13:24.954369Z", "iopub.status.idle": "2022-04-26T13:13:24.959555Z", "shell.execute_reply": "2022-04-26T13:13:24.958878Z" }, "origin_pos": 13, "tab": [ "tensorflow" ] }, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(tf.config.experimental.list_physical_devices('GPU'))" ] }, { "cell_type": "markdown", "id": "adff6339", "metadata": { "origin_pos": 14 }, "source": [ "Bây giờ chúng ta [** xác định hai hàm tiện lợi cho phép chúng tôi chạy mã ngay cả khi GPU được yêu cầu không tồn tại.**]\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "1d09d825", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:24.962942Z", "iopub.status.busy": "2022-04-26T13:13:24.962537Z", "iopub.status.idle": "2022-04-26T13:13:24.970415Z", "shell.execute_reply": "2022-04-26T13:13:24.969740Z" }, "origin_pos": 17, "tab": [ "tensorflow" ] }, "outputs": [ { "data": { "text/plain": [ "(,\n", " ,\n", " [,\n", " ])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def try_gpu(i=0): #@save\n", " \"\"\"Return gpu(i) if exists, otherwise return cpu().\"\"\"\n", " if len(tf.config.experimental.list_physical_devices('GPU')) >= i + 1:\n", " return tf.device(f'/GPU:{i}')\n", " return tf.device('/CPU:0')\n", "\n", "def try_all_gpus(): #@save\n", " \"\"\"Return all available GPUs, or [cpu(),] if no GPU exists.\"\"\"\n", " num_gpus = len(tf.config.experimental.list_physical_devices('GPU'))\n", " devices = [tf.device(f'/GPU:{i}') for i in range(num_gpus)]\n", " return devices if devices else [tf.device('/CPU:0')]\n", "\n", "try_gpu(), try_gpu(10), try_all_gpus()" ] }, { "cell_type": "markdown", "id": "cf15aa07", "metadata": { "origin_pos": 18 }, "source": [ "## Tensors và GPU\n", "\n", "Theo mặc định, hàng chục được tạo trên CPU. Chúng ta có thể [**truy vấn thiết bị nơi tensor được đặt.**]\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "e480b59e", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:24.973744Z", "iopub.status.busy": "2022-04-26T13:13:24.973127Z", "iopub.status.idle": "2022-04-26T13:13:24.982117Z", "shell.execute_reply": "2022-04-26T13:13:24.981429Z" }, "origin_pos": 21, "tab": [ "tensorflow" ] }, "outputs": [ { "data": { "text/plain": [ "'/job:localhost/replica:0/task:0/device:GPU:0'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x = tf.constant([1, 2, 3])\n", "x.device" ] }, { "cell_type": "markdown", "id": "6f4799a4", "metadata": { "origin_pos": 22 }, "source": [ "Điều quan trọng cần lưu ý là bất cứ khi nào chúng tôi muốn hoạt động theo nhiều điều khoản, chúng cần phải ở trên cùng một thiết bị. Ví dụ, nếu chúng ta tổng hợp hai chục, chúng ta cần đảm bảo rằng cả hai đối số đều sống trên cùng một thiết bị—nếu không khung sẽ không biết lưu trữ kết quả ở đâu hoặc thậm chí làm thế nào để quyết định nơi thực hiện tính toán. \n", "\n", "### Lưu trữ trên GPU\n", "\n", "Có một số cách để [**lưu trữ một tensor trên GPU.**] Ví dụ: chúng ta có thể chỉ định một thiết bị lưu trữ khi tạo tensor. Tiếp theo, chúng ta tạo biến tensor `X` trên `gpu` đầu tiên. Tensor được tạo trên GPU chỉ tiêu thụ bộ nhớ của GPU này. Chúng ta có thể sử dụng lệnh `nvidia-smi` để xem mức sử dụng bộ nhớ GPU. Nói chung, chúng ta cần đảm bảo rằng chúng ta không tạo dữ liệu vượt quá giới hạn bộ nhớ GPU.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "6224f1f7", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:24.985022Z", "iopub.status.busy": "2022-04-26T13:13:24.984687Z", "iopub.status.idle": "2022-04-26T13:13:24.991196Z", "shell.execute_reply": "2022-04-26T13:13:24.990529Z" }, "origin_pos": 25, "tab": [ "tensorflow" ] }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with try_gpu():\n", " X = tf.ones((2, 3))\n", "X" ] }, { "cell_type": "markdown", "id": "b10f1762", "metadata": { "origin_pos": 26 }, "source": [ "Giả sử rằng bạn có ít nhất hai GPU, mã sau sẽ (**tạo tensor ngẫu nhiên trên GPU. thứ hai**)\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "4e62212f", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:24.994638Z", "iopub.status.busy": "2022-04-26T13:13:24.994214Z", "iopub.status.idle": "2022-04-26T13:13:25.000959Z", "shell.execute_reply": "2022-04-26T13:13:25.000249Z" }, "origin_pos": 29, "tab": [ "tensorflow" ] }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with try_gpu(1):\n", " Y = tf.random.uniform((2, 3))\n", "Y" ] }, { "cell_type": "markdown", "id": "9dc47d46", "metadata": { "origin_pos": 30 }, "source": [ "### Sao chép\n", "\n", "[**Nếu chúng ta muốn tính `X + Y`, chúng ta cần quyết định nơi thực hiện thao tác này.**] Ví dụ, như thể hiện trong :numref:`fig_copyto`, chúng ta có thể chuyển `X` sang GPU thứ hai và thực hiện thao tác ở đó.\n", "*Làm không* chỉ cần thêm `X` và `Y`,\n", "vì điều này sẽ dẫn đến một ngoại lệ. Công cụ thời gian chạy sẽ không biết phải làm gì: nó không thể tìm thấy dữ liệu trên cùng một thiết bị và nó không thành công. Vì `Y` sống trên GPU thứ hai, chúng ta cần phải di chuyển `X` ở đó trước khi chúng ta có thể thêm hai. \n", "\n", "![Copy data to perform an operation on the same device.](../img/copyto.svg)\n", ":label:`fig_copyto`\n" ] }, { "cell_type": "code", "execution_count": 8, "id": "aa2242d5", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:25.004394Z", "iopub.status.busy": "2022-04-26T13:13:25.004037Z", "iopub.status.idle": "2022-04-26T13:13:25.008703Z", "shell.execute_reply": "2022-04-26T13:13:25.008051Z" }, "origin_pos": 33, "tab": [ "tensorflow" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tf.Tensor(\n", "[[1. 1. 1.]\n", " [1. 1. 1.]], shape=(2, 3), dtype=float32)\n", "tf.Tensor(\n", "[[1. 1. 1.]\n", " [1. 1. 1.]], shape=(2, 3), dtype=float32)\n" ] } ], "source": [ "with try_gpu(1):\n", " Z = X\n", "print(X)\n", "print(Z)" ] }, { "cell_type": "markdown", "id": "bc4f7072", "metadata": { "origin_pos": 34 }, "source": [ "Bây giờ [**dữ liệu nằm trên cùng một GPU (cả `Z` và `Y` đều là), chúng ta có thể thêm chúng lên.**]\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "5140690e", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:25.011807Z", "iopub.status.busy": "2022-04-26T13:13:25.011266Z", "iopub.status.idle": "2022-04-26T13:13:25.016950Z", "shell.execute_reply": "2022-04-26T13:13:25.016323Z" }, "origin_pos": 35, "tab": [ "tensorflow" ] }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Y + Z" ] }, { "cell_type": "markdown", "id": "782a2214", "metadata": { "origin_pos": 38, "tab": [ "tensorflow" ] }, "source": [ "Hãy tưởng tượng rằng biến `Z` của bạn đã sống trên GPU thứ hai của bạn. Điều gì xảy ra nếu chúng ta vẫn gọi `Z2 = Z` trong cùng một phạm vi thiết bị? Nó sẽ trả về `Z` thay vì tạo một bản sao và phân bổ bộ nhớ mới.\n" ] }, { "cell_type": "code", "execution_count": 10, "id": "ff92d8c0", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:25.020110Z", "iopub.status.busy": "2022-04-26T13:13:25.019583Z", "iopub.status.idle": "2022-04-26T13:13:25.024551Z", "shell.execute_reply": "2022-04-26T13:13:25.023802Z" }, "origin_pos": 41, "tab": [ "tensorflow" ] }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with try_gpu(1):\n", " Z2 = Z\n", "Z2 is Z" ] }, { "cell_type": "markdown", "id": "7a7f5ccd", "metadata": { "origin_pos": 42 }, "source": [ "### Ghi chú bên\n", "\n", "Mọi người sử dụng GPU để học máy vì họ mong đợi chúng sẽ nhanh chóng. Nhưng chuyển các biến giữa các thiết bị chậm. Vì vậy, chúng tôi muốn bạn chắc chắn 100% rằng bạn muốn làm điều gì đó chậm trước khi chúng tôi cho phép bạn làm điều đó. Nếu khung học sâu chỉ thực hiện bản sao tự động mà không bị rơi sau đó bạn có thể không nhận ra rằng bạn đã viết một số mã chậm. \n", "\n", "Ngoài ra, truyền dữ liệu giữa các thiết bị (CPU, GPU và các máy khác) là thứ chậm hơn nhiều so với tính toán. Nó cũng làm cho việc song song trở nên khó khăn hơn rất nhiều, vì chúng ta phải đợi dữ liệu được gửi (hay đúng hơn là được nhận) trước khi chúng ta có thể tiến hành nhiều thao tác hơn. Đây là lý do tại sao các thao tác sao chép nên được thực hiện cẩn thận. Theo nguyên tắc chung, nhiều hoạt động nhỏ tồi tệ hơn nhiều so với một hoạt động lớn. Hơn nữa, một số hoạt động tại một thời điểm tốt hơn nhiều so với nhiều thao tác đơn lẻ xen kẽ trong mã trừ khi bạn biết những gì bạn đang làm. Đây là trường hợp vì các hoạt động như vậy có thể chặn nếu một thiết bị phải đợi thiết bị kia trước khi nó có thể làm một cái gì đó khác. Nó là một chút giống như đặt hàng cà phê của bạn trong một hàng đợi thay vì đặt hàng trước nó qua điện thoại và phát hiện ra rằng nó đã sẵn sàng khi bạn đang có. \n", "\n", "Cuối cùng, khi chúng ta in hàng chục hoặc chuyển đổi hàng chục sang định dạng NumPy, nếu dữ liệu không nằm trong bộ nhớ chính, khung sẽ sao chép nó vào bộ nhớ chính trước, dẫn đến chi phí truyền bổ sung. Thậm chí tệ hơn, bây giờ nó phải tuân theo khóa thông dịch viên toàn cầu đáng sợ khiến mọi thứ chờ đợi Python hoàn thành. \n", "\n", "## [**Mạng nơ-ron và GPU **]\n", "\n", "Tương tự, một mô hình mạng thần kinh có thể chỉ định các thiết bị. Mã sau đặt các tham số mô hình trên GPU.\n" ] }, { "cell_type": "code", "execution_count": 11, "id": "cf99241b", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:25.027613Z", "iopub.status.busy": "2022-04-26T13:13:25.027218Z", "iopub.status.idle": "2022-04-26T13:13:25.052277Z", "shell.execute_reply": "2022-04-26T13:13:25.051579Z" }, "origin_pos": 45, "tab": [ "tensorflow" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO:tensorflow:Using MirroredStrategy with devices ('/job:localhost/replica:0/task:0/device:GPU:0', '/job:localhost/replica:0/task:0/device:GPU:1')\n" ] } ], "source": [ "strategy = tf.distribute.MirroredStrategy()\n", "with strategy.scope():\n", " net = tf.keras.models.Sequential([\n", " tf.keras.layers.Dense(1)])" ] }, { "cell_type": "markdown", "id": "90098d7e", "metadata": { "origin_pos": 46 }, "source": [ "Chúng ta sẽ thấy nhiều ví dụ khác về cách chạy các mô hình trên GPU trong các chương sau, đơn giản vì chúng sẽ trở nên chuyên sâu hơn về mặt tính toán. \n", "\n", "Khi đầu vào là một tensor trên GPU, mô hình sẽ tính toán kết quả trên cùng một GPU.\n" ] }, { "cell_type": "code", "execution_count": 12, "id": "48e6f690", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:25.055595Z", "iopub.status.busy": "2022-04-26T13:13:25.055236Z", "iopub.status.idle": "2022-04-26T13:13:25.504122Z", "shell.execute_reply": "2022-04-26T13:13:25.503345Z" }, "origin_pos": 47, "tab": [ "tensorflow" ] }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net(X)" ] }, { "cell_type": "markdown", "id": "4f8bfaa0", "metadata": { "origin_pos": 48 }, "source": [ "Hãy để chúng tôi (**xác nhận rằng các tham số mô hình được lưu trữ trên cùng một GPU.**)\n" ] }, { "cell_type": "code", "execution_count": 13, "id": "ff742cb9", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T13:13:25.507447Z", "iopub.status.busy": "2022-04-26T13:13:25.506961Z", "iopub.status.idle": "2022-04-26T13:13:25.512157Z", "shell.execute_reply": "2022-04-26T13:13:25.511519Z" }, "origin_pos": 51, "tab": [ "tensorflow" ] }, "outputs": [ { "data": { "text/plain": [ "('/job:localhost/replica:0/task:0/device:GPU:0',\n", " '/job:localhost/replica:0/task:0/device:GPU:0')" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.layers[0].weights[0].device, net.layers[0].weights[1].device" ] }, { "cell_type": "markdown", "id": "406b0883", "metadata": { "origin_pos": 52 }, "source": [ "Nói tóm lại, miễn là tất cả dữ liệu và tham số đều trên cùng một thiết bị, chúng ta có thể học các mô hình một cách hiệu quả. Trong các chương sau, chúng ta sẽ thấy một số ví dụ như vậy. \n", "\n", "## Tóm tắt\n", "\n", "* Chúng tôi có thể chỉ định các thiết bị để lưu trữ và tính toán, chẳng hạn như CPU hoặc GPU. Theo mặc định, dữ liệu được tạo trong bộ nhớ chính và sau đó sử dụng CPU để tính toán.\n", "* Khung học sâu yêu cầu tất cả dữ liệu đầu vào để tính toán phải nằm trên cùng một thiết bị, có thể là CPU hoặc cùng một GPU.\n", "* Bạn có thể mất hiệu suất đáng kể bằng cách di chuyển dữ liệu mà không cần quan tâm. Một sai lầm điển hình như sau: tính toán tổn thất cho mỗi minibatch trên GPU và báo cáo lại cho người dùng trên dòng lệnh (hoặc đăng nhập nó trong NumPy `ndarray`) sẽ kích hoạt một khóa thông dịch viên toàn cầu ngăn chặn tất cả các GPU. Tốt hơn là phân bổ bộ nhớ để đăng nhập vào GPU và chỉ di chuyển các bản ghi lớn hơn.\n", "\n", "## Bài tập\n", "\n", "1. Hãy thử một nhiệm vụ tính toán lớn hơn, chẳng hạn như phép nhân của ma trận lớn và xem sự khác biệt về tốc độ giữa CPU và GPU. Còn một nhiệm vụ với một lượng nhỏ tính toán thì sao?\n", "1. Làm thế nào chúng ta nên đọc và viết các tham số mô hình trên GPU?\n", "1. Đo thời gian cần thiết để tính toán 1000 phép nhân ma trận ma trận của $100 \\times 100$ ma trận và ghi lại định mức Frobenius của ma trận đầu ra một kết quả tại một thời điểm so với giữ nhật ký trên GPU và chỉ chuyển kết quả cuối cùng.\n", "1. Đo lường thời gian cần thiết để thực hiện hai phép nhân ma trận ma trận trên hai GPU cùng một lúc so với theo thứ tự trên một GPU. Gợi ý: bạn sẽ thấy tỷ lệ gần như tuyến tính.\n" ] }, { "cell_type": "markdown", "id": "a4e79a3c", "metadata": { "origin_pos": 55, "tab": [ "tensorflow" ] }, "source": [ "[Discussions](https://discuss.d2l.ai/t/270)\n" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }