paint-brush
Phân loại nhiều lớp: Tìm hiểu các chức năng kích hoạt và mất mát trong mạng thần kinhtừ tác giả@owlgrey
1,969 lượt đọc
1,969 lượt đọc

Phân loại nhiều lớp: Tìm hiểu các chức năng kích hoạt và mất mát trong mạng thần kinh

từ tác giả Dmitrii Matveichev 25m2024/01/24
Read on Terminal Reader

dài quá đọc không nổi

Để xây dựng mạng nơron phân loại nhiều lớp, bạn cần sử dụng hàm kích hoạt softmax trên lớp cuối cùng của nó cùng với mất entropy chéo. Kích thước lớp cuối cùng phải là k, trong đó k là số lớp. ID lớp phải được xử lý trước bằng mã hóa một lần. Mạng nơ-ron như vậy sẽ xuất ra xác suất p_i mà đầu vào thuộc về lớp i. Để tìm ID lớp dự đoán, bạn cần tìm chỉ số có xác suất tối đa.
featured image - Phân loại nhiều lớp: Tìm hiểu các chức năng kích hoạt và mất mát trong mạng thần kinh
Dmitrii Matveichev  HackerNoon profile picture


Bài viết trước của tôi xây dựng bài toán phân loại và chia nó thành 3 loại (nhị phân, nhiều lớp và nhiều nhãn) và trả lời câu hỏi “Bạn cần sử dụng hàm kích hoạt và hàm mất nào để giải quyết nhiệm vụ phân loại nhị phân?”.


Trong bài đăng này, tôi sẽ trả lời câu hỏi tương tự nhưng dành cho nhiệm vụ phân loại nhiều lớp và cung cấp cho bạn một ví dụ về triển khai pytorch trong Google colab .


Bạn cần sử dụng chức năng kích hoạt và mất mát nào để giải quyết nhiệm vụ phân loại nhiều lớp?


Mã được cung cấp phần lớn dựa trên việc triển khai phân loại nhị phân vì bạn cần thêm rất ít sửa đổi vào mã và NN của mình để chuyển từ phân loại nhị phân sang nhiều lớp. Các khối mã đã sửa đổi được đánh dấu bằng (Đã thay đổi) để điều hướng dễ dàng hơn.


1 Tại sao điều quan trọng là phải hiểu hàm kích hoạt và hàm mất mát được sử dụng để phân loại nhiều lớp?

Như sẽ được trình bày sau, hàm kích hoạt được sử dụng để phân loại nhiều lớp là kích hoạt softmax. Softmax được sử dụng rộng rãi trong các kiến trúc NN khác nhau ngoài việc phân loại nhiều lớp. Ví dụ: softmax là cốt lõi của khối chú ý nhiều đầu được sử dụng trong các mô hình Transformer (xem Chú ý là tất cả những gì bạn cần ) do khả năng chuyển đổi các giá trị đầu vào thành phân bố xác suất (xem thêm về điều đó sau).


Chú ý sản phẩm chấm theo tỷ lệ (phổ biến nhất trong mô-đun chú ý nhiều đầu)



Nếu bạn biết động lực đằng sau việc áp dụng kích hoạt softmax và mất CE để giải quyết các vấn đề phân loại nhiều lớp, bạn sẽ có thể hiểu và triển khai các kiến trúc NN và hàm mất mát phức tạp hơn nhiều.


2 Xây dựng bài toán phân loại nhiều lớp

Bài toán phân loại nhiều lớp có thể được biểu diễn dưới dạng một tập hợp các mẫu {(x_1, y_1), (x_2, y_2),...,(x_n, y_n)} , trong đó x_i là một vectơ m chiều chứa các đặc điểm của mẫu iy_i là lớp mà x_i thuộc về. Trong đó nhãn y_i có thể giả sử một trong các giá trị k , trong đó k là số lớp cao hơn 2. Mục tiêu là xây dựng mô hình dự đoán nhãn y_i cho mỗi mẫu đầu vào x_i .

Ví dụ về các nhiệm vụ có thể được coi là vấn đề phân loại nhiều lớp:

  • chẩn đoán y tế - chẩn đoán bệnh nhân mắc một trong các bệnh dựa trên dữ liệu được cung cấp (lịch sử y tế, kết quả xét nghiệm, triệu chứng)
  • phân loại sản phẩm - phân loại sản phẩm tự động cho nền tảng thương mại điện tử
  • dự đoán thời tiết - phân loại thời tiết trong tương lai như nắng, mây, mưa, v.v.
  • phân loại phim, nhạc và bài viết thành các thể loại khác nhau
  • phân loại đánh giá của khách hàng trực tuyến thành các danh mục như phản hồi về sản phẩm, phản hồi về dịch vụ, khiếu nại, v.v.


3 Hàm kích hoạt và mất dữ liệu để phân loại nhiều lớp


Trong phân loại nhiều lớp, bạn được cung cấp:

  • một tập hợp các mẫu {(x_1, y_1), (x_2, y_2),...,(x_n, y_n)}

  • x_i là vectơ m chiều chứa các đặc điểm của mẫu i

  • y_i là lớp mà x_i thuộc về và có thể nhận một trong các giá trị k , trong đó k>2 là số lượng lớp.


Để xây dựng mạng nơ ron phân loại nhiều lớp như một bộ phân loại xác suất, chúng ta cần:

  • một lớp được kết nối đầy đủ đầu ra với kích thước k
  • giá trị đầu ra phải nằm trong phạm vi [0,1]
  • tổng các giá trị đầu ra phải bằng 1. Trong phân loại nhiều lớp, mỗi đầu vào x chỉ có thể thuộc về một lớp (các lớp loại trừ lẫn nhau), do đó tổng xác suất của tất cả các lớp phải là 1: SUM(p_0,…,p_k )=1 .
  • một hàm mất mát có giá trị thấp nhất khi dự đoán và sự thật cơ bản giống nhau


3.1 Hàm kích hoạt softmax

Lớp tuyến tính cuối cùng của mạng nơ-ron tạo ra một vectơ "giá trị đầu ra thô". Trong trường hợp phân loại, các giá trị đầu ra thể hiện độ tin cậy của mô hình rằng đầu vào thuộc về một trong k lớp. Như đã thảo luận trước, lớp đầu ra cần phải có kích thước k và các giá trị đầu ra phải biểu thị xác suất p_i cho mỗi lớp k và SUM(p_i)=1 .


Bài viết về phân loại nhị phân sử dụng kích hoạt sigmoid để chuyển đổi giá trị đầu ra NN thành xác suất. Hãy thử áp dụng sigmoid cho k giá trị đầu ra trong phạm vi [-3, 3] và xem liệu sigmoid có thỏa mãn các yêu cầu được liệt kê trước đó hay không:


  • k giá trị đầu ra phải nằm trong phạm vi (0,1), trong đó k là số lớp

  • tổng của k giá trị đầu ra phải bằng 1


    Định nghĩa hàm sigmoid


    Bài viết trước cho thấy hàm sigmoid ánh xạ các giá trị đầu vào vào một phạm vi (0,1). Hãy xem liệu kích hoạt sigmoid có đáp ứng yêu cầu thứ hai hay không. Trong bảng ví dụ bên dưới, tôi đã xử lý một vectơ có kích thước k (k=7) với kích hoạt sigmoid và tính tổng tất cả các giá trị này - tổng của 7 giá trị này bằng 3,5. Một cách đơn giản để khắc phục điều đó là chia tất cả các giá trị k cho tổng của chúng.


Đầu vào

-3

-2

-1

0

1

2

3

TỔNG

đầu ra sigmoid

0,04743

0.11920

0,26894

0,50000

0,73106

0,88080

0,95257

3,5000


Một cách khác là lấy số mũ của giá trị đầu vào và chia cho tổng số mũ của tất cả các giá trị đầu vào:


Định nghĩa hàm Softmax


Hàm softmax biến đổi vectơ số thực thành vectơ xác suất. Mỗi xác suất trong kết quả nằm trong phạm vi (0,1) và tổng các xác suất là 1.

Đầu vào

-3

-2

-1

0

1

2

3

TỔNG

softmax

0,00157

0,00426

0,01159

0,03150

0,08563

0,23276

0,63270

1

Đồ thị của số mũ trong phạm vi [-10, 10]


Softmax của vectơ có kích thước 21 với các giá trị [-10, 10]


Có một điều bạn cần lưu ý khi làm việc với softmax: giá trị đầu ra p_i phụ thuộc vào tất cả các giá trị trong mảng đầu vào vì chúng ta chia nó cho tổng số mũ của tất cả các giá trị. Bảng dưới đây chứng minh điều này: hai vectơ đầu vào có 3 giá trị chung {1, 3, 4}, nhưng giá trị softmax đầu ra khác nhau do phần tử thứ hai khác nhau (2 và 4).

Đầu vào 1

1

2

3

4

phần mềm tối đa 1

0,0321

0,0871

0,2369

0,6439

Đầu vào 2

1

4

3

4

softmax 2

0,0206

0,4136

0,1522

0,4136


3.2 Mất entropy chéo

Mất entropy chéo nhị phân được định nghĩa là:

Mất entropy chéo nhị phân


Trong phân loại nhị phân, có hai xác suất đầu ra p_i(1-p_i) và giá trị chân lý cơ bản y_i(1-y_i).


Bài toán phân loại đa lớp sử dụng khái quát hóa tổn thất BCE cho N lớp: mất mát entropy chéo.


Mất entropy chéo


N là số lượng mẫu đầu vào, y_i là sự thật cơ bản và p_i là xác suất dự đoán của lớp i .


4 Ví dụ NN phân loại nhiều lớp với PyTorch

Để triển khai NN phân loại nhiều lớp theo xác suất, chúng ta cần:

  • sự thật và dự đoán cơ bản phải có kích thước [N,k] trong đó N là số lượng mẫu đầu vào, k là số lượng lớp - id lớp cần được mã hóa thành một vectơ có kích thước k
  • kích thước lớp tuyến tính cuối cùng phải là k
  • đầu ra từ lớp cuối cùng phải được xử lý bằng kích hoạt softmax để đạt được xác suất đầu ra
  • Mất CE nên được áp dụng cho xác suất lớp được dự đoán và giá trị chân lý cơ bản
  • tìm id lớp đầu ra từ vectơ đầu ra có kích thước k



Quá trình huấn luyện phân loại đa lớp NN


Hầu hết các phần của mã đều dựa trên mã từ bài viết trước về phân loại nhị phân.


Các phần thay đổi được đánh dấu bằng (Đã thay đổi) :

  • tiền xử lý và hậu xử lý dữ liệu
  • chức năng kích hoạt
  • mất chức năng
  • chỉ số hiệu suất
  • ma trận hỗn loạn


Hãy mã hóa mạng lưới thần kinh để phân loại nhiều lớp bằng khung PyTorch.

Đầu tiên, cài đặt phép đo ngọn đuốc - gói này sẽ được sử dụng sau này để tính toán độ chính xác phân loại và ma trận nhầm lẫn.


 # used for accuracy metric and confusion matrix !pip install torchmetrics


Nhập các gói sẽ được sử dụng sau này trong mã

 from sklearn.datasets import make_classification import numpy as np import torch import torchmetrics import matplotlib.pyplot as plt import seaborn as sn import pandas as pd from sklearn.decomposition import PCA


4.1 Tạo tập dữ liệu

Đặt biến toàn cục với số lượng lớp (nếu bạn đặt thành 2 và nhận NN phân loại nhị phân sử dụng softmax và mất Cross-Entropy)


 number_of_classes=4


tôi sẽ sử dụng sklearn.datasets.make_classification để tạo tập dữ liệu phân loại nhị phân:

  • n_samples - là số lượng mẫu được tạo

  • n_features - đặt số lượng kích thước của mẫu X được tạo

  • n_classes - số lượng lớp trong tập dữ liệu được tạo. Trong bài toán phân loại nhiều lớp cần có nhiều hơn 2 lớp


Tập dữ liệu được tạo sẽ có X có hình dạng [n_samples, n_features] và Y có hình dạng [n_samples, ] .

 def get_dataset(n_samples=10000, n_features=20, n_classes=2): # https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html#sklearn.datasets.make_classification data_X, data_y = make_classification(n_samples=n_samples, n_features=n_features, n_classes=n_classes, n_informative=n_classes, n_redundant=0, n_clusters_per_class=2, random_state=42, class_sep=4) return data_X, data_y


4.2 Trực quan hóa dữ liệu

Xác định các chức năng để trực quan hóa và in ra số liệu thống kê về tập dữ liệu. sử dụng hàm show_dataset PCA để giảm kích thước của X từ bất kỳ số nào xuống 2 để đơn giản hiển thị dữ liệu đầu vào X trong biểu đồ 2D.


 def print_dataset(X, y): print(f'X shape: {X.shape}, min: {X.min()}, max: {X.max()}') print(f'y shape: {y.shape}') print(y[:10]) def show_dataset(X, y, title=''): if X.shape[1] > 2: X_pca = PCA(n_components=2).fit_transform(X) else: X_pca = X fig = plt.figure(figsize=(4, 4)) plt.scatter(x=X_pca[:, 0], y=X_pca[:, 1], c=y, alpha=0.5) # generate colors for all classes colors = plt.cm.rainbow(np.linspace(0, 1, number_of_classes)) # iterate over classes and visualize them with the dedicated color for class_id in range(number_of_classes): class_mask = np.argwhere(y == class_id) X_class = X_pca[class_mask[:, 0]] plt.scatter(x=X_class[:, 0], y=X_class[:, 1], c=np.full((X_class[:, 0].shape[0], 4), colors[class_id]), label=class_id, alpha=0.5) plt.title(title) plt.legend(loc="best", title="Classes") plt.xticks() plt.yticks() plt.show()



4.3 Bộ chia tỷ lệ tập dữ liệu

Chia tỷ lệ tập dữ liệu có tính năng X thành phạm vi [0,1] với bộ chia tỷ lệ tối đa tối thiểu. Điều này thường được thực hiện để đào tạo nhanh hơn và ổn định hơn.


 def scale(x_in): return (x_in - x_in.min(axis=0))/(x_in.max(axis=0)-x_in.min(axis=0))


Hãy in ra số liệu thống kê về tập dữ liệu đã tạo và trực quan hóa nó bằng các hàm ở trên.

 X, y = get_dataset(n_classes=number_of_classes) print('before scaling') print_dataset(X, y) show_dataset(X, y, 'before') X_scaled = scale(X) print('after scaling') print_dataset(X_scaled, y) show_dataset(X_scaled, y, 'after')


Các kết quả đầu ra bạn sẽ nhận được dưới đây.

 before scaling X shape: (10000, 20), min: -9.549551632357336, max: 9.727761741276673 y shape: (10000,) [0 2 1 2 0 2 0 1 1 2] 

Tập dữ liệu trước khi chia tỷ lệ tối thiểu-tối đa


 after scaling X shape: (10000, 20), min: 0.0, max: 1.0 y shape: (10000,) [0 2 1 2 0 2 0 1 1 2] 

Tập dữ liệu sau khi chia tỷ lệ tối thiểu-tối đa


Tỷ lệ tối thiểu-tối đa không làm biến dạng các tính năng của tập dữ liệu, nó biến đổi tuyến tính chúng thành phạm vi [0,1]. Hình "tập dữ liệu sau khi chia tỷ lệ tối thiểu-tối đa" dường như bị biến dạng so với hình trước đó vì 20 chiều bị giảm xuống còn 2 bởi thuật toán PCA và thuật toán PCA có thể bị ảnh hưởng bởi tỷ lệ tối thiểu-tối đa.


Tạo trình tải dữ liệu PyTorch. sklearn.datasets.make_classification tạo tập dữ liệu dưới dạng hai mảng có nhiều mảng. Để tạo trình tải dữ liệu PyTorch, chúng ta cần chuyển đổi tập dữ liệu gọn gàng thành torch.tensor với torch.utils.data.TensorDataset.


 def get_data_loaders(dataset, batch_size=32, shuffle=True): data_X, data_y = dataset # https://pytorch.org/docs/stable/data.html#torch.utils.data.TensorDataset torch_dataset = torch.utils.data.TensorDataset(torch.tensor(data_X, dtype=torch.float32), torch.tensor(data_y, dtype=torch.float32)) # https://pytorch.org/docs/stable/data.html#torch.utils.data.random_split train_dataset, val_dataset = torch.utils.data.random_split(torch_dataset, [int(len(torch_dataset)*0.8), int(len(torch_dataset)*0.2)], torch.Generator().manual_seed(42)) # https://pytorch.org/docs/stable/data.html#torch.utils.data.DataLoader loader_train = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=shuffle) loader_val = torch.utils.data.DataLoader(val_dataset, batch_size=batch_size, shuffle=shuffle) return loader_train, loader_val


Kiểm tra trình tải dữ liệu PyTorch

 dataloader_train, dataloader_val = get_data_loaders(get_dataset(n_classes=number_of_classes), batch_size=32) train_batch_0 = next(iter(dataloader_train)) print(f'Batches in the train dataloader: {len(dataloader_train)}, X: {train_batch_0[0].shape}, Y: {train_batch_0[1].shape}') val_batch_0 = next(iter(dataloader_val)) print(f'Batches in the validation dataloader: {len(dataloader_val)}, X: {val_batch_0[0].shape}, Y: {val_batch_0[1].shape}')


Đầu ra:

 Batches in the train dataloader: 250, X: torch.Size([32, 20]), Y: torch.Size([32]) Batches in the validation dataloader: 63, X: torch.Size([32, 20]), Y: torch.Size([32])


4.4 Tiền xử lý và hậu xử lý tập dữ liệu (Đã thay đổi)

Tạo các hàm tiền xử lý và hậu xử lý. Như bạn có thể đã lưu ý trước khi hình chữ Y hiện tại là [N], chúng ta cần nó là [N,number_of_classes]. Để làm được điều đó, chúng ta cần mã hóa một lần các giá trị trong vectơ Y.


Mã hóa một lần là một quá trình chuyển đổi các chỉ mục lớp thành biểu diễn nhị phân trong đó mỗi lớp được biểu thị bằng một vectơ nhị phân duy nhất.


Nói cách khác: tạo một vectơ 0 có kích thước [number_of_classes] và đặt phần tử ở vị trí class_id thành 1, trong đó class_ids {0,1,…,number_of_classes-1}:

0 >> [1. 0. 0. 0.]

1 >> [0. 1. 0. 0.]

2 >> [0. 0. 1. 0.]

2 >> [0. 0. 0. 1.]


Các tensor Pytorch có thể được xử lý bằng torch.nn.function.one_hot và việc triển khai gọn gàng rất đơn giản. Vectơ đầu ra sẽ có dạng [N,number_of_classes].

 def preprocessing(y, n_classes): ''' one-hot encoding for input numpy array or pytorch Tensor input: y - [N,] numpy array or pytorch Tensor output: [N, n_classes] the same type as input ''' assert type(y)==np.ndarray or torch.is_tensor(y), f'input should be numpy array or torch tensor. Received input is: {type(categorical)}' assert len(y.shape)==1, f'input shape should be [N,]. Received input shape is: {y.shape}' if torch.is_tensor(y): return torch.nn.functional.one_hot(y, num_classes=n_classes) else: categorical = np.zeros([y.shape[0], n_classes]) categorical[np.arange(y.shape[0]), y]=1 return categorical


Để chuyển đổi vectơ được mã hóa một nóng trở lại id lớp, chúng ta cần tìm chỉ mục của phần tử tối đa trong vectơ được mã hóa một nóng. Nó có thể được thực hiện với torch.argmax hoặc np.argmax bên dưới.

 def postprocessing(categorical): ''' one-hot to classes decoding with .argmax() input: categorical - [N,classes] numpy array or pytorch Tensor output: [N,] the same type as input ''' assert type(categorical)==np.ndarray or torch.is_tensor(categorical), f'input should be numpy array or torch tensor. Received input is: {type(categorical)}' assert len(categorical.shape)==2, f'input shape should be [N,classes]. Received input shape is: {categorical.shape}' if torch.is_tensor(categorical): return torch.argmax(categorical,dim=1) else: return np.argmax(categorical, axis=1)


Kiểm tra các chức năng tiền xử lý và hậu xử lý đã xác định.

 y = get_dataset(n_classes=number_of_classes)[1] y_logits = preprocessing(y, n_classes=number_of_classes) y_class = postprocessing(y_logits) print(f'y shape: {y.shape}, y preprocessed shape: {y_logits.shape}, y postprocessed shape: {y_class.shape}') print('Preprocessing does one-hot encoding of class ids.') print('Postprocessing does one-hot decoding of class one-hot encoded class ids.') for i in range(10): print(f'{y[i]} >> {y_logits[i]} >> {y_class[i]}')


Đầu ra:

 y shape: (10000,), y preprocessed shape: (10000, 4), y postprocessed shape: (10000,) Preprocessing does one-hot encoding of class ids. Postprocessing does one-hot decoding of one-hot encoded class ids. id>>one-hot encoding>>id 0 >> [1. 0. 0. 0.] >> 0 2 >> [0. 0. 1. 0.] >> 2 1 >> [0. 1. 0. 0.] >> 1 2 >> [0. 0. 1. 0.] >> 2 0 >> [1. 0. 0. 0.] >> 0 2 >> [0. 0. 1. 0.] >> 2 0 >> [1. 0. 0. 0.] >> 0 1 >> [0. 1. 0. 0.] >> 1 1 >> [0. 1. 0. 0.] >> 1 2 >> [0. 0. 1. 0.] >> 2


4.5 Tạo và huấn luyện mô hình phân loại nhiều lớp

Phần này trình bày cách triển khai tất cả các chức năng cần thiết để huấn luyện mô hình phân loại nhị phân.


4.5.1 Kích hoạt Softmax (Đã thay đổi)

Việc triển khai công thức softmax dựa trên PyTorch

Định nghĩa kích hoạt Softmax


 def softmax(x): assert len(x.shape)==2, f'input shape should be [N,classes]. Received input shape is: {x.shape}' # Subtract the maximum value for numerical stability # you can find explanation here: https://www.deeplearningbook.org/contents/numerical.html x = x - torch.max(x, dim=1, keepdim=True)[0] # Exponentiate the values exp_x = torch.exp(x) # Sum along the specified dimension sum_exp_x = torch.sum(exp_x, dim=1, keepdim=True) # Compute the softmax return exp_x / sum_exp_x


Hãy kiểm tra softmax:

  1. tạo mảng numpy test_input trong phạm vi [-10, 11] với bước 1

  2. định hình lại nó thành một tensor có hình dạng [7,3]

  3. xử lý test_input với hàm softmax đã triển khai và triển khai mặc định PyTorch torch.nn.function.softmax

  4. so sánh kết quả (chúng phải giống hệt nhau)

  5. xuất các giá trị softmax và tổng cho tất cả bảy tensor [1,3]


 test_input = torch.arange(-10, 11, 1, dtype=torch.float32) test_input = test_input.reshape(-1,3) softmax_output = softmax(test_input) print(f'Input data shape: {test_input.shape}') print(f'input data range: [{test_input.min():.3f}, {test_input.max():.3f}]') print(f'softmax output data range: [{softmax_output.min():.3f}, {softmax_output.max():.3f}]') print(f'softmax output data sum along axis 1: [{softmax_output.sum(axis=1).numpy()}]') softmax_output_pytorch = torch.nn.functional.softmax(test_input, dim=1) print(f'softmax output is the same with pytorch implementation: {(softmax_output_pytorch==softmax_output).all().numpy()}') print('Softmax activation changes values in the chosen axis (1) so that they always sum up to 1:') for i in range(softmax_output.shape[0]): print(f'\t{i}. Sum before softmax: {test_input[i].sum().numpy()} | Sum after softmax: {softmax_output[i].sum().numpy()}') print(f'\t values before softmax: {test_input[i].numpy()}, softmax output values: {softmax_output[i].numpy()}')


Đầu ra:

 Input data shape: torch.Size([7, 3]) input data range: [-10.000, 10.000] softmax output data range: [0.090, 0.665] softmax output data sum along axis 1: [[1. 1. 1. 1. 1. 1. 1.]] softmax output is the same with pytorch implementation: True Softmax activation changes values in the chosen axis (1) so that they always sum up to 1: 0. Sum before softmax: -27.0 | Sum after softmax: 1.0 values before softmax: [-10. -9. -8.], softmax output values: [0.09003057 0.24472848 0.66524094] 1. Sum before softmax: -18.0 | Sum after softmax: 1.0 values before softmax: [-7. -6. -5.], softmax output values: [0.09003057 0.24472848 0.66524094] 2. Sum before softmax: -9.0 | Sum after softmax: 1.0 values before softmax: [-4. -3. -2.], softmax output values: [0.09003057 0.24472848 0.66524094] 3. Sum before softmax: 0.0 | Sum after softmax: 1.0 values before softmax: [-1. 0. 1.], softmax output values: [0.09003057 0.24472848 0.66524094] 4. Sum before softmax: 9.0 | Sum after softmax: 1.0 values before softmax: [2. 3. 4.], softmax output values: [0.09003057 0.24472848 0.66524094] 5. Sum before softmax: 18.0 | Sum after softmax: 1.0 values before softmax: [5. 6. 7.], softmax output values: [0.09003057 0.24472848 0.66524094] 6. Sum before softmax: 27.0 | Sum after softmax: 1.0 values before softmax: [ 8. 9. 10.], softmax output values: [0.09003057 0.24472848 0.66524094]


4.5.2 Hàm mất: entropy chéo (Đã thay đổi)

Việc triển khai công thức CE dựa trên PyTorch

 def cross_entropy_loss(softmax_logits, labels): # Calculate the cross-entropy loss loss = -torch.sum(labels * torch.log(softmax_logits)) / softmax_logits.size(0) return loss


Kiểm tra việc thực hiện CE:


  1. tạo mảng test_input có hình dạng [10,5] và các giá trị trong phạm vi [0,1) với ngọn đuốc.rand

  2. tạo mảng test_target có hình dạng [10,] và các giá trị trong phạm vi [0,4].

  3. mảng test_target mã hóa một lần nóng

  4. tính toán tổn thất với hàm cross_entropy đã triển khai và triển khai PyTorch torch.nn.feft.binary_cross_entropy

  5. so sánh kết quả (chúng phải giống hệt nhau)


 test_input = torch.rand(10, 5, requires_grad=False) test_target = torch.randint(0, 5, (10,), requires_grad=False) test_target = preprocessing(test_target, n_classes=5).float() print(f'test_input shape: {list(test_input.shape)}, test_target shape: {list(test_target.shape)}') # get loss with the cross_entropy_loss implementation loss = cross_entropy_loss(softmax(test_input), test_target) # get loss with the torch.nn.functional.cross_entropy implementation # !!!torch.nn.functional.cross_entropy applies softmax on input logits # !!!pass it test_input without softmax activation loss_pytorch = torch.nn.functional.cross_entropy(test_input, test_target) print(f'Loss outputs are the same: {(loss==loss_pytorch).numpy()}')


Sản lượng dự kiến:

 test_input shape: [10, 5], test_target shape: [10, 5] Loss outputs are the same: True


4.5.3 Số liệu chính xác (đã thay đổi)

tôi sẽ sử dụng phép đo ngọn đuốc triển khai để tính toán độ chính xác dựa trên dự đoán mô hình và sự thật cơ bản.


Để tạo số liệu độ chính xác phân loại nhiều lớp, cần có hai tham số:

  • loại nhiệm vụ "đa lớp"

  • số lớp num_classes


 # https://torchmetrics.readthedocs.io/en/stable/classification/accuracy.html#module-interface accuracy_metric=torchmetrics.classification.Accuracy(task="multiclass", num_classes=number_of_classes) def compute_accuracy(y_pred, y): assert len(y_pred.shape)==2 and y_pred.shape[1] == number_of_classes, 'y_pred shape should be [N, C]' assert len(y.shape)==2 and y.shape[1] == number_of_classes, 'y shape should be [N, C]' return accuracy_metric(postprocessing(y_pred), postprocessing(y))


4.5.4 Mô hình NN

NN được sử dụng trong ví dụ này là NN sâu với 2 lớp ẩn. Lớp đầu vào và lớp ẩn sử dụng kích hoạt ReLU và lớp cuối cùng sử dụng chức năng kích hoạt được cung cấp làm đầu vào lớp (nó sẽ là chức năng kích hoạt sigmoid đã được triển khai trước đó).


 class ClassifierNN(torch.nn.Module): def __init__(self, loss_function, activation_function, input_dims=2, output_dims=1): super().__init__() self.linear1 = torch.nn.Linear(input_dims, input_dims * 4) self.linear2 = torch.nn.Linear(input_dims * 4, input_dims * 8) self.linear3 = torch.nn.Linear(input_dims * 8, input_dims * 4) self.output = torch.nn.Linear(input_dims * 4, output_dims) self.loss_function = loss_function self.activation_function = activation_function def forward(self, x): x = torch.nn.functional.relu(self.linear1(x)) x = torch.nn.functional.relu(self.linear2(x)) x = torch.nn.functional.relu(self.linear3(x)) x = self.activation_function(self.output(x)) return x


4.5.5 Đào tạo, đánh giá và dự đoán

Quá trình huấn luyện phân loại đa lớp NN


Hình trên mô tả logic huấn luyện cho một đợt. Sau này, hàm train_epoch sẽ được gọi nhiều lần (số kỷ nguyên đã chọn).


 def train_epoch(model, optimizer, dataloader_train): # set the model to the training mode # https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.train model.train() losses = [] accuracies = [] for step, (X_batch, y_batch) in enumerate(dataloader_train): ### forward propagation # get model output and use loss function y_pred = model(X_batch) # get class probabilities with shape [N,1] # apply loss function on predicted probabilities and ground truth loss = model.loss_function(y_pred, y_batch) ### backward propagation # set gradients to zero before backpropagation # https://pytorch.org/docs/stable/generated/torch.optim.Optimizer.zero_grad.html optimizer.zero_grad() # compute gradients # https://pytorch.org/docs/stable/generated/torch.Tensor.backward.html loss.backward() # update weights # https://pytorch.org/docs/stable/optim.html#taking-an-optimization-step optimizer.step() # update model weights # calculate batch accuracy acc = compute_accuracy(y_pred, y_batch) # append batch loss and accuracy to corresponding lists for later use accuracies.append(acc) losses.append(float(loss.detach().numpy())) # compute average epoch accuracy train_acc = np.array(accuracies).mean() # compute average epoch loss loss_epoch = np.array(losses).mean() return train_acc, loss_epoch


Hàm đánh giá lặp lại trên trình tải dữ liệu PyTorch được cung cấp để tính toán độ chính xác của mô hình hiện tại và trả về tổn thất trung bình cũng như độ chính xác trung bình.


 def evaluate(model, dataloader_in): # set the model to the evaluation mode # https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.eval model.eval() val_acc_epoch = 0 losses = [] accuracies = [] # disable gradient calculation for evaluation # https://pytorch.org/docs/stable/generated/torch.no_grad.html with torch.no_grad(): for step, (X_batch, y_batch) in enumerate(dataloader_in): # get predictions y_pred = model(X_batch) # calculate loss loss = model.loss_function(y_pred, y_batch) # calculate batch accuracy acc = compute_accuracy(y_pred, y_batch) accuracies.append(acc) losses.append(float(loss.detach().numpy())) # compute average accuracy val_acc = np.array(accuracies).mean() # compute average loss loss_epoch = np.array(losses).mean() return val_acc, loss_epoch 


Hàm dự đoán lặp lại trên trình tải dữ liệu được cung cấp, thu thập các dự đoán mô hình được xử lý sau (được giải mã một lần) và đưa các giá trị chân lý vào mảng PyTorch [N,1] rồi trả về cả hai mảng. Sau này, hàm này sẽ được sử dụng để tính toán ma trận nhầm lẫn và trực quan hóa các dự đoán.


 def predict(model, dataloader): # set the model to the evaluation mode # https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.eval model.eval() xs, ys = next(iter(dataloader)) y_pred = torch.empty([0, ys.shape[1]]) x = torch.empty([0, xs.shape[1]]) y = torch.empty([0, ys.shape[1]]) # disable gradient calculation for evaluation # https://pytorch.org/docs/stable/generated/torch.no_grad.html with torch.no_grad(): for step, (X_batch, y_batch) in enumerate(dataloader): # get predictions y_batch_pred = model(X_batch) y_pred = torch.cat([y_pred, y_batch_pred]) y = torch.cat([y, y_batch]) x = torch.cat([x, X_batch]) # print(y_pred.shape, y.shape) y_pred = postprocessing(y_pred) y = postprocessing(y) return y_pred, y, x


Để huấn luyện mô hình, chúng ta chỉ cần gọi hàm train_epoch N lần, trong đó N là số epoch. Hàm đánh giá được gọi để ghi lại độ chính xác của mô hình hiện tại trên tập dữ liệu xác thực. Cuối cùng, mô hình tốt nhất được cập nhật dựa trên độ chính xác xác thực. Hàm model_train trả về độ chính xác xác thực tốt nhất và lịch sử đào tạo.


 def model_train(model, optimizer, dataloader_train, dataloader_val, n_epochs=50): best_acc = 0 best_weights = None history = {'loss': {'train': [], 'validation': []}, 'accuracy': {'train': [], 'validation': []}} for epoch in range(n_epochs): # train on dataloader_train acc_train, loss_train = train_epoch(model, optimizer, dataloader_train) # evaluate on dataloader_val acc_val, loss_val = evaluate(model, dataloader_val) print(f'Epoch: {epoch} | Accuracy: {acc_train:.3f} / {acc_val:.3f} | ' + f'loss: {loss_train:.5f} / {loss_val:.5f}') # save epoch losses and accuracies in history dictionary history['loss']['train'].append(loss_train) history['loss']['validation'].append(loss_val) history['accuracy']['train'].append(acc_train) history['accuracy']['validation'].append(acc_val) # Save the best validation accuracy model if acc_val >= best_acc: print(f'\tBest weights updated. Old accuracy: {best_acc:.4f}. New accuracy: {acc_val:.4f}') best_acc = acc_val torch.save(model.state_dict(), 'best_weights.pt') # restore model and return best accuracy model.load_state_dict(torch.load('best_weights.pt')) return best_acc, history


4.5.6 Lấy tập dữ liệu, tạo mô hình và huấn luyện nó (Đã thay đổi)

Hãy tập hợp mọi thứ lại với nhau và huấn luyện mô hình phân loại nhiều lớp.

 ######################################### # Get the dataset X, y = get_dataset(n_classes=number_of_classes) print(f'Generated dataset shape. X:{X.shape}, y:{y.shape}') # change y numpy array shape from [N,] to [N, C] for multi-class classification y = preprocessing(y, n_classes=number_of_classes) print(f'Dataset shape prepared for multi-class classification with softmax activation and CE loss.') print(f'X:{X.shape}, y:{y.shape}') # Get train and validation datal loaders dataloader_train, dataloader_val = get_data_loaders(dataset=(scale(X), y), batch_size=32) # get a batch from dataloader and output intput and output shape X_0, y_0 = next(iter(dataloader_train)) print(f'Model input data shape: {X_0.shape}, output (ground truth) data shape: {y_0.shape}') ######################################### # Create ClassifierNN for multi-class classification problem # input dims: [N, features] # output dims: [N, C] where C is number of classes # activation - softmax to output [,C] probabilities so that their sum(p_1,p_2,...,p_c)=1 # loss - cross-entropy model = ClassifierNN(loss_function=cross_entropy_loss, activation_function=softmax, input_dims=X.shape[1], output_dims=y.shape[1]) ######################################### # create optimizer and train the model on the dataset optimizer = torch.optim.Adam(model.parameters(), lr=0.001) print(f'Model size: {sum([x.reshape(-1).shape[0] for x in model.parameters()])} parameters') print('#'*10) print('Start training') acc, history = model_train(model, optimizer, dataloader_train, dataloader_val, n_epochs=20) print('Finished training') print('#'*10) print("Model accuracy: %.2f%%" % (acc*100))


Kết quả đầu ra dự kiến phải tương tự như kết quả được cung cấp bên dưới.

 Generated dataset shape. X:(10000, 20), y:(10000,) Dataset shape prepared for multi-class classification with softmax activation and CE loss. X:(10000, 20), y:(10000, 4) Model input data shape: torch.Size([32, 20]), output (ground truth) data shape: torch.Size([32, 4]) Model size: 27844 parameters ########## Start training Epoch: 0 | Accuracy: 0.682 / 0.943 | loss: 0.78574 / 0.37459 Best weights updated. Old accuracy: 0.0000. New accuracy: 0.9435 Epoch: 1 | Accuracy: 0.960 / 0.967 | loss: 0.20272 / 0.17840 Best weights updated. Old accuracy: 0.9435. New accuracy: 0.9668 Epoch: 2 | Accuracy: 0.978 / 0.962 | loss: 0.12004 / 0.17931 Epoch: 3 | Accuracy: 0.984 / 0.979 | loss: 0.10028 / 0.13246 Best weights updated. Old accuracy: 0.9668. New accuracy: 0.9787 Epoch: 4 | Accuracy: 0.985 / 0.981 | loss: 0.08838 / 0.12720 Best weights updated. Old accuracy: 0.9787. New accuracy: 0.9807 Epoch: 5 | Accuracy: 0.986 / 0.981 | loss: 0.08096 / 0.12174 Best weights updated. Old accuracy: 0.9807. New accuracy: 0.9812 Epoch: 6 | Accuracy: 0.986 / 0.981 | loss: 0.07944 / 0.12036 Epoch: 7 | Accuracy: 0.988 / 0.982 | loss: 0.07605 / 0.11773 Best weights updated. Old accuracy: 0.9812. New accuracy: 0.9821 Epoch: 8 | Accuracy: 0.989 / 0.982 | loss: 0.07168 / 0.11514 Best weights updated. Old accuracy: 0.9821. New accuracy: 0.9821 Epoch: 9 | Accuracy: 0.989 / 0.983 | loss: 0.06890 / 0.11409 Best weights updated. Old accuracy: 0.9821. New accuracy: 0.9831 Epoch: 10 | Accuracy: 0.989 / 0.984 | loss: 0.06750 / 0.11128 Best weights updated. Old accuracy: 0.9831. New accuracy: 0.9841 Epoch: 11 | Accuracy: 0.990 / 0.982 | loss: 0.06505 / 0.11265 Epoch: 12 | Accuracy: 0.990 / 0.983 | loss: 0.06507 / 0.11272 Epoch: 13 | Accuracy: 0.991 / 0.985 | loss: 0.06209 / 0.11240 Best weights updated. Old accuracy: 0.9841. New accuracy: 0.9851 Epoch: 14 | Accuracy: 0.990 / 0.984 | loss: 0.06273 / 0.11157 Epoch: 15 | Accuracy: 0.991 / 0.984 | loss: 0.05998 / 0.11029 Epoch: 16 | Accuracy: 0.990 / 0.985 | loss: 0.06056 / 0.11164 Epoch: 17 | Accuracy: 0.991 / 0.984 | loss: 0.05981 / 0.11096 Epoch: 18 | Accuracy: 0.991 / 0.985 | loss: 0.05642 / 0.10975 Best weights updated. Old accuracy: 0.9851. New accuracy: 0.9851 Epoch: 19 | Accuracy: 0.990 / 0.986 | loss: 0.05929 / 0.10821 Best weights updated. Old accuracy: 0.9851. New accuracy: 0.9856 Finished training ########## Model accuracy: 98.56%


4.5.7 Lịch sử huấn luyện cốt truyện

 def plot_history(history): fig = plt.figure(figsize=(8, 4), facecolor=(0.0, 1.0, 0.0)) ax = fig.add_subplot(1, 2, 1) ax.plot(np.arange(0, len(history['loss']['train'])), history['loss']['train'], color='red', label='train') ax.plot(np.arange(0, len(history['loss']['validation'])), history['loss']['validation'], color='blue', label='validation') ax.set_title('Loss history') ax.set_facecolor((0.0, 1.0, 0.0)) ax.legend() ax = fig.add_subplot(1, 2, 2) ax.plot(np.arange(0, len(history['accuracy']['train'])), history['accuracy']['train'], color='red', label='train') ax.plot(np.arange(0, len(history['accuracy']['validation'])), history['accuracy']['validation'], color='blue', label='validation') ax.set_title('Accuracy history') ax.legend() fig.tight_layout() ax.set_facecolor((0.0, 1.0, 0.0)) fig.show() 

Lịch sử mất mát và độ chính xác của đào tạo và xác nhận


4.6 Đánh giá mô hình


4.6.1 Tính toán độ chính xác của việc huấn luyện và xác nhận

 acc_train, _ = evaluate(model, dataloader_train) acc_validation, _ = evaluate(model, dataloader_val) print(f'Accuracy - Train: {acc_train:.4f} | Validation: {acc_validation:.4f}')
 Accuracy - Train: 0.9901 | Validation: 0.9851


4.6.2 Ma trận nhầm lẫn khi in (Đã thay đổi)

 val_preds, val_y, _ = predict(model, dataloader_val) print(val_preds.shape, val_y.shape) multiclass_confusion_matrix = torchmetrics.classification.ConfusionMatrix('multiclass', num_classes=number_of_classes) cm = multiclass_confusion_matrix(val_preds, val_y) print(cm) df_cm = pd.DataFrame(cm) plt.figure(figsize = (6,5), facecolor=(0.0,1.0,0.0)) sn.heatmap(df_cm, annot=True, fmt='d') plt.show() 

Ma trận nhầm lẫn trên tập dữ liệu xác thực


4.6.3 Dự đoán cốt truyện và sự thật cơ bản

 val_preds, val_y, val_x = predict(model, dataloader_val) val_preds, val_y, val_x = val_preds.numpy(), val_y.numpy(), val_x.numpy() show_dataset(val_x, val_y,'Ground Truth') show_dataset(val_x, val_preds, 'Predictions') 


Sự thật cơ bản của tập dữ liệu xác thực

Dự đoán mô hình trên tập dữ liệu xác thực


Phần kết luận

Để phân loại nhiều lớp, bạn cần sử dụng kích hoạt softmax và mất entropy chéo. Có một số sửa đổi mã cần thiết để chuyển từ phân loại nhị phân sang phân loại nhiều lớp: các hàm tiền xử lý và xử lý hậu kỳ, kích hoạt và mất dữ liệu. Hơn nữa, bạn có thể giải quyết vấn đề phân loại nhị phân bằng cách đặt số lượng lớp thành 2 với mã hóa một nóng, softmax và mất entropy chéo.