Mục lục
- Mục tiêu bài học
- Tạo array từ list
- Array hằng số: zeros, ones, full, eye
- Range số: arange và linspace
- empty và các hàm *_like
- Random array và Generator API mới
- Indexing 1D
- Indexing 2D / nD với dấu phẩy
- Modify phần tử qua indexing
- Fancy indexing (preview)
- Lưu ý về dtype khi tạo array
- Use case trong AI
- Bài tập
- Tổng kết và bài tiếp theo
Mục tiêu bài học
Sau bài học, bạn sẽ:
- Tạo array bằng các hàm phổ biến:
np.array,zeros,ones,full,eye,arange,linspace,empty, các hàm*_like. - Tạo random array bằng cả API cũ (
np.random.rand / randn / randint) và API mớinp.random.default_rng. - Truy cập phần tử trong array 1D, 2D, nD bằng cú pháp NumPy
a[i, j, k]. - Modify phần tử qua indexing.
- Biết khái niệm fancy indexing bằng list / array số nguyên.
- Hiểu dtype mặc định khi tạo array hằng số và cách chỉ định
dtype.
Ở bài 28 đã giới thiệu ndarray và lý do nhanh hơn list. Bài này tập trung vào hai thao tác đầu tiên: tạo dữ liệu và đọc / ghi từng phần tử.
Tạo array từ list
Cách trực tiếp nhất là gọi np.array với một list hoặc nested list:
import numpy as np
# 1D — từ list phẳng
a = np.array([1, 2, 3])
print(a) # [1 2 3]
print(a.shape) # (3,)
print(a.dtype) # int64
# 2D — từ list lồng list, mỗi list con là một hàng
b = np.array([[1, 2, 3],
[4, 5, 6]])
print(b.shape) # (2, 3)
# 3D — list của list của list
c = np.array([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]])
print(c.shape) # (2, 2, 2)
NumPy yêu cầu các list con cùng độ dài. Nếu lệch nhau, từ NumPy 1.20 trở đi sẽ raise ValueError: setting an array element with a sequence (trước đó tạo ra object array kèm warning).
Array hằng số: zeros, ones, full, eye
Khi đã biết shape và muốn array chứa cùng một giá trị:
np.zeros((3, 4)) # toàn 0, shape (3, 4), dtype float64
np.ones((2, 3)) # toàn 1, shape (2, 3), dtype float64
np.full((2, 3), 7) # toàn 7, shape (2, 3), dtype suy từ 7 -> int64
np.eye(4) # ma trận đơn vị 4x4 (đường chéo 1, còn lại 0)
Ma trận đơn vị (identity matrix) thoả \( I \cdot A = A \) với mọi ma trận \( A \) phù hợp shape — sẽ gặp lại ở phần đại số tuyến tính và linear layer của neural network.
np.eye(N, M) còn cho phép tạo ma trận chữ nhật với đường chéo 1; np.eye(N, k=1) dịch đường chéo lên / xuống.
Range số: arange và linspace
Hai hàm tạo dãy số cách đều — khác nhau ở "bước nhảy" hay "số điểm":
# arange(start, stop, step) — giống range, KHÔNG bao gồm stop
np.arange(0, 10, 2)
# array([0, 2, 4, 6, 8])
# linspace(start, stop, num) — chia [start, stop] thành num điểm cách đều, BAO GỒM stop
np.linspace(0, 1, 11)
# array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])
Khác biệt cần nhớ:
arangenhận step, không bao gồmstop. Với step float, tích luỹ sai số nên dễ ra số phần tử lệch — nên ưu tiênlinspacecho dãy float.linspacenhận num, bao gồm cảstartvàstop. Phù hợp khi cần đúng \( n \) mẫu trên một khoảng (ví dụ vẽ đồ thị, sample learning rate).
empty và các hàm *_like
np.empty cấp phát bộ nhớ cho array nhưng không ghi giá trị khởi tạo — nhanh nhất, nhưng nội dung là "rác" tuỳ vùng nhớ cũ:
x = np.empty((2, 3))
print(x) # giá trị bất kỳ — KHÔNG được giả định là 0
Chỉ dùng empty khi bạn chắc chắn sẽ ghi đè toàn bộ ngay sau đó (ví dụ chuẩn bị buffer cho vòng lặp). Nếu chưa chắc, dùng zeros cho an toàn.
Nhóm *_like tạo array mới có shape và dtype giống một array đã có:
a = np.array([[1, 2, 3],
[4, 5, 6]])
np.zeros_like(a) # array 2x3 toàn 0, dtype int64 (giống a)
np.ones_like(a) # array 2x3 toàn 1, dtype int64
np.full_like(a, 9) # array 2x3 toàn 9, dtype int64
np.empty_like(a) # shape giống a, giá trị rác
Hữu ích khi cần khởi tạo buffer cùng shape với input mà không phải viết lại shape thủ công.
Random array và Generator API mới
NumPy có hai API random:
- API cũ (legacy, vẫn chạy được):
np.random.rand,randn,randint,np.random.seed... - API mới (NumPy 1.17+):
np.random.default_rng()trả về một đối tượngGeneratorvới phương thức cùng tên. Đây là cách được khuyến nghị trong tài liệu chính thức.
API cũ:
np.random.seed(42) # cố định trạng thái global
np.random.rand(3, 4) # uniform [0, 1), shape (3, 4)
np.random.randn(3, 4) # standard normal N(0, 1)
np.random.randint(0, 10, size=(3, 4)) # int trong [0, 10)
API mới — dùng Generator riêng biệt thay vì state toàn cục:
rng = np.random.default_rng(42) # seed = 42
rng.random((3, 4)) # uniform [0, 1)
rng.standard_normal((3, 4)) # N(0, 1)
rng.integers(0, 10, size=(3, 4)) # int trong [0, 10)
rng.normal(loc=0, scale=1, size=(3, 4)) # N(loc, scale^2)
Ưu điểm của default_rng: mỗi rng độc lập (không bị code khác đổi seed ngầm), thread-safe hơn, và thuật toán PRNG mặc định là PCG64 — chất lượng tốt hơn Mersenne Twister của API cũ. Khi viết code mới, ưu tiên cách này.
Seed (như 42) chỉ là một số nguyên dùng để khởi tạo PRNG — dùng cùng seed sẽ cho cùng kết quả random, tiện cho việc reproduce thí nghiệm.
Indexing 1D
Với array 1D, cú pháp giống list Python — index từ 0, index âm tính từ cuối:
a = np.array([10, 20, 30, 40, 50, 60])
a[0] # 10 — phần tử đầu
a[5] # 60 — phần tử thứ 6
a[-1] # 60 — phần tử cuối
a[-2] # 50 — phần tử áp cuối
Truy cập ngoài phạm vi (ví dụ a[10]) raise IndexError — giống list.
Indexing 2D / nD với dấu phẩy
Với array nhiều chiều, có hai cú pháp:
m = np.array([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])
# Pythonic style — vẫn chạy được
m[1][2] # 60
# NumPy style — recommended
m[1, 2] # 60
Hai cách cho cùng kết quả, nhưng:
m[1][2]tạo ra một view trung gianm[1](là 1D array của hàng 1), rồi mới index tiếp. Có chi phí trung gian.m[1, 2]đi thẳng tới phần tử cần lấy — đây là cú pháp NumPy thiết kế cho.
Quy tắc tổng quát: dấu phẩy phân tách các trục. Với array 3D:
t = np.arange(24).reshape(2, 3, 4) # shape (2, 3, 4)
t[0, 1, 2] # phần tử ở block 0, hàng 1, cột 2
t[1, 2, 3] # phần tử cuối cùng
Số index bằng số trục thì kết quả là một scalar (0-D). Ít index hơn số trục thì NumPy trả về sub-array — sẽ học kỹ ở bài 30 (slicing).
Modify phần tử qua indexing
Cùng cú pháp đó, đặt ở vế trái phép gán là ghi giá trị:
a = np.array([10, 20, 30, 40, 50])
a[0] = 99
print(a) # [99 20 30 40 50]
m = np.zeros((2, 3))
m[0, 0] = 5
m[1, 2] = 7
print(m)
# [[5. 0. 0.]
# [0. 0. 7.]]
Khi gán một giá trị có dtype khác với array, NumPy ép kiểu về dtype của array. Ví dụ array int64 gán 3.7 sẽ thành 3 (truncate). Nếu cần lưu float, tạo array với dtype=float ngay từ đầu.
Fancy indexing (preview)
Ngoài integer đơn lẻ, NumPy cho phép dùng list / array các integer làm index — gọi là fancy indexing:
a = np.array([10, 20, 30, 40, 50])
# Lấy phần tử thứ 0, 2, 4 cùng lúc
a[[0, 2, 4]]
# array([10, 30, 50])
# Thứ tự index quyết định thứ tự kết quả; có thể lặp
a[[4, 0, 0, 2]]
# array([50, 10, 10, 30])
Với 2D, mỗi danh sách index ứng với một trục:
m = np.array([[10, 20, 30, 40],
[50, 60, 70, 80]])
# Lấy hai phần tử: (hàng 0, cột 2) và (hàng 1, cột 3)
m[[0, 1], [2, 3]]
# array([30, 80])
Khác biệt quan trọng với slicing: fancy indexing trả về copy, không phải view — sửa kết quả không ảnh hưởng array gốc. Slicing và boolean indexing chi tiết ở bài 30.
Lưu ý về dtype khi tạo array
Dtype mặc định của một vài hàm tạo array:
| Hàm | Dtype mặc định |
|---|---|
np.zeros, np.ones, np.empty, np.eye, np.linspace | float64 |
np.array([1, 2, 3]) | int64 (Linux/macOS) hoặc int32 (Windows) |
np.array([1.0, 2.0]) | float64 |
np.arange(0, 10) | int64 nếu mọi đối số là int; float64 nếu có float |
np.full(shape, fill_value) | Suy từ fill_value |
Có thể chỉ định dtype rõ ràng:
np.zeros((2, 3), dtype=np.int32)
np.ones((2, 3), dtype=np.float32)
np.array([1, 2, 3], dtype=np.float64)
Trong deep learning, weight thường ở float32 (đủ chính xác, ít tốn RAM / VRAM hơn float64 2 lần). NumPy mặc định float64 nên khi nối với PyTorch / TensorFlow đôi khi cần ép kiểu xuống float32.
Use case trong AI
np.zeroskhởi tạo bias hoặc placeholder buffer. Weight thì hiếm khi để toàn 0 vì làm các neuron giống nhau (symmetry problem) — thực tế dùng Xavier / He initialization (preview, học chi tiết ở series Deep Learning).np.arangetạo dãy chỉ số: time step trong RNN, vị trí token trước khi tính positional encoding, index cho học DataLoader.np.linspacesample khoảng giá trị cho hyperparameter search (ví dụ learning rate, threshold).rng.standard_normal(...)tạo noise cho data augmentation, sample từ N(0, 1), khởi tạo weight kiểu Gaussian.rng.integers(...)sample chỉ số batch ngẫu nhiên khi shuffle dataset.np.eyedùng cho identity matrix trong test (kiểm tra linear layer học được mapping identity), one-hot encoding cũng có thể viết quanp.eye(num_classes)[labels].
Bài tập
- Tạo array shape
(5, 5)chứa các số nguyên 1, 2, 3, ..., 25. - Tạo identity matrix 4x4.
- Cho array 2D bất kỳ. Lấy ra phần tử ở vị trí
(1, 2)bằng cú pháp NumPy. - Tạo array random shape
(3, 3)sample từN(0, 1)với seed 42. Yêu cầu dùngnp.random.default_rng.
Đáp án
-
a = np.arange(1, 26).reshape(5, 5) # array([[ 1, 2, 3, 4, 5], # [ 6, 7, 8, 9, 10], # [11, 12, 13, 14, 15], # [16, 17, 18, 19, 20], # [21, 22, 23, 24, 25]]) -
I = np.eye(4) # array([[1., 0., 0., 0.], # [0., 1., 0., 0.], # [0., 0., 1., 0.], # [0., 0., 0., 1.]]) -
m = np.array([[10, 20, 30], [40, 50, 60]]) m[1, 2] # 60 -
Với cùng seed, kết quả lặp lại được trên mọi máy có cùng NumPy version.rng = np.random.default_rng(42) r = rng.standard_normal((3, 3)) # array([[ 0.30471708, -1.03998411, 0.7504512 ], # [ 0.94056472, -1.95103519, -1.30217951], # [ 0.1278404 , -0.31624259, -0.01680116]])
Tổng kết và bài tiếp theo
- Tạo array:
np.array(từ list),zeros / ones / full / eye(hằng số),arange / linspace(dãy số),empty(chưa init),*_like(cùng shape với array có sẵn). - Random: API cũ
np.random.rand / randn / randint; API mớinp.random.default_rng(seed)— recommended. - Indexing:
a[i]cho 1D,a[i, j]cho 2D,a[i, j, k]cho 3D. Dấu phẩy phân tách trục. - Modify: gán giá trị mới vào vị trí được index. Lưu ý ép kiểu theo dtype của array.
- Fancy indexing: dùng list / array integer làm index — trả về copy, không phải view.
- Dtype mặc định:
zeros / ones / linspacelàfloat64;np.array([int...])làint64. Trong DL thường ép xuốngfloat32.
Bài 30 mở rộng từ indexing sang slicing (a[start:stop:step] trên nhiều trục) và boolean indexing (a[a > 0]) — hai công cụ chính để lọc và biến đổi dữ liệu trong NumPy / Pandas.
