Градиентная прошивка в простой задаче классификации MNIST

Да, это вызывает неопределенное поведение. Lambdas будет ссылаться на объекты, расположенные в стеке, которые вышли за рамки. (Технически, как я понимаю, поведение определяется до доступа lambdas a и / или b. Если вы никогда не вызываете возвращенные lambdas, тогда нет UB.)

Это неопределенное поведение так же, как неопределенное поведение, чтобы вернуть ссылку на локализованный стек, а затем использовать эту ссылку после локального выхода из области видимости, за исключением того, что в этом случае она немного запутывается lambda.

Кроме того, обратите внимание, что порядок, в котором вызывается lambdas, не указан - компилятор может вызывать f.second() перед f.first(), потому что оба являются частью одного и того же полного выражения. Поэтому, даже если мы исправим неопределенное поведение, вызванное использованием ссылок на уничтоженные объекты, как 2 0, так и 2 1 являются еще действительными выходами из этой программы, и которые вы получаете, зависит от порядка, в котором ваш компилятор решает выполнить лямбды. Обратите внимание, что это не неопределенное поведение , потому что компилятор вообще не может делать вообще , а просто имеет некоторую свободу в решении порядка некоторые вещи .

(Имейте в виду, что << в вашей функции main() вызывает пользовательскую функцию operator<<, а порядок, в котором вычисляются аргументы функции, не указан. могут испускать код, который оценивает все аргументы функции в одном и том же полном выражении в любом порядке, с ограничением, что все аргументы функции должны быть оценены до того, как эта функция будет вызвана.)

Исправить первая проблема, используйте std::shared_ptr для создания объекта с подсчетом ссылок. Захватите этот общий указатель по значению, и lambdas сохранит объект, указывающий на объект, до тех пор, пока они (и любые их копии) существуют. Этот выделенный кучей объект - это где мы будем хранить общее состояние a и b.

Чтобы исправить вторую проблему, оцените каждую лямбда в отдельном заявлении.

Вот ваш код, переписанный с неопределенным поведением, и с f.first() гарантированно вызывается перед f.second():

std::pair, std::function> addSome() {
    // We store the "a" and "b" ints instead in a shared_ptr containing a pair.
    auto numbers = std::make_shared>(0, 0);

    // a becomes numbers->first
    // b becomes numbers->second

    // And we capture the shared_ptr by value.
    return std::make_pair(
        [numbers] {
            ++numbers->first;
            ++numbers->second;
            return numbers->first + numbers->second;
        },
        [numbers] {
            return numbers->first;
        }
    );
}

int main() {
    auto f = addSome();
    // We break apart the output into two statements to guarantee that f.first()
    // is evaluated prior to f.second().
    std::cout << f.first();
    std::cout << " " << f.second();
    return 0;
}

( Посмотрите, как он работает .)

0
задан Narsil Zhang 13 July 2018 в 16:09
поделиться

1 ответ

Вы инициализируете переменную весов нулями, поэтому оптимизатор не может вычислить градиенты. Кроме того, 0.5 чрезвычайно высока для скорости обучения спуска градиента.

Попробуйте инициализировать переменную вашего веса случайным образом:

W = tf.get_variable("W", shape=[784, 10], initializer=tf.random_uniform_initializer())
b = tf.get_variable("b", shape=[10], initializer=tf.zeros_initializer())

И установите скорость обучения на что-то порядка 0.0005.

Есть также другие инициализаторы , которые вы можете предпочесть вместо этого.

Учебное видео для Tensorboard охватывает точный сценарий инициализации нулевого веса. Вы уже пишете сводку в своем коде, это может быть хорошей идеей, чтобы просмотреть это видео, чтобы посмотреть, как его использовать, чтобы помочь отлаживать.

2
ответ дан argx 17 August 2018 в 12:26
поделиться
  • 1
    Большое спасибо! Для новичков, как я, это действительно очень важно. – Narsil Zhang 14 July 2018 в 06:36
Другие вопросы по тегам:

Похожие вопросы: