{ "cells": [ { "cell_type": "markdown", "id": "87437f6d", "metadata": { "origin_pos": 0 }, "source": [ "# Trình biên dịch và phiên dịch\n", ":label:`sec_hybridize`\n", "\n", "Cho đến nay, cuốn sách này đã tập trung vào lập trình bắt buộc, làm cho việc sử dụng các tuyên bố như `print`, `+`, và `if` để thay đổi trạng thái của một chương trình. Hãy xem xét ví dụ sau đây của một chương trình bắt buộc đơn giản.\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "b3b0717f", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:06.842820Z", "iopub.status.busy": "2022-04-26T10:53:06.842211Z", "iopub.status.idle": "2022-04-26T10:53:06.863684Z", "shell.execute_reply": "2022-04-26T10:53:06.857846Z" }, "origin_pos": 1, "tab": [ "mxnet" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10\n" ] } ], "source": [ "def add(a, b):\n", " return a + b\n", "\n", "def fancy_func(a, b, c, d):\n", " e = add(a, b)\n", " f = add(c, d)\n", " g = add(e, f)\n", " return g\n", "\n", "print(fancy_func(1, 2, 3, 4))" ] }, { "cell_type": "markdown", "id": "86609d4f", "metadata": { "origin_pos": 2 }, "source": [ "Python là một ngôn ngữ*được giải thị*. Khi đánh giá hàm `fancy_func` trên, nó thực hiện các thao tác tạo nên cơ thể của hàm * theo trình tự *. Đó là, nó sẽ đánh giá `e = add(a, b)` và lưu trữ các kết quả dưới dạng biến `e`, do đó thay đổi trạng thái của chương trình. Hai câu lệnh tiếp theo `f = add(c, d)` và `g = add(e, f)` sẽ được thực thi tương tự, thực hiện bổ sung và lưu trữ các kết quả dưới dạng biến. :numref:`fig_compute_graph` minh họa luồng dữ liệu. \n", "\n", "![Data flow in an imperative program.](../img/computegraph.svg)\n", ":label:`fig_compute_graph`\n", "\n", "Mặc dù lập trình bắt buộc là thuận tiện, nó có thể không hiệu quả. Một mặt, ngay cả khi hàm `add` được gọi nhiều lần trong suốt `fancy_func`, Python sẽ thực hiện ba cuộc gọi hàm riêng lẻ. Nếu chúng được thực thi, giả sử, trên GPU (hoặc thậm chí trên nhiều GPU), chi phí phát sinh từ trình thông dịch Python có thể trở nên áp đảo. Hơn nữa, nó sẽ cần phải lưu các giá trị biến của `e` và `f` cho đến khi tất cả các câu lệnh trong `fancy_func` đã được thực thi. Điều này là do chúng ta không biết liệu các biến `e` và `f` sẽ được sử dụng bởi các phần khác của chương trình sau khi các câu lệnh `e = add(a, b)` và `f = add(c, d)` được thực thi. \n", "\n", "## Lập trình tượng trưng\n", "\n", "Hãy xem xét phương pháp thay thế, *lập trình tượng trưng, trong đó tính toán thường chỉ được thực hiện một khi quá trình đã được xác định đầy đủ. Chiến lược này được sử dụng bởi nhiều khuôn khổ học sâu, bao gồm Theano và TensorFlow (sau này đã có được các phần mở rộng bắt buộc). Nó thường bao gồm các bước sau: \n", "\n", "1. Xác định các hoạt động sẽ được thực thi.\n", "1. Biên dịch các hoạt động thành một chương trình thực thi.\n", "1. Cung cấp các đầu vào cần thiết và gọi chương trình được biên dịch để thực hiện.\n", "\n", "Điều này cho phép tối ưu hóa một lượng đáng kể. Đầu tiên, chúng ta có thể bỏ qua trình thông dịch Python trong nhiều trường hợp, do đó loại bỏ một nút cổ chai hiệu suất có thể trở nên đáng kể trên nhiều GPU nhanh được ghép nối với một luồng Python duy nhất trên CPU. Thứ hai, một trình biên dịch có thể tối ưu hóa và viết lại mã trên vào `print((1 + 2) + (3 + 4))` hoặc thậm chí `print(10)`. Điều này là có thể kể từ khi một trình biên dịch được để xem mã đầy đủ trước khi biến nó thành hướng dẫn máy. Ví dụ, nó có thể giải phóng bộ nhớ (hoặc không bao giờ phân bổ nó) bất cứ khi nào một biến không còn cần thiết nữa. Hoặc nó có thể biến đổi mã hoàn toàn thành một phần tương đương. Để có được một ý tưởng tốt hơn, hãy xem xét mô phỏng sau đây của lập trình bắt buộc (nó là Python sau tất cả) dưới đây.\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "883db78f", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:06.868537Z", "iopub.status.busy": "2022-04-26T10:53:06.867645Z", "iopub.status.idle": "2022-04-26T10:53:06.875618Z", "shell.execute_reply": "2022-04-26T10:53:06.874364Z" }, "origin_pos": 3, "tab": [ "mxnet" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "def add(a, b):\n", " return a + b\n", "\n", "def fancy_func(a, b, c, d):\n", " e = add(a, b)\n", " f = add(c, d)\n", " g = add(e, f)\n", " return g\n", "print(fancy_func(1, 2, 3, 4))\n", "10\n" ] } ], "source": [ "def add_():\n", " return '''\n", "def add(a, b):\n", " return a + b\n", "'''\n", "\n", "def fancy_func_():\n", " return '''\n", "def fancy_func(a, b, c, d):\n", " e = add(a, b)\n", " f = add(c, d)\n", " g = add(e, f)\n", " return g\n", "'''\n", "\n", "def evoke_():\n", " return add_() + fancy_func_() + 'print(fancy_func(1, 2, 3, 4))'\n", "\n", "prog = evoke_()\n", "print(prog)\n", "y = compile(prog, '', 'exec')\n", "exec(y)" ] }, { "cell_type": "markdown", "id": "6e61f56a", "metadata": { "origin_pos": 4 }, "source": [ "Sự khác biệt giữa lập trình bắt buộc (giải thích) và lập trình tượng trưng như sau: \n", "\n", "* Lập trình bắt buộc dễ dàng hơn. Khi lập trình bắt buộc được sử dụng trong Python, phần lớn mã là đơn giản và dễ viết. Nó cũng dễ dàng hơn để gỡ lỗi mã lập trình bắt buộc. Điều này là do dễ dàng hơn để có được và in tất cả các giá trị biến trung gian có liên quan hoặc sử dụng các công cụ gỡ lỗi tích hợp sẵn của Python.\n", "* Lập trình tượng trưng hiệu quả hơn và dễ dàng hơn để cổng. Lập trình tượng trưng giúp tối ưu hóa mã trong quá trình biên dịch dễ dàng hơn, đồng thời có khả năng chuyển chương trình thành một định dạng độc lập với Python. Điều này cho phép chương trình được chạy trong môi trường không phải Python, do đó tránh mọi vấn đề hiệu suất tiềm ẩn liên quan đến trình thông dịch Python.\n", "\n", "## Lập trình lai\n", "\n", "Trong lịch sử hầu hết các khuôn khổ học sâu lựa chọn giữa một cách tiếp cận bắt buộc hoặc mang tính biểu tượng. Ví dụ, Theano, TensorFlow (lấy cảm hứng từ trước đây), Keras và CNTK xây dựng các mô hình tượng trưng. Ngược lại, Chainer và PyTorch có một cách tiếp cận bắt buộc. Một chế độ bắt buộc đã được thêm vào TensorFlow 2.0 và Keras trong các phiên bản sau này.\n" ] }, { "cell_type": "markdown", "id": "a0d27060", "metadata": { "origin_pos": 5, "tab": [ "mxnet" ] }, "source": [ "Khi thiết kế Gluon, các nhà phát triển đã xem xét liệu có thể kết hợp những lợi ích của cả hai mô hình lập trình hay không. Điều này dẫn đến một mô hình lai cho phép người dùng phát triển và gỡ lỗi với lập trình bắt buộc thuần túy, trong khi có khả năng chuyển đổi hầu hết các chương trình thành các chương trình tượng trưng để chạy khi hiệu suất tính toán cấp sản phẩm và triển khai được yêu cầu. \n", "\n", "Trong thực tế, điều này có nghĩa là chúng tôi xây dựng các mô hình bằng cách sử dụng lớp `HybridBlock` hoặc `HybridSequential`. Theo mặc định, một trong số chúng được thực thi theo cùng một cách lớp `Block` hoặc `Sequential` được thực thi trong lập trình bắt buộc. Lớp `HybridSequential` là một lớp con của `HybridBlock` (giống như `Sequential` lớp con `Block`). Khi hàm `hybridize` được gọi, Gluon biên dịch mô hình thành dạng được sử dụng trong lập trình tượng trưng. Điều này cho phép người ta tối ưu hóa các thành phần chuyên sâu tính toán mà không phải hy sinh theo cách thực hiện mô hình. Chúng tôi sẽ minh họa những lợi ích dưới đây, tập trung vào các mô hình và khối tuần tự.\n" ] }, { "cell_type": "markdown", "id": "9f053495", "metadata": { "origin_pos": 8 }, "source": [ "## Lai tạo lớp `Sequential`\n", "\n", "Cách dễ nhất để có được cảm nhận về cách thức hoạt động của lai là xem xét các mạng sâu với nhiều lớp. Thông thường, trình thông dịch Python sẽ cần thực thi mã cho tất cả các lớp để tạo ra một lệnh sau đó có thể được chuyển tiếp đến CPU hoặc GPU. Đối với một thiết bị điện toán (nhanh) duy nhất, điều này không gây ra bất kỳ vấn đề lớn nào. Mặt khác, nếu chúng ta sử dụng một máy chủ 8 GPU tiên tiến như một phiên bản AWS P3dn.24xlarge Python sẽ phải vật lộn để giữ cho tất cả các GPU bận rộn. Trình thông dịch Python đơn luồng trở thành nút cổ chai ở đây. Hãy để chúng tôi xem cách chúng tôi có thể giải quyết vấn đề này cho các phần quan trọng của mã bằng cách thay thế `Sequential` bằng `HybridSequential`. Chúng tôi bắt đầu bằng cách xác định một MLP đơn giản.\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "fa9055e1", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:06.879776Z", "iopub.status.busy": "2022-04-26T10:53:06.879353Z", "iopub.status.idle": "2022-04-26T10:53:12.312022Z", "shell.execute_reply": "2022-04-26T10:53:12.311040Z" }, "origin_pos": 9, "tab": [ "mxnet" ] }, "outputs": [ { "data": { "text/plain": [ "array([[ 0.16526186, -0.14005628]])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from mxnet import np, npx\n", "from mxnet.gluon import nn\n", "from d2l import mxnet as d2l\n", "\n", "npx.set_np()\n", "\n", "# Factory for networks\n", "def get_net():\n", " net = nn.HybridSequential()\n", " net.add(nn.Dense(256, activation='relu'),\n", " nn.Dense(128, activation='relu'),\n", " nn.Dense(2))\n", " net.initialize()\n", " return net\n", "\n", "x = np.random.normal(size=(1, 512))\n", "net = get_net()\n", "net(x)" ] }, { "cell_type": "markdown", "id": "13286164", "metadata": { "origin_pos": 12, "tab": [ "mxnet" ] }, "source": [ "Bằng cách gọi hàm `hybridize`, chúng ta có thể biên dịch và tối ưu hóa tính toán trong MLP. Kết quả tính toán của mô hình vẫn không thay đổi.\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "a7496688", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:12.316607Z", "iopub.status.busy": "2022-04-26T10:53:12.315980Z", "iopub.status.idle": "2022-04-26T10:53:12.359625Z", "shell.execute_reply": "2022-04-26T10:53:12.358332Z" }, "origin_pos": 15, "tab": [ "mxnet" ] }, "outputs": [ { "data": { "text/plain": [ "array([[ 0.16526186, -0.14005628]])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.hybridize()\n", "net(x)" ] }, { "cell_type": "markdown", "id": "0f769b47", "metadata": { "origin_pos": 18, "tab": [ "mxnet" ] }, "source": [ "Điều này có vẻ gần như quá tốt để đúng: chỉ cần chỉ định một khối là `HybridSequential`, viết cùng một mã như trước và gọi `hybridize`. Khi điều này xảy ra, mạng được tối ưu hóa (chúng tôi sẽ chuẩn hóa hiệu suất bên dưới). Thật không may, điều này không hoạt động kỳ diệu cho mỗi lớp. Điều đó nói rằng, một layer sẽ không được tối ưu hóa nếu nó kế thừa từ lớp `Block` thay vì lớp `HybridBlock`.\n" ] }, { "cell_type": "markdown", "id": "4470e88d", "metadata": { "origin_pos": 21 }, "source": [ "### Tăng tốc bằng cách lai\n", "\n", "Để chứng minh sự cải thiện hiệu suất đạt được bằng cách biên dịch, chúng tôi so sánh thời gian cần thiết để đánh giá `net(x)` trước và sau khi lai. Hãy để chúng tôi xác định một lớp để đo lần này đầu tiên. Nó sẽ có ích trong suốt chương khi chúng tôi đặt ra để đo lường (và cải thiện) hiệu suất.\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "1ecc2cc1", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:12.364311Z", "iopub.status.busy": "2022-04-26T10:53:12.364008Z", "iopub.status.idle": "2022-04-26T10:53:12.372070Z", "shell.execute_reply": "2022-04-26T10:53:12.371160Z" }, "origin_pos": 22, "tab": [ "mxnet" ] }, "outputs": [], "source": [ "#@save\n", "class Benchmark:\n", " \"\"\"For measuring running time.\"\"\"\n", " def __init__(self, description='Done'):\n", " self.description = description\n", "\n", " def __enter__(self):\n", " self.timer = d2l.Timer()\n", " return self\n", "\n", " def __exit__(self, *args):\n", " print(f'{self.description}: {self.timer.stop():.4f} sec')" ] }, { "cell_type": "markdown", "id": "bb7fe3b9", "metadata": { "origin_pos": 23, "tab": [ "mxnet" ] }, "source": [ "Bây giờ chúng ta có thể gọi mạng hai lần, một lần với và một lần mà không cần lai tạo.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "86427d5b", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:12.381643Z", "iopub.status.busy": "2022-04-26T10:53:12.381315Z", "iopub.status.idle": "2022-04-26T10:53:13.417084Z", "shell.execute_reply": "2022-04-26T10:53:13.416133Z" }, "origin_pos": 26, "tab": [ "mxnet" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Without hybridization: 0.7786 sec\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "With hybridization: 0.2446 sec\n" ] } ], "source": [ "net = get_net()\n", "with Benchmark('Without hybridization'):\n", " for i in range(1000): net(x)\n", " npx.waitall()\n", "\n", "net.hybridize()\n", "with Benchmark('With hybridization'):\n", " for i in range(1000): net(x)\n", " npx.waitall()" ] }, { "cell_type": "markdown", "id": "87f0db5b", "metadata": { "origin_pos": 29, "tab": [ "mxnet" ] }, "source": [ "Như đã quan sát thấy trong các kết quả trên, sau khi một phiên bản `HybridSequential` gọi hàm `hybridize`, hiệu suất tính toán được cải thiện thông qua việc sử dụng lập trình tượng trưng.\n" ] }, { "cell_type": "markdown", "id": "e9f92454", "metadata": { "origin_pos": 32 }, "source": [ "### Lập số sê-ri\n" ] }, { "cell_type": "markdown", "id": "0d2d349f", "metadata": { "origin_pos": 33, "tab": [ "mxnet" ] }, "source": [ "Một trong những lợi ích của việc biên dịch các mô hình là chúng ta có thể serialize (lưu) mô hình và các tham số của nó vào đĩa. Điều này cho phép chúng tôi lưu trữ một mô hình theo cách độc lập với ngôn ngữ front-end của sự lựa chọn. Điều này cho phép chúng tôi triển khai các mô hình được đào tạo cho các thiết bị khác và dễ dàng sử dụng các ngôn ngữ lập trình front-end khác. Đồng thời mã thường nhanh hơn những gì có thể đạt được trong lập trình bắt buộc. Chúng ta hãy xem chức năng `export` đang hoạt động.\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "544dd919", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:13.421574Z", "iopub.status.busy": "2022-04-26T10:53:13.420828Z", "iopub.status.idle": "2022-04-26T10:53:14.184372Z", "shell.execute_reply": "2022-04-26T10:53:14.183459Z" }, "origin_pos": 36, "tab": [ "mxnet" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-rw-rw-r-- 1 d2l-worker d2l-worker 643K Apr 26 10:53 my_mlp-0000.params\r\n", "-rw-rw-r-- 1 d2l-worker d2l-worker 3.0K Apr 26 10:53 my_mlp-symbol.json\r\n" ] } ], "source": [ "net.export('my_mlp')\n", "!ls -lh my_mlp*" ] }, { "cell_type": "markdown", "id": "fe806411", "metadata": { "origin_pos": 39, "tab": [ "mxnet" ] }, "source": [ "Mô hình được phân hủy thành một tệp tham số (nhị phân lớn) và mô tả JSON của chương trình cần thiết để thực thi tính toán mô hình. Các tập tin có thể được đọc bởi các ngôn ngữ front-end khác được hỗ trợ bởi Python hoặc MXNet, chẳng hạn như C ++, R, Scala, và Perl. Chúng ta hãy xem xét một vài dòng đầu tiên trong mô tả mô hình.\n" ] }, { "cell_type": "code", "execution_count": 8, "id": "55ec1590", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:14.188440Z", "iopub.status.busy": "2022-04-26T10:53:14.187877Z", "iopub.status.idle": "2022-04-26T10:53:14.959567Z", "shell.execute_reply": "2022-04-26T10:53:14.958440Z" }, "origin_pos": 40, "tab": [ "mxnet" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\r\n", " \"nodes\": [\r\n", " {\r\n", " \"op\": \"null\", \r\n", " \"name\": \"data\", \r\n", " \"inputs\": []\r\n", " }, \r\n", " {\r\n", " \"op\": \"null\", \r\n", " \"name\": \"dense3_weight\", \r\n" ] } ], "source": [ "!head my_mlp-symbol.json" ] }, { "cell_type": "markdown", "id": "27bc7473", "metadata": { "origin_pos": 41, "tab": [ "mxnet" ] }, "source": [ "Trước đó, chúng tôi đã chứng minh rằng, sau khi gọi hàm `hybridize`, mô hình này có thể đạt được hiệu suất tính toán và tính di động vượt trội. Lưu ý, mặc dù việc lai tạo có thể ảnh hưởng đến tính linh hoạt của mô hình, đặc biệt là về dòng chảy điều khiển. \n", "\n", "Bên cạnh đó, trái với phiên bản `Block`, cần sử dụng hàm `forward`, đối với một ví dụ `HybridBlock` chúng ta cần sử dụng hàm `hybrid_forward`.\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "20f307e1", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:14.964791Z", "iopub.status.busy": "2022-04-26T10:53:14.964367Z", "iopub.status.idle": "2022-04-26T10:53:14.972927Z", "shell.execute_reply": "2022-04-26T10:53:14.971977Z" }, "origin_pos": 42, "tab": [ "mxnet" ] }, "outputs": [], "source": [ "class HybridNet(nn.HybridBlock):\n", " def __init__(self, **kwargs):\n", " super(HybridNet, self).__init__(**kwargs)\n", " self.hidden = nn.Dense(4)\n", " self.output = nn.Dense(2)\n", "\n", " def hybrid_forward(self, F, x):\n", " print('module F: ', F)\n", " print('value x: ', x)\n", " x = F.npx.relu(self.hidden(x))\n", " print('result : ', x)\n", " return self.output(x)" ] }, { "cell_type": "markdown", "id": "c087b628", "metadata": { "origin_pos": 43, "tab": [ "mxnet" ] }, "source": [ "Mã trên thực hiện một mạng đơn giản với 4 đơn vị ẩn và 2 đầu ra. Hàm `hybrid_forward` mất một đối số bổ sung `F`. Điều này là cần thiết vì, tùy thuộc vào việc mã đã được lai hay không, nó sẽ sử dụng một thư viện hơi khác (`ndarray` hoặc `symbol`) để xử lý. Cả hai lớp đều thực hiện các hàm rất giống nhau và MXNet tự động xác định đối số. Để hiểu những gì đang xảy ra, chúng ta in các đối số như một phần của lệnh gọi hàm.\n" ] }, { "cell_type": "code", "execution_count": 10, "id": "4ddc9609", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:14.977143Z", "iopub.status.busy": "2022-04-26T10:53:14.976666Z", "iopub.status.idle": "2022-04-26T10:53:15.001651Z", "shell.execute_reply": "2022-04-26T10:53:15.000478Z" }, "origin_pos": 44, "tab": [ "mxnet" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "module F: \n", "value x: [[-0.6338663 0.40156594 0.46456942]]\n", "result : [[0.01641375 0. 0. 0. ]]\n" ] }, { "data": { "text/plain": [ "array([[0.00097611, 0.00019453]])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net = HybridNet()\n", "net.initialize()\n", "x = np.random.normal(size=(1, 3))\n", "net(x)" ] }, { "cell_type": "markdown", "id": "572493b2", "metadata": { "origin_pos": 45, "tab": [ "mxnet" ] }, "source": [ "Lặp lại tính toán chuyển tiếp sẽ dẫn đến cùng một đầu ra (chúng tôi bỏ qua chi tiết). Bây giờ chúng ta hãy xem những gì sẽ xảy ra nếu chúng ta gọi hàm `hybridize`.\n" ] }, { "cell_type": "code", "execution_count": 11, "id": "e4f50c91", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:15.006918Z", "iopub.status.busy": "2022-04-26T10:53:15.006341Z", "iopub.status.idle": "2022-04-26T10:53:15.018783Z", "shell.execute_reply": "2022-04-26T10:53:15.017707Z" }, "origin_pos": 46, "tab": [ "mxnet" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "module F: \n", "value x: <_Symbol data>\n", "result : <_Symbol hybridnet0_relu0>\n" ] }, { "data": { "text/plain": [ "array([[0.00097611, 0.00019453]])" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.hybridize()\n", "net(x)" ] }, { "cell_type": "markdown", "id": "fe6bf1bc", "metadata": { "origin_pos": 47, "tab": [ "mxnet" ] }, "source": [ "Thay vì sử dụng `ndarray`, bây giờ chúng tôi sử dụng mô-đun `symbol` cho `F`. Hơn nữa, mặc dù đầu vào là loại `ndarray`, dữ liệu chảy qua mạng hiện được chuyển đổi thành loại `symbol` như một phần của quá trình biên dịch. Lặp lại cuộc gọi hàm dẫn đến một kết quả đáng ngạc nhiên:\n" ] }, { "cell_type": "code", "execution_count": 12, "id": "e82f0a16", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T10:53:15.024222Z", "iopub.status.busy": "2022-04-26T10:53:15.023478Z", "iopub.status.idle": "2022-04-26T10:53:15.031692Z", "shell.execute_reply": "2022-04-26T10:53:15.030664Z" }, "origin_pos": 48, "tab": [ "mxnet" ] }, "outputs": [ { "data": { "text/plain": [ "array([[0.00097611, 0.00019453]])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net(x)" ] }, { "cell_type": "markdown", "id": "e496169f", "metadata": { "origin_pos": 49, "tab": [ "mxnet" ] }, "source": [ "Điều này khá khác với những gì chúng ta đã thấy trước đây. Tất cả các câu lệnh in, như được định nghĩa trong `hybrid_forward`, đều bị bỏ qua. Thật vậy, sau khi lai, việc thực hiện `net(x)` không liên quan đến thông dịch viên Python nữa. Điều này có nghĩa là bất kỳ mã Python giả nào đều bị bỏ qua (chẳng hạn như các câu lệnh in) có lợi cho việc thực thi hợp lý hơn nhiều và hiệu suất tốt hơn. Thay vào đó, MXNet trực tiếp gọi phụ trợ C ++. Cũng lưu ý rằng một số chức năng không được hỗ trợ trong mô-đun `symbol` (ví dụ, `asnumpy`) và các hoạt động tại chỗ như `a += b` và `a[:] = a + b` phải được viết lại là `a = a + b`. Tuy nhiên, việc tổng hợp các mô hình đáng để nỗ lực bất cứ khi nào tốc độ quan trọng. Lợi ích có thể dao động từ các điểm phần trăm nhỏ đến hơn gấp đôi tốc độ, tùy thuộc vào độ phức tạp của mô hình, tốc độ của CPU và tốc độ và số lượng GPU.\n" ] }, { "cell_type": "markdown", "id": "66e67db7", "metadata": { "origin_pos": 50 }, "source": [ "## Tóm tắt\n", "\n", "* Lập trình bắt buộc giúp bạn dễ dàng thiết kế các mô hình mới vì có thể viết mã với luồng điều khiển và khả năng sử dụng một lượng lớn hệ sinh thái phần mềm Python.\n", "* Lập trình tượng trưng yêu cầu chúng ta chỉ định chương trình và biên dịch nó trước khi thực hiện nó. Lợi ích là cải thiện hiệu suất.\n" ] }, { "cell_type": "markdown", "id": "3bd94ff4", "metadata": { "origin_pos": 51, "tab": [ "mxnet" ] }, "source": [ "* MXNet có thể kết hợp những lợi thế của cả hai cách tiếp cận khi cần thiết.\n", "* Các mô hình được xây dựng bởi các lớp `HybridSequential` và `HybridBlock` có thể chuyển đổi các chương trình bắt buộc thành các chương trình tượng trưng bằng cách gọi hàm `hybridize`.\n" ] }, { "cell_type": "markdown", "id": "5815a36c", "metadata": { "origin_pos": 52 }, "source": [ "## Bài tập\n" ] }, { "cell_type": "markdown", "id": "928f0dd3", "metadata": { "origin_pos": 53, "tab": [ "mxnet" ] }, "source": [ "1. Thêm `x.asnumpy()` vào dòng đầu tiên của hàm `hybrid_forward` của lớp `HybridNet` trong phần này. Thực hiện mã và quan sát các lỗi bạn gặp phải. Tại sao họ lại xảy ra?\n", "1. Điều gì xảy ra nếu chúng ta thêm luồng điều khiển, tức là, các câu lệnh Python `if` và `for` trong hàm `hybrid_forward`?\n", "1. Xem lại các mô hình mà bạn quan tâm trong các chương trước. Bạn có thể cải thiện hiệu suất tính toán của họ bằng cách triển khai lại chúng không?\n" ] }, { "cell_type": "markdown", "id": "88399902", "metadata": { "origin_pos": 55, "tab": [ "mxnet" ] }, "source": [ "[Discussions](https://discuss.d2l.ai/t/360)\n" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }