Este proyecto consiste en trabajar procesamiento de texto implementando técnicas incluidas en el procesamiento de lenguaje natural (NLP) y web scraping para la extracción de los datos, seguido por un modelo de clasificación de texto utilizando Python, como referencia guía usaré el siguiente tutorial de DataCamp.
¿Qué es la clasificación de texto?
Clasificación de texto es una tarea común en el NPL usado para resolver problemas de negocios en varios campos, el objetivo de la clasificación de texto es categorizar o predecir una clase de documentos de textos desconocidos, frecuentemente con la ayuda del aprendizaje de máquina supervizado. Moez All (Nov 2022) DataCamp.
Clasificación de webpages o urls.
En este caso entrenaremos un modelo de regresión logística sobre data previamente procesada y categorizada, el conjunto de datos incluye tanto el texto no procesado como su categoría, una vez el modelo es entrenado es usado para obtener una categoría sobre nuevas urls o páginas web las cuales no tenemos su categoría.
Fase de entrenamiento.
El modelo de regresión logística es entrenado sobre registros de un conjunto de datos categorizado durante la fase de entrenamiento. Al final de este proceso obtenemos un modelo que usaremos para obtener predicciones (clasificaciones) sobre registros desconocidos.
Fase de predicción.
Como ya mencionamos, una vez que el modelo es entrenado, puede ser usado para predecir categorías sobre nuevos registros, esto es usualmente hecho una vez desarrollado el “mejor” modelo en la fase de entrenamiento, el modelo debe ser validado a través de distintas métricas de rendimiento, en este caso usaré solo la matriz de confusión.
Pipeline para procesamiento de texto.
Procesamiento de texto es un importante paso en cualquier tarea de procesamiento de lenguaje natural, un pipeline de procesamiento de texto es una serie de pasos que son aplicados a datos de texto sin formato. Los pasos en el procesamiento de texto pueden variar, sin embargo típicamente incluyen tareas tales como tokenización, remover palabras tales como conectores, entre otras (stop words) y lematización. Estos pasos ayudan a reducir el tamaño de la data de texto y tambien mejoran la exactitud en la clasificación.
La data de texto es difícil de procesar porque es no estructurada y frecuentemente contiene mucho “ruido”. El ruido puede estar en forma de errores gramaticales, un formato no estándar, entre otros. El pipeline de procesamiento de texto esta enfocado a limpiar ese ruido así los datos puede ser más facil de analizar.
Extracción de características
Un método común para la extracción de características de texto o en otras palabras convertir la data de texto a campos numéricos así poder entrenar un modelo de aprendizaje de máquina es conocida como Bag of Words o CountVectorizer. El método de Bag of Words es una simple manera de representar data de texto como campos numéricos. Consiste en crear un vocabulario de palabras conocidas en el corpus y luego crear un vector para cada texto que contiene el conteo de la frecuencia de cada palabra.
Modelo de clasificación en Python
Para el proyecto se quiere elaborar un clasificador de paginas web, para ello se hizo previamente la recolección de los datos donde diariamente se recolectó las diez primeras búsquedas en Google de las consultas: sports, news y entertainment, una vez obtenidos los resultados se extrajo la siguiente información: url de la página, título de la página, descripción de la página, posición de la búsqueda y categoría.
El objetivo es entrenar un modelo de regresión logística que aprenda del texto del título, descripción y categoría de la página web para así poder predecir la clase de nuevas páginas web.
Las siguientes líneas de código realizan el proceso de webscraping para la obtención de los datos.
Code
import urllib.request, urllib.parse, urllib.error, osimport csvimport sslfrom bs4 import BeautifulSoupfrom googlesearch import search# Ignore SSL certificate errorsctx = ssl.create_default_context()ctx.check_hostname =Falsectx.verify_mode = ssl.CERT_NONE# urls to searchqueries = ['sports', 'news', 'entertainment']urls = []for q in queries: count =1for url in search(q, tld="com", lang='en', num=10, start=1, stop=10, pause=2): urls.append((url, count, q)) count +=1# get url, title and descriptionmy_data = []for url in urls:try: html = urllib.request.urlopen(url[0], context=ctx, timeout=10).read() soup = BeautifulSoup(html, 'html.parser') title = soup.title.getText() des = soup.find_all('meta')except:continuefor d in des: my_dict = d.attrsif my_dict.get('name', 'no') =='description': my_data.append([url[1], url[0], title, my_dict.get('content'), url[2]])break# check for urls that could not be readfor i in urls:if i[0] notin [k[1] for k in my_data]:print(i)# write/append the data to the data.csv fileifnot os.path.isfile("data.csv"):withopen('data.csv', 'w') as csvfile: writer = csv.writer(csvfile, delimiter =';') writer.writerow(['position', 'url', 'title', 'description', 'tag']) writer.writerows(my_data)else:withopen('data.csv', 'a') as csvfile: writer = csv.writer(csvfile, delimiter =';') writer.writerows(my_data) csvfile.close()
Una vez hecha la recolección de los datos procedemos a realizar la lectura de los mismos.
En esta fase se realizará toda la limpieza de datos que incluye eliminación de caracteres especiales, estandarización del texto (todo el texto en minúsculas), tokenización, eliminación de palabras no deseadas (stopwords) y lematización.
Para ello almaceno tanto los títulos como las descripciones de las páginas web en una lista, para luego pasar cada elemento de la lista de texto por una función previamente definida que realice todo el proceso anteriormente dicho y así obtener mi corpus.
Code
# create a list textfields = ('title', 'description')text = [list(data[i]) for i in fields]# preprocessing loopnltk.download('all')lemmatizer = WordNetLemmatizer()def clean_data(string): r = re.sub('[^a-zA-Z]', ' ', string) r = r.lower() r = r.split() r = [word for word in r if word notin stopwords.words('english')] r = [lemmatizer.lemmatize(word) for word in r] r =' '.join(r)return rcorpus =list(map(lambda lst: list(map(clean_data, lst)), text))
Code
# view of the first five web page titles cleanedcorpus[0][:5]
['yahoo sport news score video fantasy game schedule yahoo sport',
'espn serving sport fan anytime anywhere',
'cbs sport news live score schedule fantasy game video cbssports com',
'sport news score prediction analysis usa today',
'nbc sport news score stats rumor video']
Code
# view of the first five web page descriptions cleanedcorpus[1][:5]
['sport news score fantasy game',
'visit espn live score highlight sport news stream exclusive game espn play fantasy sport',
'cbs sport feature live scoring news stats player info nfl football mlb baseball nba basketball nhl hockey college basketball football',
'usa today provides latest sport news score schedule stats odds nfl mlb nba nhl college sport',
'stay date latest sport news score nbc sport']
Una vez obtenido el texto ya “limpio” procedo a fusionar el texto de los títulos y las descripciones de las páginas web en un solo campo y para luego ser anexado a la data.
Code
# merge title and description into one fieldtitle_des = [' '.join(i) for i inzip(corpus[0], corpus[1])]data = data.assign(text = title_des)# assign corpus to datafor i inrange(len(fields)): data[fields[i]] = corpus[i]data.head()
position
url
title
description
tag
text
0
2
https://sports.yahoo.com/
yahoo sport news score video fantasy game sche...
sport news score fantasy game
sports
yahoo sport news score video fantasy game sche...
1
3
https://www.espn.com/
espn serving sport fan anytime anywhere
visit espn live score highlight sport news str...
sports
espn serving sport fan anytime anywhere visit ...
2
4
https://www.cbssports.com/
cbs sport news live score schedule fantasy gam...
cbs sport feature live scoring news stats play...
sports
cbs sport news live score schedule fantasy gam...
3
5
https://www.usatoday.com/sports/
sport news score prediction analysis usa today
usa today provides latest sport news score sch...
sports
sport news score prediction analysis usa today...
4
6
https://www.nbcsports.com/
nbc sport news score stats rumor video
stay date latest sport news score nbc sport
sports
nbc sport news score stats rumor video stay da...
Procedemos a separar el dataset en datos de entrenamiento y datos de prueba, en este caso trabajaré con un 70% de los datos para entrenar el modelo y un 30% para probar el modelo.
Luego, se procede a implementar el modelo de “Bag of Words” (CountVectorizer) para convertir el texto ya limpio en campos numéricos. Esto es necesario para el entrenamiento del modelo.
Para finalizar con el proyecto se procedera a predecir las posiciones de las url para cada una de sus categorías.
Ejemplo, supongamos que durante cinco días consecutivos se toman las primeras diez urls de Google de las búsquedas Sports, News y Entertainment, para la búsqueda Sports se tiene la url www.ESPN.com con los siguientes registros:
Día
Posición
URL
Categoría
1
1
www.ESPN.com
Sports
2
2
www.ESPN.com
Sports
3
1
www.ESPN.com
Sports
4
1
www.ESPN.com
Sports
5
2
www.ESPN.com
Sports
6
?
www.ESPN.com
Sports
Con base en los siguientes registros nos gustaría saber qué posición ocupará la url www.ESPN.com al día siguiente. Para ello volvemos a entrenar un modelo de regresión para cada una de las categorías, pero en esta ocasión los datos de entrenamiento y prueba no se dividirán aleatoriamente, sino que trabajaremos con los datos recopilados antes del último web scraping, con esos datos se entrenará el modelo, para luego ser probado con los datos del último web scraping, de esta manera saber cuantos registros fueron predichos correctamente.
Code
# train a new model now with the position variable as response variable per tagurl_var =list(data['url'])pos_var =list(data['position'])tag_var =list(data['tag'])# make a nested list where each element of the list will be a list with each record of the daily webscrapingdaily_data = []df = []pos = pos_var[0]n_tag =0for i inrange(len(url_var)):if pos <= pos_var[i]: pos = pos_var[i] df.append([url_var[i], str(pos_var[i]), tag_var[i]])else: n_tag +=1 pos = pos_var[i]if n_tag <3: df.append([url_var[i], str(pos_var[i]), tag_var[i]])if n_tag ==3: n_tag =0 daily_data.append(df) df = [] df.append([url_var[i], str(pos_var[i]), tag_var[i]])if i ==len(url_var) -1: daily_data.append(df)# get train data sets and test data sets by tag x_train_sport, y_train_sport, x_train_news, y_train_news, x_train_enter, y_train_enter = [],[],[],[],[],[]x_test_sport, y_test_sport, x_test_news, y_test_news, x_test_enter, y_test_enter = [],[],[],[],[],[]# train data setfor day in daily_data[:len(daily_data)-1]:for row in day:if row[2] =='sports': x_train_sport.append(row[0]) y_train_sport.append(row[1])elif row[2] =='news': x_train_news.append(row[0]) y_train_news.append(row[1])else: x_train_enter.append(row[0]) y_train_enter.append(row[1])# test data setfor row in daily_data[-1]:if row[2] =='sports': x_test_sport.append(row[0]) y_test_sport.append(row[1])elif row[2] =='news': x_test_news.append(row[0]) y_test_news.append(row[1])else: x_test_enter.append(row[0]) y_test_enter.append(row[1])# function to train the model def models(x_train, y_train, x_test, y_test): x_train_cv = cv.fit_transform(x_train)# training Logistic Regression model lr = LogisticRegression() lr.fit(x_train_cv, y_train)# transform x_test using CV x_test_cv = cv.transform(x_test)# generate predictions predictions = lr.predict(x_test_cv)# confusion matrix cm = pd.DataFrame(metrics.confusion_matrix(y_test, predictions))print('records:', len(y_test))return cm# resultx_train = [x_train_sport, x_train_news, x_train_enter]y_train = [y_train_sport, y_train_news, y_train_enter]x_test = [x_test_sport, x_test_news, x_test_enter]y_test = [y_test_sport, y_test_news, y_test_enter]tag = ['Sport', 'News', 'Entertainment']for idx inrange(len(tag)):print(tag[idx])print(models(x_train=x_train[idx], y_train=y_train[idx], x_test=x_test[idx], y_test=y_test[idx]))print('')
La clasificación de texto es una tarea ampliamente implementada en el procesamiento del lenguaje natural, ya sea para clasificar o predecir una clase. Existen diferentes algoritmos para configurar un clasificador de texto, es importante experimentar para encontrar el “mejor” modelo dependiendo de nuestro propósito.
En el siguiente repositorio GitHub se pueden encontrar algunos proyectos donde se detallan algunas tareas comunes de ciencia de datos.