Toán tin vuotlen.com

Numpy

3.1. Khởi tạo một mảng trên numpy

3.1.1. Khởi tạo ngay từ đầu

Để khởi tạo một mảng trên numpy chúng ta sử dụng câu lệnh rất quen thuộc là np.array(). Numpy cho phép chúng ta khởi tạo mảng được cấu hình theo định dạng dữ liệu cụ thể như float, interger, boolean, string. Chúng ta cũng lưu ý rằng các phần tử của một mảng trong numpy phải đồng nhất về định dạng dữ liệu.

import numpy as np
A = np.array([[1, 2],
              [3, 4]])
print(A)
print("dtype of matrix A: ", A.dtype)

 

[[1 2]
[3 4]]
dtype of matrix A:  int64

Ban đầu các phần tử của ma trận 𝐴 gồm toàn những giá trị nguyên. Chúng ta có thể xác định định dạng dữ liệu cho ma trận ngay tại lúc khởi tạo thông qua đối số dtype.

B = np.array([[1, 2],
              [3, 4]], dtype=np.float32)
print(B)
print("dtype of matrix B: ", B.dtype)

 

[[1. 2.]
[3. 4.]]
dtype of matrix B:  float32

 

Những mảng kiểu float thì giá trị các phần tử có thêm dấu . ở cuối để phân biệt với mảng số nguyên.

Ngoài cách thay đổi trên, định dạng của ma trận cũng có thể được biến đổi thông qua hàm A.astype(). Đây là một hàm thuộc tính có sẵn ở mỗi mảng.

# Lưu ý phải gán A = A.astype() để lưu thay đổi cho A
A = A.astype(np.float32)
print(A.dtype)

 

float32

1.2. Khởi tạo ngẫu nhiên

Nếu khởi tạo một ma trận nhỏ khoảng vài phần tử thì việc gõ tay là khả thi. Nhưng đối với những mảng kích thước lớn chúng ta sẽ không thể nhập hết toàn bộ các giá trị. Khi đó ta sẽ khởi tạo ngẫu nhiên cho những biến này.

R = np.random.randn(2, 3)
print(R)

 

[[-0.84705218 -0.44591015 -0.68057193]
[ 1.96496359  1.90712193 -0.41465353]]

𝑝𝑑𝑓(𝑥;𝜇,𝜎)=exp⁡(−(𝑥−𝜇)22𝜎2)2𝜋𝜎2

Chúng ta sẽ gặp lại phân phối này ở chương lý thuyết xác suất.

R = np.random.normal(loc=1, scale=2, size=(2, 3))
print(R)

 

[[ 4.35532037  1.23970291  1.54447465]
[ 2.38912377  2.01603469 -1.46485996]]

 

𝑝𝑑𝑓(𝑥;𝑙𝑜𝑤,ℎ𝑖𝑔ℎ)=1ℎ𝑖𝑔ℎ−𝑙𝑜𝑤

R = np.random.uniform(low=-1, high=1, size=(2, 3))
print(R)

 

[[-0.67195259 -0.11710832  0.40114137]
[-0.40054265 -0.72930944 -0.75715529]]

 

Trong trường hợp bạn chỉ muốn sinh ngẫu nhiên đối với số nguyên thì có thể sử dụng hàm np.randint()

R = np.random.randint(low=-5, high=5, size=(2, 3))
print(R)

 

[[ 3  3 -3]
[-1  0 -1]]

 

Ngoài ra còn các khởi tạo ngẫu nhiên theo phân phối khác như t-student, gamma, beta, chi-square, Fisher, ..... Bạn đọc có thể sử dụng như list các hàm mình liệt kê bên dưới:

2. Đọc và save numpy từ file

2.1. Save numpy

Chúng ta có thể lưu numpy dưới nhiều định dạng khác nhau như file npy và txt.

# from google.colab import drive
# import os
# Mount google driver
# drive.mount('/content/drive/')
# os.chdir("drive/My Drive/mybook")
import numpy as np
# Khởi tạo một mảng ngẫu nhiên A
A = np.random.randn(3, 3)
A

 

array([[-1.74299725, -0.05220996,  0.99926429],
       [-0.67952482, -0.21394614,  0.09439494],
       [-2.19331425,  1.08744612,  1.16118466]])

Save mảng A theo hàm np.save() và np.savetxt() có cú pháp:

Trong các công thức trên thì file là đường link tới vị trí save và arr là mảng cần save.

# Định dạng npy
np.save('numpy_save/A', A)
# Định dạng txt
np.savetxt('numpy_save/A.txt', A)
# List các file
!ls -a numpy_save

 

.  ..  A.npy  A.txt

2.2. Load numpy từ file

Sau khi save numpy, ta có thể load chúng lại theo hàm np.load() và np.loadtxt().

A = np.load("numpy_save/A.npy")
print(A)

 

[[-1.74299725 -0.05220996  0.99926429]
[-0.67952482 -0.21394614  0.09439494]
[-2.19331425  1.08744612  1.16118466]]

 

np.loadtxt("numpy_save/A.txt")
print(A)

 

[[-1.74299725 -0.05220996  0.99926429]
[-0.67952482 -0.21394614  0.09439494]

[-2.19331425  1.08744612  1.16118466]]

2.3. convert mảng từ dataframe

Trong nhiều trường hợp khi xây dựng mô hình chúng ta có thể lấy giá trị của mảng từ dataframe bằng cách gọi thuộc tính df.values.

import pandas as pd
df = pd.read_csv("numpy_save/A.txt", header=None, sep=" ")
df

 

  0 1 2
0 -1.742997 -0.052210 0.999264
1 -0.679525 -0.213946 0.094395
2 -2.193314 1.087446 1.161185

 

A = df.values
print(A)

 

[[-1.74299725 -0.05220996  0.99926429]
[-0.67952482 -0.21394614  0.09439494]
[-2.19331425  1.08744612  1.16118466]]

3. Truy cập mảng trên numpy

Chúng ta có thể truy cập mảng con theo các chiều thành phần của mảng cha dựa vào khai báo indices trên từng chiều. Lưu ý rằng trong lập trình thì số thứ tự sẽ giảm 1 so với thực tế do theo qui ước của python thì STT (số thứ tự) bắt đầu từ 0. Do đó ở dưới khi mình nói đến STT thì bạn hiểu là mình đang nói tới STT trong lập trình và được giảm 1 so với thực tế. Ví dụ: dòng số 0 tức là dòng đầu tiên (số 1) trong thực tế.

Để khai báo indices chúng ta có thể sử dụng slice indices được ngăn cách bởi dấu :. Slice indices giúp rút ngắn việc phải nhập từng indices. Nó rất phù hợp với các tình huống các vị trí cần truy cập là liên tục nhau. Bên dưới là 3 công thức slice indice thường sử dụng:

Trong trường hợp các vị trí là không liên tục thì ta có thể liệt kê ra các vị trí cần lấy ở từng chiều vào một list.

import numpy as np
X = np.array([[1, 2, 3, 4],
              [4, 5, 6, 2],
              [7, 8, 9, 1],
              [2, 5, 1, 5]])

Tiếp theo ta sẽ truy cập vào các phần tử của X. Bạn sẽ hình dung cách truy cập qua lần lượt các ví dụ bên dưới:

# Truy cập vào phần tử thuộc dòng 1 và cột 2
print(X[1, 2])

 

6

Khi truy cập theo slice indice thì vị trí kết thúc phải cộng thêm một. Tức là chúng ta cần truy cập các dòng có indice từ 1 đến 2 thì phải khai báo indice là 1:3. Đây là một điều dễ bị nhầm lẫn đối với người mới bắt đầu lập trình mà chúng ta cần đặc biệt lưu ý.

# Truy cập vào các dòng từ 1 đến 2 và các cột từ 1 đến 2
print(X[1:3, 1:3])

 

[[5 6]
[8 9]]

 

# Truy cập vào 2 dòng, 2 cột đầu tiên
print(X[:2, :2])

 

[[1 2]
[4 5]]

 

# Truy cập vào 2 dòng, 2 cột cuối cùng
print(X[-2:, -2:])

 

[[9 1]
[1 5]]

 

# Truy cập vào dòng 0 và 2. Cách lấy indices không liên tục.
print(X[[0, 2], :])

 

[[1 2 3 4]
[7 8 9 1]]

3. Thay đổi shape của mảng

3.1. Reshape mảng

Chúng ta có thể thay đổi shape cho mảng dựa vào câu lệnh np.reshape(). Hàm này vừa là một thuộc tính sẵn có trong mỗi mảng (tức là với ma tận A ta có thể gọi A.reshape()) vừa là hàm của numpy.

B = np.array(np.arange(12))
print("B: \n", B)
print("B.shape: ", B.shape)

 

B:
[ 0  1  2  3  4  5  6  7  8  9 10 11]
B.shape:  (12,)

Mảng B là một véc tơ 1 chiều có độ dài là 12. Chúng ta có thể reshape mảng B thành một ma trận 2 dòng, 6 cột.

# dùng B.reshape()
B.reshape(2, 6)

 

array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11]])

 

# dùng np.reshape()
np.reshape(B, (2, 6))

 

array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11]])

Trong một mảng có 𝑛 chiều, khi đã biết 𝑛−1 chiều thì chiều còn lại có thể tính ra được bằng cách chia tổng số phần tử cho tích của 𝑛−1 chiều. Do đó chúng ta có thể đánh dấu chiều chưa biết bằng -1 để numpy sẽ tự tính.

B.reshape(2, -1)

 

array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11]])

 

B = np.reshape(B, (2, -1))
print(B)

 

[[ 0  1  2  3  4  5]
[ 6  7  8  9 10 11]]

3.2. Chuyển vị các chiều

Trong các mô hình deep learning chúng ta có rất nhiều những lựa chọn khác nhau về định dạng đầu vào của một tensor. Ví dụ như đối với dữ liệu ảnh thì có sự khác biệt về input mặc định giữa các framework như pytorch và tensorflow. Tensorflow chấp nhận ảnh đầu vào có các chiều được sắp xếp theo dạng batch_size x width x height x channels trong khi pytorch lại chấp nhận kiểu batch_size x channels x width x height.

Chúng ta có thể hoán vị các chiều của mảng nhiều chiều bằng câu lệnh np.transpose(arr, (d_i, d_j,..., d_n)). Trong đó, arr là ma trận cần hoán vị chiều và (d_i, d_j,...,d_n) là thứ tự các chiều mới mà chúng ta hoán vị.

# Khởi một bức ảnh ngẫu nhiên có kích thước channels x width x height= 3 x 28 x 28
A = np.random.randint(0, 255, size=(3, 28, 28))
print("A.shape: ", A.shape)
# Chuyển channels về cuối
A = np.transpose(A, (1, 2, 0))
print("A transpose shape: ", A.shape)

 

A.shape:  (3, 28, 28)
A transpose shape:  (28, 28, 3)

3.3. Concatenate và Stack hai mảng

Giả sử bạn đang có rất nhiều bức ảnh khác nhau. Bạn muốn ghép các bức ảnh đó theo chiều height (một bức nằm trái và một bức nằm phải). Để thực hiện được điều này thì bạn cần tới câu lệnh concatenate.

A = np.random.randint(0, 255, size=(28, 28, 3))
B = np.random.randint(0, 255, size=(28, 28, 3))
print("A.shape:", A.shape)
print("B.shape:", B.shape)
AB = np.concatenate((A, B), axis=1)
print("shape after concatenate: ", AB.shape)

 

A.shape: (28, 28, 3)
B.shape: (28, 28, 3)
shape after concatenate:  (28, 56, 3)

 

Ngoài ra muốn tạo một batch cho huấn luyện, trong batch đó gồm 2 ảnh là A và B thì chúng ta sử dụng câu lệnh stack.

np.stack([A, B]).shape

 

(2, 28, 28, 3)

 

# Stack theo chiều vertical
print(np.vstack([A, B]).shape)
# Stack theo chiều horizontal
print(np.hstack([A, B]).shape)

 

(56, 28, 3)
(28, 56, 3)

3.4. Mở rộng mảng

Trong nhiều tình huống, sự khác biệt về số lượng các chiều trong mảng có thể dẫn tới những lỗi liên quan tới shape. Ví dụ: Chúng ta không thể nhân một mảng kích thước 3 chiều với một ma trận kích thước 2 chiều.

A = np.random.randint(0, 2, size=(1, 2, 3))
B = np.random.randint(0, 2, size=(1, 3))
# A.dot(B)

Khi đó chúng ta cần mở rộng mảng thêm một chiều ở vị trí trong cùng thì mới thực hiện được phép nhân.

# Mở rộng mảng
B = np.expand_dims(B, axis=-1)
print("B new shape: ", B.shape)
print("A.dot(B) shape: ", A.dot(B).shape)

 

B new shape:  (1, 3, 1)
A.dot(B) shape:  (1, 2, 1, 1)

Đối số axis=-1 có nghĩa là ta sẽ mở rộng mảng tại chiều cuối cùng. Khi đó shape (1, 3) trở thành (1, 3, 1).