Ceci est l'article du 9ème jour du Calendrier de l'Avent TensorFlow 2016.
TensorFlow est sorti en novembre 2015, mais la fonction «namespace» était prise en charge depuis le début. Ceci est utilisé dans la visualisation graphique avec TensorBoard, mais bien sûr, ce n'est pas seulement pour cela. Les espaces de noms sont très utiles pour gérer les identifiants. La prise en charge solide des "espaces de noms" me rappelle le C ++, mais je vais le citer dans le manuel d'instructions C ++ (auto-apprentissage C ++).
Le but des espaces de noms est de localiser les noms d'identificateurs et d'éviter les conflits de noms. Dans l'environnement de programmation C ++, les noms de variables, de fonctions et de classes ont continué à proliférer. Avant l'avènement des espaces de noms, tous ces noms se disputaient l'espace dans l'espace de noms global, créant de nombreux conflits.
D'autre part, la portée des variables de Python n'a que le minimum de local, global (global
), + α ( nonlocal
), donc un ingénieur de Google qui a écrit la partie principale de TensorFlow en C ++ Il semble naturel de penser à implémenter la prise en charge des espaces de noms au niveau C ++ dans TensorFlow.
Dans MLP (Multi-Layer Perceptron), qui n'a pas beaucoup de couches dans Neural Network, la gestion des noms n'est pas un problème, mais pour les CNN profonds et les grands modèles trouvés dans les RNN, le partage de poids est également possible, donc des variables appropriées. Une portée est requise. De plus, compte tenu de la mise à l'échelle (même si j'ai peu d'expérience avec moi-même), je dois appliquer le code à des environnements distribués tels que Multi-Device (GPU) et clusters. Encore une fois, nous avons besoin d'une portée variable.
Dans cet article, je voudrais confirmer l'API associée dans le but de bien comprendre «l'espace de nom» de TensorFlow. (L'environnement de programmation est TensorFlow 0.11.0, Python 3.5.2, Ubuntu 16.04LTS.)
Si vous lisez correctement le document, la portée des variables n'est pas difficile, mais si vous la comprenez «de manière ambiguë», les points suivants seront capturés.
--Il existe tf.name_scope () et tf.variable_scope () pour définir la portée, mais quelle est la différence?
J'écrirai d'abord la réponse, mais la réponse à la question 1 est que tf.name_scope () est une définition de portée plus générale, et tf.variable_scope () est une définition de portée dédiée pour la gestion des variables (identificateurs). Il devient. De plus, la réponse à la question 2 est que tf.Variable () est une définition de variable plus primitive (niveau inférieur), tandis que tf.get_variable () est une variable (de niveau supérieur) qui considère la portée de la variable. Cela devient une définition. (Le document connexe, TesorFlow - "COMMENT" - "Partage de variable" explique en détail la relation de variable partagée.)
Ci-dessous, je voudrais déplacer le code et étudier les détails.
# tf.name_scope
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
Tout d'abord, j'ai utilisé tf.name_scope ()
pour définir les variables qu'il contient. L'identifiant géré par TensorFlow est sorti dans la seconde moitié, mais la sortie est affichée sous forme de commentaire à droite de l'instruction d'impression. La portée de "my_scope" est correctement définie pour la variable v2 définie par tf.Variable ()
et l'opération d'ajout a. D'un autre côté, la v1 définie par tf.get_varible ()
a brillamment ignoré la portée.
# tf.variable_scope
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_1/var2:0 ...Le nom de l'étendue a été mis à jour.
print(a.name) # my_scope_1/Add:0 ...Le nom de l'étendue après la mise à jour est conservé.
Ensuite, j'ai utilisé tf.variable_sope ()
. (Notez que l'extrait de code précédent et l'extrait actuel sont en mouvement continu.)
L'identifiant de la variable v1 défini par tf.get_variable ()
a été créé avec "my_scope" attaché au nom de la variable comme prévu. De plus, la variable v2 en dessous et l'opération a ont ajouté "my_scope_1" (malgré le fait qu'il s'agit de tf.variable_scope ("my_scope")). La raison en est qu'il aurait dû être donné "my_scope" (dans l'état initial du programme), mais c'est automatique car le même identifiant ("my_scope / var2: 0") a déjà été utilisé dans l'extrait de code précédent. C'est parce que j'ai mis à jour "my_scope_1". (L'instruction ʻa = tf.add (v1, v2) ʻaprès la mise à jour du nom de la portée ("my_scope" -> "my_scope_1") semble conserver cette portée ("my_scope_1").)
Ça devient compliqué, alors je vais régler un peu les choses.
--tf.name_scope () est une définition de portée de nom à usage général. (Comme vous le savez, la sortie vers TensorBoard utilise ce paramètre d'identificateur.) --tf.variable_scope () est une définition de portée utilisée pour la gestion des variables. (Le nom de la fonction variable_scope est tel quel ...) --tf.get_variable () définit les variables tout en gérant les identificateurs de nom de variable (nouveau ou dupliqué?). Assurez-vous de l'utiliser comme un ensemble avec tf.variable_scope ().
Dans les deux extraits ci-dessus, la situation était compliquée à cause de l'expérience, mais ce n'est pas particulièrement difficile si vous suivez le principe de base que "tf.get_variable () est utilisé comme un ensemble avec tf.variable_scope ()".
Voyons maintenant comment utiliser les variables partagées en utilisant tf.get_variable ().
with tf.variable_scope("my_scope2"):
v4_init = tf.constant_initializer([4.])
v4 = tf.get_variable("var4", shape=[1], initializer=v4_init)
print(v4.name) # my_scope2/var4:0
Tout d'abord, dans la portée "my_scope2", nous avons défini la variable v4. tf.get_variable ()
spécifie un initialiseur de variable pour définir une variable. Ici, nous avons utilisé un initialiseur constant pour faire une instruction qui contient 4. dans la v4. Pour l'identificateur de TensorFlow, "var4" a été spécifié dans le premier argument.
Ensuite, essayez d'allouer une variable avec le même identifiant.
with tf.variable_scope("my_scope2"):
v5 = tf.get_variable("var4", shape=[1], initializer=v4_init)
ValueError: Variable my_scope2/var4 already exists, disallowed. Did you mean to set reuse=True in VarScope? Originally defined at:
File "name_scope_ex.py", line 47, in <module>
v4 = tf.get_variable("var4", shape=[1], initializer=v4_init)
Comme prévu. Une ValueError s'est produite. L'erreur est "N'est-il pas étrange de prendre des variables avec le même identifiant?" La réallocation de variable ** utilisant le même identifiant utilise l'option réutilisation
comme suit.
with tf.variable_scope("my_scope2", reuse=True):
v5 = tf.get_variable("var4", shape=[1])
print(v5.name) # my_scope2/var4:0
Vous pouvez également effectuer les opérations suivantes.
with tf.variable_scope("my_scope2"):
tf.get_variable_scope().reuse_variables()
v5 = tf.get_variable("var4", shape=[1])
print(v5.name) # my_scope2/var4:0
Jusqu'à présent, nous avons confirmé les fonctions de base de tf.variable_scope ()
et tf.get_variable ()
.
Regardons maintenant un exemple d'utilisation de variables partagées, mais dans le document TensorFlow --Sharing Variable , Veuillez vous référer à l'exemple d'utilisation suivant.
Étant donné que les deux sont une quantité considérable de code, cette fois, je voudrais reprendre le partage de poids d'un auto-encodeur (ci-après, Autoencoder) différent de ceux-ci. Le côté codage / côté décodage d'Autoencoder peut être exprimé comme suit.
y = f(\textbf{W}x + \textbf{b}) \\
\hat{x} = \tilde{f}(\tilde{\textbf{W}}y + \tilde{\textbf{b}})
Le partage de poids suivant peut être utilisé dans un tel Autoencoder symétrique.
\tilde{\textbf{W} } = \textbf{W} ^{\mathrm{T}}
Implémentons le réseau de configuration ci-dessus en utilisant des variables partagées de TensorFlow. Tout d'abord, définissez la classe Encoder.
# Encoder Layer
class Encoder(object):
def __init__(self, input, n_in, n_out, vs_enc='encoder'):
self.input = input
with tf.variable_scope(vs_enc):
weight_init = tf.truncated_normal_initializer(mean=0.0, stddev=0.05)
W = tf.get_variable('W', [n_in, n_out], initializer=weight_init)
bias_init = tf.constant_initializer(value=0.0)
b = tf.get_variable('b', [n_out], initializer=bias_init)
self.w = W
self.b = b
def output(self):
linarg = tf.matmul(self.input, self.w) + self.b
self.output = tf.sigmoid(linarg)
return self.output
La portée de la variable est spécifiée par l'option «vs_enc» et définie, et «W» est définie par «tf.get_variable ()». Vient ensuite la classe Decoder, qui est la suivante.
# Decoder Layer
class Decoder(object):
def __init__(self, input, n_in, n_out, vs_dec='decoder'):
self.input = input
if vs_dec == 'decoder': # independent weight
with tf.variable_scope(vs_dec):
weight_init = tf.truncated_normal_initializer(mean=0.0, stddev=0.05)
W = tf.get_variable('W', [n_in, n_out], initializer=weight_init)
else: # weight sharing (tying)
with tf.variable_scope(vs_dec, reuse=True): # set reuse option
W = tf.get_variable('W', [n_out, n_in])
W = tf.transpose(W)
with tf.variable_scope('decoder'): # in all case, need new bias
bias_init = tf.constant_initializer(value=0.0)
b = tf.get_variable('b', [n_out], initializer=bias_init)
self.w = W
self.b = b
def output(self):
linarg = tf.matmul(self.input, self.w) + self.b
self.output = tf.sigmoid(linarg)
return self.output
La plupart sont identiques à la classe Encoder, mais l'instruction de définition de la variable W est traitée par branchement. La partie définition du réseau est la suivante.
# make neural network model
def make_model(x):
enc_layer = Encoder(x, 784, 625, vs_enc='encoder')
enc_out = enc_layer.output()
dec_layer = Decoder(enc_out, 625, 784, vs_dec='encoder')
dec_out = dec_layer.output()
return enc_out, dec_out
Si vous spécifiez vs_dec = 'decoder'
lors de la création d'un objet Decoder, ou si vous omettez cette option, une nouvelle variable de poids W
sera allouée, et Encoder l'allouera comme vs_dec =' encoder'
ci-dessus. Lorsque la même portée de variable a été utilisée, la variable de pondération a été implémentée de sorte que «W» soit réutilisé comme variable partagée. (Si vous souhaitez le réutiliser, transposez «W» pour qu'il corresponde au réseau.)
Un exemple d'exécution d'un calcul avec des données MNIST est présenté. Premièrement, si le partage de poids n'est pas effectué, le résultat est le suivant.
Training...
step, loss = 0: 0.732
step, loss = 1000: 0.271
step, loss = 2000: 0.261
step, loss = 3000: 0.240
step, loss = 4000: 0.234
step, loss = 5000: 0.229
step, loss = 6000: 0.219
step, loss = 7000: 0.197
step, loss = 8000: 0.195
step, loss = 9000: 0.193
step, loss = 10000: 0.189
loss (test) = 0.183986
Lorsque le partage du poids est effectué, c'est comme suit.
Training...
step, loss = 0: 0.707
step, loss = 1000: 0.233
step, loss = 2000: 0.215
step, loss = 3000: 0.194
step, loss = 4000: 0.186
step, loss = 5000: 0.174
step, loss = 6000: 0.167
step, loss = 7000: 0.154
step, loss = 8000: 0.159
step, loss = 9000: 0.152
step, loss = 10000: 0.152
loss (test) = 0.147831
En raison du paramètre de partage de poids, la perte (entropie croisée) diminue plus rapidement avec le même nombre d'apprentissage. Puisque le degré de liberté du réseau est d'environ la moitié, on peut dire que le résultat est comme attendu.
Considérons un autre modèle légèrement compliqué. (Bien que ce ne soit pas si compliqué ...) MNIST est utilisé pour les données à traiter comme ci-dessus. Cette fois, nous effectuerons une classification multi-classes. En tant que classificateur, nous avons utilisé MLP (Multi-layer Perceptron) avec 2 couches cachées et 1 couche de sortie. La figure ci-dessous est un graphique de TensorBoard.
Fig. Graph of 2 MLP networks
(Je ne connais pas TensorBoard. Veuillez le prendre comme une image approximative.)
Tout d'abord, définissez les classes de la couche masquée (couche entièrement connectée) et de la couche de sortie.
# Full-connected Layer
class FullConnected(object):
def __init__(self, input, n_in, n_out, vn=('W', 'b')):
self.input = input
weight_init = tf.truncated_normal_initializer(mean=0.0, stddev=0.05)
bias_init = tf.constant_initializer(value=0.0)
W = tf.get_variable(vn[0], [n_in, n_out], initializer=weight_init)
b = tf.get_variable(vn[1], [n_out], initializer=bias_init)
self.w = W
self.b = b
self.params = [self.w, self.b]
def output(self):
linarg = tf.matmul(self.input, self.w) + self.b
self.output = tf.nn.relu(linarg)
return self.output
#
# Read-out Layer
class ReadOutLayer(object):
def __init__(self, input, n_in, n_out, vn=('W', 'b')):
self.input = input
weight_init = tf.random_normal_initializer(mean=0.0, stddev=0.05)
bias_init = tf.constant_initializer(value=0.0)
W = tf.get_variable(vn[0], [n_in, n_out], initializer=weight_init)
b = tf.get_variable(vn[1], [n_out], initializer=bias_init)
self.w = W
self.b = b
self.params = [self.w, self.b]
def output(self):
linarg = tf.matmul(self.input, self.w) + self.b
self.output = tf.nn.softmax(linarg)
return self.output
Le nom de la variable est défini comme une option du constructeur de classe, mais l'opération de partage de variable n'est pas effectuée ici. Ensuite, la partie qui définit le réseau est la suivante.
# Create the model
def mk_NN_model(scope='mlp', reuse=False):
'''
args.:
scope : variable scope ID of networks
reuse : reuse flag of weights/biases
'''
with tf.variable_scope(scope, reuse=reuse):
hidden1 = FullConnected(x, 784, 625, vn=('W_hid_1','b_hid_1'))
h1out = hidden1.output()
hidden2 = FullConnected(h1out, 625, 625, vn=('W_hid_2','b_hid_2'))
h2out = hidden2.output()
readout = ReadOutLayer(h2out, 625, 10, vn=('W_RO', 'b_RO'))
y_pred = readout.output()
cross_entropy = -tf.reduce_sum(y_*tf.log(y_pred))
# Regularization terms (weight decay)
L2_sqr = tf.nn.l2_loss(hidden1.w) + tf.nn.l2_loss(hidden2.w)
lambda_2 = 0.01
# the loss and accuracy
with tf.name_scope('loss'):
loss = cross_entropy + lambda_2 * L2_sqr
with tf.name_scope('accuracy'):
correct_prediction = tf.equal(tf.argmax(y_pred,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
return y_pred, loss, accuracy
Cette fonction est spécifiée pour prendre la portée de la variable «scope» et l'indicateur de partage de variable «réutiliser» comme options. Dans deux réseaux MLP, le partage de poids alignera les noms de portée et définira l'indicateur réutilisation
comme suit:
y_pred1, loss1, accuracy1 = mk_NN_model(scope='mlp1')
y_pred2, loss2, accuracy2 = mk_NN_model(scope='mlp1', reuse=True)
Si vous ne souhaitez pas partager de poids, définissez comme suit. (Bien que ce soit une syntaxe naturelle ...)
y_pred1, loss1, accuracy1 = mk_NN_model(scope='mlp1')
y_pred2, loss2, accuracy2 = mk_NN_model(scope='mlp2')
Les deux cas suivants ont été réalisés comme expériences de calcul.
Puisque je voulais expérimenter le partage de poids, le nombre de couches et le nombre d'unités des deux classificateurs doivent être les mêmes. Cependant, comme le même classificateur est ennuyeux, l'optimiseur est différent et le taux d'apprentissage est finement ajusté.
Premièrement, le résultat de l'exécution du cas n ° 1 est le suivant.
Training...
Network No.1 :
step, loss, accurary = 0: 178.722, 0.470
step, loss, accurary = 1000: 22.757, 0.950
step, loss, accurary = 2000: 15.717, 0.990
step, loss, accurary = 3000: 10.343, 1.000
step, loss, accurary = 4000: 9.234, 1.000
step, loss, accurary = 5000: 8.950, 1.000
Network No.2 :
step, loss, accurary = 0: 14.552, 0.980
step, loss, accurary = 1000: 7.353, 1.000
step, loss, accurary = 2000: 5.806, 1.000
step, loss, accurary = 3000: 5.171, 1.000
step, loss, accurary = 4000: 5.043, 1.000
step, loss, accurary = 5000: 4.499, 1.000
accuracy1 = 0.9757
accuracy2 = 0.9744
Notez que la perte augmente légèrement au début de l'apprentissage du réseau n ° 2, mais elle est considérablement inférieure à la valeur au début de l'apprentissage n ° 1. Cela indique qu'à la suite du partage de poids, les paramètres (héritant du résultat d'apprentissage du n ° 1) ont commencé à partir du début du n ° 2. Cependant, la précision de la classification finale, ʻaccuracy2 = 0.9744, ne s'est pas améliorée par rapport à ʻaccuracy1
, et il a été constaté que cet "apprentissage d'ensemble" était un échec.
Naturellement, quand vous y réfléchissez, vous êtes dans une situation où vous utilisez le même classificateur dans deux sessions d'apprentissage. Puisque les données d'apprentissage ont été simplement divisées en deux parties et fournies, on ne peut pas s'attendre à ce que cela améliore la précision de l'ensemble.
Le résultat de l'exécution de l'ensemble correct avec la configuration de classificateur indépendant du cas n ° 2 est le suivant.
Training...
Network No.1 :
step, loss, accurary = 0: 178.722, 0.470
step, loss, accurary = 1000: 15.329, 0.990
step, loss, accurary = 2000: 12.242, 0.990
step, loss, accurary = 3000: 10.827, 1.000
step, loss, accurary = 4000: 10.167, 0.990
step, loss, accurary = 5000: 8.178, 1.000
Network No.2 :
step, loss, accurary = 0: 192.382, 0.570
step, loss, accurary = 1000: 10.037, 0.990
step, loss, accurary = 2000: 7.590, 1.000
step, loss, accurary = 3000: 5.855, 1.000
step, loss, accurary = 4000: 4.678, 1.000
step, loss, accurary = 5000: 4.693, 1.000
accuracy1 = 0.9751
accuracy2 = 0.9756
accuracy (model averaged) = 0.9810
Comme prévu, la précision (taux de réponse correcte), qui était d'environ 0,975 pour chaque classificateur, est légèrement meilleure à 0,980 selon la moyenne du modèle.
(Le code créé cette fois a été téléchargé sur Gist.)
Cela semble un peu hors des sentiers battus, mais j'espère que vous avez une idée de la façon d'utiliser des portées variables et des variables partagées. Je ne pense pas qu'il soit vraiment nécessaire d'utiliser des portées variables pour gérer les variables dans les modèles plus petits, mais dans les modèles plus grands, vous pouvez utiliser des portées variables et des variables partagées. C'est une fonctionnalité de TensorFlow que l'on ne trouve pas souvent dans d'autres frameworks d'apprentissage profond, alors veuillez l'utiliser!
Recommended Posts