Серия статей о глубокой нейронной сети с нуля в Rust
После [[3. Глубокая нейронная сеть с нуля в Rust — часть 3 — прямое распространение | Прямое распространение]] нам нужно определить функцию потерь, чтобы вычислить, насколько неверна наша модель в данный момент. Для простой задачи бинарной классификации функция потерь представлена ниже.
где,
m ⇾ количество обучающих примеров
Y ⇾ Истинные обучающие метки
A[L]⇾ Прогнозируемые метки от прямого распространения
Цель функции потерь — измерить несоответствие между предсказанными метками и истинными метками. Минимизируя эту потерю, мы стремимся сделать прогнозы нашей модели как можно ближе к истине.
Чтобы обучить модель и минимизировать потери, мы используем технику, называемую обратным распространением. Этот метод вычисляет градиенты функции стоимости относительно весов и смещений, которые указывают направление и величину корректировок, необходимых для каждого параметра. Вычисления градиента выполняются с использованием следующих уравнений для каждого слоя:
После того, как мы вычислили градиенты, мы можем настроить веса и смещения, чтобы минимизировать потери. Следующие уравнения используются для обновления параметров с использованием альфа скорости обучения:
Выводы этих уравнений можно найти здесь
Эти уравнения обновляют веса и смещения каждого слоя на основе их соответствующих градиентов. Итеративно выполняя прямые и обратные проходы и обновляя параметры с помощью градиентов, мы позволяем модели обучаться и улучшать свою производительность с течением времени.
Репозиторий git для всего кода, кроме этой части, представленной по ссылке ниже. Пожалуйста, обратитесь к нему, если вы где-то застряли.
Функция стоимости
Чтобы рассчитать функцию стоимости на основе приведенного выше уравнения стоимости, нам нужно сначала предоставить характеристику журнала для Array2<f32>
, поскольку вы не можете напрямую получить журнал массива в ржавчине. Мы сделаем это, написав следующий код в начале lib.rs
trait Log { fn log(&self) -> Array2<f32>; } impl Log for Array2<f32> { fn log(&self) -> Array2<f32> { self.mapv(|x| x.log(std::f32::consts::E)) } } Next, in our impl DeepNeuralNetwork we will add a function to calculate the cost. pub fn cost(&self, al: &Array2<f32>, y: &Array2<f32>) -> f32 { let m = y.shape()[1] as f32; let cost = -(1.0 / m) * (y.dot(&al.clone().reversed_axes().log()) + (1.0 - y).dot(&(1.0 - al).reversed_axes().log())); return cost.sum(); }
Далее в нашем impl DeepNeuralNetwork
мы добавим функцию для расчета стоимости.
Здесь мы передаем активацию последнего слоя al
и истинные метки y
для расчета стоимости.
Обратная активация
pub fn sigmoid_prime(z: &f32) -> f32 { sigmoid(z) * (1.0 - sigmoid(z)) } pub fn relu_prime(z: &f32) -> f32 { match *z > 0.0 { true => 1.0, false => 0.0, } } pub fn sigmoid_backward(da: &Array2<f32>, activation_cache: ActivationCache) -> Array2<f32> { da * activation_cache.z.mapv(|x| sigmoid_prime(&x)) } pub fn relu_backward(da: &Array2<f32>, activation_cache: ActivationCache) -> Array2<f32> { da * activation_cache.z.mapv(|x| relu_prime(&x)) }
Функция sigmoid_prime
вычисляет производную сигмовидной функции активации. Он принимает входные данные z
и возвращает значение производной, которое вычисляется как сигмоид z
, умноженный на 1.0
минус сигмоид z
.
Функция relu_prime
вычисляет производную функции активации ReLU. Он принимает входные данные z
и возвращает 1.0
, если z
больше 0
, и 0.0
в противном случае.
Функция sigmoid_backward
вычисляет обратное распространение для сигмовидной функции активации. Он берет производную функции стоимости по активации da
и кешу активации activation_cache
. Он выполняет поэлементное умножение между da
и производной сигмовидной функции, примененной к значениям в кэше активации, activation_cache.z
.
Функция relu_backward
вычисляет обратное распространение для функции активации ReLU. Он берет производную функции стоимости по активации da
и кешу активации activation_cache
. Он выполняет поэлементное умножение между da
и производной функции ReLU, примененной к значениям в кэше активации, activation_cache.z
.
Линейный назад
pub fn linear_backward( dz: &Array2<f32>, linear_cache: LinearCache, ) -> (Array2<f32>, Array2<f32>, Array2<f32>) { let (a_prev, w, _b) = (linear_cache.a, linear_cache.w, linear_cache.b); let m = a_prev.shape()[1] as f32; let dw = (1.0 / m) * (dz.dot(&a_prev.reversed_axes())); let db_vec = ((1.0 / m) * dz.sum_axis(Axis(1))).to_vec(); let db = Array2::from_shape_vec((db_vec.len(), 1), db_vec).unwrap(); let da_prev = w.reversed_axes().dot(dz); (da_prev, dw, db) }
Функция linear_backward
вычисляет обратное распространение для линейной составляющей слоя. Он принимает градиент функции стоимости относительно линейного выхода dz
и линейного кеша linear_cache
. Он возвращает градиенты по отношению к активации предыдущего слоя da_prev
, веса dw
и смещения db
.
Сначала функция извлекает активацию предыдущего слоя a_prev
, матрицу весов w
и матрицу смещения _b
из линейного кеша. Он вычисляет количество обучающих примеров m
путем доступа к форме a_prev
и деления количества примеров на m
.
Затем функция вычисляет градиент весов dw
, используя скалярное произведение между dz
и транспонированным a_prev
, масштабированным на 1/m
. Он вычисляет градиент смещений db
, суммируя элементы dz
по Axis(1)
и масштабируя результат на 1/m
. Наконец, он вычисляет градиент активации предыдущего слоя da_prev
, выполняя скалярное произведение между транспонированными w
и dz
.
Функция возвращает da_prev
, dw
и db
.
Обратное распространение
impl DeepNeuralNetwork { pub fn initialize_parameters(&self) -> HashMap<String, Array2<f32>> { // same as last part } pub fn forward( &self, x: &Array2<f32>, parameters: &HashMap<String, Array2<f32>>, ) -> (Array2<f32>, HashMap<String, (LinearCache, ActivationCache)>) { //same as last part } pub fn backward( &self, al: &Array2<f32>, y: &Array2<f32>, caches: HashMap<String, (LinearCache, ActivationCache)>, ) -> HashMap<String, Array2<f32>> { let mut grads = HashMap::new(); let num_of_layers = self.layers.len() - 1; let dal = -(y / al - (1.0 - y) / (1.0 - al)); let current_cache = caches[&num_of_layers.to_string()].clone(); let (mut da_prev, mut dw, mut db) = linear_backward_activation(&dal, current_cache, "sigmoid"); let weight_string = ["dW", &num_of_layers.to_string()].join("").to_string(); let bias_string = ["db", &num_of_layers.to_string()].join("").to_string(); let activation_string = ["dA", &num_of_layers.to_string()].join("").to_string(); grads.insert(weight_string, dw); grads.insert(bias_string, db); grads.insert(activation_string, da_prev.clone()); for l in (1..num_of_layers).rev() { let current_cache = caches[&l.to_string()].clone(); (da_prev, dw, db) = linear_backward_activation(&da_prev, current_cache, "relu"); let weight_string = ["dW", &l.to_string()].join("").to_string(); let bias_string = ["db", &l.to_string()].join("").to_string(); let activation_string = ["dA", &l.to_string()].join("").to_string(); grads.insert(weight_string, dw); grads.insert(bias_string, db); grads.insert(activation_string, da_prev.clone()); } grads }
Метод backward
в структуре DeepNeuralNetwork
выполняет алгоритм обратного распространения для вычисления градиентов функции стоимости относительно параметров (весов и смещений) каждого слоя.
Метод принимает окончательную активацию al
, полученную из прямого распространения, истинные метки y
и кэши, содержащие линейные значения и значения активации для каждого слоя.
Во-первых, он инициализирует пустой HashMap
с именем grads
для хранения градиентов. Он вычисляет начальную производную функции стоимости по отношению к al
, используя предоставленную формулу.
Затем, начиная с последнего слоя (выходного слоя), он извлекает кэш для текущего слоя и вызывает функцию linear_backward_activation
для вычисления градиентов функции стоимости по отношению к параметрам этого слоя. Используемая функция активации — «сигмовидная» для последнего слоя. Вычисленные градиенты для весов, смещений и активации хранятся на карте grads
.
Далее метод перебирает оставшиеся слои в обратном порядке. Для каждого слоя он извлекает кэш, вызывает функцию linear_backward_activation
для расчета градиентов и сохраняет их на карте grads
.
Наконец, метод возвращает карту grads
, содержащую градиенты функции стоимости по каждому параметру нейронной сети.
Это завершает этап обратного распространения, на котором градиенты функции стоимости вычисляются относительно весов, смещений и активаций каждого слоя. Эти градиенты будут использоваться на этапе оптимизации для обновления параметров и минимизации затрат.
Обновить параметры
pub fn update_parameters( &self, params: &HashMap<String, Array2<f32>>, grads: HashMap<String, Array2<f32>>, m: f32, learning_rate: f32, ) -> HashMap<String, Array2<f32>> { let mut parameters = params.clone(); let num_of_layers = self.layer_dims.len() - 1; for l in 1..num_of_layers + 1 { let weight_string_grad = ["dW", &l.to_string()].join("").to_string(); let bias_string_grad = ["db", &l.to_string()].join("").to_string(); let weight_string = ["W", &l.to_string()].join("").to_string(); let bias_string = ["b", &l.to_string()].join("").to_string(); *parameters.get_mut(&weight_string).unwrap() = parameters[&weight_string].clone() - (learning_rate * (grads[&weight_string_grad].clone() + (self.lambda/m) *parameters[&weight_string].clone()) ); *parameters.get_mut(&bias_string).unwrap() = parameters[&bias_string].clone() - (learning_rate * grads[&bias_string_grad].clone()); } parameters }
Давайте теперь обновим параметры, используя рассчитанные нами градиенты.
В этом коде мы проходим через каждый слой и обновляем параметры в HashMap
для каждого слоя, используя HashMap
градиентов в этом слое. Это вернет нам обновленные параметры.
Это все для этой части. Я знаю, что это было немного сложно, но это часть глубокой нейронной сети. Вот некоторые ресурсы, которые могут помочь вам понять алгоритм более наглядно.
Обзор алгоритма обратного распространения: https://www.youtube.com/watch?v=Ilg3gGewQ5U&t=203s
Расчет позади алгоритма обратного распространения: https://www.youtube.com/watch?v=tIeHLnjs5U8
В следующей и последней части этой серии мы запустим наш цикл обучения и протестируем нашу модель на некоторых изображениях кошек 🐈.
Репозиторий GitHub: https://github.com/akshayballal95/dnn_rust_blog.git
Want to Connect? My website LinkedIn Twitter
Первоначально опубликовано на https://www.akshaymakes.com.