Gần đây tôi đã làm một bài tập về nhà, nơi tôi phải học một mô hình cho phân loại 10 chữ số của MNIST. CTNH có một số mã giàn giáo và tôi phải làm việc trong bối cảnh của mã này.
Bài tập về nhà của tôi hoạt động / vượt qua các bài kiểm tra nhưng bây giờ tôi đang cố gắng làm tất cả từ đầu (khung nn của riêng tôi, không có mã giàn giáo hw) và tôi bị mắc kẹt khi áp dụng phương pháp softmax trong bước backprop, và thậm chí nghĩ gì về hw mã giàn giáo có thể không chính xác.
Hw đã cho tôi sử dụng cái mà họ gọi là "mất softmax" làm nút cuối cùng trong nn. Điều đó có nghĩa là, vì một số lý do, họ đã quyết định tham gia kích hoạt softmax với mất entropy chéo tất cả trong một, thay vì coi softmax là một hàm kích hoạt và entropy chéo như một hàm mất riêng.
Sau đó, hw loss func trông như thế này (được chỉnh sửa tối thiểu bởi tôi):
class SoftmaxLoss:
"""
A batched softmax loss, used for classification problems.
input[0] (the prediction) = np.array of dims batch_size x 10
input[1] (the truth) = np.array of dims batch_size x 10
"""
@staticmethod
def softmax(input):
exp = np.exp(input - np.max(input, axis=1, keepdims=True))
return exp / np.sum(exp, axis=1, keepdims=True)
@staticmethod
def forward(inputs):
softmax = SoftmaxLoss.softmax(inputs[0])
labels = inputs[1]
return np.mean(-np.sum(labels * np.log(softmax), axis=1))
@staticmethod
def backward(inputs, gradient):
softmax = SoftmaxLoss.softmax(inputs[0])
return [
gradient * (softmax - inputs[1]) / inputs[0].shape[0],
gradient * (-np.log(softmax)) / inputs[0].shape[0]
]
Như bạn có thể thấy, về phía trước, nó có softmax (x) và sau đó mất entropy chéo.
Nhưng trên backprop, nó dường như chỉ làm đạo hàm của entropy chéo chứ không phải của softmax. Softmax là trái như vậy.
Không phải nó cũng nên lấy đạo hàm của softmax đối với đầu vào thành softmax sao?
Giả sử rằng nó nên lấy đạo hàm của softmax, tôi không chắc hw này thực sự vượt qua các bài kiểm tra như thế nào ...
Bây giờ, trong quá trình thực hiện của riêng tôi từ đầu, tôi đã tạo ra các nút riêng biệt mềm và chéo entropy, như vậy (p và t là viết tắt của dự đoán và sự thật):
class SoftMax(NetNode):
def __init__(self, x):
ex = np.exp(x.data - np.max(x.data, axis=1, keepdims=True))
super().__init__(ex / np.sum(ex, axis=1, keepdims=True), x)
def _back(self, x):
g = self.data * (np.eye(self.data.shape[0]) - self.data)
x.g += self.g * g
super()._back()
class LCE(NetNode):
def __init__(self, p, t):
super().__init__(
np.mean(-np.sum(t.data * np.log(p.data), axis=1)),
p, t
)
def _back(self, p, t):
p.g += self.g * (p.data - t.data) / t.data.shape[0]
t.g += self.g * -np.log(p.data) / t.data.shape[0]
super()._back()
Như bạn có thể thấy, mất entropy chéo của tôi (LCE) có cùng đạo hàm với một trong hw, bởi vì đó là đạo hàm cho chính sự mất mát, mà chưa đi vào softmax.
Nhưng sau đó, tôi vẫn sẽ phải thực hiện đạo hàm của softmax để xâu chuỗi nó với đạo hàm của sự mất mát. Đây là nơi tôi bị mắc kẹt.
Đối với softmax được định nghĩa là:
Đạo hàm thường được định nghĩa là:
Nhưng tôi cần một công cụ phái sinh dẫn đến một tenxơ có cùng kích thước với đầu vào thành softmax, trong trường hợp này, batch_size x 10. Vì vậy, tôi không chắc cách áp dụng ở trên chỉ cho 10 thành phần, vì nó ngụ ý rằng tôi sẽ khác biệt cho tất cả các đầu vào đối với tất cả các đầu ra (tất cả các kết hợp) hoặc ở dạng ma trận.