Quelle est la différence de portée de nom et de portée variable dans tensorflow?

276

Quelles sont les différences entre ces fonctions?

tf.variable_op_scope(values, name, default_name, initializer=None)

Renvoie un gestionnaire de contexte pour définir une opération qui crée des variables. Ce gestionnaire de contexte valide que les valeurs données proviennent du même graphique, garantit que ce graphique est le graphique par défaut et envoie une étendue de nom et une étendue de variable.


tf.op_scope(values, name, default_name=None)

Renvoie un gestionnaire de contexte à utiliser lors de la définition d'une opération Python. Ce gestionnaire de contexte valide que les valeurs données proviennent du même graphique, s'assure que ce graphique est le graphique par défaut et pousse une étendue de nom.


tf.name_scope(name)

Wrapper pour Graph.name_scope()utiliser le graphique par défaut. Voir Graph.name_scope()pour plus de détails.


tf.variable_scope(name_or_scope, reuse=None, initializer=None)

Renvoie un contexte pour la portée variable. La portée variable permet de créer de nouvelles variables et de partager celles déjà créées tout en fournissant des contrôles pour ne pas créer ou partager par accident. Pour plus de détails, reportez-vous au How To Scope variable, ici nous ne présentons que quelques exemples de base.

Xiuyi Yang
la source

Réponses:

377

Commençons par une brève introduction au partage de variables. C'est un mécanisme TensorFlowqui permet de partager des variables accessibles dans différentes parties du code sans passer de références à la variable autour.

La méthode tf.get_variablepeut être utilisée avec le nom de la variable comme argument pour créer une nouvelle variable avec ce nom ou récupérer celle qui a été créée auparavant. Ceci est différent de l'utilisation du tf.Variableconstructeur qui créera une nouvelle variable à chaque appel (et ajoutera potentiellement un suffixe au nom de la variable si une variable avec un tel nom existe déjà).

C'est aux fins du mécanisme de partage des variables qu'un type distinct de portée (portée variable) a été introduit.

En conséquence, nous finissons par avoir deux types différents de portées:

Les deux étendues ont le même effet sur toutes les opérations ainsi que sur les variables créées à l'aide tf.Variable, c'est-à-dire que l'étendue sera ajoutée en tant que préfixe à l'opération ou au nom de la variable.

Cependant, la portée du nom est ignorée par tf.get_variable. Nous pouvons voir cela dans l'exemple suivant:

with tf.name_scope("my_scope"):
    v1 = tf.get_variable("var1", [1], dtype=tf.float32)
    v2 = tf.Variable(1, name="var2", dtype=tf.float32)
    a = tf.add(v1, v2)

print(v1.name)  # var1:0
print(v2.name)  # my_scope/var2:0
print(a.name)   # my_scope/Add:0

La seule façon de placer une variable accessible à l'aide tf.get_variabledans une étendue est d'utiliser une étendue variable, comme dans l'exemple suivant:

with tf.variable_scope("my_scope"):
    v1 = tf.get_variable("var1", [1], dtype=tf.float32)
    v2 = tf.Variable(1, name="var2", dtype=tf.float32)
    a = tf.add(v1, v2)

print(v1.name)  # my_scope/var1:0
print(v2.name)  # my_scope/var2:0
print(a.name)   # my_scope/Add:0

Cela nous permet de partager facilement des variables entre différentes parties du programme, même dans différentes étendues de noms:

with tf.name_scope("foo"):
    with tf.variable_scope("var_scope"):
        v = tf.get_variable("var", [1])
with tf.name_scope("bar"):
    with tf.variable_scope("var_scope", reuse=True):
        v1 = tf.get_variable("var", [1])
assert v1 == v
print(v.name)   # var_scope/var:0
print(v1.name)  # var_scope/var:0

METTRE À JOUR

De la version r0.11, op_scopeet variable_op_scopesont à la fois dépréciée et remplacés par name_scopeet variable_scope.

Andrzej Pronobis
la source
41
Merci pour l'explication claire. Naturellement, une question de suivi serait " Pourquoi Tensorflow a- t- il ces deux mécanismes similaires à la confusion? Pourquoi ne pas les remplacer par une seule scopeméthode qui fait effectivement un variable_scope?"
John
8
Je ne pense pas comprendre conceptuellement pourquoi la distinction entre variable_scopevs name_scopeest même nécessaire. Si l'on crée une variable (de quelque façon que ce tf.Variablesoit avec ou tf.get_variable), il me semble plus naturel que nous devrions toujours pouvoir l'obtenir si nous spécifions la portée ou son nom complet. Je ne comprends pas pourquoi l'un ignore le nom de la portée alors que l'autre ne le fait pas. Comprenez-vous le rationnel de ce comportement étrange?
Charlie Parker
23
La raison en est qu'avec l'étendue des variables, on peut définir des étendues distinctes pour les variables réutilisables qui ne sont pas affectées par l'étendue du nom actuel utilisé pour définir les opérations.
Andrzej Pronobis
6
Bonjour , pouvez-vous expliquer pourquoi le nom de variable dans une variable_scope se termine toujours par un: 0? Cela signifie-t-il qu'il peut y avoir des noms de variables se terminant par: 1,: 2, etc., alors comment cela peut-il se produire?
James Fan
2
@JamesFan Chaque "déclaration" est une opération, donc quand vous dites a = tf.Variable (.. nom) vous récupérez un tenseur, mais cela crée aussi une opération. si vous imprimez un, vous obtiendrez le tenseur avec un: 0. Si vous imprimez a.op, vous obtenez l'opération qui calculera cette valeur de tenseur.
Robert Lugg
84

Les deux variable_op_scope et op_scope sont maintenant déconseillés et ne doivent pas être utilisés du tout.

En ce qui concerne les deux autres, j'ai également eu des problèmes pour comprendre la différence entre variable_scope et name_scope (ils avaient presque la même apparence) avant d'essayer de tout visualiser en créant un exemple simple:

import tensorflow as tf


def scoping(fn, scope1, scope2, vals):
    with fn(scope1):
        a = tf.Variable(vals[0], name='a')
        b = tf.get_variable('b', initializer=vals[1])
        c = tf.constant(vals[2], name='c')

        with fn(scope2):
            d = tf.add(a * b, c, name='res')

        print '\n  '.join([scope1, a.name, b.name, c.name, d.name]), '\n'
    return d

d1 = scoping(tf.variable_scope, 'scope_vars', 'res', [1, 2, 3])
d2 = scoping(tf.name_scope,     'scope_name', 'res', [1, 2, 3])

with tf.Session() as sess:
    writer = tf.summary.FileWriter('logs', sess.graph)
    sess.run(tf.global_variables_initializer())
    print sess.run([d1, d2])
    writer.close()

Ici, je crée une fonction qui crée des variables et des constantes et les regroupe dans des étendues (selon le type que j'ai fourni). Dans cette fonction, j'imprime également les noms de toutes les variables. Après cela, j'exécute le graphique pour obtenir les valeurs des valeurs résultantes et enregistrer les fichiers d'événements pour les étudier dans TensorBoard. Si vous exécutez ceci, vous obtiendrez les éléments suivants:

scope_vars
  scope_vars/a:0
  scope_vars/b:0
  scope_vars/c:0
  scope_vars/res/res:0 

scope_name
  scope_name/a:0
  b:0
  scope_name/c:0
  scope_name/res/res:0 

Vous voyez le modèle similaire si vous ouvrez TensorBoard (comme vous le voyez best en dehors de scope_namerectangulaire):


Cela vous donne la réponse :

Maintenant, vous voyez que cela tf.variable_scope()ajoute un préfixe aux noms de toutes les variables (peu importe comment vous les créez), ops, constantes. D'un autre côté, tf.name_scope()ignore les variables créées avec tf.get_variable()car cela suppose que vous savez quelle variable et dans quelle portée vous souhaitez utiliser.

Une bonne documentation sur le partage de variables vous indique que

tf.variable_scope(): Gère les espaces de noms pour les noms passés à tf.get_variable().

La même documentation fournit plus de détails sur le fonctionnement de la portée variable et quand elle est utile.

Salvador Dali
la source
2
Réponse fabuleuse avec l'exemple et les visuels, obtenons cette réponse les gens votés!
David Parks
43

Les espaces de noms sont un moyen d'organiser les noms des variables et des opérateurs de manière hiérarchique (par exemple "scopeA / scopeB / scopeC / op1")

  • tf.name_scope crée un espace de noms pour les opérateurs dans le graphique par défaut.
  • tf.variable_scope crée un espace de noms pour les variables et les opérateurs dans le graphique par défaut.

  • tf.op_scopecomme tf.name_scopepour le graphique dans lequel les variables spécifiées ont été créées.

  • tf.variable_op_scopecomme tf.variable_scopepour le graphique dans lequel les variables spécifiées ont été créées.

Les liens vers les sources ci-dessus permettent de lever l'ambiguïté de ce problème de documentation.

Cet exemple montre que tous les types d'étendues définissent des espaces de noms pour les variables et les opérateurs avec les différences suivantes:

  1. portées définies par tf.variable_op_scopeou tf.variable_scopecompatibles avectf.get_variable (il ignore deux autres étendues)
  2. tf.op_scopeet tf.variable_op_scopesélectionnez simplement un graphique dans une liste de variables spécifiées pour créer une portée pour. Autre que leur comportement égal tf.name_scopeet en tf.variable_scopeconséquence
  3. tf.variable_scopeet variable_op_scopeajoutez l'initialiseur spécifié ou par défaut.
Alexander Gorban
la source
Pour le graphique dans lequel les variables spécifiées ont été créées? Cela signifie-t-il que l'exemple ci-dessus par fabrizioM, avec tf.variable_op_scope ([a, b], nom, "mysum2") comme portée, ici les paramètres a et b ne sont pas affectés par cette fonction et les variables définies dans cette portée sont affectées?
Xiuyi Yang
La réponse pour les deux questions est oui: le graphique dans lequel les variables spécifiées ont été créées et elles ne sont pas modifiées.
Alexander Gorban
Cela signifie-t-il que tf.name_scope et tf.variable_scope ne peuvent être utilisés que dans le graphique par défaut, mais lorsque vous définissez et construisez évidemment un graphique à l'aide de tf.Graph (), les deux autres fonctions tf.op_scope et tf.variable_op_scope ne peuvent pas être utilisées dans ce graphique!
Xiuyi Yang
12

Rendons les choses simples: utilisez simplement tf.variable_scope. Citant un développeur de TF, :

Actuellement, nous recommandons à tout le monde d'utiliser variable_scopeet de ne pas utiliser, name_scopesauf le code interne et les bibliothèques.

Outre le fait que variable_scopela fonctionnalité étend essentiellement ceux de name_scope, considérez comment ils ne jouent pas si bien ensemble:

with tf.name_scope('foo'):
  with tf.variable_scope('bar'):
    x = tf.get_variable('x', shape=())
    x2 = tf.square(x**2, name='x2')
print(x.name)
# bar/x:0
print(x2.name)
# foo/bar/x2:0

En vous en tenant variable_scopeseulement, vous évitez certains maux de tête dus à ce type d'incompatibilité.

P-Gn
la source
9

Quant à l'API r0.11, op_scopeet variable_op_scopesont tous deux obsolètes . name_scopeet variable_scopepeuvent être imbriqués:

with tf.name_scope('ns'):
    with tf.variable_scope('vs'): #scope creation
        v1 = tf.get_variable("v1",[1.0])   #v1.name = 'vs/v1:0'
        v2 = tf.Variable([2.0],name = 'v2')  #v2.name= 'ns/vs/v2:0'
        v3 = v1 + v2       #v3.name = 'ns/vs/add:0'
sgu
la source
8

Vous pouvez les considérer comme deux groupes: variable_op_scopeet op_scopeprendre un ensemble de variables en entrée et sont conçus pour créer des opérations. La différence réside dans la façon dont ils affectent la création de variables avec tf.get_variable:

def mysum(a,b,name=None):
    with tf.op_scope([a,b],name,"mysum") as scope:
        v = tf.get_variable("v", 1)
        v2 = tf.Variable([0], name="v2")
        assert v.name == "v:0", v.name
        assert v2.name == "mysum/v2:0", v2.name
        return tf.add(a,b)

def mysum2(a,b,name=None):
    with tf.variable_op_scope([a,b],name,"mysum2") as scope:
        v = tf.get_variable("v", 1)
        v2 = tf.Variable([0], name="v2")
        assert v.name == "mysum2/v:0", v.name
        assert v2.name == "mysum2/v2:0", v2.name
        return tf.add(a,b)

with tf.Graph().as_default():
    op = mysum(tf.Variable(1), tf.Variable(2))
    op2 = mysum2(tf.Variable(1), tf.Variable(2))
    assert op.name == 'mysum/Add:0', op.name
    assert op2.name == 'mysum2/Add:0', op2.name

remarquez le nom de la variable v dans les deux exemples.

idem pour tf.name_scopeet tf.variable_scope:

with tf.Graph().as_default():
    with tf.name_scope("name_scope") as scope:
        v = tf.get_variable("v", [1])
        op = tf.add(v, v)
        v2 = tf.Variable([0], name="v2")
        assert v.name == "v:0", v.name
        assert op.name == "name_scope/Add:0", op.name
        assert v2.name == "name_scope/v2:0", v2.name

with tf.Graph().as_default():
    with tf.variable_scope("name_scope") as scope:
        v = tf.get_variable("v", [1])
        op = tf.add(v, v)
        v2 = tf.Variable([0], name="v2")
        assert v.name == "name_scope/v:0", v.name
        assert op.name == "name_scope/Add:0", op.name
        assert v2.name == "name_scope/v2:0", v2.name

Vous pouvez en savoir plus sur la portée variable dans le didacticiel . Une question similaire a été posée auparavant sur Stack Overflow.

fabrizioM
la source
2

De la dernière section de cette page de la documentation tensorflow: Noms des ops danstf.variable_scope()

[...] quand nous le faisons with tf.variable_scope("name"), cela ouvre implicitement a tf.name_scope("name"). Par exemple:

with tf.variable_scope("foo"):
  x = 1.0 + tf.get_variable("v", [1])
assert x.op.name == "foo/add"

Les étendues de nom peuvent être ouvertes en plus d'une étendue de variable, et elles n'affecteront alors que les noms des opérations, mais pas des variables.

with tf.variable_scope("foo"):
    with tf.name_scope("bar"):
        v = tf.get_variable("v", [1])
        x = 1.0 + v
assert v.name == "foo/v:0"
assert x.op.name == "foo/bar/add"

Lors de l'ouverture d'une étendue de variable à l'aide d'un objet capturé au lieu d'une chaîne, nous ne modifions pas l'étendue de nom actuelle pour les opérations.

Guillermo González de Garibay
la source
2

Réponse compatible Tensorflow 2.0 : Les explications Andrzej Pronobiset Salvador Daliles détails des fonctions liées à Scope.

Parmi les fonctions de portée décrites ci-dessus, qui sont actives à partir de maintenant (17 février 2020) sont variable_scopeetname_scope .

Spécifier les appels compatibles 2.0 pour ces fonctions, nous avons discuté ci-dessus, pour le bénéfice de la communauté.

Fonction en 1.x :

tf.variable_scope

tf.name_scope

Fonction respective dans 2.x :

tf.compat.v1.variable_scope

tf.name_scope( tf.compat.v2.name_scopesi migré de1.x to 2.x )

Pour plus d'informations sur la migration de 1.x vers 2.x, veuillez consulter ce Guide de migration .

Prise en charge de Tensorflow
la source