Нативная нейронная сеть, часть I: вычислительная диаграмма

искусственный интеллект TensorFlow алгоритм Нейронные сети
Нативная нейронная сеть, часть I: вычислительная диаграмма

оригинал:Deep Learning From Scratch I: Computational Graphs

Перевод: Сунь Имэн

Рецензент: Кайзер


Это первая глава в этой серии руководств. В этой главе вы познакомитесь с математическими и алгоритмическими основами глубоких нейронных сетей. Затем мы будем следовать TensorFlow API и самостоятельно реализовывать библиотеку нейронной сети на Python.

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

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

Я буду делать код как можно более простым и понятным, его легче понять, чем эффективно запускать. Поскольку наш API смоделирован по образцу TensorFlow, вы, естественно, узнаете, как использовать API TensorFlow и операционный механизм, лежащий в основе TensorFlow, после того, как вы закончите эту главу (вместо того, чтобы тратить время на изучение универсального и наиболее эффективного API).




Вычислительные графики

Начнем с вычислительного графа (computational graph), поскольку нейронные сети сами по себе являются особой формой вычислительных графов.

Вычислительный граф — это ориентированный граф, в котором вершины соответствуют операциям (Operation) или переменная (Variable).

Переменные могут передавать свои значения Операциям, а Операции могут передавать свои выходные данные другим Операциям. В этом случае каждый узел в графе вычислений определяет функцию переменной в графе (значение этого предложения может относиться к определению «функции», что означает, что один вход соответствует одному выходу).

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

Вычислительный граф в приведенном ниже примере добавляет два входа x и y и вычисляет сумму z.

В этом примере x и y являются входными узлами z, а z является потребителем x и y. z таким образом определяет функцию, а именно:

z:R^2 -> R where z(x,y) = x + y


По мере усложнения вычислений концепция вычислительного графа становится все более важной. Например, следующий вычислительный граф определяет аффинное преобразование:

z(A, x, b) = Ax + b




Операции

Каждая операция имеет три характеристики:

  • Функция расчета: используется для расчета значения, которое должно быть выведено для данного входа.
  • входной узел (node): их может быть несколько, это может быть переменная или другая операция.
  • consumer: Их может быть более одного, использующих выходные данные Операции в качестве входных данных.

Код реализации:

class Operation:
    """Represents a graph node that performs a computation.

    An `Operation` is a node in a `Graph` that takes zero or
    more objects as input, and produces zero or more objects
    as output.
    """

    def __init__(self, input_nodes=[]):
        """Construct Operation
        """
        self.input_nodes = input_nodes

        # Initialize list of consumers (i.e. nodes that receive this operation's output as input)
        self.consumers = []

        # Append this operation to the list of consumers of all input nodes
        for input_node in input_nodes:
            input_node.consumers.append(self)

        # Append this operation to the list of operations in the currently active default graph
        _default_graph.operations.append(self)

    def compute(self):
        """Computes the output of this operation.
        "" Must be implemented by the particular operation.
        """
        pass

несколько простых операций

Чтобы познакомиться с классом операции (он понадобится вам в будущем), давайте реализуем несколько простых операций. В обеих операциях мы предполагаем, что все тензорыNumPyМассивы, чтобы поэлементное сложение и умножение матриц (.dots) не нужно было реализовывать самим.

добавление

class add(Operation):
    """Returns x + y element-wise.
    """

    def __init__(self, x, y):
        """Construct add

        Args:
          x: First summand node
          y: Second summand node
        """
        super().__init__([x, y])

    def compute(self, x_value, y_value):
        """Compute the output of the add operation

        Args:
          x_value: First summand value
          y_value: Second summand value
        """
        return x_value + y_value

умножение матриц

class matmul(Operation):
    """Multiplies matrix a by matrix b, producing a * b.
    """

    def __init__(self, a, b):
        """Construct matmul

        Args:
          a: First matrix
          b: Second matrix
        """
        super().__init__([a, b])

    def compute(self, a_value, b_value):
        """Compute the output of the matmul operation

        Args:
          a_value: First matrix value
          b_value: Second matrix value
        """
        return a_value.dot(b_value)



Заполнители

В графе вычислений не все узлы являются операциями, как, например, в графе аффинных изменений,A, xиbНи то, ни другое не является операцией. Вместо этого они являются входными данными для графика, и если мы хотим вычислить выходные данные графика, мы должны указать значение для каждого из них. Чтобы предоставить такое значение, мы вводим заполнитель.

class placeholder:
    """Represents a placeholder node that has to be provided with a value
       when computing the output of a computational graph
    """

    def __init__(self):
        """Construct placeholder
        """
        self.consumers = []

        # Append this placeholder to the list of placeholders in the currently active default graph
        _default_graph.placeholders.append(self)



Переменные

В аффинно преобразованном графеxиAиbЕсть принципиальная разница. x — вход в операцию, а A и b — параметры операции, т. е. они присущи самому графу. Мы называем такие параметры, как A и b, переменными.

class Variable:
    """Represents a variable (i.e. an intrinsic, changeable parameter of a computational graph).
    """

    def __init__(self, initial_value=None):
        """Construct Variable

        Args:
          initial_value: The initial value of this variable
        """
        self.value = initial_value
        self.consumers = []

        # Append this variable to the list of variables in the currently active default graph
        _default_graph.variables.append(self)



Класс графа

Наконец, нам нуженoperation, placeholderиvariableКлассы, которые включены вместе. При создании нового графика вы можете вызватьas_defaultспособ установить его_defaultgraph.

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

class Graph:
    """Represents a computational graph
    """

    def __init__(self):
        """Construct Graph"""
        self.operations = []
        self.placeholders = []
        self.variables = []

    def as_default(self):
        global _default_graph
        _default_graph = self



Пример

Теперь давайте создадим вычислительный граф аффинных преобразований, используя перечисленные выше классы:

# Create a new graph
Graph().as_default()

# Create variables
A = Variable([[1, 0], [0, -1]])
b = Variable([1, 1])

# Create placeholder
x = placeholder()

# Create hidden node y
y = matmul(A, x)

# Create output node z
z = add(y, b)



Выходные данные вычислительной операции

Теперь, когда мы научились создавать вычислительные графы, пришло время рассмотреть, как вычислить результат операции.

создать сеанс (Session) для включения выполнения операции. Мы хотим иметь возможность вызывать метод запуска для экземпляра сеанса, передавая операцию для вычисления и словарь, содержащий все значения, необходимые заполнителю.

session = Session()
output = session.run(z, {
    x: [1, 2]
})

Процесс расчета здесь следующий:

Чтобы вычислить функцию, представленную операцией, нам нужно выполнить вычисления в правильном порядке. Например, если промежуточный результат y не был рассчитан, мы не можем сначала вычислить z. Поэтому мы должны убедиться, что операции выполняются в правильном порядке.Только таким образом мы можем гарантировать, что значения входных узлов, требуемых операцией, были рассчитаны до ее вычисления. Это можно сделать с помощьюобход дерева после заказавыполнить.

import numpy as np


class Session:
    """Represents a particular execution of a computational graph.
    """

    def run(self, operation, feed_dict={}):
        """Computes the output of an operation

        Args:
          operation: The operation whose output we'd like to compute.
          feed_dict: A dictionary that maps placeholders to values for this session
        """

        # Perform a post-order traversal of the graph to bring the nodes into the right order
        nodes_postorder = traverse_postorder(operation)

        # Iterate all nodes to determine their value
        for node in nodes_postorder:

            if type(node) == placeholder:
                # Set the node value to the placeholder value from feed_dict
                node.output = feed_dict[node]
            elif type(node) == Variable:
                # Set the node value to the variable's value attribute
                node.output = node.value
            else:  # Operation
                # Get the input values for this operation from node_values
                node.inputs = [input_node.output for input_node in node.input_nodes]

                # Compute the output of this operation
                node.output = node.compute(*node.inputs)

            # Convert lists to numpy arrays
            if type(node.output) == list:
                node.output = np.array(node.output)

        # Return the requested node value
        return operation.output


def traverse_postorder(operation):
    """Performs a post-order traversal, returning a list of nodes
    in the order in which they have to be computed

    Args:
       operation: The operation to start traversal at
    """

    nodes_postorder = []

    def recurse(node):
        if isinstance(node, Operation):
            for input_node in node.input_nodes:
                recurse(input_node)
        nodes_postorder.append(node)

    recurse(operation)
    return nodes_postorder

Протестируйте класс, который мы написали в примере выше:

session = Session()
output = session.run(z, {
    x: [1, 2]
})
print(output)

Низкое масло, не плохо.

Если у вас есть какие-либо вопросы, пожалуйста, прокомментируйте и обменяйтесь.




Рекомендуемое чтение

Поддразнивание мошенников электронной почты с помощью PaddlePaddle (конец)

Этот комментарий ядовит! ——Общая процедура для классификации текста

Создайте свой собственный AlphaZero с помощью Python и Keras