На самом деле это требуется для реализации любой структуры данных, которая выделяет больше памяти, чем минимально необходимое для количества вставленных элементов (т. е. ничего, кроме связанной структуры, которая выделяет один узел за раз).
Возьмите контейнеры наподобие unordered_map
, vector
или deque
. Все они выделяют больше памяти, чем минимально требуется для вставленных вами элементов, чтобы избежать необходимости распределения кучи для каждой отдельной вставки. Давайте используем vector
в качестве простейшего примера.
Когда вы это делаете:
vector<Foo> vec;
// Allocate memory for a thousand Foos:
vec.reserve(1000);
... который фактически не создает тысячу Foos. Он просто выделяет / резервирует память для них. Если vector
не использовал здесь место размещения, это было бы построение по умолчанию Foos
по всему месту, а также необходимость вызывать их деструкторы даже для элементов, которые вы никогда не вставили в первую очередь.
Allocation! = Construction, Freeing! = Destruction
Как правило, для реализации многих структур данных, подобных описанным выше, вы не можете рассматривать выделение памяти и создание элементов как одну неделимую вещь, и вы также не можете обрабатывать освобождение памяти и уничтожая элементы как одну неделимую вещь.
Должно быть разделение между этими идеями, чтобы избежать излишнего вызова конструкторов и деструкторов излишне левым и правым, и поэтому стандартная библиотека разделяет идею std::allocator
(которая не создает или не уничтожает элементы, когда он выделяет / освобождает память *) вдали от используемых им контейнеров, которые вручную создают элементы, используя размещение новых и вручную уничтожая элементы, используя явные вызовы деструкторов.
std::allocator
, но это другой предмет, о котором я не буду говорить. : -D Так или иначе, я, как правило, очень сильно его использую, так как я написал несколько стандартных контейнеров C ++ общего назначения, которые нельзя было построить в условия существующих. Среди них была небольшая реализация векторов, которую я построил пару десятилетий назад, чтобы избежать распределения кучи в обычных случаях и эффективного управления памятью (не выделяет один узел за раз). В обоих случаях я не мог реализовать их с использованием существующих контейнеров, поэтому мне пришлось использовать placement new
, чтобы избежать излишнего вызова конструкторов и деструкторов на ненужные вещи слева и справа.
Естественно, если вы когда-нибудь работаете с помощью пользовательских распределителей для выделения объектов по отдельности, например, в виде бесплатного списка, вы также обычно хотели бы использовать placement new
, как это (основной пример, который не беспокоит безопасность исключений или RAII):
Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);
На что-либо определенное внутри класса должно ссылаться квалифицированное имя, либо непосредственно на класс, либо на его экземпляр. Таким образом, самое простое решение здесь - это явный вызов NN.generate_random_nodes
для рекурсивного вызова и self.generate_random_nodes
в начальных вызовах к нему (показаны только методы с изменениями):
@staticmethod
def generate_random_nodes(dropout_prob, size):
temp = np.random.binomial(1, dropout_prob, size)
# Must explicitly qualify recursive call
return temp if not sum(temp) else NN.generate_random_nodes(dropout_prob, size)
def compute_dropout(self, activations, dropout_prob = 0.5):
[...]
mult = np.copy(activations)
# Can call static on self just fine, and avoids hard-coding class name
temp = self.generate_random_nodes(dropout_prob, size=activations.shape[0])
mult[:,i] = temp
activations*=mult
return activations
Обратите внимание, что в качестве реализации CPython подробно о Python 3.x, ссылка __class__
внутри метода, определенного в классе, создает область замыкания, которая дает вам доступ к классу, в котором он был определен, что позволяет вам избежать повторения, явно указав класс, поэтому generate_random_nodes
может быть:
@staticmethod
def generate_random_nodes(dropout_prob, size):
temp = np.random.binomial(1, dropout_prob, size)
# Must qualify recursive call
return temp if not sum(temp) else __class__.generate_random_nodes(dropout_prob, size)
, что имеет пару преимуществ:
__class__
немного быстрее, чем поиск глобальных областей действия NN
, и NN
класса изменяется во время разработки, вам вообще не нужно менять generate_random_nodes
(потому что он неявно получает ссылку на класс, в котором он был определен). Вы также можете (не полагаясь на детали реализации CPython) изменить его на classmethod
, чтобы получить то же основное преимущество:
@classmethod
def generate_random_nodes(cls, dropout_prob, size):
temp = np.random.binomial(1, dropout_prob, size)
# Must qualify recursive call
return temp if not sum(temp) else cls.generate_random_nodes(dropout_prob, size)
, поскольку classmethod
s получают ссылку на класс, к которому они были вызваны (класс экземпляра, к которому они были вызваны, если вызван на экземпляре) Это небольшое злоупотребление classmethod
(единственное предназначение classmethod
предназначено для альтернативных конструкторов в иерархиях классов, где подклассы должны быть в состоянии быть созданы с использованием альтернативного конструктора без перегрузки его в подклассе); это совершенно законно, просто немного неортодоксально.
Как описано ниже в комментариях:
temp
только если sum
его 0
, что означает temp
- массив всех нулей), что значительно увеличивает вероятность рекурсии и делает ошибку рекурсии почти наверняка для достаточно высоких аргументов dropout_prob
/ size
. Итак, вы хотите изменить temp if not sum(temp) else <recursive call>
на temp if sum(temp) else <recursive call>
, или для лучшей производительности / очевидности, учитывая, что это массив numpy
, temp if temp.any() else <recursive call>
. И хотя вероятность того, что ошибки рекурсии могут начаться с малой вероятности, может начаться, если вы хотите быть очень осторожным, просто перейдите на подход while
, основанный на циклах, который не может рисковать неопределенной рекурсией:
@staticmethod
def generate_random_nodes(dropout_prob, size):
while True:
temp = np.random.binomial(1, dropout_prob, size)
if temp.any():
return temp