Un point de selle est un point où la pente est de 0 et le minimum est pris dans une certaine direction et le maximum est pris dans une certaine direction. C'est un thème important dans divers domaines, comme représenté par le GAN dans l'apprentissage automatique et les états de transition dans la recherche de voies de réaction chimique. Le GAN se compose d'un générateur qui génère des données qui ressemblent exactement à la chose réelle et d'un discriminateur qui fait la distinction entre les données réelles et les données générées. Le générateur apprend à prendre le maximum et le discriminateur apprend à prendre le minimum. Dans la réaction chimique, diverses matières premières stables passent par les points de selle sur la surface incurvée d'énergie potentielle et se transforment en composés plus stables. Dans cet article, nous rechercherons des points de selle en utilisant la méthode du dégradé. Cette méthode est efficace pour les modèles simples, mais elle se comporte quelque peu instable pour les modèles complexes (par exemple, chimie quantique, etc.), donc je l'ajouterai dès qu'elle pourra être améliorée. Si cet article vous intéresse, LGTM! S'il vous plaît.
Décrivez le point de selle comme suit. La fonction objectif est $ y = f (\ boldsymbol {x}) $, $ \ boldsymbol {x} = (x_1, x_2, \ cdots, x_n) ^ \ rm {T} $, et le vecteur unitaire correspondant à la valeur minimale est $ \ boldsymbol {b} = (b_1, b_2, \ cdots, b_n) ^ \ rm {T} $, le vecteur unitaire correspondant à la valeur maximale est $ \ boldsymbol {c} = (c_1, c_2, \ cdots, c_n) ^ Défini comme \ rm {T} $. A ce moment, le gradient de $ f $ devient $ 0 $, et $ y = f (\ boldsymbol {x} + \ boldsymbol {b} t) $ prend la valeur minimale à $ t = 0 $ par rapport à $ t $, et $ y Le point de selle est que = f (\ boldsymbol {x} + \ boldsymbol {c} t) $ prend la valeur maximale à $ t = 0 $ par rapport à $ t $. La formule est la suivante.
Au point de selle $ \ boldsymbol {x} = \ boldsymbol {x} _0 $
\left.\frac{\partial f(\boldsymbol{x})}{\partial \boldsymbol{x}}\right |_{\boldsymbol{x}=\boldsymbol{x}_0}= 0\\
\left.\frac{\partial^2 f(\boldsymbol{x}+\boldsymbol{b}t)}{\partial t^2}\right |_{\boldsymbol{x}=\boldsymbol{x}_0, t=0} > 0\\
\left.\frac{\partial^2 f(\boldsymbol{x}+\boldsymbol{c}t)}{\partial t^2}\right |_{\boldsymbol{x}=\boldsymbol{x}_0, t=0} < 0
Est établi.
Considérez $ y = x_1 ^ 2-x_2 ^ 2 $ comme exemple. À $ x_1 = 0 $, $ x_2 = 0 $, le dégradé devient $ 0 $, et à $ \ boldsymbol {b} = (1, 0) ^ \ rm {T} $, $ y = (0 + 1 t) ^ 2- (0 + 0 t) ^ 2 = t ^ 2 $ prend une valeur minimale à $ t = 0 $, et $ y = à $ \ boldsymbol {c} = (0, 1) ^ \ rm {T} $ (0 + 0 t) ^ 2- (0 + 1t) ^ 2 = -t ^ 2 $ prend la valeur maximale à $ t = 0 $. Par conséquent, ce point peut être estimé comme étant un point de selle.
Cette méthode comprend deux étapes: l'initialisation et l'exploration. L'initialisation trouve les valeurs initiales appropriées pour $ \ boldsymbol {b} $ et $ \ boldsymbol {c} $. Dans la recherche, les points de selle sont recherchés en fonction du gradient.
Comme mentionné ci-dessus, $ \ boldsymbol {b} $ correspond à la valeur maximale, et $ \ boldsymbol {c} $ correspond à la valeur minimale. Tout d'abord, déterminez $ \ boldsymbol {b} $ qui maximise la différenciation de second ordre de $ y = f (\ boldsymbol {x} + \ boldsymbol {b} t) $ par rapport à $ t $. Par souci de simplicité, nous utiliserons la méthode répétitive utilisant la méthode re-deep. La formule est la suivante.
\mathrm{grad}\ \boldsymbol{b}_1 \leftarrow \frac{1}{\delta} \left( \nabla f \left(\boldsymbol{x}+\delta \boldsymbol{b} \right) - \nabla f \left(\boldsymbol{x}-\delta \boldsymbol{b} \right) \right)\\
\mathrm{grad}\ \boldsymbol{b}_2 \leftarrow \mathrm{grad}\ \boldsymbol{b}_1 - \left( \boldsymbol{b} \cdot \mathrm{grad}\ \boldsymbol{b}_1 \right)\boldsymbol{b}\\
\boldsymbol{b} \leftarrow \boldsymbol{b} + \epsilon_1 \ \mathrm{grad}\ \boldsymbol{b}_2\\
\boldsymbol{b} \leftarrow \frac{\boldsymbol{b}}{\mathrm{norm} \left(\boldsymbol{b}\right)}\\
Le montant des minutes est de $ \ delta $ et le taux d'apprentissage est de $ \ epsilon_1 $. La première équation est la différenciation de l'espace euclidien de dimension $ n $ par rapport à la base. En tant qu'appareil, la formule a été transformée pour que le dégradé puisse être utilisé. Si cela est utilisé comme montant de mise à jour tel quel, il n'est pas approprié pour le vecteur unitaire $ \ boldsymbol {b} $, il est donc nécessaire de le convertir en montant de mise à jour de $ \ boldsymbol {b} $ le long de la sphère unitaire dans la deuxième équation. il y a. La troisième formule est mise à jour vers la valeur maximale et la quatrième formule est normalisée pour en faire un vecteur unitaire. De même, déterminez $ \ boldsymbol {c} $ tel que le différentiel du second ordre de $ y = f (\ boldsymbol {x} + \ boldsymbol {c} t) $ par rapport à $ t $ soit maximisé. La formule est la suivante.
\mathrm{grad}\ \boldsymbol{c}_1 \leftarrow \frac{1}{\delta} \left( \nabla f \left(\boldsymbol{x}+\delta \boldsymbol{c} \right) - \nabla f \left(\boldsymbol{x}-\delta \boldsymbol{c} \right) \right)\\
\mathrm{grad}\ \boldsymbol{c}_2 \leftarrow \mathrm{grad}\ \boldsymbol{c}_1 - \left( \boldsymbol{c} \cdot \mathrm{grad}\ \boldsymbol{c}_1 \right)\boldsymbol{c}\\
\boldsymbol{c} \leftarrow \boldsymbol{c} - \epsilon_1 \ \mathrm{grad}\ \boldsymbol{c}_2\\
\boldsymbol{c} \leftarrow \frac{\boldsymbol{c}}{\mathrm{norm} \left(\boldsymbol{c}\right)}
La différence avec $ \ boldsymbol {b} $ est qu'il est mis à jour dans le sens de la valeur minimale dans la troisième équation. Vous pouvez utiliser $ \ mathrm {grad} \ \ boldsymbol {b} \ _2 $ et $ \ mathrm {grad} \ \ boldsymbol {c} \ _2 $ comme jugements de convergence.
La recherche utilise $ \ mathrm {grad} \ \ boldsymbol {b} \ _2 $ et $ \ mathrm {grad} \ \ boldsymbol {c} \ _2 $. En effet, il existe une valeur minimale dans le sens $ - \ mathrm {grad} \ \ boldsymbol {b} \ _2 $ et une valeur maximale dans le sens $ \ mathrm {grad} \ \ boldsymbol {c} \ _2 $. Vous pouvez atteindre le point de selle en mettant à jour en fonction de. La formule est la suivante.
\boldsymbol{x} \leftarrow \boldsymbol{x} + \epsilon_2 \ \left( -\mathrm{grad}\ \boldsymbol{b}_2 +\mathrm{grad}\ \boldsymbol{c}_2 \right)
De plus, $ \ mathrm {grad} \ \ boldsymbol {b} _2 $ et $ \ mathrm {grad} \ \ boldsymbol {c} _2 $ ont changé en fonction de la mise à jour de $ \ boldsymbol {x} $, donc l'initialisation Vous devez également faire $ \ boldsymbol {b} $ et $ \ boldsymbol {c} $ en même temps.
Les points de selle de diverses fonctions ont été calculés selon l'algorithme ci-dessus. La méthode de mise à jour a utilisé la méthode de re-plongée et a été exécutée en Python. La ligne rouge est le vecteur de valeur minimum et la ligne verte est le vecteur de valeur maximum.
・ Fonction $ y = x_1 ^ 2-x_2 ^ 2 $
Valeur initiale $ x_1 = -2 $, $ x_2 = -1 $
Petit montant $ \ delta = 0,001 $
Taux d'apprentissage $ \ epsilon_1 = 0,1 $, $ \ epsilon_1 = 0,1 $
résultat
Point de selle $ x_1 = -0,0003 $, $ x_2 = -0,001 $
Initialisation
Recherche de point de selle
・ Fonction $ y = x_1 ^ 2 + x_2 ^ 3-x_2 $
Valeur initiale $ x_1 = -2 $, $ x_2 = -0,5 $
Petit montant $ \ delta = 0,05 $
Taux d'apprentissage $ \ epsilon_1 = 0,1 $, $ \ epsilon_1 = 0,1 $
résultat
Point de selle $ x_1 = -0,0011 $, $ x_2 = -0,5774 $
Initialisation
Recherche de point de selle
Comme amélioration, puisqu'il s'agit d'une méthode de gradient, divers optimiseurs (par exemple Momentum, RMSProp, Adam, RAdam) et des méthodes wegstain dans la méthode du gradient conjugué et l'apprentissage automatique peuvent être utilisés. Le problème est qu'il n'est pas stable. Je me suis demandé s'il pouvait être utilisé pour la recherche de point de selle qui pourrait être utilisé pour la prédiction de réactions chimiques, mais il a convergé avec une structure étrange. De plus, en fonction de la valeur initiale, il peut ne pas converger vers le point de selle. Un exemple est présenté ci-dessous. Ceci est différent de la valeur initiale de l'exemple précédent.
・ Fonction $ y = x_1 ^ 2 + x_2 ^ 3-x_2 $ Valeur initiale $ x_1 = -2 $, $ x_2 = -0,5 $ Petit montant $ \ delta = 0,05 $ Taux d'apprentissage $ \ epsilon_1 = 0,1 $, $ \ epsilon_1 = 0,1 $
Initialisation
Recherche de point de selle
Dans cet exemple, $ \ boldsymbol {b} $ et $ \ boldsymbol {c} $ ont été mis à jour afin que la direction de $ x_2 $ ait la valeur minimale et la direction de $ x_1 $ la valeur maximale. Par conséquent, tout en actualisant vers la valeur minimale de la fonction cubique, nous montons régulièrement la pente de la fonction quadratique.
Dans cet article, nous avons recherché des points de selle à l'aide de la méthode du dégradé. Un exemple de fonction à deux variables est présenté pour la visibilité, mais vous pouvez en fait utiliser une fonction de variable arbitraire. Si vous avez des questions, nous vous répondrons dans les commentaires. N'hésitez pas à commenter si vous avez des demandes de transformations de formules ou de code source.
Il existe des paramètres tels que les fonctions, les valeurs initiales et la vitesse d'apprentissage dans «Initialisation du calcul 1». Vous pouvez modifier le contenu de la classe SDG en différents optimiseurs.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from datetime import datetime
np.set_printoptions(precision=4, floatmode='maxprec')
class SDG:
def __init__(self, learning_rate):
self.learning_rate = learning_rate
def solve(self, coord, gradient):
return coord - self.learning_rate*gradient
class SaddlePoint:
def __init__(self, function, gradient, coordinate, delta=1.0e-3,
learning_rate=0.5, max_iteration=1000,
judgment1=1.0e-8, judgment2=1.0e-8, log_span=100):
"""
Constructeur d'initialisation
function --Fonction objective
differential --Différenciation de premier ordre des fonctions
coordinates --Coordonnées initiales
delta --Petite valeur
learning_rate --Taux d'apprentissage
judgment --Jugement de convergence 1
judgment --Arrêt de convergence 2
log_span --Intervalle d'affichage du journal
"""
self.function = function
self.gradient = gradient
self.coordinate = coordinate.copy()
self.dim = len(coordinate)
self.delta = delta
self.learning_rate = learning_rate
self.max_iteration = max_iteration
self.judgment1 = judgment1
self.judgment2 = judgment2
self.log_span = log_span
#Vecteur de base
self.b = np.random.rand(self.dim)
self.b /= np.linalg.norm(self.b)
self.c = np.random.rand(self.dim)
self.c /= np.linalg.norm(self.c)
# SDG
self.sdg_b_init = SDG(learning_rate)
self.sdg_c_init = SDG(learning_rate)
self.sdg_b_solv = SDG(learning_rate)
self.sdg_c_solv = SDG(learning_rate)
self.sdg_a_solv = SDG(learning_rate)
def initialize(self):
"""
Fonction à initialiser. Approprié b,Déterminer c
Valeur de retour--Vecteur de direction de valeur minimale b,Vecteur de direction de valeur maximale c
"""
#Pente
gradient = self.gradient
#Coordonnée b
coordinate = self.coordinate
#Vecteur de base
b = self.b.copy()
c = self.c.copy()
#Mettre à jour le montant
diff_b = np.zeros_like(b)
diff_c = np.zeros_like(c)
#Taux d'apprentissage
learning_rate = self.learning_rate
#Petite valeur
delta = self.delta
#Standardisation
norm = np.linalg.norm
#Jugement
judgement1 = self.judgment1
#Intervalle de journalisation
log_span = self.log_span
# SDG
sdg_b = self.sdg_b_init
sdg_c = self.sdg_c_init
z, _ = gradient(coordinate)
print("-----Initialization of b has started.-----")
for i in range(self.max_iteration):
#Différentiel de premier ordre
z_b1, grad_b1 = gradient(coordinate + delta*b)
z_b2, grad_b2 = gradient(coordinate - delta*b)
#Modifier le calcul du montant
nabla_b = (grad_b1 - grad_b2)/delta
grad_b = nabla_b - (np.dot(b, nabla_b))*b
#mise à jour
b = sdg_b.solve(b, -grad_b)
#Standardisation
b /= norm(b)
#Jugement de convergence
error = np.linalg.norm(grad_b)
if i%log_span == 0:
print("Iteration = {}, Error = {}".format(i, error))
if error < judgement1:
print("Converged! Iteration = {}, Error = {}".format(i, error))
break
self.b = b.copy()
print()
print("-----Initialization of c has started.-----")
for i in range(self.max_iteration):
#Calcul du gradient
z_c1, grad_c1 = gradient(coordinate + delta*c)
z_c2, grad_c2 = gradient(coordinate - delta*c)
#Modifier le calcul du montant
nabla_c = (grad_c1 - grad_c2)/delta
grad_c = nabla_c - (np.dot(c, nabla_c))*c
#mise à jour
c = sdg_c.solve(c, grad_c)
#Standardisation
c /= norm(c)
#Jugement de convergence
error = np.linalg.norm(grad_c)
if i%log_span == 0:
print("Iteration = {}, Error = {}".format(i, error))
if error < judgement1:
print("Converged! Iteration = {}, Error = {}".format(i, error))
break
self.c = c.copy()
print()
print("Result")
print("b = {}".format(self.b))
print("c = {}".format(self.c))
print()
return self.b, self.c
def solve(self):
"""
Rechercher des points de selle
Valeur de retour--Coordonnée des coordonnées du point de selle,Vecteur de direction de valeur minimale b,Vecteur de direction de valeur maximale c
"""
#Pente
gradient = self.gradient
#Coordonnées, une collection de coordonnées
coordinate = self.coordinate.copy()
coordinate_Array = coordinate.copy()
#Vecteur de base
b = self.b.copy()
c = self.c.copy()
#Mettre à jour le montant
diff_b = np.zeros_like(b)
diff_c = np.zeros_like(c)
#Taux d'apprentissage
learning_rate = self.learning_rate
#Petite valeur
delta = self.delta
#Standardisation
norm = np.linalg.norm
#Jugement
judgement1 = self.judgment1
judgement2 = self.judgment2
#Intervalle de journalisation
log_span = self.log_span
# SDG
sdg_a = self.sdg_a_solv
sdg_b = self.sdg_b_solv
sdg_c = self.sdg_c_solv
print("-----Saddle-point solver has started.-----")
for i in range(self.max_iteration):
#Différentiel de premier ordre
z_b1, grad_b1 = gradient(coordinate + delta*b)
z_b2, grad_b2 = gradient(coordinate - delta*b)
z_c1, grad_c1 = gradient(coordinate + delta*c)
z_c2, grad_c2 = gradient(coordinate - delta*c)
grad_through_b = (z_b1-z_b2) / (2.0*delta)
grad_through_c = (z_c1-z_c2) / (2.0*delta)
#Différentiel de second ordre
z, _ = gradient(coordinate)
grad2_through_b = (z_b1-2.0*z+z_b2) / delta**2.0
grad2_through_c = (z_c1-2.0*z+z_c2) / delta**2.0
#mise à jour
# coordinate = sdg_a.solve(coordinate,
# grad_through_b*b/(np.linalg.norm(grad_through_b)+np.linalg.norm(grad2_through_b))
# -grad_through_c*c/(np.linalg.norm(grad_through_c)+np.linalg.norm(grad2_through_c)))
coordinate = sdg_a.solve(coordinate, grad_through_b*b - grad_through_c*c)
coordinate_Array = np.vstack([coordinate_Array, coordinate])
#Jugement de convergence
error_coordinate = np.linalg.norm(grad_through_b**2 + grad_through_c**2)
# b,mise à jour de c
nabla_b = -(grad_b1 - grad_b2)/delta
grad_b = nabla_b - (np.dot(b, nabla_b))*b
#mise à jour
b = sdg_b.solve(b, grad_b)
#Standardisation
b /= norm(b)
#Jugement de convergence
error_b = np.linalg.norm(grad_b)
nabla_c = (grad_c1 - grad_c2)/delta
grad_c = nabla_c - (np.dot(c, nabla_c))*c
#mise à jour
c = sdg_c.solve(c, grad_c)
#Standardisation
c /= norm(c)
#Jugement de convergence
error_c = np.linalg.norm(grad_c)
if i%log_span == 0:
print("B converged! Iteration = {}, Error = {}".format(i, error_b))
print("C converged! Iteration = {}, Error = {}".format(i, error_c))
print("Iteration = {}, Error = {}".format(i, error_coordinate))
print()
if error_coordinate < judgement2:
print("Converged! Iteration = {}, Error = {}".format(i, error_coordinate))
break
self.coordinate = coordinate.copy()
self.b = b.copy()
self.c = c.copy()
print()
print("Result")
print("coordinate = {}".format(self.coordinate))
print("b = {}".format(self.b))
print("c = {}".format(self.c))
print()
return self.coordinate, coordinate_Array, self.b, self.c
# =============================================================================
#Initialisation du calcul 1
# =============================================================================
def f(x):
#une fonction
return x[0]**2 - x[1]**2
def gradient_f(x):
#Différenciation de premier ordre des fonctions
return f(x), np.array([2*x[0], -2*x[1]])
x_init = np.array([-2.0, -1.0], dtype="float")
saddlePoint = SaddlePoint(f, gradient_f, x_init, delta=1e-3,
learning_rate=0.1, max_iteration=100,
judgment1=1.0e-5, judgment2=1.0e-5, log_span=1)
b, c = saddlePoint.initialize() #Initialisation
# =============================================================================
#Dessin graphique(2D)
# =============================================================================
t = np.linspace(-1.0, 1.0, 100)
tb = np.linspace(x_init-1.0*b, x_init+1.0*b, 100)
tc = np.linspace(x_init-1.0*c, x_init+1.0*c, 100)
fb = f(tb.T)
fc = f(tc.T)
plt.xlabel("t")
plt.ylabel("z")
plt.plot(t, fb, c="red")
plt.plot(t, fc, c="green")
plt.savefig("file/" + str(datetime.now().strftime("%Y_%m_%d_%H_%M_%S")) + "_2d1.png ", dpi=300)
plt.show()
# =============================================================================
#Dessin graphique(3D)
# =============================================================================
#Cadre en fil
fig = plt.figure(figsize=(6, 6))
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
X = np.linspace(-2.5, 2.5, 50)
Y = np.linspace(-2.5, 2.5, 50)
X, Y = np.meshgrid(X, Y)
Z = f([X,Y])
#Vecteur de base
width = 1.0
bt = np.linspace(x_init-width*b,x_init+width*b,10)
bz = f(bt.T)
ct = np.linspace(x_init-width*c,x_init+width*c,10)
cz = f(ct.T)
#Trajectoire
zArray = f(x_init)
#afficher
ax.plot_wireframe(X,Y,Z,color="gray",linewidth=0.2) #Cadre en fil
ax.plot(bt[:,0],bt[:,1],bz,color="red") #Minimal
ax.plot(ct[:,0],ct[:,1],cz,color="green") #maximum
ax.scatter(x_init[0],x_init[1],f(x_init),color="blue") #Trajectoire
plt.savefig("file/" + str(datetime.now().strftime("%Y_%m_%d_%H_%M_%S")) + "_3d1.png ", dpi=300)
plt.show()
# =============================================================================
#Calcul 2 recherche de point de selle
# =============================================================================
x, xArray, b, c = saddlePoint.solve() #Calcul du point de selle
# =============================================================================
#Dessin graphique(2D)
# =============================================================================
t = np.linspace(-1.0, 1.0, 100)
tb = np.linspace(x-1.0*b, x+1.0*b, 100)
tc = np.linspace(x-1.0*c, x+1.0*c, 100)
fb = f(tb.T)
fc = f(tc.T)
plt.xlabel("t")
plt.ylabel("z")
plt.plot(t, fb, c="red")
plt.plot(t, fc, c="green")
plt.savefig("file/" + str(datetime.now().strftime("%Y_%m_%d_%H_%M_%S")) + "_2d2.png ", dpi=300)
plt.show()
# =============================================================================
#Dessin graphique(2D)
# =============================================================================
#Cadre en fil
fig = plt.figure(figsize=(6, 6))
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
X = np.linspace(-2.5, 2.5, 50)
Y = np.linspace(-2.5, 2.5, 50)
X, Y = np.meshgrid(X, Y)
Z = f([X,Y])
#Vecteur de base
width = 1.0
bt = np.linspace(x-width*b,x+width*b,10)
bz = f(bt.T)
ct = np.linspace(x-width*c,x+width*c,10)
cz = f(ct.T)
#Trajectoire
zArray = f(xArray.T)
#afficher
ax.plot_wireframe(X,Y,Z,color="gray",linewidth=0.2) #Cadre en fil
ax.plot(bt[:,0],bt[:,1],bz,color="red") #Minimal
ax.plot(ct[:,0],ct[:,1],cz,color="green") #maximum
ax.scatter(xArray[:,0],xArray[:,1],zArray,color="blue") #Trajectoire
ax.text(xArray[0,0],xArray[0,1],zArray[0], "start")
ax.text(xArray[-1,0],xArray[-1,1],zArray[-1], "goal")
plt.savefig("file/" + str(datetime.now().strftime("%Y_%m_%d_%H_%M_%S")) + "_3d2.png ", dpi=300)
plt.show()
Recommended Posts