Introducción a los conceptos básicos de PaddlePaddle

Recientemente, solicité la certificación de aprendizaje profundo de Baidu y necesito usar Paddle para la programación. Encontré algunos tutoriales básicos y los grabé específicamente para profundizar mi impresión. El mapa mental es el siguiente:

 

1. Proceso de ejecución interno de Paddle

2. Explicación interna detallada

1.Variable (variable)

(1) Los parámetros que se pueden aprender en el modelo

(2) Variable de ocupación

(3) Variable constante

2.Tensor

3.Lod-tensor

4.Operador

5.Programa

6.Ejecutor (albacea)

7. Programación imperativa

8. Guía de programación


1. Proceso de ejecución interno de Paddle

Paddle usa un proceso de ejecución de estilo compilador, que se divide en dos partes: tiempo de compilación y tiempo de ejecución, que incluyen: el compilador define el Programa y el Ejecutor se crea para ejecutar el Programa. El diagrama de flujo de ejecución es el siguiente:

1. Al compilar, el programa Python agrega variables (Tensor) y operaciones sobre variables (Operadores o Capas) a una sección del Programa llamando a los párrafos proporcionados por Paddle. Los usuarios solo necesitan describir el núcleo de la computación directa y no necesitan la computación inversa relacional, distribuida y cómo calcular en dispositivos heterogéneos.

2. El programa original se convierte a un lenguaje de descripción intermedio dentro del marco: ProgramDesc

3. Transpiler acepta una sección de ProgramDesc y genera una sección de ProgramDesc cambiado como el programa que el Ejecutor de back-end necesita para ejecutar finalmente. El transpilador no es un paso obligatorio.

4. Ejecute el Operador definido en ProgramDesc (que puede ser análogo a las instrucciones en un lenguaje de programación), y durante el proceso de ejecución, la entrada y salida requeridas serán creadas y administradas para el Operador.

2. Explicación interna detallada

1.Variable (variable)

La variable puede contener cualquier tipo de variable de valor. El tipo utilizado en la API proporcionada es Tensor.

Hay tres tipos de variables:

(1) Los parámetros que se pueden aprender en el modelo

Por ejemplo, los pesos y sesgos en la red neuronal serán actualizados por el algoritmo de optimización. Por lo tanto, debe ser un tipo de datos variable, expresado como:

w = fluid.layers.create_parameter(name="w",shape=[1],dtype='float32')

Sin embargo, la mayoría de las redes neuronales en Paddle están encapsuladas, y los pesos y los sesgos se pueden crear directamente, sin la necesidad de mostrar y llamar a interfaces relacionadas con parámetros para crearlos. P.ej:

y = fluid.layers.fc(input=x, size=128, bias_attr=True)

Entre ellos, fluid.layers.fc es la API de una red neuronal totalmente conectada.

(2) Variable de ocupación

En el modo de programación declarativa (gráfico estático), la red no conoce la información de entrada real, en este momento se necesita una Variable ocupada por espacio para indicar una Variable a ingresar. fluid.data indica que se debe proporcionar la información de forma del tensor de entrada. Cuando se encuentra una dimensión incierta, la dimensión correspondiente se designa como Ninguna, de la siguiente manera:

import paddle.fluid as fluid

#定义x的维度为[3,None],其中我们只能确定x的第一的维度为3,第二个维度未知,要在程序执行过程中才能确定
x = fluid.data(name="x", shape=[3,None], dtype="int64")

#若图片的宽度和高度在运行时可变,将宽度和高度定义为None。
#shape的三个维度含义分别是:batch_size, channel、图片的宽度、图片的高度
b = fluid.data(name="image",shape=[None, 3,None,None],dtype="float32")

dtype es el tipo de datos, int64 es un tipo de datos entero de 64 bits con signo, los tipos admitidos son los siguientes:

(3) Variable constante

La variable constante se puede realizar a través de fluid.layers.fill_constant, y se pueden especificar la forma interna del tensor, el tipo de datos y el valor constante.

import paddle.fluid as fluid
data = fluid.layers.fill_constant(shape=[1], value=0, dtype='int64')

2.Tensor

El tensor puede entenderse simplemente como una matriz multidimensional. El tipo de datos de cada elemento del mismo tensor es el mismo y la forma del tensor es la dimensión del tensor.

El tensor (tensor) en Tensorflow es similar al objeto ndarray de Numpy. Cada tensor tiene una forma y un tipo de datos. La diferencia entre la matriz de Numpy y el tensor es:

(1) El tensor puede tener soporte de memoria aceleradora (CPU, GPU)

(2) El tensor es invariante

La siguiente figura expresa intuitivamente el tensor de 1 ~ 6 dimensiones:

Para el problema de tamaños de muestra inconsistentes en lotes en algunas tareas, Paddle ofrece dos soluciones:

(1) Relleno: relleno de muestras con tamaños inconsistentes al mismo tamaño.

(2) Lod-Tensor: registre el tamaño de cada muestra para reducir la cantidad de cálculos inútiles. Lod sacrifica la flexibilidad para mejorar el rendimiento. Lod-Tensor se puede utilizar cuando la muestra no se puede acercar en tamaño mediante agrupamiento, clasificación, etc.

La API de Tensor es:

  • create_tensor: crea una variable Lod-Tensor de un tipo de datos especificado
  • create_parameter: crea un parámetro que se pueda aprender
  • create_grobal_var: crea un tensor global
  • cast: convierte los datos al tipo especificado
  • concat: conecta los datos de entrada a lo largo de la dimensión especificada
  • sumas: suma los datos de entrada
  • fill_constant: crea un tensor con una forma y tipo específicos, y el valor inicial de la variable se puede establecer por valor
  • asignar: copiar una variable
  • argmin: calcula el índice del elemento más pequeño en el eje especificado del tensor de entrada
  • argmax: calcula el índice del elemento más grande en el eje especificado del tensor de entrada
  • argsort: ordena el tensor de entrada en el eje especificado y devuelve la variable de datos ordenada y su valor de índice correspondiente
  • unos: crea un tensor de un tamaño y tipo de datos específicos, y el valor inicial es 1
  • ceros: crea un tensor con un tamaño y tipo de datos específicos, y el valor inicial es 0
  • reverse: use reverse para invertir el tensor a lo largo del eje especificado

3.Lod-tensor

En la tarea nlp, un lote contiene N oraciones y la longitud de las oraciones es diferente. Para resolver el problema de la longitud inconsistente, Paddle ofrece dos soluciones: (1) relleno, (2) Lod-Tensor y guardar el secuencia en tensor al mismo tiempo La información de longitud.

Para el método de relleno, la cantidad de cálculo del marco aumentará, pero la longitud de las oraciones en un lote se puede hacer lo más cerca posible mediante mecanismos como el agrupamiento y la clasificación, que pueden reducir la proporción de relleno. Es posible registrar la información del acolchado mediante la introducción de una máscara, además del impacto del acolchado en el efecto de entrenamiento.

Pero para algunas tareas de nlp, como las tareas de chat, debe calcular la similitud entre la consulta (pregunta) y las respuestas múltiples, y las respuestas están todas en un lote, puede usar el método Lod-Tensor y Lod-Tensor almacena la información de longitud de la muestra, no es necesario agregar palabras de relleno.

Lod-Tensor empalma dimensiones de longitud inconsistente en una dimensión grande e introduce una estructura de datos de índice (LoD) para dividir el tensor en secuencias.

4.Operador

Todas las operaciones sobre los datos están representadas por el Operador, en el módulo paddle.fluid.layers, paddle.fluid.nets. Por ejemplo: use paddle.fluid.layers.elementwise_add () para implementar la suma:

import paddle.fluid as fluid
import numpy

a = fluid.data(name="a",shape=[1],dtype='float32')
b = fluid.data(name="b",shape=[1],dtype='float32')

result = fluid.layers.elementwise_add(a,b)

# 定义执行器,并且制定执行的设备为CPU
cpu = fluid.core.CPUPlace()
exe = fluid.Executor(cpu)
exe.run(fluid.default_startup_program())

x = numpy.array([5]).astype("float32")
y = numpy.array([7]).astype("float32")

outs = exe.run(
        feed={'a':x,'b':y},
        fetch_list=[a,b,result])
# 打印输出结果,[array([12.], dtype=float32)]
print( outs )

El resultado de salida es:

[matriz ([5.], dtype = float32), matriz ([7.], dtype = float32), matriz ([12.], dtype = float32)]

En los lenguajes de programación, el flujo de control determina el orden de ejecución de las sentencias. Los flujos de control comunes incluyen ejecución secuencial, bifurcación y bucle.

(1) Rama condicional If-else

La rama condicional permite la entrada del mismo lote para seleccionar la ejecución lógica en true_block o false_block de acuerdo con las condiciones dadas, y luego fusionar las salidas de las dos ramas en una salida después de la ejecución.

(2) Interruptor

La estructura de selección de múltiples ramas, como la declaración de caso de cambio que se encuentra comúnmente en los lenguajes de programación, selecciona diferentes ramas para su ejecución de acuerdo con el valor de la expresión de entrada. Las características del caudal de control definidas por Fluid son:

  • La condición del caso es un valor de tipo bool, es decir, es un tipo de tensor Variable in Program;
  • Verifique caso por caso, seleccione el primer caso que cumpla con las condiciones para la ejecución y salga del bloque al que pertenece una vez finalizada la ejecución;
  • Si todos los casos no cumplen las condiciones, se seleccionará el caso predeterminado para su ejecución.

(3) mientras

Mientras que el bucle, cuando la condición es verdadera, el Whileflujo de control del bucle de ejecución pertenece a la blocklógica, las salidas de bucle condicional son falsas. Las API relacionadas son

  • incremento : API de acumulación, generalmente utilizada para contar el número de ciclos;
  • array_read : desde la LOD_TENSOR_ARRAYubicación especificada en la variable de lectura, calculada;
  • array_write : la variable escrita en la LOD_TENSOR_ARRAYubicación especificada, almacena los resultados.

(4) DynamicRNN

Dynamic RNN puede procesar un lote de datos de secuencia de longitud desigual y acepta como entrada Variable con lod_level = 1. En el bloque DynamicRNNde, el usuario necesita personalizar la lógica de cálculo de un solo paso de RNN. En cada paso de tiempo, el usuario puede escribir el estado que se va a memorizar en la memoria de DynamicRNN y escribir la salida requerida en su salida.

(5) StaticRNN

RNN estático, solo puede manejar secuencias de datos de longitud fija, recibiendo lod_level=0la Variable como entrada. Y DynamicRNNsimilares, en cada paso de tiempo RNN único, el usuario necesita una lógica de cálculo personalizada y puede generar el estado de escritura

5.Programa

Describe dinámicamente todo el proceso de cálculo en forma de Programa. Este método de descripción combina la flexibilidad de la modificación de la estructura de la red y la conveniencia de la construcción de modelos, lo que mejora en gran medida la capacidad del marco para expresar modelos al tiempo que garantiza el rendimiento.

Los Operadores definidos por el usuario se colocarán en el Programa secuencialmente. Durante el proceso de construcción de la red, dado que el flujo de control de Python no se puede utilizar, Paddle proporciona soporte para las estructuras de operación de flujo de control de bucle y distribución al mismo tiempo, lo que permite a los usuarios describir cualquier combinación por combinación Modelo complejo.

Ejecución de secuencia:

x = fluid.data(name='x',shape=[None, 13], dtype='float32')
y_predict = fluid.layers.fc(input=x, size=1, act=None)
y = fluid.layers.data(name='y', shape=[1], dtype='float32')
cost = fluid.layers.square_error_cost(input=y_predict, label=y)

Hay clases switch y if else en Fluid para implementar la selección de condiciones.

Ideas de diseño de programas

Una vez que el usuario completa la definición de la red, generalmente hay 2 programas en un programa de Paddle:

1.fluid.default_startup_program: define varias operaciones como la inicialización de los parámetros del modelo, la inicialización de los parámetros del optimizador, la inicialización del lector, etc.

2.fluid.default_main_program: define varias operaciones como el modelo de red neuronal, el cálculo hacia adelante y hacia atrás, la actualización de los parámetros del modelo, la actualización de los parámetros del optimizador, etc.

Estructura básica del programa

La estructura básica del Programa de Paddle son algunos bloques anidados, que se asemejan a un programa C ++ o Java en su forma. El concepto de bloques es el mismo que el de los programas generales. Los bloques incluyen: (1) la definición de variables locales, (2) una serie de Operadores

import paddle.fluid as fluid

x = fluid.data(name='x', shape=[1], dtype='int64') # block 0
y = fluid.data(name='y', shape=[1], dtype='int64') # block 0

def true_block():
    return fluid.layers.fill_constant(dtype='int64', value=1, shape=[1]) # block 1
    
def false_block():
    return fluid.layers.fill_constant(dtype='int64', value=0, shape=[1]) # block 2

condition = fluid.layers.less_than(x, y) # block 0

out = fluid.layers.cond(condition, true_block, false_block) # block 0

BlocksDesc 和 ProgramDesc

La información de bloque y Programa descrita por el usuario se guarda en Paddle en formato protobuf. En framework.proto, se llama BlockDesc y ProgramDesc en Paddle. El concepto de BlockDesc y ProgramDesc es similar a un árbol de sintaxis abstracta.

BlocksDesc contiene la definición de variables locales vars y una serie de operaciones de Operador:

message BlockDesc {
  required int32 idx = 1;
  required int32 parent_idx = 2;
  repeated VarDesc vars = 3;
  repeated OpDesc ops = 4;
}

parent_idx representa el bloque padre, por lo que los operadores en el bloque pueden hacer referencia a variables definidas localmente o las definiciones en el bloque ancestro

Cada capa de bloques del Programa se aplana y almacena en una matriz. E indexado por Blocks ID.

message ProgramDesc {
  repeated BlockDesc blocks = 1;
}

Operador Bloques 的

El operador de If-else contiene dos bloques: rama verdadera y rama falsa.

6.Ejecutor (albacea)

La filosofía de diseño de Paddle es similar a los lenguajes de programación de alto nivel C ++ y java, etc. El proceso de ejecución del programa se divide en un proceso de compilación y un proceso de ejecución. Una vez que el usuario completa la definición del Programa, el Ejecutor acepta este programa y lo convierte en un FluidProgram verdaderamente ejecutable en el backend de C ++. Esto se llama compilación. Después de la compilación, debe utilizar Executor para ejecutar el FluidProgram compilado.

El ejecutor aceptará un ProgramDesc, uno block_idy uno en tiempo de ejecución Scope. ProgramDescblocklista, cada elemento contiene la definición blockde todos los parámetros operatoren él protobuf; block_idespecifica el bloque de entrada; Scopees un contenedor para todas las instancias de variables.

Que Scopecontiene nameel Variablemapeo, todas las variables están definidas en el Scopeinterior. La mayoría de las API se utilizarán de forma predeterminada global_scope, por ejemplo Executor.run, puede especificar la red para que se ejecute en una determinada Scope, una red puede ser diferente en la Scopeejecución interna y la Scopeactualización dentro de la diferente Variable.

El proceso específico de la compilación y ejecución completadas se muestra en la siguiente figura:

  1. El ejecutor crea un ámbito para cada bloque. Los bloques se pueden anidar, por lo que los ámbitos también se pueden anidar.
  2. Cree todas las variables en el alcance.
  3. Cree y ejecute todos los operadores.

(1) Crear ejecutor

#使用CPU
cpu=fluid.CPUPlace()
#创建执行器
exe = fluid.Executor(cpu)

(2) Ejecutar ejecutor

Antes del entrenamiento formal, debe realizar la inicialización de parámetros.

 #参数初始化
 exe.run(fluid.default_startup_program())

Dado que hay varias columnas de datos entrantes y salientes, Paddle define los datos de transmisión de los datos a través del mapeo de fuentes y recupera el resultado a través de fetch_list.

x = numpy.random.random(size=(10, 1)).astype('float32')
outs = exe.run(
	feed={'X': x},
	fetch_list=[loss.name])

7. Programación imperativa

En términos de paradigma de programación, Feizhi compatible admite programación declarativa (gráfico estático) y programación imperativa (gráfico dinámico). En el diseño de Feizhi, una red neuronal se define como una descripción de un programa similar, es decir, el usuario está escribiendo en Se define el proceso del programa, la expresión del modelo y el cálculo. En cuanto a la realización del flujo de control del gráfico estático, Feizhi utiliza su propio flujo de control OP, lo que hace que el programa definido en el Feizhi sea un modelo de red, que puede tener una expresión interna y puede ser compilado y ejecutado globalmente con optimización. Proporciona un flujo de control nativo de Python y lo ejecuta de manera interpretada. Este es el modelo de programación imperativo. (No entendiste, yo tampoco)

Programación declarativa: primero compila y luego ejecuta, el usuario necesita definir la estructura de red completa de antemano, luego compila y optimiza la estructura de red, y luego ejecuta para obtener el resultado.

Programación imperativa: ejecución analítica, cada vez que escribe una línea de código, puede obtener el resultado del cálculo

Problemas con la programación declarativa:

  • Usando el método de compilar primero y luego ejecutar, la fase de red se separa de la fase de ejecución, lo que resulta en una depuración inconveniente.
  • Pertenece a un método de programación simbólico Para aprender un nuevo método de programación, existe un cierto umbral de entrada.
  • La estructura de la red es fija y el soporte para algunas tareas de estructura de árbol no es lo suficientemente bueno.

Ventajas de la programación imperativa:

  • Una vez que se ejecuta el código, los resultados se pueden obtener de inmediato y se admite la función de depuración de puntos de interrupción del IDE, lo que hace que la depuración sea más conveniente.
  • Es un método de programación imperativo, que es similar a la forma de escribir Python y es más fácil de aprender.
  • La estructura de la red se puede cambiar en diferentes niveles, haciéndola más flexible de usar.

Comparación de código entre los dos:

import numpy as np
import paddle.fluid as fluid
from paddle.fluid.dygraph.base import to_variable

main_program = fluid.Program()
startup_program = fluid.Program()
with fluid.program_guard(main_program=main_program, startup_program=startup_program):
    # 利用np.ones函数构造出[2*2]的二维数组,值为1
    data = np.ones([2, 2], np.float32)

    # 静态图模式下,使用layers.data构建占位符用于数据输入
    x = fluid.layers.data(name='x', shape=[2], dtype='float32')
    print('In static mode, after calling layers.data, x = ', x)
    # 静态图模式下,对Variable类型的数据执行x=x+10操作
    x += 10
    # 在静态图模式下,需要用户显示指定运行设备
    # 此处调用fluid.CPUPlace() API来指定在CPU设备上运行程序
    place = fluid.CPUPlace()
    # 创建“执行器”,并用place参数指明需要在何种设备上运行
    exe = fluid.Executor(place=place)
    # 初始化操作,包括为所有变量分配空间等,比如上面的‘x’,在下面这行代码执行后才会被分配实际的内存空间
    exe.run(fluid.default_startup_program())
    # 使用执行器“执行”已经记录的所有操作,在本例中即执行layers.data、x += 10操作
    # 在调用执行器的run接口时,可以通过fetch_list参数来指定要获取哪些变量的计算结果,这里我们要获取‘x += 10’执行完成后‘x’的结果;
    # 同时也可以通过feed参数来传入数据,这里我们将data数据传递给‘fluid.layers.data’指定的‘x’。
    data_after_run = exe.run(fetch_list=[x], feed={'x': data})
    # 此时我们打印执行器返回的结果,可以看到“执行”后,Tensor中的数据已经被赋值并进行了运算,每个元素的值都是11
    print('In static mode, data after run:', data_after_run)

# 开启动态图模式
with fluid.dygraph.guard():
    # 动态图模式下,将numpy的ndarray类型的数据转换为Variable类型
    x = fluid.dygraph.to_variable(data)
    print('In DyGraph mode, after calling dygraph.to_variable, x = ', x)
    # 动态图模式下,对Variable类型的数据执行x=x+10操作
    x += 10
    # 动态图模式下,调用Variable的numpy函数将Variable类型的数据转换为numpy的ndarray类型的数据
    print('In DyGraph mode, data after run:', x.numpy())

Salida de resultado de imagen estática:

In static mode, after calling layers.data, x =  name: "x"
type {
  type: LOD_TENSOR
  lod_tensor {
    tensor {
      data_type: FP32
      dims: -1
      dims: 2
    }
    lod_level: 0
  }
}
persistable: false

In static mode, data after run: [array([[11., 11.],
       [11., 11.]], dtype=float32)]

Salida de imagen dinámica:

In DyGraph mode, after calling dygraph.to_variable, x =  name generated_var_0, dtype: VarType.FP32 shape: [2, 2] 	lod: {}
	dim: 2, 2
	layout: NCHW
	dtype: float
	data: [1 1 1 1]

In DyGraph mode, data after run: [[11. 11.]
 [11. 11.]]

8. Guía de programación

Muestre un ejemplo de programación completo.

# 加载库
import paddle.fluid as fluid
import numpy

# 定义输入数据
train_data=numpy.array([[1.0],[2.0],[3.0],[4.0]]).astype('float32')
y_true = numpy.array([[2.0],[4.0],[6.0],[8.0]]).astype('float32')

# 组建网络
x = fluid.data(name="x",shape=[None, 1],dtype='float32')
y = fluid.data(name="y",shape=[None, 1],dtype='float32')
y_predict = fluid.layers.fc(input=x,size=1,act=None)

# 定义损失函数
cost = fluid.layers.square_error_cost(input=y_predict,label=y)
avg_cost = fluid.layers.mean(cost)

# 选择优化方法
sgd_optimizer = fluid.optimizer.SGD(learning_rate=0.01)
sgd_optimizer.minimize(avg_cost)

# 网络参数初始化
cpu = fluid.CPUPlace()
exe = fluid.Executor(cpu)
exe.run(fluid.default_startup_program())

# 开始训练,迭代100次
for i in range(100):
    outs = exe.run(
        feed={'x':train_data, 'y':y_true},
        fetch_list=[y_predict, avg_cost])

# 输出训练结果
print (outs)

El resultado de salida es:

[matriz ([[2.2075021], [4.1005487], [5.9935956], [7.8866425]], dtype = float32), matriz ([0.01651453], dtype = float32)]

Supongo que te gusta

Origin blog.csdn.net/qq_28409193/article/details/108123466
Recomendado
Clasificación