J'ai vu tf.identity
utilisé à quelques endroits, comme le didacticiel officiel CIFAR-10 et l'implémentation de la normalisation par lots sur stackoverflow, mais je ne vois pas pourquoi c'est nécessaire.
À quoi sert-il? Quelqu'un peut-il donner un cas d'utilisation ou deux?
Une réponse proposée est qu'il peut être utilisé pour le transfert entre le processeur et le processeur graphique. Ce n'est pas clair pour moi. L'extension à la question, basée sur this : loss = tower_loss(scope)
se trouve sous le bloc GPU, ce qui me suggère que tous les opérateurs définis dans tower_loss
sont mappés sur le GPU. Ensuite, à la fin de tower_loss
, nous voyons total_loss = tf.identity(total_loss)
avant qu’il ne soit renvoyé. Pourquoi? Quel serait le défaut de ne pas utiliser tf.identity
ici?
Après avoir trébuché, je pense avoir remarqué un cas d'utilisation unique qui correspond à tous les exemples que j'ai vus. S'il existe d'autres cas d'utilisation, veuillez développer un exemple.
Cas d'utilisation:
Supposons que vous souhaitiez exécuter un opérateur chaque fois qu'une variable particulière est évaluée. Par exemple, supposons que vous souhaitiez en ajouter un à x
chaque fois que la variable y
est évaluée. Cela peut sembler fonctionner:
x = tf.Variable(0.0)
x_plus_1 = tf.assign_add(x, 1)
with tf.control_dependencies([x_plus_1]):
y = x
init = tf.initialize_all_variables()
with tf.Session() as session:
init.run()
for i in xrange(5):
print(y.eval())
Cela ne marche pas: cela imprimera 0, 0, 0, 0, 0. Au lieu de cela, il semble que nous devions ajouter un nouveau nœud au graphique dans le bloc control_dependencies
. Nous utilisons donc cette astuce:
x = tf.Variable(0.0)
x_plus_1 = tf.assign_add(x, 1)
with tf.control_dependencies([x_plus_1]):
y = tf.identity(x)
init = tf.initialize_all_variables()
with tf.Session() as session:
init.run()
for i in xrange(5):
print(y.eval())
Cela fonctionne: il imprime 1, 2, 3, 4, 5.
Si dans le tutoriel CIFAR-10, nous avons supprimé tf.identity
, alors loss_averages_op
ne fonctionnerait jamais.
tf.identity
est utile lorsque vous souhaitez transférer explicitement le tenseur entre des périphériques (comme, du GPU vers un processeur) . L'opérateur ajoute des nœuds send/recv au graphique, qui font une copie lorsque les périphériques de l'entrée et de la sortie sont différent.
Un comportement par défaut est que les nœuds send/recv sont ajoutés implicitement lorsque l'opération est effectuée sur un autre périphérique, mais vous pouvez imaginer certaines situations (notamment dans les paramètres multithreads/distribués) lorsqu'il peut être utile d'extraire la valeur de la variable plusieurs fois au cours d'une même exécution du session.run
. tf.identity
permet de mieux contrôler le moment où la valeur doit être lue à partir du périphérique source. Peut-être un nom plus approprié pour cette opération serait read
.
Veuillez également noter que dans l’implémentation de tf.Variable
link , l’opération d’identité est ajoutée dans le constructeur, ce qui permet de s’assurer que tous les accès à la variable copient les données de la source une seule fois. Plusieurs copies peuvent être coûteuses dans les cas où la variable réside sur un GPU mais qu'elle est lue par plusieurs opérations de la CPU (ou inversement). Les utilisateurs peuvent modifier le comportement avec plusieurs appels en tf.identity
quand ils le souhaitent.
EDIT: Réponse mise à jour après la modification de la question.
De plus, tf.identity
peut être utilisé en tant que noeud factice pour mettre à jour une référence au tenseur. Ceci est utile avec diverses opérations de flux de contrôle. Dans le cas CIFAR, nous voulons imposer que ExponentialMovingAverageOp mettra à jour les variables pertinentes avant de récupérer la valeur de la perte. Ceci peut être implémenté comme:
with tf.control_dependencies([loss_averages_op]):
total_loss = tf.identity(total_loss)
Ici, le tf.identity
ne fait rien d'utile en dehors du marquage du tenseur total_loss
à exécuter après l'évaluation de loss_averages_op
.
En plus de ce qui précède, je l'utilise simplement lorsque j'ai besoin d'attribuer un nom à des opérations sans argument de nom, comme lors de l'initialisation d'un état dans un RNN:
rnn_cell = tf.contrib.rnn.MultiRNNCell([cells])
# no name arg
initial_state = rnn_cell.zero_state(batch_size,tf.float32)
# give it a name with tf.identity()
initial_state = tf.identity(input=initial_state,name="initial_state")
Je suis tombé sur un autre cas d'utilisation qui n'est pas complètement couvert par les autres réponses.
def conv_layer(input_tensor, kernel_shape, output_dim, layer_name, decay=None, act=tf.nn.relu):
"""Reusable code for making a simple convolutional layer.
"""
# Adding a name scope ensures logical grouping of the layers in the graph.
with tf.name_scope(layer_name):
# This Variable will hold the state of the weights for the layer
with tf.name_scope('weights'):
weights = weight_variable(kernel_shape, decay)
variable_summaries(weights, layer_name + '/weights')
with tf.name_scope('biases'):
biases = bias_variable([output_dim])
variable_summaries(biases, layer_name + '/biases')
with tf.name_scope('convolution'):
preactivate = tf.nn.conv2d(input_tensor, weights, strides=[1, 1, 1, 1], padding='SAME')
biased = tf.nn.bias_add(preactivate, biases)
tf.histogram_summary(layer_name + '/pre_activations', biased)
activations = act(biased, 'activation')
tf.histogram_summary(layer_name + '/activations', activations)
return activations
La plupart du temps, lors de la construction d'une couche convolutive, vous souhaitez simplement que les activations soient renvoyées afin de pouvoir les insérer dans la couche suivante. Parfois, cependant - par exemple lors de la construction d’un encodeur automatique - vous souhaitez obtenir les valeurs de pré-activation.
Dans cette situation, une solution élégante consiste à passer tf.identity
en tant que fonction d'activation, sans activer le calque.
J'ai trouvé une autre application de tf.identity dans Tensorboard . Si vous utilisez tf.shuffle_batch, il renvoie plusieurs tenseurs à la fois, de sorte que vous voyez une image confuse lorsque vous visualisez le graphique, vous ne pouvez pas séparer le pipeline de création de tenseur de tenseurs d'entrée réels. : désordonné
Mais avec tf.identity, vous pouvez créer des nœuds en double, qui n’affectent pas le flux de calcul: Nice
Lorsque nos données d'entrée sont sérialisées en octets, nous souhaitons extraire des entités de cet ensemble de données. Nous pouvons le faire au format clé-valeur, puis obtenir un espace réservé pour cela. Ses avantages sont plus concrets lorsqu'il existe plusieurs fonctionnalités et que chaque fonctionnalité doit être lue dans un format différent.
#read the entire file in this placeholder
serialized_tf_example = tf.placeholder(tf.string, name='tf_example')
#Create a pattern in which data is to be extracted from input files
feature_configs = {'image': tf.FixedLenFeature(shape=[256], dtype=tf.float32),/
'text': tf.FixedLenFeature(shape=[128], dtype=tf.string),/
'label': tf.FixedLenFeature(shape=[128], dtype=tf.string),}
#parse the example in key: tensor dictionary
tf_example = tf.parse_example(serialized_tf_example, feature_configs)
#Create seperate placeholders operation and tensor for each feature
image = tf.identity(tf_example['image'], name='image')
text = tf.identity(tf_example['text'], name='text')
label = tf.identity(tf_example['text'], name='label')
Je vois ce genre de bidouille à vérifier affirmer:
assertion = tf.assert_equal(tf.shape(image)[-1], 3, message="image must have 3 color channels")
with tf.control_dependencies([assertion]):
image = tf.identity(image)
En outre, il est juste utilisé pour donner un nom:
image = tf.identity(image, name='my_image')
Dans la formation à la distribution, nous devrions utiliser l’identité ou les travailleurs resteront dans l’attente de l’initialisation du travailleur principal:
vec = tf.identity(tf.nn.embedding_lookup(embedding_tbl, id)) * mask
with tf.variable_scope("BiRNN", reuse=None):
out, _ = tf.nn.bidirectional_dynamic_rnn(fw, bw, vec, sequence_length=id_sz, dtype=tf.float32)
Pour plus de détails, sans identité, l’agent principal traiterait certaines variables comme des variables locales de manière inappropriée et les autres agents attendraient une opération d’initialisation qui ne puisse pas se terminer.