Premiers pas avec les concepts de base de PaddlePaddle

Récemment, j'ai postulé pour la certification Deep Learning de Baidu, et je dois utiliser Paddle pour la programmation.J'ai trouvé quelques tutoriels de base et les ai enregistrés spécifiquement pour approfondir mon impression. La carte mentale est la suivante:

 

1. Processus d'exécution interne de Paddle

2. Explication interne détaillée

1.Variable (variable)

(1) Les paramètres apprenables dans le modèle

(2) Variable d'occupation

(3) Variable constante

2. tenseur

3.Lod-Tensor

4. opérateur

5. programme

6. exécuteur testamentaire (exécuteur testamentaire)

7. Programmation impérative

8. Guide de programmation


1. Processus d'exécution interne de Paddle

Paddle utilise un processus d'exécution de style compilateur, qui est divisé en deux parties: le temps de compilation et le temps d'exécution, y compris: le compilateur définit le programme et l'exécuteur est créé pour exécuter le programme. L'organigramme d'exécution est le suivant:

1. Lors de la compilation, le programme python ajoute des variables (Tensor) et des opérations sur des variables (Operators ou Layers) à une section de Program en appelant les paragraphes fournis par Paddle. Les utilisateurs n'ont besoin que de décrire le core forward computing, et n'ont pas besoin de reverse computing relationnel, distribué et comment calculer sous des appareils hétérogènes.

2. Le programme original est converti en un langage de description intermédiaire dans le cadre: ProgramDesc

3. Transpiler accepte une section de ProgramDesc et génère une section de ProgramDesc modifié en tant que programme que l'exécuteur d'arrière-plan doit enfin exécuter. Transpiler n'est pas une étape obligatoire.

4. Exécutez l'Opérateur défini dans ProgramDesc (qui peut être analogue aux instructions d'un langage de programmation), et pendant le processus d'exécution, l'entrée et la sortie requises seront créées et gérées pour l'Opérateur.

2. Explication interne détaillée

1.Variable (variable)

La variable peut contenir n'importe quel type de variable de valeur. Le type utilisé dans l'API fournie est Tensor.

Il existe trois types de variables:

(1) Les paramètres apprenables dans le modèle

Par exemple, les pondérations et les biais dans le réseau de neurones seront mis à jour par l'algorithme d'optimisation. Par conséquent, il doit s'agir d'un type de données variable, exprimé comme suit:

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

Cependant, la plupart des réseaux de neurones de Paddle sont encapsulés, et les pondérations et les biais peuvent être créés directement, sans qu'il soit nécessaire d'afficher et d'appeler des interfaces liées aux paramètres pour les créer. Par exemple:

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

Parmi eux, fluid.layers.fc est l'API du réseau de neurones entièrement connecté.

(2) Variable d'occupation

En mode de programmation déclarative (graphe statique), le réseau ne connaît pas les informations d'entrée réelles. A ce moment, une variable occupée par un espace est nécessaire pour indiquer une variable à saisir. fluid.data indique que les informations de forme du Tensor d'entrée doivent être fournies. Lorsque vous rencontrez une dimension incertaine, la dimension correspondante est désignée comme Aucune, comme suit:

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 est le type de données, int64 est un type de données entier 64 bits signé, les types pris en charge sont les suivants:

(3) Variable constante

La variable constante peut être réalisée via fluid.layers.fill_constant, et la forme interne du Tensor, le type de données et la valeur constante peuvent être spécifiés.

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

2. tenseur

Tensor peut être simplement compris comme un tableau multidimensionnel. Le type de données de chaque élément du même Tensor est le même et la forme du Tensor est la dimension du Tensor.

Tensor (tenseur) dans Tensorflow est similaire à l'objet ndarray de Numpy. Chaque Tensor a une forme et un type de données. La différence entre le tableau Numpy et Tensor est la suivante:

(1) Le tenseur peut prendre en charge la mémoire accélératrice (CPU, GPU)

(2) Le tenseur est invariant

La figure suivante exprime intuitivement le Tensor de 1 ~ 6 dimensions:

Pour le problème des tailles d'échantillons incohérentes dans les lots dans certaines tâches, Paddle propose deux solutions:

(1) Rembourrage: rembourrage d'échantillons de tailles incohérentes à la même taille.

(2) Lod-Tensor: enregistrez la taille de chaque échantillon pour réduire la quantité de calculs inutiles. Lod sacrifie la flexibilité pour améliorer les performances. Lod-Tensor peut être utilisé lorsque la taille de l'échantillon ne peut pas être rapprochée par seau, tri, etc.

L'API de Tensor est:

  • create_tensor: crée une variable Lod-Tensor d'un type de données spécifié
  • create_parameter: crée un paramètre apprenable
  • create_grobal_var: Crée un tenseur global
  • cast: convertir les données dans le type spécifié
  • concat: connectez les données d'entrée le long de la dimension spécifiée
  • somme: additionner les données d'entrée
  • fill_constant: crée un tenseur avec une forme et un type spécifiques, et la valeur initiale de la variable peut être définie par valeur
  • assign: copier une variable
  • argmin: Calcule l'indice du plus petit élément sur l'axe spécifié du tenseur d'entrée
  • argmax: Calcule l'indice du plus grand élément sur l'axe spécifié du tenseur d'entrée
  • argsort: trie le Tensor d'entrée sur l'axe spécifié et renvoie la variable de données triées et sa valeur d'index correspondante
  • ones: créer un Tensor d'une taille et d'un type de données spécifiés, et la valeur initiale est 1
  • zéros: créer un Tensor avec une taille et un type de données spécifiés, et la valeur initiale est 0
  • reverse: utilisez la fonction inverse pour inverser le Tensor le long de l'axe spécifié

3.Lod-Tensor

Dans la tâche nlp, un lot contient N phrases, et la longueur des phrases est différente. Afin de résoudre le problème de longueur incohérente, Paddle propose deux solutions: (1) padding, (2) Lod-Tensor, et enregistrer le séquence en tenseur en même temps L'information de longueur.

Pour la méthode de remplissage, la quantité de calcul du cadre sera augmentée, mais la longueur des phrases dans un lot peut être rendue aussi proche que possible grâce à des mécanismes tels que le seau et le tri, qui peuvent réduire la proportion de remplissage. C'est aussi possibilité d'enregistrer des informations de remplissage en introduisant un masque.

Mais pour certaines tâches nlp, telles que les tâches de chat, vous devez calculer la similitude entre la requête (question) et plusieurs réponses, et les réponses sont toutes dans un lot, vous pouvez utiliser la méthode Lod-Tensor et le Lod-Tensor stocke les informations de longueur de l'échantillon, pas besoin d'ajouter des mots de remplissage.

Lod-Tensor joint des dimensions de longueur incohérente en une seule grande dimension et introduit une structure de données d'index (LoD) pour diviser le tenseur en séquences.

4. opérateur

Toutes les opérations sur les données sont représentées par l'Opérateur, dans le module paddle.fluid.layers, paddle.fluid.nets. Par exemple: utilisez paddle.fluid.layers.elementwise_add () pour implémenter l'addition:

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 )

Le résultat de sortie est:

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

Dans les langages de programmation, le flux de contrôle détermine l'ordre d'exécution des instructions. Les flux de contrôle courants incluent l'exécution séquentielle, le branchement et la mise en boucle.

(1) Branche conditionnelle If-else

La branche conditionnelle permet à l'entrée du même lot de sélectionner l'exécution logique dans true_block ou false_block selon les conditions données, puis de fusionner les sorties des deux branches en une seule sortie après exécution.

(2) Commutateur

La structure de sélection multi-branches, comme l'instruction switch-case couramment trouvée dans les langages de programmation, sélectionne différentes branches pour l'exécution en fonction de la valeur de l'expression d'entrée. Les caractéristiques du débit de contrôle défini par Fluid sont:

  • La condition du cas est une valeur de type booléen, c'est-à-dire qu'il s'agit d'une variable de type tenseur dans le programme;
  • Vérifiez au cas par cas tour à tour, sélectionnez le premier cas qui remplit les conditions d'exécution, et quittez le bloc auquel il appartient une fois l'exécution terminée;
  • Si tous les cas ne remplissent pas les conditions, le cas par défaut sera sélectionné pour l'exécution

(3) pendant

Alors que la boucle, lorsque la condition est vraie, Whilele flux de contrôle de la boucle d'exécution appartient à la blocklogique à l'intérieur, les sorties de boucle conditionnelles sont fausses. Les API associées sont

  • incrément : API d'accumulation, généralement utilisée pour compter le nombre de cycles;
  • array_read : à partir de l' LOD_TENSOR_ARRAYemplacement spécifié dans la variable de lecture, calculé;
  • array_write : La variable réécrite à l' LOD_TENSOR_ARRAYemplacement spécifié, stocke les résultats.

(4) DynamicRNN

Dynamic RNN peut traiter un lot de données de séquence de longueur inégale, et il accepte Variable avec lod_level = 1 comme entrée. Dans le bloc DynamicRNNde, l'utilisateur doit personnaliser la logique de calcul en une seule étape RNN. A chaque pas de temps, l'utilisateur peut écrire l'état à mémoriser dans la merory de DynamicRNN, et écrire la sortie requise sur sa sortie.

(5) StatiqueRNN

RNN statique, ne peut gérer que des séquences de données de longueur fixe, recevant lod_level=0la variable en entrée. Et DynamicRNNainsi de suite, à chaque étape RNN unique, l'utilisateur a besoin d'une logique de calcul personnalisée et peut sortir l'état d'écriture

5. programme

Décrivez dynamiquement l'ensemble du processus de calcul sous la forme d'un programme. Cette méthode de description combine la flexibilité de la modification de la structure du réseau et la commodité de la construction de modèles, ce qui améliore considérablement la capacité du framework à exprimer des modèles tout en garantissant les performances.

Les opérateurs définis par l'utilisateur seront placés dans le programme de manière séquentielle. Pendant le processus de construction du réseau, étant donné que le flux de contrôle de python ne peut pas être utilisé, Paddle prend en charge à la fois les structures d'opérations de flux de contrôle de distribution et de boucle, permettant aux utilisateurs de décrire tout combinaison par combinaison Modèle complexe.

Exécution de séquence:

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)

Il existe des classes switch et if else dans Fluid pour implémenter la sélection de condition.

Idées de conception de programme

Une fois que l'utilisateur a terminé la définition du réseau, il y a généralement 2 programmes dans un programme Paddle:

1.fluid.default_startup_program: définit diverses opérations telles que l'initialisation des paramètres du modèle, l'initialisation des paramètres de l'optimiseur, l'initialisation du lecteur, etc.

2.fluid.default_main_program: définit diverses opérations telles que le modèle de réseau neuronal, le calcul avant et arrière, la mise à jour des paramètres du modèle, la mise à jour des paramètres de l'optimiseur, etc.

Structure de base du programme

La structure de base du programme de Paddle est constituée de quelques blocs imbriqués, qui ressemblent à un programme C ++ ou Java dans la forme. Le concept de blocs est le même que celui des programmes généraux. Les blocs comprennent: (1) la définition des variables locales, (2) une série d'opérateurs

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

Les informations sur le bloc et le programme décrites par l'utilisateur sont enregistrées dans Paddle au format protobuf. Dans framework.proto, elles sont appelées BlockDesc et ProgramDesc dans Paddle. Le concept de BlockDesc et ProgramDesc est similaire à un arbre syntaxique abstrait.

BlocksDesc contient la définition des variables locales vars et une série d'opérations Operator:

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

parent_idx représente le bloc parent, de sorte que les opérateurs du bloc peuvent faire référence à des variables définies localement ou aux définitions dans le bloc ancêtre

Chaque couche de blocs du programme est aplatie et stockée dans un tableau. Et indexé par Blocks ID.

message ProgramDesc {
  repeated BlockDesc blocks = 1;
}

Opérateur Blocks 的

L'opérateur de If-else contient deux blocs-True branch et false branch.

6. exécuteur testamentaire (exécuteur testamentaire)

La philosophie de conception de Paddle est similaire aux langages de programmation de haut niveau C ++ et java, etc. Le processus d'exécution du programme est divisé en un processus de compilation et un processus d'exécution. Une fois que l'utilisateur a terminé la définition du programme, l'exécuteur accepte ce programme et le convertit en un FluidProgram véritablement exécutable dans le backend C ++. C'est ce qu'on appelle la compilation. Après la compilation, vous devez utiliser Executor pour exécuter le FluidProgram compilé.

L'exécuteur acceptera un ProgramDesc, un block_idet un au moment de l'exécution Scope. ProgramDescOui blockliste, chaque élément contient la définition blockde tous les paramètres operatorqu'il contient protobuf; block_idspécifie le bloc d'entrée; Scopeest un conteneur pour toutes les instances de variable.

Qui Scopecontient namele Variablemappage, toutes les variables sont définies à l' Scopeintérieur. La plupart des API seront utilisées par défaut global_scope, par exemple Executor.run, vous pouvez spécifier le réseau à exécuter à un particulier Scope, un réseau peut être différent dans l' Scopeexécution à l'intérieur et la Scopemise à jour dans le différent Variable.

Le processus spécifique de la compilation et de l'exécution terminées est illustré dans la figure suivante:

  1. L'exécuteur crée une étendue pour chaque bloc. Les blocs peuvent être imbriqués, de sorte que les étendues peuvent également être imbriquées.
  2. Créez toutes les variables de la portée.
  3. Créez et exécutez tous les opérateurs.

(1) Créer un exécuteur

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

(2) Exécuter l'exécuteur

Avant la formation formelle, vous devez effectuer l'initialisation des paramètres.

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

Comme il existe plusieurs colonnes de données entrantes et de données sortantes, Paddle définit les données de transmission des données via le mappage de flux et récupère le résultat via fetch_list.

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

7. Programmation impérative

En termes de paradigme de programmation, Feizhi compatible prend en charge la programmation déclarative (graphe statique) et la programmation impérative (graphe dynamique). Dans la conception de Feizhi, un réseau de neurones est défini comme la description d'un programme similaire, c'est-à-dire que l'utilisateur écrit In le processus du programme, l'expression du modèle et le calcul sont définis. En termes de réalisation du flux de contrôle du graphe statique, Feizhi utilise son propre flux de contrôle OP, qui fait du programme défini dans le Feizhi un modèle de réseau, qui peut avoir une expression interne et peut être compilé et exécuté globalement avec optimisation. Fournit un flux de contrôle natif Python et l'exécute de manière interprétée. C'est le modèle de programmation impératif. (N'as-tu pas compris, moi non plus)

Programmation déclarative: d'abord compiler puis exécuter, l'utilisateur doit définir à l'avance la structure complète du réseau, puis compiler et optimiser la structure du réseau, puis exécuter pour obtenir le résultat.

Programmation impérative: exécution analytique, à chaque fois que vous écrivez une ligne de code, vous pouvez obtenir le résultat du calcul

Problèmes avec la programmation déclarative:

  • En utilisant la méthode de compilation d'abord, puis d'exécution, la phase de mise en réseau est séparée de la phase d'exécution, ce qui entraîne un débogage peu pratique.
  • Il appartient à une méthode de programmation symbolique.Pour apprendre une nouvelle méthode de programmation, il y a un certain seuil d'entrée.
  • La structure du réseau est fixe et la prise en charge de certaines tâches d'arborescence n'est pas suffisante.

Avantages de la programmation impérative:

  • Une fois le code exécuté, les résultats peuvent être obtenus immédiatement et la fonction de débogage des points d'arrêt IDE est prise en charge, ce qui rend le débogage plus pratique.
  • C'est une méthode de programmation impérative, similaire à la façon d'écrire Python et plus facile à apprendre.
  • La structure du réseau peut être modifiée à différents niveaux, ce qui le rend plus flexible à utiliser.

Comparaison de code entre les deux:

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())

Sortie de résultat d'image statique:

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)]

Sortie d'image dynamique:

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. Guide de programmation

Montrez un exemple de programmation terminé.

# 加载库
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)

Le résultat de sortie est:

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

Je suppose que tu aimes

Origine blog.csdn.net/qq_28409193/article/details/108123466
conseillé
Classement