Sử dụng Vectorization – Một sự thay thế siêu nhanh cho các vòng lặp trong Python
Giới thiệu
Loops đến với chúng ta một cách tự nhiên, chúng ta tìm hiểu về Loops trong hầu hết các ngôn ngữ lập trình. Vì vậy, theo mặc định, chúng tôi bắt đầu thực hiện các vòng lặp bất cứ khi nào có một hoạt động lặp đi lặp lại. Nhưng khi chúng ta làm việc với một số lượng lớn các lần lặp lại (hàng triệu/ hàng tỷ hàng), sử dụng các vòng lặp là một tội ác. Bạn có thể bị mắc kẹt trong nhiều giờ, để sau đó nhận ra rằng nó sẽ không hoạt động. Đây là nơi mà việc triển khai Vectorisation trong python trở nên siêu quan trọng.
Vectorization là gì?
Vectorization là kỹ thuật thực hiện các phép toán mảng (NumPy) trên tập dữ liệu. Trong nền, nó áp dụng các phép toán cho tất cả các phần tử của một mảng hoặc chuỗi trong một lần (không giống như vòng lặp ‘for’ thao tác một hàng tại một thời điểm).
Trong bài viết này, chúng ta sẽ xem xét một số trường hợp sử dụng mà chúng ta có thể dễ dàng thay thế các vòng lặp python bằng Vectorization. Điều này sẽ giúp bạn tiết kiệm thời gian và trở nên ‘pro’ hơn trong coding.
Vấn đề 1: Tìm tổng số số
Đầu tiên, chúng ta sẽ xem xét một ví dụ cơ bản về việc tìm tổng các số bằng cách sử dụng các vòng lặp và Vectorization trong python.
Dùng vòng lặp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">import</span> time start <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment"># iterative sum</span> total <span class="token operator">=</span> <span class="token number">0</span> <span class="token comment"># iterating through 1.5 Million numbers</span> <span class="token keyword">for</span> item <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1500000</span><span class="token punctuation">)</span><span class="token punctuation">:</span> total <span class="token operator">=</span> total <span class="token operator">+</span> item <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'sum is:'</span> <span class="token operator">+</span> <span class="token builtin">str</span><span class="token punctuation">(</span>total<span class="token punctuation">)</span><span class="token punctuation">)</span> end <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span>end <span class="token operator">-</span> start<span class="token punctuation">)</span> <span class="token comment">#1124999250000</span> <span class="token comment">#0.14 Seconds</span> |
Dùng Vectorization
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token keyword">import</span> numpy <span class="token keyword">as</span> np start <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment"># vectorized sum - using numpy for vectorization</span> <span class="token comment"># np.arange create the sequence of numbers from 0 to 1499999</span> <span class="token keyword">print</span><span class="token punctuation">(</span>np<span class="token punctuation">.</span><span class="token builtin">sum</span><span class="token punctuation">(</span>np<span class="token punctuation">.</span>arange<span class="token punctuation">(</span><span class="token number">1500000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> end <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span>end <span class="token operator">-</span> start<span class="token punctuation">)</span> <span class="token comment">##1124999250000</span> <span class="token comment">##0.008 Seconds</span> |
Vectorization mất ít thời gian hơn ~ 18 lần để thực hiện so với lần lặp lại bằng cách sử dụng hàm phạm vi. Sự khác biệt này sẽ trở nên đáng kể hơn khi làm việc với Pandas DataFrame.
Vấn đề 2: Hoạt động tính toán (trên DataFrame)
trong khi làm việc với Pandas DataFrame, các nhà phát triển sử dụng các vòng lặp để tạo các cột dẫn xuất mới bằng cách sử dụng các phép toán. Trong ví dụ sau, chúng ta có thể thấy các vòng lặp có thể được thay thế dễ dàng như thế nào bằng Vectorization cho các trường hợp sử dụng như vậy.
Tạo DataFrame
DataFrame là dữ liệu dạng bảng dưới dạng hàng và cột. Chúng ta sẽ tạo một pandas DataFrame có 5 triệu hàng và 4 cột chứa đầy các giá trị ngẫu nhiên từ 0 đến 50.
1 2 3 4 5 6 7 | <span class="token keyword">import</span> numpy <span class="token keyword">as</span> np <span class="token keyword">import</span> pandas <span class="token keyword">as</span> pd df <span class="token operator">=</span> pd<span class="token punctuation">.</span>DataFrame<span class="token punctuation">(</span>np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>randint<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">,</span> size<span class="token operator">=</span><span class="token punctuation">(</span><span class="token number">5000000</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> columns<span class="token operator">=</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">,</span><span class="token string">'b'</span><span class="token punctuation">,</span><span class="token string">'c'</span><span class="token punctuation">,</span><span class="token string">'d'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> df<span class="token punctuation">.</span>shape <span class="token comment"># (5000000, 5)</span> df<span class="token punctuation">.</span>head<span class="token punctuation">(</span><span class="token punctuation">)</span> |
Chúng ta sẽ tạo một cột mới ‘tỷ lệ’ để tìm tỷ lệ của cột ‘d’ và ‘c’.
Dùng vòng lặp
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">import</span> time start <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment"># Iterating through DataFrame using iterrows</span> <span class="token keyword">for</span> idx<span class="token punctuation">,</span> row <span class="token keyword">in</span> df<span class="token punctuation">.</span>iterrows<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token comment"># creating a new column </span> df<span class="token punctuation">.</span>at<span class="token punctuation">[</span>idx<span class="token punctuation">,</span><span class="token string">'ratio'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">100</span> <span class="token operator">*</span> <span class="token punctuation">(</span>row<span class="token punctuation">[</span><span class="token string">"d"</span><span class="token punctuation">]</span> <span class="token operator">/</span> row<span class="token punctuation">[</span><span class="token string">"c"</span><span class="token punctuation">]</span><span class="token punctuation">)</span> end <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span>end <span class="token operator">-</span> start<span class="token punctuation">)</span> <span class="token comment">### 109 Seconds</span> |
Dùng Vectorization
1 2 3 4 5 6 7 | start <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> df<span class="token punctuation">[</span><span class="token string">"ratio"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">100</span> <span class="token operator">*</span> <span class="token punctuation">(</span>df<span class="token punctuation">[</span><span class="token string">"d"</span><span class="token punctuation">]</span> <span class="token operator">/</span> df<span class="token punctuation">[</span><span class="token string">"c"</span><span class="token punctuation">]</span><span class="token punctuation">)</span> end <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span>end <span class="token operator">-</span> start<span class="token punctuation">)</span> <span class="token comment">### 0.12 seconds</span> |
Chúng ta có thể thấy một sự cải thiện đáng kể với DataFrame, thời gian được thực hiện bởi hoạt động vectơ hóa nhanh hơn gần 1000 lần so với các vòng lặp trong Python.
Vấn đề 3: Các câu lệnh IF-Else (trên DataFrame)
Chúng ta thực hiện rất nhiều hoạt động yêu cầu sử dụng loại logic ‘if-else ‘. Chúng ta có thể dễ dàng thay thế các logic này bằng các hoạt động vector hóa trong Python. Chúng ta hãy xem xét ví dụ sau để hiểu nó tốt hơn (chúng ta sẽ sử dụng DataFrame mà chúng ta đã tạo trong vấn đề 2): Hãy tưởng tượng chúng ta muốn tạo một cột mới ‘e ‘dựa trên một số điều kiện trên cột cũ ‘a’.
Dùng vòng lặp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">import</span> time start <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment"># Iterating through DataFrame using iterrows</span> <span class="token keyword">for</span> idx<span class="token punctuation">,</span> row <span class="token keyword">in</span> df<span class="token punctuation">.</span>iterrows<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">if</span> row<span class="token punctuation">.</span>a <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">:</span> df<span class="token punctuation">.</span>at<span class="token punctuation">[</span>idx<span class="token punctuation">,</span><span class="token string">'e'</span><span class="token punctuation">]</span> <span class="token operator">=</span> row<span class="token punctuation">.</span>d <span class="token keyword">elif</span> <span class="token punctuation">(</span>row<span class="token punctuation">.</span>a <span class="token operator"><=</span> <span class="token number">25</span><span class="token punctuation">)</span> <span class="token operator">&</span> <span class="token punctuation">(</span>row<span class="token punctuation">.</span>a <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">:</span> df<span class="token punctuation">.</span>at<span class="token punctuation">[</span>idx<span class="token punctuation">,</span><span class="token string">'e'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">(</span>row<span class="token punctuation">.</span>b<span class="token punctuation">)</span><span class="token operator">-</span><span class="token punctuation">(</span>row<span class="token punctuation">.</span>c<span class="token punctuation">)</span> <span class="token keyword">else</span><span class="token punctuation">:</span> df<span class="token punctuation">.</span>at<span class="token punctuation">[</span>idx<span class="token punctuation">,</span><span class="token string">'e'</span><span class="token punctuation">]</span> <span class="token operator">=</span> row<span class="token punctuation">.</span>b <span class="token operator">+</span> row<span class="token punctuation">.</span>c end <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span>end <span class="token operator">-</span> start<span class="token punctuation">)</span> <span class="token comment">### Time taken: 177 seconds</span> |
Dùng Vectorization
1 2 3 4 5 6 7 8 | start <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> df<span class="token punctuation">[</span><span class="token string">'e'</span><span class="token punctuation">]</span> <span class="token operator">=</span> df<span class="token punctuation">[</span><span class="token string">'b'</span><span class="token punctuation">]</span> <span class="token operator">+</span> df<span class="token punctuation">[</span><span class="token string">'c'</span><span class="token punctuation">]</span> df<span class="token punctuation">.</span>loc<span class="token punctuation">[</span>df<span class="token punctuation">[</span><span class="token string">'a'</span><span class="token punctuation">]</span> <span class="token operator"><=</span> <span class="token number">25</span><span class="token punctuation">,</span> <span class="token string">'e'</span><span class="token punctuation">]</span> <span class="token operator">=</span> df<span class="token punctuation">[</span><span class="token string">'b'</span><span class="token punctuation">]</span> <span class="token operator">-</span>df<span class="token punctuation">[</span><span class="token string">'c'</span><span class="token punctuation">]</span> df<span class="token punctuation">.</span>loc<span class="token punctuation">[</span>df<span class="token punctuation">[</span><span class="token string">'a'</span><span class="token punctuation">]</span><span class="token operator">==</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">'e'</span><span class="token punctuation">]</span> <span class="token operator">=</span> df<span class="token punctuation">[</span><span class="token string">'d'</span><span class="token punctuation">]</span>end <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span>end <span class="token operator">-</span> start<span class="token punctuation">)</span> <span class="token comment">## 0.28007707595825195 sec</span> |
Thời gian thực hiện bởi phép toán Vectorization nhanh hơn 600 lần so với các vòng lặp python với các câu lệnh if-else.
Vân đề 4 (nâng cao): Dùng trong Machine Learning/Deep Learning Networks
Deep Learning đòi hỏi chúng ta phải giải nhiều phương trình phức tạp và điều đó cũng cho hàng triệu, hàng tỷ hàng. Chạy các vòng lặp trong Python để giải các phương trình này rất chậm và Vectorization là giải pháp tối ưu.
Ví dụ: để tính giá trị của y cho hàng triệu hàng trong phương trình hồi quy đa tuyến tính sau đây:
chúng ta có thể thay thế các vòng lặp bằng Vectorization. Các giá trị của m1, m2, m3… được xác định bằng cách giải phương trình trên bằng cách sử dụng hàng triệu giá trị tương ứng với x1,x2,x3… (để đơn giản, chúng ta sẽ chỉ xem xét một bước nhân đơn giản)
Tạo data
1 2 3 4 5 6 7 | <span class="token keyword">import</span> numpy <span class="token keyword">as</span> np <span class="token comment"># setting initial values of m </span> m <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>rand<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token comment"># input values for 5 million rows</span> x <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>rand<span class="token punctuation">(</span><span class="token number">5000000</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">)</span> |
Dùng vòng lặp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">import</span> numpy <span class="token keyword">as</span> np m <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>rand<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">)</span> x <span class="token operator">=</span> np<span class="token punctuation">.</span>random<span class="token punctuation">.</span>rand<span class="token punctuation">(</span><span class="token number">5000000</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">)</span> total <span class="token operator">=</span> <span class="token number">0</span> tic <span class="token operator">=</span> time<span class="token punctuation">.</span>process_time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">5000000</span><span class="token punctuation">)</span><span class="token punctuation">:</span> total <span class="token operator">=</span> <span class="token number">0</span> <span class="token keyword">for</span> j <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">:</span> total <span class="token operator">=</span> total <span class="token operator">+</span> x<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token operator">*</span>m<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> zer<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> total toc <span class="token operator">=</span> time<span class="token punctuation">.</span>process_time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span> <span class="token punctuation">(</span><span class="token string">"Computation time = "</span> <span class="token operator">+</span> <span class="token builtin">str</span><span class="token punctuation">(</span><span class="token punctuation">(</span>toc <span class="token operator">-</span> tic<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"seconds"</span><span class="token punctuation">)</span> <span class="token comment">####Computation time = 28.228 seconds</span> |
Dùng Vectorization
1 2 3 4 5 6 7 8 9 10 | tic <span class="token operator">=</span> time<span class="token punctuation">.</span>process_time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">#dot product </span> np<span class="token punctuation">.</span>dot<span class="token punctuation">(</span>x<span class="token punctuation">,</span>m<span class="token punctuation">.</span>T<span class="token punctuation">)</span> toc <span class="token operator">=</span> time<span class="token punctuation">.</span>process_time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span> <span class="token punctuation">(</span><span class="token string">"Computation time = "</span> <span class="token operator">+</span> <span class="token builtin">str</span><span class="token punctuation">(</span><span class="token punctuation">(</span>toc <span class="token operator">-</span> tic<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"seconds"</span><span class="token punctuation">)</span> <span class="token comment">####Computation time = 0.107 seconds</span> |
np.dot thực hiện phép nhân ma trận Vectorized trong phần phụ trợ. Nó nhanh hơn 165 lần so với các vòng lặp trong python.
Kết luận
Vectorization trong Python là siêu nhanh và nên được ưu tiên hơn các vòng lặp, bất cứ khi nào chúng tôi làm việc với các bộ dữ liệu rất lớn. Bắt đầu thực hiện nó theo thời gian và bạn sẽ trở nên thoải mái với suy nghĩ dọc theo dòng với code của bạn.
Kham khảo
https://medium.com/codex/say-goodbye-to-loops-in-python-and-welcome-vectorization-e4df66615a52
https://towardsdatascience.com/how-to-speedup-data-processing-with-numpy-vectorization-12acac71cfca