Я думаю, что предубеждения почти всегда полезны. Фактически, значение смещения позволяет вам переключать функцию активации влево или вправо, что может иметь решающее значение для успешного обучения.
Это может помочь взглянуть на простой пример. Рассмотрим эту 1-входную сеть с 1 выходом, которая не имеет смещения:
[/g0]
Выходной сигнал сети вычисляется путем умножения ввода (x) по весу (w0) и передаче результата через какую-то функцию активации (например, сигмоидальную функцию).
Вот функция, которую эта сеть вычисляет для различных значений w0:
[/g1]
Изменение веса w0 существенно изменяет «крутизну» сигмоида. Это полезно, но что, если вы хотите, чтобы сеть выводила 0, когда x равно 2? Просто изменение крутизны сигмоида не будет действительно работать - вы хотите, чтобы вы могли сдвинуть всю кривую вправо.
Именно это и позволяет вам сделать смещение. Если мы добавим смещение в эту сеть, например:
[/g2]
... тогда выход сети станет sig (w0 * x + w1 * 1,0). Вот как выглядит выход сети для разных значений w1:
[/g3]
Имея вес -5 для w1, сдвигает кривую на справа, что позволяет нам иметь сеть, которая выводит 0, когда x равно 2.
Это корректно.
Ваши статические инициализаторы, затем статический конструктор выполняется перед Вашим типичным конструктором, но когда это работает, это использует новый (), таким образом проходя через Ваш нестатический путь конструктора. Это вызывает сообщения, которые Вы видите.
Вот полный путь выполнения:
Когда Вы сначала звоните var a = new A();
в Вашей программе это - первый раз, когда к A получают доступ.
Это исчерпает статическую инициализацию A._A
На данном этапе A. _ конструкции с _A = (new A()).I();
Это совершает нападки
Console.WriteLine("new A()");
if (_A == null)
Console.WriteLine("_A == null");
с тех пор в этой точке, _A (еще) не был установлен с возвращенным, созданным типом.
Затем, статический конструктор A { static A(); }
выполняется. Это печатает "помехи ()" сообщение.
Наконец, Ваш исходный оператор (var a = new A();
) выполняется, но в этой точке, создаются помехи, таким образом, Вы получаете заключительную печать.
Кажется, компилятор делает ожидаемое.
1st - весь статический код выполняется (сначала поля, затем статический конструктор) в классе:
public static string _A = (new A()).I();
// and
static A()
{
Console.WriteLine("static A()");
}
2nd - вызывается конструктор класса:
public A()
{
Console.WriteLine("new A()");
if (_A == null)
Console.WriteLine("_A == null");
else
Console.WriteLine("_A == " + _A);
}
Вы спрашиваете, почему это возможно. Ну, по моему мнению, экземпляр не обязательно требует инициализации всех переменных класса при создании. Это просто требует, чтобы они существовали. Я думаю, что этот конкретный случай поддерживает эту мысль, потому что экземпляр создается до того, как будет выполнена вся статическая инициализация.
Я на самом деле полагаю, что это делает то, что Вы думаете. Ваш тест мешает говорить.
Ваш initalization для _A
public static string _A = (new A()).I();
Сначала создает новый экземпляр A, таким образом Ваши записи новых () и _A = пустой указатель. Поскольку это было пустым, когда это запустилось, поскольку это - инициализация. Однажды initalized, статического конструктора вызывают, который возвращает новый экземпляр.
Да, статическая полевая инициализация должна завершиться, прежде чем конструктора вызывают. Но Вы помещаете компилятор в ненормальную ситуацию, и это просто не может соблюсти это правило.
Это - интересный прием, но он не собирается происходить в нормальном приложении.