Dans le cadre de l'apprentissage automatique, il y a l'apprentissage du classement (apprentissage du classement, apprentissage du classement). RankNet, qui utilise un réseau neuronal, est l'un des modèles d'apprentissage des rangs. Dans cet article, je parlerai de la mise en œuvre de RankNet à l'aide de Chainer.
En regardant les résultats de la prédiction, la mise en œuvre semble être correcte, mais s'il y a des erreurs telles que la façon d'utiliser Chainer, je vous serais reconnaissant de bien vouloir le signaler.
Dans l'apprentissage des classements, un ensemble de $ ({\ mathbf x}, y) = $ (caractéristiques, degré d'aptitude) est appris. Apprenez la fonction $ f $ qui renvoie une grande valeur pour les éléments avec un degré de conformité élevé et une petite valeur pour les éléments avec un faible degré de conformité.
Supposons maintenant que deux éléments $ A $ et $ B $ soient représentés comme suit.
A=(\mathbf{x}_A, y_A) \\
B=(\mathbf{x}_B, y_B)
Supposons également que $ A $ est plus approprié que $ B $. Autrement dit, supposons que $ y_A> y_B $ soit valable. A ce moment, la fonction $ f $ est apprise de sorte que la relation suivante soit vraie.
f(\mathbf{x}_A)>f(\mathbf{x}_B)
On a l'impression que quelque chose ne va pas, alors aime vivre! Je vais expliquer l'utilisation. Tout d'abord, aimez vivre! Ajoutons le degré d'aptitude (☆, ☆☆, ☆☆☆) au personnage de. Plus le nombre de ☆ est élevé, plus le degré de conformité (favori) est élevé.
Si vous apprenez la paire suivante, vous devriez obtenir la fonction $ f $ comme ceci.
Lorsque $ f ({\ bf x}) = {\ bf w} ^ T {\ bf x} $, le poids $ {\ bf w} $ est projeté orthogonalement comme la quantité de caractéristiques comme indiqué dans la figure ci-dessous. On estime que les points sont classés par ordre de conformité. Dans un vrai problème, il n'y aurait pas $ {\ bf w} $ qui serait parfaitement aligné par ordre d'aptitude ... (⇒ linéairement inséparable)
En utilisant la fonction $ f $ obtenue à la suite de l'apprentissage, il est possible de prédire le classement des caractères qui n'ont pas encore été ajustés.
Ici, j'ai écrit des «paires d'apprentissage», mais en fait, il existe à peu près trois méthodes d'apprentissage par rang, et cela correspond à la méthode appelée par paires. Il existe d'autres méthodes telles que par point et par liste, mais veuillez vous référer aux documents suivants pour ces méthodes.
RankNet est un modèle d'apprentissage de rang basé sur un réseau neuronal et est une méthode par paires. Le modèle de prédiction lui-même est le même qu'un réseau neuronal normal, mais la conception de la fonction de coût est différente. Cependant, l'idée est la même que la fonction d'entropie croisée qui est souvent utilisée comme fonction de coût.
Comme ci-dessus, supposons que les éléments $ A $ et $ B $ soient représentés comme suit, et $ A $ est plus adapté que $ B $.
A=(\mathbf{x}_A, y_A) \\
B=(\mathbf{x}_B, y_B)
Les scores de $ A $ et $ B $ par la fonction $ f $ sont calculés comme suit.
s_A=f(\mathbf{x}_A) \\
s_B=f(\mathbf{x}_B)
La probabilité que $ A $ soit classé plus haut que la sortie de $ B $ par RankNet est exprimée comme suit en utilisant $ s_A $ et $ s_B $.
P_{AB}=\frac{1}{1+e^{-\sigma(s_A-s_B)}}
Intuitivement, nous pouvons voir que lorsque $ s_A $ augmente, $ P_ {AB} $ s'approche de 1, et lorsque $ s_B $ augmente, $ P_ {AB} $ s'approche de 0.
En utilisant $ P_ {AB} $, la fonction de coût est exprimée comme suit.
C_{AB}=-\bar{P}_{AB}\log{P_{AB}}-(1-\bar{P}_{AB})\log(1-P_{AB})
Ici, $ \ bar {P} _ {AB} $ est la probabilité connue que $ A $ soit classé au-dessus de $ B $. Dans Référence, il a été défini comme suit.
\bar{P}_{AB}=\begin{cases}
1 & (A \succ B) \\
0 & (A \prec B) \\
\frac{1}{2} & (otherwise)
\end{cases}
$ C_ {AB} $ est juste une fonction d'entropie croisée, et plus $ P_ {AB} $ est éloigné $ \ bar {P} _ {AB} $, plus la valeur est grande. RankNet apprend à minimiser cette fonction de coût.
Maintenant, implémentons RankNet avec Chainer. La fonction de perte softmax_cross_entropy () est implémentée dans Chainer, mais softmax_cross_entropy () ne peut pas être utilisée car elle ne peut recevoir que le type int32 comme variable cible. ($ \ Bar {P} _ {AB} $ peut être $ \ frac {1} {2} $.)
Par conséquent, je dois implémenter la fonction de coût $ C_ {AB} $ par moi-même, mais j'ai pu apprendre sans implémenter ** backward (), qui est implémentée dans d'autres fonctions de perte *. *… Je me demande si backward () n'est pas implémenté pour effectuer automatiquement la différenciation numérique, et je vous serais reconnaissant si vous pouviez commenter.
Voici l'implémentation de RankNet En tant que modèle du réseau neuronal, un simple perceptron multicouche de [couche d'entrée] -> [couche cachée] -> [couche cachée] -> [couche de sortie] est utilisé.
net.py
from chainer import Chain
import chainer.functions as F
import chainer.links as L
class MLP(Chain):
def __init__(self, n_in, n_hidden):
super(MLP, self).__init__(
l1=L.Linear(n_in, n_hidden),
l2=L.Linear(n_hidden, n_hidden),
l3=L.Linear(n_hidden, 1)
)
def __call__(self, x):
h1 = F.relu(self.l1(x))
h2 = F.relu(self.l2(h1))
return self.l3(h2)
class RankNet(Chain):
def __init__(self, predictor):
super(RankNet, self).__init__(predictor=predictor)
def __call__(self, x_i, x_j, t_i, t_j):
s_i = self.predictor(x_i)
s_j = self.predictor(x_j)
s_diff = s_i - s_j
if t_i.data > t_j.data:
S_ij = 1
elif t_i.data < t_j.data:
S_ij = -1
else:
S_ij = 0
self.loss = (1 - S_ij) * s_diff / 2. + \
F.math.exponential.Log()(1 + F.math.exponential.Exp()(-s_diff))
return self.loss
Appliquons le RankNet implémenté aux données artificielles. Les données artificielles ont été générées comme suit.
X=({\bf x}_1, {\bf x}_2, \dots, {\bf x}_{1000})^{\rm T} \\
{\bf y}=(y_1, y_2, \dots, y_{1000}) \\
{\bf w} = {\mathcal N}({\bf 0}, {\bf 1}) \\
y_i = uniform(1,5) \\
{\bf x}_i={\mathcal N}(0, 5) + y_i{\bf w}
1000 pièces de données à 50 dimensions, le degré de conformité est soit [1,2,3,4,5]. Lorsqu'elle est tracée dans l'espace PCA, la figure est la suivante.
D'une manière ou d'une autre, il semble que le degré de conformité soit plus élevé vers le coin supérieur gauche de la figure et diminue vers le coin inférieur droit.
Vous trouverez ci-dessous le code d'apprentissage et d'évaluation. La formation utilise la méthode de descente de gradient stochastique, et les données d'apprentissage sont échantillonnées au hasard à chaque fois. NDCG @ 100 a été utilisé pour évaluer la précision de la prédiction.
train_toy.py
import numpy as np
from chainer import Variable, optimizers
from sklearn.cross_validation import train_test_split
import net
import matplotlib.pyplot as plt
import seaborn as sns
def make_dataset(n_dim, n_rank, n_sample, sigma):
ys = np.random.random_integers(n_rank, size=n_sample)
w = np.random.randn(n_dim)
X = [sigma * np.random.randn(n_dim) + w * y for y in ys]
X = np.array(X).astype(np.float32)
ys = np.reshape(np.array(ys), (-1, 1))
return X, ys
def ndcg(y_true, y_score, k=100):
y_true = y_true.ravel()
y_score = y_score.ravel()
y_true_sorted = sorted(y_true, reverse=True)
ideal_dcg = 0
for i in range(k):
ideal_dcg += (2 ** y_true_sorted[i] - 1.) / np.log2(i + 2)
dcg = 0
argsort_indices = np.argsort(y_score)[::-1]
for i in range(k):
dcg += (2 ** y_true[argsort_indices[i]] - 1.) / np.log2(i + 2)
ndcg = dcg / ideal_dcg
return ndcg
if __name__ == '__main__':
np.random.seed(0)
n_dim = 50
n_rank = 5
n_sample = 1000
sigma = 5.
X, ys = make_dataset(n_dim, n_rank, n_sample, sigma)
X_train, X_test, y_train, y_test = train_test_split(X, ys, test_size=0.33)
n_iter = 2000
n_hidden = 20
loss_step = 50
N_train = np.shape(X_train)[0]
model = net.RankNet(net.MLP(n_dim, n_hidden))
optimizer = optimizers.Adam()
optimizer.setup(model)
N_train = np.shape(X_train)[0]
train_ndcgs = []
test_ndcgs = []
for step in range(n_iter):
i, j = np.random.randint(N_train, size=2)
x_i = Variable(X_train[i].reshape(1, -1))
x_j = Variable(X_train[j].reshape(1, -1))
y_i = Variable(y_train[i])
y_j = Variable(y_train[j])
model.zerograds()
loss = model(x_i, x_j, y_i, y_j)
loss.backward()
optimizer.update()
if (step + 1) % loss_step == 0:
train_score = model.predictor(Variable(X_train))
test_score = model.predictor(Variable(X_test))
train_ndcg = ndcg(y_train, train_score.data)
test_ndcg = ndcg(y_test, test_score.data)
train_ndcgs.append(train_ndcg)
test_ndcgs.append(test_ndcg)
print("step: {0}".format(step + 1))
print("NDCG@100 | train: {0}, test: {1}".format(train_ndcg, test_ndcg))
plt.plot(train_ndcgs, label="Train")
plt.plot(test_ndcgs, label="Test")
xx = np.linspace(0, n_iter / loss_step, num=n_iter / loss_step + 1)
labels = np.arange(loss_step, n_iter + 1, loss_step)
plt.xticks(xx, labels, rotation=45)
plt.legend(loc="best")
plt.xlabel("step")
plt.ylabel("NDCG@10")
plt.ylim(0, 1.1)
plt.tight_layout()
plt.savefig("result.pdf")
Étant donné que les données d'entraînement et les données de test sont générées à partir de la même distribution, il est naturel de dire que c'est naturel, mais vous pouvez voir comment les valeurs d'évaluation augmentent ensemble.
Je veux faire quelque chose d'intéressant
Recommended Posts