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.
[3, 4]])
print("dtype of matrix A: ", A.dtype)
[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.
[3, 4]], dtype=np.float32)
print("dtype of matrix B: ", B.dtype)
[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.
A = A.astype(np.float32)
print(A.dtype)
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.
-
np.random.randn(d0, d1, d2,..., dn): 𝑑𝑖 là chiều thứ 𝑖 của mảng. Theo cách này các giá trị sẽ được lấy mẫu ngẫu nhiên từ phân phối chuẩn hoá (normal distribution) có trung bình bằng 0 và phương sai bằng 1.
print(R)
[ 1.96496359 1.90712193 -0.41465353]]
-
np.random.normal(loc=0.0, scale=1.0, size=None): Khởi tạo mảng mà các phần tử của mảng tuân theo phân phối chuẩn (Gaussian distribution) với trung bình chính làlocvà phương sai làscale. Phân phối Gaussian (hay còn gọi là phân phối chuẩn) là trường hợp tổng quát của phân phối chuẩn hoá, chúng có hàm mật độ xác suấtpdf(probability density function) được tính dựa trên hai tham số trung bình và phương sai như sau:
𝑝𝑑𝑓(𝑥;𝜇,𝜎)=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.
print(R)
[ 2.38912377 2.01603469 -1.46485996]]
-
np.random.uniform(low=0.0, high=1.0, size=None): Các phần sẽ được khởi tạo theo phân phối đều trong khoảng từlowtớihigh. Trong phân phối đều thì mật độ xác suất tại mọi điểm là như nhau giữa[low, high]:
𝑝𝑑𝑓(𝑥;𝑙𝑜𝑤,ℎ𝑖𝑔ℎ)=1ℎ𝑖𝑔ℎ−𝑙𝑜𝑤
print(R)
[-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()
print(R)
[-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:
-
np.random.standard_t(df, size=None): Phân phối t-student. -
np.random.chisquare(df, size=None): Phân phối Chi-square. -
np.random.f(dfnum, dfden, size=None): Phân phối Fisher. -
np.random.gamma(shape, scale=1.0, size=None): Phân phối gamma -
np.random.beta(a, b, size=None): Phân phối beta
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.
# import os
# drive.mount('/content/drive/')
# os.chdir("drive/My Drive/mybook")
A = np.random.randn(3, 3)
A
[-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:
-
np.save(file, arr): Output là định dạng npy. Save được mọi tensor với số chiều tuỳ ý. -
np.savetxt(file, arr): Output là định dạng txt. Cách này ít phổ biến hơn vì ta chỉ save được mảng 1D và 2D.
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.
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
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().
print(A)
[-0.67952482 -0.21394614 0.09439494]
[-2.19331425 1.08744612 1.16118466]]
print(A)
[-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.
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 |
print(A)
[-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:
-
indice_start:(indice_end+1): lấy toàn bộ các indices bắt đầu từindice_startvà kết thúc làindice_end. -
-num_indices:: Lấy một số lượngnum_indicesở vị trí cuối cùng. Ký hiệu-num_indicescó thể hiểu là trước vị trí cuối cùngnum_indicesvị trí. -
:num_indices: Lấy một số lượngnum_indicesở vị trí đầu tiên.
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.
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:
print(X[1, 2])
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 ý.
print(X[1:3, 1:3])
[8 9]]
print(X[:2, :2])
[4 5]]
print(X[-2:, -2:])
[1 5]]
print(X[[0, 2], :])
[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.
print("B: \n", B)
print("B.shape: ", B.shape)
[ 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.
B.reshape(2, 6)
[ 6, 7, 8, 9, 10, 11]])
np.reshape(B, (2, 6))
[ 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.
[ 6, 7, 8, 9, 10, 11]])
print(B)
[ 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ị.
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 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.
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)
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.
print(np.vstack([A, B]).shape)
print(np.hstack([A, B]).shape)
(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.
B = np.random.randint(0, 2, size=(1, 3))
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.
B = np.expand_dims(B, axis=-1)
print("B new shape: ", B.shape)
print("A.dot(B) shape: ", A.dot(B).shape)
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).