🍀Tóm tắt buổi trước

Trong tập trước, chúng ta đã đi qua các khái niệm cơ bản liên quan tới thư viện NumPy cũng như làm quen với mảng đa chiều NumPy (ndarray).

Có lẽ phần trừu tượng nhất của NumPy cơ bản là cách các bạn Indexing và Slicing vào từng chiều của ndarray. May mắn thay như mình có hướng dẫn trong buổi Livestream chúng ta chỉ cần hình tượng hóa nó như những lớp vỏ và khi đó mọi chuyện hóa ra lại đơn giản hơn rất nhiều:

Ngày 10

🔴 Buổi Live ngày 10:

Còn trong tập này là phần mở rộng của NumPy, cụ thể hơn chúng ta sẽ học qua các hàm ufunc, cách sử dụng các phương thức vectorization, I/O ndarray và Đại số tuyến tính trong NumPy

🍀Pseudorandom number generation

Nếu bỗng nhiên bạn cần một tập hợp bộ dữ liệu các số tuân theo phân phối chuẩn với Mean = 0 và Standard Deviation = 1 hay một mảng (NumPy array) tuân theo một phân phối xác suất nào đó! Rõ ràng là việc lần mò tìm được bộ dữ liệu đúng nhu cầu là chuyện rất khổ sở, chưa kể là mất thời gian.

May thay thư viện numpy hỗ trợ chung ta nhưng phương thức tự động tạo ra nhưng bộ dữ liệu này mà lại rất nhanh chóng nữa

Ví dụ bạn có thể tạo một mảng 4 x 4 một mẫu tuân theo phân phối chuẩn bằng cách dùng numpy.random.standard.normal()

In [141]: samples = np.random.standard_normal(size=(4, 4))

In [142]: samples
Out[142]: 
array([[-0.2047,  0.4789, -0.5194, -0.5557],
       [ 1.9658,  1.3934,  0.0929,  0.2817],
       [ 0.769 ,  1.2464,  1.0072, -1.2962],
       [ 0.275 ,  0.2289,  1.3529,  0.8864]])

🔥 Test speed

Như bài trước mình có đề cập thì mặc định Python có hàm random – gọi là mô_đun cũng dược, nhưng nó chỉ tạo từng giá trị mỗi lúc. Còn với hàm numpy.random thì đem lại hiệu suất nhanh hơn gấp nhiều lần để tạo ra những mẫu lớn:

In [143]: from random import normalvariate

In [144]: N = 1_000_000

In [145]: %timeit samples = [normalvariate(0, 1) for _ in range(N)]
490 ms +- 2.23 ms per loop (mean +- std. dev. of 7 runs, 1 loop each)

In [146]: %timeit np.random.standard_normal(N)
32.6 ms +- 271 us per loop (mean +- std. dev. of 7 runs, 10 loops each)

Trên thực tế thì các số ngẫu nhiên này không thực sự “ngẫu nhiên” ( hay đúng hơn là giả ngẫu nhiên – pseudorandom) mà chúng được tạo bởi một bộ khởi tạo "generator" đã cấu hình sẵn giúp xác định giá trị nào được tạo.

Các hàm như numpy.random.standard_normal sử dụng mô-đun numpy.random mặc định của bộ khởi tạo trên nhưng bạn hoàn toán có thể tùy biến dùng bộ khởi tạo với cấu hình riêng:

In [147]: rng = np.random.default_rng(seed=12345)

In [148]: data = rng.standard_normal((2, 3))

Trong câu lệnh trên, tham số seed xác định trạng thái đầu vào của bộ khởi tạo, và trạng thái sẽ thay đổi mỗi lần đối tượng rng được sử dụng để tạo ra dữ liệu.

Bản thân đổi tượng rng tách biệt riêng giữa những generators khác và kiểu đối tượng random_generator_objects:

In [149]: type(rng)
Out[149]: numpy.random._generator.Generator
Note 💡

Dưới đây là danh sách một số phương thức có sẵn cho đối tượng rng chúng ta gọi để khởi tạo:

NumPy random number generator methods

🍀 Universal Functions: Các hàm tính toán mảng nhanh

Một universal function hay ufunc là một hàm thực hiện các phép tính toàn bộ phần tử ("element-wise operations") của các dữ liệu trong ndarrays.

Một cách ngắn gọn hơn bạn có thể hình dung ufunc giống một gói phép tính vec-tơ ăn liền ("fast vectorized wrappers") cho các hàm đơn giản nhận một hoặc nhiều giá trị đại số và tạo ra một hoặc các giá trị đại số tương ứng.

Để dễ hiểu ta tìm hiểu qua về hai biến đổi toàn mảng đơn giản, numpy.sqrtnumpy.exp (tính ex với e là số Euler 2.71828)

In [150]: arr = np.arange(10)

In [151]: arr
Out[151]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [152]: np.sqrt(arr)
Out[152]: 
array([0.    , 1.    , 1.4142, 1.7321, 2.    , 2.2361, 2.4495, 2.6458,
       2.8284, 3.    ])

In [153]: np.exp(arr)
Out[153]: 
array([   1.    ,    2.7183,    7.3891,   20.0855,   54.5982,  148.4132,
        403.4288, 1096.6332, 2980.958 , 8103.0839])

Các ufunc được chia thành hai loại:

  • ufunc unary: là những ufunc chỉ lấy một tham số đầu vào và trả về một tham số đầu ra duy nhất
  • ufunc binary: là những ufunc lấy hai tham số đầu vào và trả về một tham số đầu ra duy nhất. Vd: numpy.add hay numpy.maximum
In [154]: x = rng.standard_normal(8)

In [155]: y = rng.standard_normal(8)

In [156]: x
Out[156]: 
array([-1.3678,  0.6489,  0.3611, -1.9529,  2.3474,  0.9685, -0.7594,
        0.9022])

In [157]: y
Out[157]: 
array([-0.467 , -0.0607,  0.7888, -1.2567,  0.5759,  1.399 ,  1.3223,
       -0.2997])

In [158]: np.maximum(x, y)
Out[158]: 
array([-0.467 ,  0.6489,  0.7888, -1.2567,  2.3474,  1.399 ,  1.3223,
        0.9022])

Mặc dù không phổ biến, một ufunc có thể trả về nhiều mảng. Ví dụ: numpy.modf một phiên bản vector của hàm math.modf được tích hợp sẵn trong Python, nó trả về phần thập phân và phần nguyên của một mảng số thực ("a floating-point array"):

In [159]: arr = rng.standard_normal(7) * 5

In [160]: arr
Out[160]: array([ 4.5146, -8.1079, -0.7909,  2.2474, -6.718 , -0.4084,  8.6237])

In [161]: remainder, whole_part = np.modf(arr)

In [162]: remainder
Out[162]: array([ 0.5146, -0.1079, -0.7909,  0.2474, -0.718 , -0.4084,  0.6237])

In [163]: whole_part
Out[163]: array([ 4., -8., -0.,  2., -6., -0.,  8.])

Ufunc chấp nhận một tham số tùy chọn out cho phép chúng gán kết quả của chúng vào một mảng hiện có thay vì tạo một mảng mới:

In [164]: arr
Out[164]: array([ 4.5146, -8.1079, -0.7909,  2.2474, -6.718 , -0.4084,  8.6237])

In [165]: out = np.zeros_like(arr)

In [166]: np.add(arr, 1)
Out[166]: array([ 5.5146, -7.1079,  0.2091,  3.2474, -5.718 ,  0.5916,  9.6237])

In [167]: np.add(arr, 1, out=out)
Out[167]: array([ 5.5146, -7.1079,  0.2091,  3.2474, -5.718 ,  0.5916,  9.6237])

In [168]: out
Out[168]: array([ 5.5146, -7.1079,  0.2091,  3.2474, -5.718 ,  0.5916,  9.6237])
Bảng unary ufunc
Bảng binary ufunc

Trên chỉ là hai bảng rút trích một số hàm, thư viện Numpy vẫn đang bổ sung rất nhiều hàm mới vậy nên mọi người cứ cần hàm gì thì google ra tài liệu để đọc hen

🍀 Array-oriented programming

Lập trình hướng mảng với NumPy arrays cho phép bạn thực hiện nhiều tác vụ xử lý dữ liệu liên quan tới mảng đa chiều ngắn gọn mà không cần viết các vòng lặp. Khả năng vectorize này trong bài lần trước mình có đề cập qua rồi á.

Nói chung các toán tử mảng vector luôn nhanh hơn các cách xử lý mã thuần Python.

Lấy ví dụ đơn giản sau, giả sử chúng ta muốn thực hiện hàm sqrt(x^2 + y^2) trên một lưới các giá trị.

B1: Ta tạo ra các mảng 2 chiều bằng hàm numpy.meshgrid sẽ nhận 2 mảng một chiều và trả về 2 ma trận 2-chiều tương ứng cho các cặp (x, y) trong 2 mảng đầu:

In [169]: points = np.arange(-5, 5, 0.01) # 100 equally spaced points

In [170]: xs, ys = np.meshgrid(points, points)

In [171]: ys
Out[171]: 
array([[-5.  , -5.  , -5.  , ..., -5.  , -5.  , -5.  ],
       [-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99],
       [-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98],
       ...,
       [ 4.97,  4.97,  4.97, ...,  4.97,  4.97,  4.97],
       [ 4.98,  4.98,  4.98, ...,  4.98,  4.98,  4.98],
       [ 4.99,  4.99,  4.99, ...,  4.99,  4.99,  4.99]])

B2: Thực hiện phép tinh mong muốn bằng cách xây dựng hàm với biểu thức tương ứng:

In [172]: z = np.sqrt(xs ** 2 + ys ** 2)

In [173]: z
Out[173]: 
array([[7.0711, 7.064 , 7.0569, ..., 7.0499, 7.0569, 7.064 ],
       [7.064 , 7.0569, 7.0499, ..., 7.0428, 7.0499, 7.0569],
       [7.0569, 7.0499, 7.0428, ..., 7.0357, 7.0428, 7.0499],
       ...,
       [7.0499, 7.0428, 7.0357, ..., 7.0286, 7.0357, 7.0428],
       [7.0569, 7.0499, 7.0428, ..., 7.0357, 7.0428, 7.0499],
       [7.064 , 7.0569, 7.0499, ..., 7.0428, 7.0499, 7.0569]])

📑 Mở rộng xíu: Ta có thể dùng thư viện matplotlib để biểu diễn kết quả z trên:

In [174]: import matplotlib.pyplot as plt

In [175]: plt.imshow(z, cmap=plt.cm.gray, extent=[-5, 5, -5, 5])
Out[175]: <matplotlib.image.AxesImage at 0x17f04b040>

In [176]: plt.colorbar()
Out[176]: <matplotlib.colorbar.Colorbar at 0x1810661a0>

In [177]: plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
Out[177]: Text(0.5, 1.0, 'Image plot of $\\sqrt{x^2 + y^2}$ for a grid of values'
)
Plot of function evaluated on a grid

Nếu làm trên IPython console thì để đóng biểu đồ lại ta thêm hàm sau:

In [179]: plt.close("all")

Thuật ngữ "vectorization" được sử dụng để mô tả một số khái niệm khoa học máy tính khác, nhưng trong cuốn sách này, mình sử dụng nó để mô tả các thao tác trên toàn bộ mảng dữ liệu cùng một lúc thay vì đi từng giá trị bằng cách sử dụng vòng lặp for của Python.

Note 💡

🔥 Biểu thức điều kiện Logic với Toán tử mảng

Ta có numpy.where là hàm vectorized của biểu thức tương ứng x if condition else y. Gỉa sử ta có một mảng Boolean và hai mảng giá trị có kích cỡ giống nhau:

In [180]: xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])

In [181]: yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])

In [182]: cond = np.array([True, False, True, True, False])

Nếu ta muốn lấy các phần tử của xarr khi cond là True ngược lại lấy từ yarr. Nếu chưa học tới đây ta có thể dùng list comprehension để thực hiện yêu cầu này:

In [183]: result = [(x if c else y)
   .....:           for x, y, c in zip(xarr, yarr, cond)]

In [184]: result
Out[184]: [1.1, 2.2, 1.3, 1.4, 2.5]

Nhưng cách làm trên có vài vấn đề:

  • Với các mảng lớn thì hiệu năng sẽ rất chậm (vì tất cả tác vụ phải được thực hiện và biên dịch)
  • Ta chỉ có thể làm với mảng một chiều

Với numpy.where mọi chuyện lại khác

In [185]: result = np.where(cond, xarr, yarr)

In [186]: result
Out[186]: array([1.1, 2.2, 1.3, 1.4, 2.5])

Tham số thứ 2 và thứ 3 của numpy.where không nhất thiết phải là mảng mà một hoặc cả hai có thể là giá trị đại số:

Ví dụ 1: Cả hai đều là giá trị đại số

Một cách dùng hay sử dụng là khởi tạo một mảng mới từ mảng khác: Gỉa sử bạn có một ma trận các số random và bạn muốn thay thế các giá trị dương bằng 2 và âm bằng -2:

In [187]: arr = rng.standard_normal((4, 4))

In [188]: arr
Out[188]: 
array([[ 2.6182,  0.7774,  0.8286, -0.959 ],
       [-1.2094, -1.4123,  0.5415,  0.7519],
       [-0.6588, -1.2287,  0.2576,  0.3129],
       [-0.1308,  1.27  , -0.093 , -0.0662]])

In [189]: arr > 0
Out[189]: 
array([[ True,  True,  True, False],
       [False, False,  True,  True],
       [False, False,  True,  True],
       [False,  True, False, False]])

In [190]: np.where(arr > 0, 2, -2)
Out[190]: 
array([[ 2,  2,  2, -2],
       [-2, -2,  2,  2],
       [-2, -2,  2,  2],
       [-2,  2, -2, -2]])

Ví dụ 2: Kết hợp số và mảng

Bạn có thể kết hợp các giá trị đại số và các mảng khi dùng numpy.where. Ví dụ bạn có thể thay các giá trị nguyên dương trong arr bằng hằng số 2 như sau:

In [191]: np.where(arr > 0, 2, arr) # set only positive values to 2
Out[191]: 
array([[ 2.    ,  2.    ,  2.    , -0.959 ],
       [-1.2094, -1.4123,  2.    ,  2.    ],
       [-0.6588, -1.2287,  2.    ,  2.    ],
       [-0.1308,  2.    , -0.093 , -0.0662]])

🔥 Các phương thức Toán và Thống kê

Một tập hợp các hàm toán học tính toán số liệu thống kê trên toàn bộ mảng hoặc trên dữ liệu dọc theo một trục có thể truy cập dưới dạng các phương thức của mảng.

Bạn có thể sử dụng các hàm tổng hợp "aggregations" (đôi khi được gọi là hàm nén "reductions") như sum, meanstd (độ lệch chuẩn).

📑 Có 2 cách để sử dụng: Truyền trực tiếp tham số mảng (“calling the array instance method”) hoặc gọi hàm (“using the top-level NumPy function.”)

Sau đây, mình khởi tạo một bộ dữ liệu các số ngẫu nhiên tuân theo phân phối bình thường ("normally distributed") và tính toán một số số liệu thống kê:

In [192]: arr = rng.standard_normal((5, 4))

In [193]: arr
Out[193]: 
array([[-1.1082,  0.136 ,  1.3471,  0.0611],
       [ 0.0709,  0.4337,  0.2775,  0.5303],
       [ 0.5367,  0.6184, -0.795 ,  0.3   ],
       [-1.6027,  0.2668, -1.2616, -0.0713],
       [ 0.474 , -0.4149,  0.0977, -1.6404]])

In [194]: arr.mean()
Out[194]: -0.08719744457434529

In [195]: np.mean(arr)
Out[195]: -0.08719744457434529

In [196]: arr.sum()
Out[196]: -1.743948891486906

📑 Các hàm meansum có thể nhận tham số tùy chọn axis để tính các thông số thống kê dựa trên một trục xác định, kết quả sẽ tùy vào mảng gốc mà sẽ cho ra một mảng có ít chiều hơn:

In [197]: arr.mean(axis=1)
Out[197]: array([ 0.109 ,  0.3281,  0.165 , -0.6672, -0.3709])

In [198]: arr.sum(axis=0)
Out[198]: array([-1.6292,  1.0399, -0.3344, -0.8203])

Ở đây arr.mean(axis=1) nghĩa là “tính trung bình dọc các cột” còn arr.sum(axis=0) là “tính tổng dọc các dòng”

📑 Một số phương thức khác như cumsumcumprod không aggregate thay vào đó tạo ra một mảng chứa các kết quả trung gian. Với hai hàm trên là hai hàm lũy tích

In [199]: arr = np.array([0, 1, 2, 3, 4, 5, 6, 7])

In [200]: arr.cumsum()
Out[200]: array([ 0,  1,  3,  6, 10, 15, 21, 28])

Các mảng đa chiều thì hàm lũy tích như cumsum trả về một mảng cùng kích thước nhưng với các tổng tích lũy theo trục chỉ định dựa trên từng lát cắt chiều thấp hơn:

In [201]: arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])

In [202]: arr
Out[202]: 
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])

In [203]: arr.cumsum(axis=0)
Out[203]: 
array([[ 0,  1,  2],
       [ 3,  5,  7],
       [ 9, 12, 15]])

In [204]: arr.cumsum(axis=1)
Out[204]: 
array([[ 0,  1,  3],
       [ 3,  7, 12],
       [ 6, 13, 21]])

Tương tự như trên biểu thức arr.cumsum(axis=0) tính “tổng tích lũy theo hàng”, trong khi arr.cumsum(axis=1) tính “tổng lũy tích theo cột”.

Basic array statistical methods

🔥Các phương thức với mảng Boolean

📑 Các giá trị Booleans chỉ gồm 1 (True) và 0 (False) vì vậy lấy ví dụ hàm sum thì ý nghĩa dùng hàm thường là đếm số lượng giá trị True trong mảng.

In [205]: arr = rng.standard_normal(100)

In [206]: (arr > 0).sum() # Number of positive values
Out[206]: 48

In [207]: (arr <= 0).sum() # Number of non-positive values
Out[207]: 52

Biểu thức trong ngoặc tròn sau (arr > 0).sum() dùng kèm hàm sum() giúp ta thao tác trên một mảng tạm arr >0

📑 Ta còn hai phương thức anyall cũng hữu ích khi làm việc với mảng Boolean. any giúp kiểm tra xem có ít nhất một giá trị True không trong khi all kiểm tra có phải toàn bộ mảng đều là True hay không:

In [208]: bools = np.array([False, False, True, False])

In [209]: bools.any()
Out[209]: True

In [210]: bools.all()
Out[210]: False

Hai hàm anyall cũng chạy được trên các mảng khác với các phần từ khác không được đánh dấu là True

Note 💡

🔥 Sắp xếp trên mảng

Giống các kiểu mặc định trong Python, NumPy array cũng có thể sort với phương thức cùng tên sort

In [211]: arr = rng.standard_normal(6)

In [212]: arr
Out[212]: array([ 0.0773, -0.6839, -0.7208,  1.1206, -0.0548, -0.0824])

In [213]: arr.sort()

In [214]: arr
Out[214]: array([-0.7208, -0.6839, -0.0824, -0.0548,  0.0773,  1.1206])

Với mảng đa chiều thì nó sẽ sort theo từng chiều dựa trên trục tọa đồ mà ta truyền vô tham số:

In [215]: arr = rng.standard_normal((5, 3))

In [216]: arr
Out[216]: 
array([[ 0.936 ,  1.2385,  1.2728],
       [ 0.4059, -0.0503,  0.2893],
       [ 0.1793,  1.3975,  0.292 ],
       [ 0.6384, -0.0279,  1.3711],
       [-2.0528,  0.3805,  0.7554]])

In [217]: arr.sort(axis=0)

In [218]: arr
Out[218]: 
array([[-2.0528, -0.0503,  0.2893],
       [ 0.1793, -0.0279,  0.292 ],
       [ 0.4059,  0.3805,  0.7554],
       [ 0.6384,  1.2385,  1.2728],
       [ 0.936 ,  1.3975,  1.3711]])

In [219]: arr.sort(axis=1)

In [220]: arr
Out[220]: 
array([[-2.0528, -0.0503,  0.2893],
       [-0.0279,  0.1793,  0.292 ],
       [ 0.3805,  0.4059,  0.7554],
       [ 0.6384,  1.2385,  1.2728],
       [ 0.936 ,  1.3711,  1.3975]])

arr.sort(axis=0)  là xếp theo mỗi cột còn arr.sort(axis=1) là theo dòng

phương thức bậc cao (top-level method) trả về một bảng sao chép đã sắp xếp sẵn (giống hàm mặc định sorted Python) thay vì sửa đổi bản gốc:

In [221]: arr2 = np.array([5, -10, 7, 1, 0, -3])

In [222]: sorted_arr2 = np.sort(arr2)

In [223]: sorted_arr2
Out[223]: array([-10,  -3,   0,   1,   5,   7])
Note 💡

Ta còn có thể sort gián tiếp, một kỹ thuật nâng cao sau này mình nói sau. Một vài kiểu biến đổi liên quan tới sort như: sort một bảng theo một hoặc nhiều cột sẽ tìm hiểu sau trong phần pandas.

Unique và các hàm Set Logic

NumPy có toán tử set (gán) cơ bản cho mảng 1-chiều ndarrays. Hàm chúng ta thường dùng là numpy.unique trả về một mảng các giá trị độc nhất đã được sắp xếp:

In [224]: names = np.array(["Bob", "Will", "Joe", "Bob", "Will", "Joe", "Joe"])

In [225]: np.unique(names)
Out[225]: array(['Bob', 'Joe', 'Will'], dtype='<U4')

In [226]: ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])

In [227]: np.unique(ints)
Out[227]: array([1, 2, 3, 4])

Nếu chưa học NumPy thì ta có thể sử dụng cách sau để ra kết quả tương tự:

In [228]: sorted(set(names))
Out[228]: ['Bob', 'Joe', 'Will']

Tất nhiên trong nhiều trường hợp khác thì phiên bản NumPy sẽ nhanh hơn (dù nó cho ra NumPy array thay vì Python list).

Một hàm khác là hàm numpy.in1d cũng hữu dụng giúp kiểm tra xem các phần tử của một mảng có mặt ở mảng ta cần kiểm tra không, kết quả trả về là Boolean array:

In [229]: values = np.array([6, 0, 0, 3, 2, 5, 6])

In [230]: np.in1d(values, [2, 3, 6])
Out[230]: array([ True, False, False,  True,  True, False,  True])
Array set operations

Nhập, xuất files với mảng NumPy

NumPy có thể lưu và mở dữ liệu từ ổ cứng ở một số định dạng như text hay binary. Phần này mình chỉ đề cập tới định dạng binary có sẵn của NumPy vì đa số chúng ta sau này đọc file text hay tabular data bằng pandas hoặc các công cụ khác hơn.

Hai hàm chính chúng ta cần quan tâm là numpy.loadnumpy.save. Các mảng thường được lưu mặc định ở định dạng uncompressed raw binary với phần đuôi là .npy:

In [231]: arr = np.arange(10)

In [232]: np.save("some_array", arr)

NumPy sẽ tự động thêm đuôi extension. Bạn có thể mở lại file bằng hàm load:

In [233]: np.load("some_array.npy")
Out[233]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Bạn có thể save nhiều nhiều mảng vào cùng một bộ lưu trữ không nén (uncompressed archieve) bằng hàm numpy.savez và truyền vào các tham số chứa các mảng muốn lưu:

In [234]: np.savez("array_archive.npz", a=arr, b=arr)

Khi mở lại file .npz lúc này bạn sẽ nhận lại đối tượng giống dictionary

In [235]: arch = np.load("array_archive.npz")

In [236]: arch["b"]
Out[236]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Tất nhiên bạn hoàn toàn có thể lưu đối tượng compressed nếu bạn ưu tiên việc giảm dung lượng bằng cách nén file:

In [237]: np.savez_compressed("arrays_compressed.npz", a=arr, b=arr)

Đại số tuyến tính – Linear Algebra

Các toán tử đại số tuyến tính như phép nhân ma trận, , phân tích, định thức và bình phương, là một phần quan trọng của nhiều thư viện mảng. Việc nhân hai mảng hai chiều bằng * là phép nhân từng phần (element-wise), trong khi nhân ma trận yêu cầu sử dụng hàm dot hoặc toán tử nối @.

Hàm dot có thể được sử dụng như một phương thức của mảng và là một hàm trong namespace numpy để thực hiện phép nhân ma trận:

In [241]: x = np.array([[1., 2., 3.], [4., 5., 6.]])

In [242]: y = np.array([[6., 23.], [-1, 7], [8, 9]])

In [243]: x
Out[243]: 
array([[1., 2., 3.],
       [4., 5., 6.]])

In [244]: y
Out[244]: 
array([[ 6., 23.],
       [-1.,  7.],
       [ 8.,  9.]])

In [245]: x.dot(y)
Out[245]: 
array([[ 28.,  64.],
       [ 67., 181.]])

Ta cũng có thể dùng cách này:

In [246]: np.dot(x, y)
Out[246]: 
array([[ 28.,  64.],
       [ 67., 181.]])

Ta cũng có thể nhân ma trận 2-chiều với 1-chiều

In [247]: x @ np.ones(3)
Out[247]: array([ 6., 15.])

Thư viện numpy.linalg có một bộ chuẩn các phép phân rã ma trận và các phép tính như nghịch đảo và định thức.

In [248]: from numpy.linalg import inv, qr

In [249]: X = rng.standard_normal((5, 5))

In [250]: mat = X.T @ X

In [251]: inv(mat)
Out[251]: 
array([[  3.4993,   2.8444,   3.5956, -16.5538,   4.4733],
       [  2.8444,   2.5667,   2.9002, -13.5774,   3.7678],
       [  3.5956,   2.9002,   4.4823, -18.3453,   4.7066],
       [-16.5538, -13.5774, -18.3453,  84.0102, -22.0484],
       [  4.4733,   3.7678,   4.7066, -22.0484,   6.0525]])

In [252]: mat @ inv(mat)
Out[252]: 
array([[ 1.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  0.],
       [ 0.,  0.,  1., -0.,  0.],
       [ 0.,  0.,  0.,  1.,  0.],
       [-0.,  0.,  0.,  0.,  1.]])

Biểu thức X.T.dot(X) tính tích vô hướng của X với ma trận chuyển vị của nó X.T.

Commonly used numpy.linalg functions

🍀 Lời kết

Mặc dù phần nâng cao này chủ yếu giới thiệu các khả năng mở rộng và không có nội dung gì khó hiểu nhưng nắm bắt được những kiến thức này trở nên vô cùng hữu ích khi bạn thao tác xử lý với dữ liệu mà không phải “phát triển lại cái bánh xe”

Các kênh cộng đồng của mình

  • Facebook: https://www.facebook.com/bocuoilamdata
  • Youtube: https://www.youtube.com/@CarrY4U_VN
  • Website: https://carry4u.org/

© Bản quyền thuộc về CarrY4U và Bò Cười Làm Data
© Copyright by CarrY4U and bocuoilamdata ☞ Do not Reup”