Im Rahmen des maschinellen Lernens gibt es Ranglernen (Rankinglernen, Rankinglernen). RankNet, das ein neuronales Netzwerk verwendet, ist eines der Ranglernmodelle. In diesem Artikel werde ich über die Implementierung von RankNet mit Chainer sprechen.
Wenn man sich die Vorhersageergebnisse ansieht, scheint die Implementierung korrekt zu sein, aber wenn es Fehler wie die Verwendung von Chainer gibt, wäre ich dankbar, wenn Sie darauf hinweisen könnten.
Beim Ranglernen wird eine Menge von $ ({\ mathbf x}, y) = $ (Merkmale, Eignungsgrad) gelernt. Lernen Sie die Funktion $ f $ kennen, die einen großen Wert für Elemente mit einem hohen Konformitätsgrad und einen kleinen Wert für Elemente mit einem niedrigen Konformitätsgrad zurückgibt.
Angenommen, zwei Elemente $ A $ und $ B $ werden wie folgt dargestellt.
A=(\mathbf{x}_A, y_A) \\
B=(\mathbf{x}_B, y_B)
Nehmen Sie außerdem an, dass $ A $ besser geeignet ist als $ B $. Angenommen, $ y_A> y_B $ gilt. Zu diesem Zeitpunkt wird die Funktion $ f $ gelernt, so dass die folgende Beziehung gilt.
f(\mathbf{x}_A)>f(\mathbf{x}_B)
Es fühlt sich an, als ob etwas nicht stimmt, also liebe es zu leben! Ich werde mit erklären. Zuallererst Liebe leben! Fügen wir dem Charakter von den Eignungsgrad (☆, ☆☆, ☆☆☆) hinzu. Je größer die Anzahl von ☆ ist, desto höher ist der Konformitätsgrad (Favorit).
Wenn Sie das folgende Paar lernen, sollten Sie die Funktion $ f $ wie folgt erhalten.
Wenn $ f ({\ bf x}) = {\ bf w} ^ T {\ bf x} $, wird das Gewicht $ {\ bf w} $ orthogonal als Merkmalsmenge projiziert, wie in der folgenden Abbildung gezeigt. Es wird geschätzt, dass die Punkte in der Reihenfolge ihrer Konformität angeordnet sind. In einem echten Problem würde es kein $ {\ bf w} $ geben, das in der Reihenfolge seiner Eignung perfekt ausgerichtet wäre ...
Mit der als Ergebnis des Lernens erhaltenen Funktion $ f $ ist es möglich, die Rangfolge von Zeichen vorherzusagen, die noch nicht angepasst wurden.
Hier habe ich "Lernpaare" geschrieben, aber tatsächlich gibt es ungefähr drei Methoden für das Ranglernen, und sie entsprechen der Methode, die paarweise genannt wird. Es gibt andere Methoden, z. B. punktweise und listweise. Informationen zu diesen Methoden finden Sie in den folgenden Materialien.
RankNet ist ein Modell des Ranglernens basierend auf einem neuronalen Netzwerk und eine paarweise Methode. Das Vorhersagemodell selbst ist dasselbe wie ein normales neuronales Netzwerk, aber das Design der Kostenfunktion ist unterschiedlich. Die Idee ist jedoch dieselbe wie die Kreuzentropiefunktion, die häufig als Kostenfunktion verwendet wird.
Angenommen, die Elemente $ A $ und $ B $ werden wie oben dargestellt und $ A $ ist passender als $ B $.
A=(\mathbf{x}_A, y_A) \\
B=(\mathbf{x}_B, y_B)
Die Punktzahlen von $ A $ und $ B $ durch die Funktion $ f $ werden wie folgt berechnet.
s_A=f(\mathbf{x}_A) \\
s_B=f(\mathbf{x}_B)
Die Wahrscheinlichkeit, dass $ A $ höher eingestuft wird als die von RankNet ausgegebene $ B $, wird unter Verwendung von $ s_A $ und $ s_B $ wie folgt ausgedrückt.
P_{AB}=\frac{1}{1+e^{-\sigma(s_A-s_B)}}
Wenn $ s_A $ zunimmt, nähert sich $ P_ {AB} $ intuitiv 1, und wenn $ s_B $ zunimmt, nähert sich $ P_ {AB} $ 0.
Unter Verwendung von $ P_ {AB} $ wird die Kostenfunktion wie folgt ausgedrückt.
C_{AB}=-\bar{P}_{AB}\log{P_{AB}}-(1-\bar{P}_{AB})\log(1-P_{AB})
Hier ist $ \ bar {P} _ {AB} $ eine bekannte Wahrscheinlichkeit, dass $ A $ höher als $ B $ eingestuft wird. In Referenz wurde Folgendes festgelegt.
\bar{P}_{AB}=\begin{cases}
1 & (A \succ B) \\
0 & (A \prec B) \\
\frac{1}{2} & (otherwise)
\end{cases}
$ C_ {AB} $ ist nur eine Kreuzentropiefunktion, und je weiter $ P_ {AB} $ $ \ bar {P} _ {AB} $ ist, desto größer ist der Wert. RankNet lernt, diese Kostenfunktion zu minimieren.
Lassen Sie uns nun RankNet mit Chainer implementieren. Die Verlustfunktion softmax_cross_entropy () ist in Chainer implementiert, softmax_cross_entropy () kann jedoch nicht verwendet werden, da nur der Typ int32 als Zielvariable empfangen werden kann. ($ \ Bar {P} _ {AB} $ kann $ \ frac {1} {2} $ sein.)
Daher muss ich die Kostenfunktion $ C_ {AB} $ selbst implementieren, aber ich konnte lernen, ohne ** backward () zu implementieren, das in anderen Verlustfunktionen * implementiert ist. *… Ich frage mich, ob backward () nicht implementiert ist, um automatisch eine numerische Differenzierung durchzuführen, und ich wäre Ihnen dankbar, wenn Sie dies kommentieren könnten.
Im Folgenden wird die Implementierung von RankNet beschrieben. Als Modell des neuronalen Netzwerks wird ein einfaches mehrschichtiges Perzeptron von [Eingabeschicht] -> [verborgene Schicht] -> [verborgene Schicht] -> [Ausgangsschicht] verwendet.
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
Wenden wir das implementierte RankNet auf künstliche Daten an. Die künstlichen Daten wurden wie folgt erzeugt.
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}
Es gibt 1000 50-dimensionale Daten, und der Konformitätsgrad beträgt entweder [1,2,3,4,5]. Bei Darstellung im PCA-Raum ist die Abbildung wie folgt.
Irgendwie scheint der Konformitätsgrad oben links in der Figur höher und unten rechts kleiner zu sein.
Unten finden Sie den Lern- und Bewertungscode. Das Training verwendet die stochastische Gradientenabstiegsmethode und die Trainingsdaten werden jedes Mal zufällig abgetastet. NDCG @ 100 wurde verwendet, um die Vorhersagegenauigkeit zu bewerten.
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")
Da sowohl Trainingsdaten als auch Testdaten aus derselben Verteilung generiert werden, ist es natürlich zu sagen, dass dies natürlich ist, aber Sie können sehen, wie sich die Bewertungswerte zusammen erhöhen.
Ich möchte etwas interessantes machen
Recommended Posts