Le filtrage coopératif utilisé dans le système de recommandation est une application de l'apprentissage automatique, mais il diffère légèrement du modèle simple «d'apprentissage supervisé». J'ai essayé un peu de littérature et d'informations sur Internet, mais il est difficile de comprendre immédiatement le concept et la configuration du système. Cependant, je me suis souvenu qu'il avait été traité dans Coursera Machine Learning (semaine 9) par le Dr Andrew Ng, et en passant en revue le matériel pédagogique, j'ai pu approfondir ma compréhension du filtrage coopératif.
Comme vous le savez peut-être, Matlab / Octave est utilisé dans les exercices de ce cours. Cette fois, j'ai implémenté le co-filtrage (prototype) en Python. Tout d'abord, nous avons confirmé l'opération avec Numpy + Scipy (Optimize), puis nous avons confirmé l'implémentation à l'aide de TensorFlow.
Extrait du lecteur de formation Data Scientist, Introduction à l'apprentissage automatique.
Le filtrage coopératif est une méthode pour réaliser les présentations suivantes sur les sites EC.
- "Les personnes qui ont acheté ce produit ont également acheté ce produit."
- "Les personnes qui achètent les mêmes produits que vous achètent également ces produits."
Je l'implémenterai lorsque j'aurai compris le concept, mais je confirmerai l'explication dans Coursera Machine Learning. Ici, l'explication est avancée à l'aide d'un exemple de classement de film.
Fig.1 Movie Rating Data
Le tableau montre les évaluations des films par chaque utilisateur, mais les évaluations sont divisées en fonction des préférences de l'utilisateur. Les données [X], qui sont un paramètre d'échelles différentes (appelées majeures dans la classe Coursera) telles que «forte tendance à la romance amoureuse» et «haute action», sont préparées en fonction du contenu du film. D'autre part, un paramètre [Theta] pour "préférence utilisateur" est également requis. Comme le montre la figure, la taille des données d'évaluation est une matrice bidimensionnelle ([Y]) du nombre de films x le nombre d'utilisateurs, mais elle est souvent indéfinie car les utilisateurs n'évaluent pas tous les films. .. Cette indéfinie? Le point principal du problème est de compléter la marque par régression.
L'explication est que si la matrice d'évaluation complémentaire peut être calculée avec précision, le traitement dans le processus suivant de recommandation de films similaires peut être bien fait.
Comme pour les autres exercices du cours Cousera, nous commençons par trouver la fonction de coût. Puisqu'il y a deux paramètres, c'est une expression assez "dure".
\begin{eqnarray}
J(x^{(1)},...,x^{n_m},\theta^{1},...,\theta^{n_u}) = &\ & \frac{1}{2} \sum_{(i,j):r(i,j)=1}
(({\theta}^{(j)})^T \ x^{(i)} - y^{(i,j)})^2 \ + \\
&\ & (\frac{\lambda}{2} \sum_{j=1}^{n_u} \sum_{k=1}^{n} ({\theta}_k^{(j)}) ^2)
+ (\frac{\lambda}{2} \sum_{i=1}^{n_m} \sum_{k=1}^{n} ({x}_k^{(i)}) ^2)
\end{eqnarray}
La fonction de coût avec les paramètres [X] et [Theta] consiste en l'erreur quadratique entre la valeur prédite nominale déterminée par le produit matriciel des paramètres et les données d'évaluation réelles (étiquette), et le terme de régularisation du paramètre. Si cela est converti en code Python, ce sera comme suit.
def cofi_costfunc(Y, R, shapes, lambda_c, params):
# [J, grad] = COFICOSTFUNC(params, Y, R, num_users, num_movies, ...
# num_features, lambda) returns the cost and gradient for the
# collaborative filtering problem.
num_users = shapes[0]
num_movies = shapes[1]
num_features = shapes[2]
boundary = num_movies * num_features
params_c1 = np.copy(params[:boundary])
params_c2 = np.copy(params[boundary:])
X = params_c1.reshape((num_movies, num_features))
Theta = params_c2.reshape((num_users, num_features))
# You need to return the following values correctly
Jcost = 0.5 * (((np.dot(X, Theta.T) - Y) * R) ** 2).sum() \
+ 0.5 * lambda_c * ((X.ravel() **2).sum() + (Theta.ravel() **2).sum())
return Jcost
Les paramètres «X» (paramètres du contenu du film) et «Theta» (paramètres orientés utilisateur) sont saisis dans un bloc «params». «Y» correspond aux données de classement sous la forme de nombre de films x nombre d'utilisateurs. «R» est une donnée binaire de la même taille que «Y», définie sur 1 lorsqu'il est évalué et 0 lorsqu'il n'y a pas encore d'évaluation. (Le type de variable du programme est un type entier.) Après le prétraitement, la déclaration finale est de trouver le coût correspondant à la formule de fonction de coût ci-dessus.
Vient ensuite l'équation du coefficient différentiel partiel (gradient) pour les paramètres (x, thêta) de cette fonction de coût.
\frac{\partial J}{\partial x_k^{(i)}} = \sum_{j:r(i,j)=1} (( {\theta}^{(j)})^T x^{(i)}
- y^{(i,j)} ) {\theta}_k^{(j)} + \lambda x_k^{(i)}\\
\frac{\partial J}{\partial {\theta}_k^{(j)}} = \sum_{i:r(i,j)=1} (( {\theta}^{(j)})^T x^{(i)}
- y^{(i,j)} ) {x}_k^{(i)} + \lambda {\theta}_k^{(j)}
C'est aussi une formule "dure" telle quelle. Cependant, il est plus facile de comprendre s'il est converti en code.
def cofi_costfunc_grad(Y, R, shapes, lambda_c, params):
# [J, grad] = COFICOSTFUNC(params, Y, R, num_users, num_movies, ...
# num_features, lambda) returns the cost and gradient for the
# collaborative filtering problem.
num_users = shapes[0]
num_movies = shapes[1]
num_features = shapes[2]
boundary = num_movies * num_features
params_c1 = np.copy(params[:boundary])
params_c2 = np.copy(params[boundary:])
X = params_c1.reshape((num_movies, num_features))
Theta = params_c2.reshape((num_users, num_features))
# You need to return the following values correctly
X_grad = np.dot(((np.dot(X, Theta.T) - Y) * R), Theta) + lambda_c * X
Tgsub = (np.dot(X, Theta.T) - Y) * R
Theta_grad = np.dot(Tgsub.T, X) + lambda_c * Theta
Jgrad = np.concatenate((X_grad.ravel(), Theta_grad.ravel()))
return Jgrad
Le gradient de coût est calculé dans les quatre dernières lignes.
Dans l'exercice de Coursera, le processus d'optimisation de la minimisation des coûts est effectué en utilisant la fonction de fmincg.m
fournie à l'avance. Lors de la conversion en Python, le même traitement peut être effectué avec scipy.optimize.minimize ().
#
import scipy.optimize as spopt
#Définir la valeur initiale du paramètre
theta_ini = np.concatenate((X.flatten(), Theta.flatten()))
#Préparer la fonction wrapper
def compute_cost_sp(theta):
global Y, R, shapes, lambda_c
j = cofi_costfunc(Y, R, shapes, lambda_c, params=theta)
return j
def compute_grad_sp(theta):
global Y, R, shapes, lambda_c
j_grad = cofi_costfunc_grad(Y, R, shapes, lambda_c, params=theta)
return j_grad
# scipy.optimize.Définir les options de réduction
options = {'gtol': 1.e-6, 'disp': True}
#Coefficient de régularisation
lambda_c = 1.5
res1 = spopt.minimize(compute_cost_sp, theta_ini, method='CG', \
jac=compute_grad_sp, options=options)
theta = res1.x
#Décomposer un ensemble de paramètres en X et Thêta
X = theta[:num_movies*num_features].reshape((num_movies, num_features))
Theta = theta[num_movies*num_features:].reshape((num_users, num_features))
Pour plus de détails sur scipy.optimize.minimize, veuillez vous référer au document, mais vous pouvez sélectionner différentes méthodes d'optimisation. Cette fois, nous définissons method = 'CG'
(méthode du gradient conjugué) en fonction du matériau d'origine (fmincg.m).
Informations nécessaires à partir des paramètres [X] et [Thêta] obtenus en minimisant le coût de cette manière (ex. "Les personnes qui ont évalué ce film ont également donné une note élevée à ce film.") Est le traitement du système de recommandation. (Il semble y avoir différentes façons de mesurer la similitude, mais je vais omettre l'explication ici.)
Cela fait environ un an que TensorFlow a été publié l'année dernière (2015). Je me souviens avoir écrit le code avec enthousiasme et avoir écrit le message Qiita à la hâte. Cette fois, nous avons décidé d'utiliser TensorFlow pour exécuter l'apprentissage du filtrage coopératif à une vitesse plus élevée.
Dans le cas d'un "apprentissage supervisé" normal, la relation entrée-sortie est claire, il n'y a donc aucun doute sur la représentation graphique de TensorFlow, mais cette fois c'était un peu difficile.
--Un problème normal qui correspond au poids w et au biais b ... Cette fois, le paramètre x du contenu du film et le paramètre préféré de l'utilisateur Theta. --Un problème normal qui correspond aux données de l'enseignant ... Cette fois, les informations de notation données par l'utilisateur à l'avance. Plus précisément, les données Y (matrice du nombre de films comprenant la note donnée par l'utilisateur au nombre d'utilisateurs x nombre d'utilisateurs) et R (données binaires indiquant si l'utilisateur a évalué ou non).
Par conséquent, le code pour la préparation des variables est le suivant.
# Parameter definition
if WARM_START:
data_param = load_data(fn='./data/ex8_movieParams.mat')
X_np = data_param['X'].astype(np.float32)
Theta_np = data_param['Theta'].astype(np.float32)
x = tf.Variable(X_np)
theta = tf.Variable(Theta_np)
else: # COLD START
x = tf.Variable(tf.random_normal([num_movies, num_features],
mean=0.0, stddev=0.05))
theta = tf.Variable(tf.random_normal([num_users, num_features],
mean=0.0, stddev=0.05))
# Placeholders
Y_ph = tf.placeholder(tf.float32, shape=[None, num_users])
R_ph = tf.placeholder(tf.float32, shape=[None, num_users])
Étant donné que les données de paramètres qui avaient été pré-apprises dans une certaine mesure étaient fournies dans les exercices du programme Coursera, nous avons préparé une option «WARM_START» pour les utiliser et une option pour repartir de zéro. Dans'WARM_START ', les données de numpy.array sont passées à tf.Variable ()
telles quelles, et dans le cas d'un démarrage à zéro, un nombre aléatoire avec une petite valeur est généré et défini dans x et thêta. En outre, l'espace réservé des données de l'enseignant a été préparé avec la forme requise.
(Shape = [None, num_users]
est OK avecshape = [num_movies, num_users]
, mais il est défini sur None
selon la convention de TensorFlow.)
Ensuite, nous définissons le calcul de la fonction de coût.
def Ypred(X, Theta):
'''
calculate rating with parameter X and Theta
args.:
X: movie contents parameter
Theta: user characteristic parameter
'''
feat_dim1 = tf.shape(X)[1]
feat_dim2 = tf.shape(Theta)[1]
tf.assert_equal(feat_dim1, feat_dim2)
rating = tf.matmul(X, tf.transpose(Theta))
return rating
# Cost Function, etc.
cost = tf.reduce_sum(((Ypred(x, theta) - Y_ph) * R_ph) ** 2)
L2_sqr = tf.nn.l2_loss(x) + tf.nn.l2_loss(theta)
lambda_c = 1.0 # L2 norm coefficient
loss = cost + lambda_c * L2_sqr
Non seulement TensorFlow, mais la chose utile à propos du framework Deep Learning est qu'il calcule automatiquement Gradient. Je pense qu'il est "génial" de définir uniquement la fonction de coût comme décrit ci-dessus. Après cela, comme d'habitude, l'optimiseur est configuré pour former une boucle d'apprentissage.
lr = 1.e-5
train_step = tf.train.GradientDescentOptimizer(lr).minimize(loss)
init_op = tf.initialize_all_variables()
# Train
with tf.Session() as sess:
sess.run(init_op)
for i in range(10001):
sess.run(train_step, feed_dict={Y_ph: Y_np, R_ph: Y_np})
if i % 1000 == 0:
loss_mon = loss.eval({Y_ph: Y_np, R_ph: Y_np})
print(' step, loss = {:6d}: {:10.1f}'.format(i, loss_mon))
# evaluate ranking with final parameters
ymat = Ypred(x, theta).eval()
sio.savemat('./ymat_tf.mat', {'Y': ymat})
print('ymat is saved to "ymat_tf.mat".')
Je pense que vous pouvez utiliser votre optimiseur préféré au lieu de tf.train.GradientDescentOptimizer ()
, mais cette fois j'ai essayé d'utiliser une fonction de base car c'est un problème de Cousera. Il faut un certain temps pour ajuster le taux d'apprentissage, mais j'ai pu effectuer le calcul en toute sécurité (à grande vitesse avec le fonctionnement du GPU).
Puisque la solution est le même algorithme, le résultat de scipy.optimize.minimize et le résultat de TensorFlow doivent être similaires. Cette fois, nous avons calculé la matrice de notation Y à partir des paramètres obtenus (X, Thêta) et l'avons faite comme indiqué dans le diagramme Heatmap. (Bleu: taux bas ~ rouge: taux élevé)
By scipy.optimize.minimize
By TensorFLow
Des différences telles que des nuances de couleur peuvent être vues dans les détails, mais le même motif peut être considéré comme un grand motif. Il ne semble y avoir aucun problème dans le processus de calcul.
Si vous souhaitez compenser la différence entre les deux, vous devez ajuster les détails tels que la régularisation, le taux d'apprentissage et l'état de convergence. Je pense que le contenu ci-dessus est correct pour «apprendre le filtrage coopératif», mais pour se battre dans des compétitions comme Kaggle, divers détails devront être examinés.
(L'environnement de programmation utilisé cette fois-ci est le suivant. Python 3.5.2, Numpy 1.11.2, Scipy 0.18.1, TensorFlow 0.11.0) (Le code a été téléchargé sur Gist: https://gist.github.com/tomokishii/2e9f83d391df82f25942c1310b41e0b5)
Recommended Posts