Sistema de recomendador de trabajos

Jesus M
LCC-Unison
Published in
10 min readDec 6, 2022

Una de las aplicaciónes que ha aportado el Machine Learning son los sistemas recomendadores. Debido al grado de efectividad que tienen podemos verlos cada día en una multitud de servicios que se consumen día a día. Netflix recomendando peliculas y series; Spotify con los artistas y canciones, o amazon recomendado nuevos artículos para comprar. Estos son uno de muchos ejemplos que se pueden utilizar para desarollar un sistema recomendador.

Antes del Machine Learning, lo más común era usar “rankings” ó listas con lo más votado, ó más popular de entre todos los productos. Entonces a todos los usuarios se les recomendaba lo mismo. Es una técnica que aún se usa y en muchos casos funciona bien, por ejemplo, en librerías ponen apartados con los libros más vendidos, best sellers.

Métodos de sistema de recomendación

Estos son algunos métodos de sistema de recomendación que existen.

Collaborative: Es el más novedoso, pues utiliza la información de “masas” para identificar perfiles similares y aprender de los datos para recomendar productos de manera individual.

Content-based: A partir de productos visitados por el usuario, se intenta predecir qué busca el usuario y ofrecer productos similares. Un ejemplo clásico es Amazón, en la que guiado por nuestras interacciones nos ofrece productos similares hasta que el algoritmo detecta nuestro cambio de “necesidad”. Este tipo de métodos de recomendación necesita recolectar información de tu perfil debido a que las recomendaciones se basan en características particulares de tu información como tu género, donde vives, cuantos años tienes.

Un ejemplo de un sistema recomendador basado en contenido es cuando ingresas una lista de géneros de peliculas que son de tu agrado cuando ingresas a Netflix por primera vez. O en nuestro sistema recomendador, que te recomienda puestos de trabajo según las skills que seleccionaste, como Python, R, Javascript, etc…

Elaboración de un sistema recomendador basado en contenido

Existen varias formas de construir un sistema de recomendación basado en contenido.

En nuestro caso, vamos a útilizar la similaridad de coseno utilizando librerías scikit learn y numpy para el procesamiento matricial.

Obtenemos los datos

Obtenemos el archivo csv de la página https://www.kaggle.com/code/rayjohnsoncomedy/job-skills/data.

El archivo tiene muchas columnas innecesarias, entonces, para mejorar los tiempos de lectura solamente se dejaron las columnas “description” y “description_tokens”, que serían el nombre del trabajo y las skills respectivamente.

import pandas as pd #cargar los datos
jobs = pd.read_csv('https://raw.githubusercontent.com/Jesusmaing/jobsRecommender/master/jobskills.csv',encoding='utf-8')

En este caso, el como podemos observar, dentro de cada trabajo y cada skills, viene separado por una coma, por lo que haremos limpieza de datos para pasar cada string con comas a una lista de strings.

#Separamos en una lista de valores los trabajos
jobs['job'] = jobs['job'].str.split(',')
#Separamos en una lista de valores los skills
jobs['skills'] = jobs['skills'].str.split(',')

Ahora, el set de datos luce de esta manera

Despues de tener una lista de strings, es más fácil proceder con la limpieza de datos. Lo que se busca con esto es borrar listas vacías (que los trabajos no tengan skills), borrar duplicados y cualquier palabra innecesaria para el sistema recomendador.

#Limpiamos los datos, pasamos a minúsculas, quitamos espacios al inicio y final y quitamos duplicados if skill is list and not float

#Pasamos todas las skills a minusculas y borramos espacios al final y al frente
#[ Python ]=> [python]
jobs['skills'] = jobs['skills'].apply(lambda x: [i.lower().strip() for i in x] if type(x) is list else x)

#Borramos todas las listas nulas
jobs.dropna()

#Borramos todos aquellos valores que están vacías o que no son una lista de valores.
jobs = jobs[jobs['skills'].map(lambda d: len(d) > 0 if type(d) is list else False)]

#Debido a que todos los datos ahora se encuentran en minusculas, sin espacios
#y contienen información, aumenta la probabilidad de que haya valores repetidos
#por ende, borraremos todas aquellas skills que estén repetidas para reducir la cantidad de datos
jobs['skills'] = jobs['skills'].apply(lambda x: list(dict.fromkeys(x) ) if type(x) is list else x)

Para observar como la distribución de nuestros datos, podemos utilizar una librería para procesamiento de lenguaje natural


import nltk as nltk#libreria de procesamiento de lenguaje natural
vocabulary1 = nltk.FreqDist()

#Este ciclo itera por el dataframe de skills
#Va añadiendo cada skill a una lista de frecuencias para obtener la cantidad de repetidos
for skill in jobs['skills']:
if type(skill) is list:
# delete empty skills
if len(skill) > 0:
vocabulary1.update(skill)

Una vez teniendo una lista de distribución de frecuencias, podemos observar que tanto se repite una skill dentro de todo el dataset de trabajos

#Graficar la lista de frecuencias 
fig , ax = plt.subplots(figsize=(40,32))
plt.barh([x[0] for x in vocabulary1.most_common(200)],[x[1] for x in vocabulary1.most_common(200)], label='skills')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.set_title('# Skills')
plt.title('Skills más comunes')
plt.show()

Como se puede observar en la gráfica, actualmente la skills más requerida en el mundo laboral de IT, es Java, luego siguen requiriendo desarolladores, programadores de base de datos en sql, etc…

Similaridad de coseno

El algoritmo de similitud de coseno se encarga de conocer el ángulo entre dos vectores n-dimensionales en un espacio n-dimensional. Esto significa que el resultado que obtendremos es el producto escalar de los dos vectores dividido por el producto de las longitudes o magnitudes de los dos vectores.

Formula de similaridad de coseno

Como dato interesante, el término de similitud de coseno proviene del término <<coseno de dirección>>.

Casos de uso de la similitud de coseno

Este importante cálculo se realiza en espacios positivos de alta dimensión. Esto es de gran utilidad en la recuperación de información valiosa y minería de textos. Para utilizarlo de forma eficiente a cada término se le asigna una dimensión diferente y al documento se le caracteriza por vectores. En cada uno de los vectores se asigna un valor para que corresponda con la dimensión en igual cantidad de veces. Así podemos detectar cuantas veces aparece un termino o un valor en un documento, ya que ejecutando el algoritmo obtenemos un conteo preciso de la información que buscamos. También puede ser utilizado para medir la cohesión de los grupos entre datos.

¿Cómo funciona la similitud de coseno?

  • Es un valor que está limitado por un rago restringido de 0 y 1.
  • La medida de similitud mide el coseno del ángulo entre los dos vectores distintios de cero A y B
  • A medida que la medida de similitud del coseno se acerca a 1, el ángulo entre los dos vectores y B es más pequeño.

En el gráfico anterior, la similitud entre estos dos items es el angulo de Θ

Debido a que el algoritmo de similitud de coseno debe de calcularse mediante vectores númericos, y en el recomendador tenemos un vector de palabras, por ende, se necesita vectorizar y normalizar estos datos para obtener un vector númerico

Para ello, utilizaremos una librería de sklearn TfidfVectorizer, que se encarga de transformar texto en vector de caracteristicas.

#Creamos una instancia para vectorizar los skills
vectorizer = TfidfVectorizer()
# Pasamos la lista de Strings a un solo string con espacios para transformar los datos
tfidf_skills = vectorizer.fit_transform(jobs['skills'].apply(lambda x: ' '.join(x) if type(x) is list else x))

Teniendo este vector normalizado, se puede utilizar la similaridad de coseno, para ello, se realizó una función que recibirá como parámetro una lista de Strings y hará todos el trabajo para regresar 20 recomendaciones según las skills mandadas como parámetro.

def recommender(listOfSkills):      
skills = listOfSkills
#to lower case and remove spaces
skills = [i.lower().strip() for i in skills]
#remove duplicates
skills = list(dict.fromkeys(skills))
#all list of single string
skills = ' '.join(skills)
#vectorize the skills
skills = vectorizer.transform([skills])

#se calcula la similitud del coseno de la lista de skills dadas con el resto de las listas de skills
#con eso se va a obtener un vector de similitud con cada uno de los trabajos de la lista de trabajos
similarity_list = cosine_similarity(skills,tfidf_skills)

#sort the list of similarity in order desc and get the index
#es una lista sorteada de distancias de menor a mayor, nosotros necesitamos la mayor similitud, por eso se hace sort descendentemente
#para obtener los indices de la mayor similitud
sorted_indexes = np.argsort(similarity_list[0])[::-1]

#get 10 recommendations jobs
return jobs['job'].iloc[sorted_indexes].values[0:20]
print(recommender(['Data Science', 'R', 'Javascript']))

Esto sería todo dentro del sistema recomendador, funciona bien y regresa trabajos haciendo sentido a las skills envíadas como parámetro.

Convertir el código a API

El código funcionaba de manera local, sin embargo, la finalidad de este proyecto es utilizar los servicios de la nube. Primeramente se recurrió a los servicios de Google Cloud Platform, pero los tiempo de respuestas y el manejo de seguridad estaba complicando el desarollo del sistema, por lo que recurrimos como segunda opción a Heroku, el siguiente paso fue convertir el código a una función que esté escuchando peticiones HTTP, para ello, se utilizó la librería de Python conocida como Flask

También, el vectorizador y el dataframe limpiado, se pasaron a un archivo .pkl para cargarlos y no tener que aplicar fit_transform cada vez que se hace una llamada a la función.

import joblib
joblib.dump(vectorizer, "./vectorizer.pkl")
joblib.dump(tfidf_skills, "./tfidf_skills.pkl")

Se construye el servidor en Flask

from flask import Flask, jsonify
from flask_restful import Resource, Api
from flask_cors import CORS

import json
import pandas as pd #cargar los daots
import numpy as np #operaciones matriciales
import nltk as nltk#libreria de procesamiento de lenguaje natural
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from flask import Flask, request, jsonify, make_response
import joblib
app = Flask(__name__)
api = Api(app)
CORS(app)
def recommender(skills): 
jobs = pd.read_csv('https://raw.githubusercontent.com/Jesusmaing/jobsRecommender/master/jobsCleaned.csv',encoding='utf-8')
vectorizer = joblib.load("./vectorizer.pkl")
tfidf_skills = joblib.load("./tfidf_skills.pkl")
#to lower case and remove spaces
skills = [i.lower().strip() for i in skills]
#remove duplicates
skills = list(dict.fromkeys(skills))
#all list of single string
skills = ' '.join(skills)
#vectorize the skills
skills = vectorizer.transform([skills])
#se calcula la similitud del coseno de la lista de skills dadas con el resto de las listas de skills
#con eso se va a obtener un vector de similitud con cada uno de los trabajos de la lista de trabajos
similarity_list = cosine_similarity(skills, tfidf_skills)

#sort the list of similarity in order desc and get the index
#es una lista sorteada de distancias de menor a mayor, nosotros necesitamos la mayor similitud, por eso se hace sort descendentemente
#para obtener los indices de la mayor similitud
sorted_indexes = np.argsort(similarity_list[0])[::-1]
return json.dumps(jobs['job'].iloc[sorted_indexes].values[0:20].tolist())
class status (Resource):
def get(self):
try:
return {'data': 'Api is Running, please type /recommender?skills=skill1,skill2,skill3'}
except:
return {'data': 'An Error Occurred during fetching Api'}

class Recommender(Resource):
def get(self):
skills = request.args.get('skills')
return jsonify({'data': recommender(skills.split(','))})

api.add_resource(status, '/')
api.add_resource(Recommender, '/recommender')

if __name__ == '__main__':
app.run()

Para obtener recomendaciones se tiene que hacer una petición a la URL que te proporciona el servidor de Heroku, o en donde sea el caso que se esté hosteando la API

https://sistema-recomendador.herokuapp.com/recommender?skills=Python,Javascript <= Aquí irán todas las skills que deseés introducir, separadas por coma.

Con esto concluimos la parte del desarollo de la API, ahora es turno para realizar un cliente que consuma esta API; en este caso, se desarolló en Angular.

Desarollando un cliente que consuma una API

Cualquier framework de frontend puede consumir esta api, solo basta con tener una librería que permita hacer HTTP REQUEST, para el caso de Angular, que su motor es nodeJS (Javascript), se utilizó la librería fetch, la cual utiliza métodos asíncronos para devolvernos una respuesta de lado del servidor

  recomend(){
fetch('https://sistema-recomendador.herokuapp.com/recommender?skills=Python,R,Javascript',
).then(res=>{
res.json().then(resp=>{
console.group('Recomendacion')
console.log("Datos de la recomendacion", JSON.parse(resp['data']))
console.groupEnd()
}).catch(err=>{ console.log(err) });
})
return;
}

El cliente está hosteado mediante Firebase Hosting, ya que nos proporciona un host gratuito y un dominio también gratuito, y al ser un proyecto escolar nos vemos beneficiados en ello.

Github del sistema recomendador

Github del cliente en Angular

Página(s) del cliente

Página de la API

https://sistema-recomendador.herokuapp.com/recommender?skills=Python

El proyecto fue desarollado por Jesus Martin Garcia Encinas y Antonio Medina Valenzuela como proyecto final de Tópicos de Inteligencia Artificial en Ciencias de la Computación, UNISON.

Referencias

--

--