Đề bài
Lan truyền ngược áp dụng quy tắc chuỗi qua từng lớp, từ đầu ra ngược về đầu vào. Mạng có n đầu vào, h nơ-ron ẩn và một đầu ra, tất cả dùng hàm sigmoid (σ(z) = 1 / (1 + e^(-z))):
- lớp ẩn: a_j = σ(inputs · hidden_weights[j] + hidden_biases[j])
- đầu ra: y = σ(a · output_weights + output_bias)
- hàm mất mát: L = (y - target)²
Các gradient:
- delta_out = 2 × (y - target) × y × (1 - y)
- Lớp đầu ra: dL/doutput_weights[j] = delta_out × a_j và dL/doutput_bias = delta_out
- delta_j = delta_out × output_weights[j] × a_j × (1 - a_j)
- Lớp ẩn: dL/dhidden_weights[j][i] = delta_j × inputs[i] và dL/dhidden_biases[j] = delta_j
Hãy cài đặt backprop_gradients(inputs, hidden_weights, hidden_biases, output_weights, output_bias, target).
Đầu ra
Trả về một ma trận có h + 1 hàng:
- Hàng j (cho từng nơ-ron ẩn j, theo thứ tự): n gradient của
hidden_weights[j], sau đó là gradient củahidden_biases[j]. - Hàng cuối: h gradient của
output_weights, sau đó là gradient củaoutput_bias.
Ràng buộc
1 ≤ n, h ≤ 20
-5 ≤ mỗi đầu vào, trọng số và bias ≤ 5
0 ≤ target ≤ 1
Ví dụ
Đầu vào:
inputs = [1.0]
target = 1.0
output_bias = 0.0
hidden_biases = [0.0]
hidden_weights = [
[0.0]
]
output_weights = [0.0]
Kết quả mong đợi:
expected = [
[0.0, 0.0],
[-0.125, -0.25]
]
Đầu vào:
std::vector<double> inputs = {1.0};
double target = 1.0;
double output_bias = 0.0;
std::vector<double> hidden_biases = {0.0};
std::vector<std::vector<double>> hidden_weights = {
{0.0}
};
std::vector<double> output_weights = {0.0};
Kết quả mong đợi:
std::vector<std::vector<double>> expected = {
{0.0, 0.0},
{-0.125, -0.25}
};
Đầu vào:
double[] inputs = new double[] {1.0};
double target = 1.0;
double output_bias = 0.0;
double[] hidden_biases = new double[] {0.0};
double[][] hidden_weights = new double[][] {
new double[] {0.0}
};
double[] output_weights = new double[] {0.0};
Kết quả mong đợi:
double[][] expected = new double[][] {
new double[] {0.0, 0.0},
new double[] {-0.125, -0.25}
};
Giải thích
a = σ(0) = 0.5 và y = σ(0) = 0.5. delta_out = 2 × (0.5 - 1) × 0.25 = -0.25. Hàng đầu ra: [-0.25 × 0.5, -0.25] = [-0.125, -0.25]. delta_1 = -0.25 × 0 × 0.25 = 0, nên hàng ẩn là [0, 0]: trọng số đầu ra bằng 0 chặn gradient lại.
Đầu vào:
inputs = [1.0, 0.0]
target = 0.0
output_bias = -1.0
hidden_biases = [0.0, 0.0]
hidden_weights = [
[0.0, 1.0],
[0.0, -1.0]
]
output_weights = [1.0, 1.0]
Kết quả mong đợi:
expected = [
[0.0625, 0.0, 0.0625],
[0.0625, 0.0, 0.0625],
[0.125, 0.125, 0.25]
]
Đầu vào:
std::vector<double> inputs = {1.0, 0.0};
double target = 0.0;
double output_bias = -1.0;
std::vector<double> hidden_biases = {0.0, 0.0};
std::vector<std::vector<double>> hidden_weights = {
{0.0, 1.0},
{0.0, -1.0}
};
std::vector<double> output_weights = {1.0, 1.0};
Kết quả mong đợi:
std::vector<std::vector<double>> expected = {
{0.0625, 0.0, 0.0625},
{0.0625, 0.0, 0.0625},
{0.125, 0.125, 0.25}
};
Đầu vào:
double[] inputs = new double[] {1.0, 0.0};
double target = 0.0;
double output_bias = -1.0;
double[] hidden_biases = new double[] {0.0, 0.0};
double[][] hidden_weights = new double[][] {
new double[] {0.0, 1.0},
new double[] {0.0, -1.0}
};
double[] output_weights = new double[] {1.0, 1.0};
Kết quả mong đợi:
double[][] expected = new double[][] {
new double[] {0.0625, 0.0, 0.0625},
new double[] {0.0625, 0.0, 0.0625},
new double[] {0.125, 0.125, 0.25}
};
Giải thích
Cả hai giá trị ẩn đều là σ(0) = 0.5, nên y = σ(0.5 + 0.5 - 1) = 0.5 và delta_out = 2 × 0.5 × 0.25 = 0.25. Hàng đầu ra: [0.125, 0.125, 0.25]. Mỗi delta_j = 0.25 × 1 × 0.25 = 0.0625, cho các hàng ẩn [0.0625, 0, 0.0625] (đầu vào thứ hai bằng 0).
Hướng tiếp cận
Hãy tự thử trước, sau đó mở từng gợi ý một.
Gợi ý 1
Đi ngược từ neuron output trước, rồi dùng tín hiệu đó để tính đóng góp của từng hidden neuron.
Gợi ý 2
Bạn cần một delta cho output layer và một delta cho mỗi hidden neuron, đều dựa trên đạo hàm sigmoid.
Gợi ý 3
Cẩn thận dùng đúng weight đầu ra của từng hidden neuron khi truyền lỗi ngược về nó.
Độ phức tạp mong đợi
thời gian O(hn), bộ nhớ phụ O(hn)
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.