Đề bài
Adam giữ hai trung bình trượt cho mỗi trọng số, của gradient (m) và của bình phương gradient (v), rồi dùng chúng để mỗi trọng số có bước nhảy riêng.
Bắt đầu với m = 0 và v = 0 cho mọi trọng số. Với mỗi bước t = 1, 2, 3, … có gradient g:
- m ← beta1 × m + (1 - beta1) × g
- v ← beta2 × v + (1 - beta2) × g²
- m̂ = m / (1 - beta1ᵗ) và v̂ = v / (1 - beta2ᵗ) (hiệu chỉnh độ lệch)
- w ← w - learning_rate × m̂ / (√v̂ + epsilon)
Hãy cài đặt adam_steps(gradients, weights, learning_rate, beta1, beta2, epsilon), áp dụng một lần cập nhật cho mỗi hàng của gradients, theo thứ tự.
Đầu vào
gradients: Mỗi bước một hàng; mỗi hàng có một gradient cho mỗi trọng số.
weights: Các trọng số ban đầu.
learning_rate, beta1, beta2, epsilon: Các siêu tham số của Adam (thường là 0.001, 0.9, 0.999 và 0.00000001).
Đầu ra
Trả về các trọng số sau tất cả các bước.
Ràng buộc
0 ≤ số bước ≤ 1.000, 1 ≤ số trọng số ≤ 100
0 < learning_rate ≤ 1, 0 ≤ beta1, beta2 < 1, 0 < epsilon ≤ 0.001
-100 ≤ mỗi giá trị ≤ 100
Ví dụ
Đầu vào:
beta1 = 0.9
beta2 = 0.999
epsilon = 1.0e-08
weights = [1.0]
gradients = [
[2.0]
]
learning_rate = 0.1
Kết quả mong đợi:
expected = [0.9]
Đầu vào:
double beta1 = 0.9;
double beta2 = 0.999;
double epsilon = 1.0e-08;
std::vector<double> weights = {1.0};
std::vector<std::vector<double>> gradients = {
{2.0}
};
double learning_rate = 0.1;
Kết quả mong đợi:
std::vector<double> expected = {0.9};
Đầu vào:
double beta1 = 0.9;
double beta2 = 0.999;
double epsilon = 1.0e-08;
double[] weights = new double[] {1.0};
double[][] gradients = new double[][] {
new double[] {2.0}
};
double learning_rate = 0.1;
Kết quả mong đợi:
double[] expected = new double[] {0.9};
Giải thích
m = 0.1 × 2 = 0.2 và v = 0.001 × 4 = 0.004. Hiệu chỉnh độ lệch cho m̂ = 0.2 / 0.1 = 2 và v̂ = 0.004 / 0.001 = 4, nên bước nhảy là 0.1 × 2 / √4 = 0.1: w = 0.9. Ở bước đầu tiên, Adam dịch mỗi trọng số khoảng bằng learning rate, bất kể độ lớn của gradient.
Đầu vào:
beta1 = 0.9
beta2 = 0.999
epsilon = 1.0e-08
weights = [0.0, 0.0]
gradients = [
[100.0, 0.01]
]
learning_rate = 0.01
Kết quả mong đợi:
expected = [-0.01, -0.01]
Đầu vào:
double beta1 = 0.9;
double beta2 = 0.999;
double epsilon = 1.0e-08;
std::vector<double> weights = {0.0, 0.0};
std::vector<std::vector<double>> gradients = {
{100.0, 0.01}
};
double learning_rate = 0.01;
Kết quả mong đợi:
std::vector<double> expected = {-0.01, -0.01};
Đầu vào:
double beta1 = 0.9;
double beta2 = 0.999;
double epsilon = 1.0e-08;
double[] weights = new double[] {0.0, 0.0};
double[][] gradients = new double[][] {
new double[] {100.0, 0.01}
};
double learning_rate = 0.01;
Kết quả mong đợi:
double[] expected = new double[] {-0.01, -0.01};
Giải thích
Hai gradient chênh nhau 10.000 lần, nhưng cả hai trọng số đều dịch khoảng 0.01, thành [-0.01, -0.01]: Adam co giãn mỗi bước theo chính độ lớn của gradient đó.
Hướng tiếp cận
Hãy tự thử trước, sau đó mở từng gợi ý một.
Gợi ý 1
Theo dõi hai moving average cho mỗi weight: một cho hướng gradient và một cho bình phương độ lớn của nó.
Gợi ý 2
Áp dụng bias correction theo số bước hiện tại trước khi chia cho hạng tử căn bậc hai.
Gợi ý 3
Chú ý epsilon ở mẫu số, vì nó ngăn chia cho 0 và làm thay đổi nhẹ cập nhật cuối cùng.
Độ phức tạp mong đợi
thời gian O(s × w), bộ nhớ phụ O(w)
Tham khảo
Thử thách này đã được điều chỉnh cho CodeUtility. Hãy làm theo đề bài và các ràng buộc ở trên.