!python -m spacy download es_core_news_sm
! pip install -U symspellpy
import nltk # importar natural language toolkit
nltk.download('punkt')
nltk.download('stopwords') # modulo para descargar stopwords en diferentes idiomas
nltk.download('wordnet')
from nltk.corpus import stopwords
import pandas as pd
import numpy as np
import re
import string
import plotly
import matplotlib.pyplot as plt
from nltk.stem import PorterStemmer
import time
import spacy
import es_core_news_sm
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.tokenize import sent_tokenize
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from nltk.probability import FreqDist
from wordcloud import WordCloud
import pickle
from symspellpy import SymSpell
import pkg_resources
from symspellpy import SymSpell, Verbosity
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting es_core_news_sm==2.2.5
Downloading https://github.com/explosion/spacy-models/releases/download/es_core_news_sm-2.2.5/es_core_news_sm-2.2.5.tar.gz (16.2 MB)
|████████████████████████████████| 16.2 MB 5.2 MB/s
Requirement already satisfied: spacy>=2.2.2 in /usr/local/lib/python3.7/dist-packages (from es_core_news_sm==2.2.5) (2.2.4)
Requirement already satisfied: cymem<2.1.0,>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (2.0.6)
Requirement already satisfied: preshed<3.1.0,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (3.0.6)
Requirement already satisfied: plac<1.2.0,>=0.9.6 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (1.1.3)
Requirement already satisfied: wasabi<1.1.0,>=0.4.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (0.9.1)
Requirement already satisfied: catalogue<1.1.0,>=0.0.7 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (1.0.0)
Requirement already satisfied: numpy>=1.15.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (1.21.6)
Requirement already satisfied: requests<3.0.0,>=2.13.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (2.23.0)
Requirement already satisfied: tqdm<5.0.0,>=4.38.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (4.64.0)
Requirement already satisfied: murmurhash<1.1.0,>=0.28.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (1.0.7)
Requirement already satisfied: srsly<1.1.0,>=1.0.2 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (1.0.5)
Requirement already satisfied: blis<0.5.0,>=0.4.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (0.4.1)
Requirement already satisfied: thinc==7.4.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (7.4.0)
Requirement already satisfied: setuptools in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_sm==2.2.5) (57.4.0)
Requirement already satisfied: importlib-metadata>=0.20 in /usr/local/lib/python3.7/dist-packages (from catalogue<1.1.0,>=0.0.7->spacy>=2.2.2->es_core_news_sm==2.2.5) (4.11.3)
Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=0.20->catalogue<1.1.0,>=0.0.7->spacy>=2.2.2->es_core_news_sm==2.2.5) (3.8.0)
Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=0.20->catalogue<1.1.0,>=0.0.7->spacy>=2.2.2->es_core_news_sm==2.2.5) (4.2.0)
Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy>=2.2.2->es_core_news_sm==2.2.5) (1.24.3)
Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy>=2.2.2->es_core_news_sm==2.2.5) (3.0.4)
Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy>=2.2.2->es_core_news_sm==2.2.5) (2.10)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy>=2.2.2->es_core_news_sm==2.2.5) (2022.5.18.1)
Building wheels for collected packages: es-core-news-sm
Building wheel for es-core-news-sm (setup.py) ... done
Created wheel for es-core-news-sm: filename=es_core_news_sm-2.2.5-py3-none-any.whl size=16172933 sha256=917e7d20c5f7f523b6fd6345df341e0f78a96c11fde073a00548603124eab74b
Stored in directory: /tmp/pip-ephem-wheel-cache-l6s3j56i/wheels/21/8d/a9/6c1a2809c55dd22cd9644ae503a52ba6206b04aa57ba83a3d8
Successfully built es-core-news-sm
Installing collected packages: es-core-news-sm
Successfully installed es-core-news-sm-2.2.5
✔ Download and installation successful
You can now load the model via spacy.load('es_core_news_sm')
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting symspellpy
Downloading symspellpy-6.7.6-py3-none-any.whl (2.6 MB)
|████████████████████████████████| 2.6 MB 5.3 MB/s
Collecting editdistpy>=0.1.3
Downloading editdistpy-0.1.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (125 kB)
|████████████████████████████████| 125 kB 52.2 MB/s
Installing collected packages: editdistpy, symspellpy
Successfully installed editdistpy-0.1.3 symspellpy-6.7.6
[nltk_data] Downloading package punkt to /root/nltk_data...
[nltk_data] Unzipping tokenizers/punkt.zip.
[nltk_data] Downloading package stopwords to /root/nltk_data...
[nltk_data] Unzipping corpora/stopwords.zip.
[nltk_data] Downloading package wordnet to /root/nltk_data...
[nltk_data] Unzipping corpora/wordnet.zip.
!python -m spacy download es_core_news_md
Collecting es_core_news_md==2.2.5
Downloading https://github.com/explosion/spacy-models/releases/download/es_core_news_md-2.2.5/es_core_news_md-2.2.5.tar.gz (78.4 MB)
|████████████████████████████████| 78.4 MB 1.1 MB/s
Requirement already satisfied: spacy>=2.2.2 in /usr/local/lib/python3.7/dist-packages (from es_core_news_md==2.2.5) (2.2.4)
Requirement already satisfied: blis<0.5.0,>=0.4.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (0.4.1)
Requirement already satisfied: tqdm<5.0.0,>=4.38.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (4.64.0)
Requirement already satisfied: murmurhash<1.1.0,>=0.28.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (1.0.7)
Requirement already satisfied: setuptools in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (57.4.0)
Requirement already satisfied: preshed<3.1.0,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (3.0.6)
Requirement already satisfied: thinc==7.4.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (7.4.0)
Requirement already satisfied: numpy>=1.15.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (1.21.6)
Requirement already satisfied: requests<3.0.0,>=2.13.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (2.23.0)
Requirement already satisfied: wasabi<1.1.0,>=0.4.0 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (0.9.1)
Requirement already satisfied: srsly<1.1.0,>=1.0.2 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (1.0.5)
Requirement already satisfied: plac<1.2.0,>=0.9.6 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (1.1.3)
Requirement already satisfied: cymem<2.1.0,>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (2.0.6)
Requirement already satisfied: catalogue<1.1.0,>=0.0.7 in /usr/local/lib/python3.7/dist-packages (from spacy>=2.2.2->es_core_news_md==2.2.5) (1.0.0)
Requirement already satisfied: importlib-metadata>=0.20 in /usr/local/lib/python3.7/dist-packages (from catalogue<1.1.0,>=0.0.7->spacy>=2.2.2->es_core_news_md==2.2.5) (4.11.3)
Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=0.20->catalogue<1.1.0,>=0.0.7->spacy>=2.2.2->es_core_news_md==2.2.5) (3.8.0)
Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=0.20->catalogue<1.1.0,>=0.0.7->spacy>=2.2.2->es_core_news_md==2.2.5) (4.2.0)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy>=2.2.2->es_core_news_md==2.2.5) (2021.10.8)
Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy>=2.2.2->es_core_news_md==2.2.5) (1.24.3)
Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy>=2.2.2->es_core_news_md==2.2.5) (3.0.4)
Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests<3.0.0,>=2.13.0->spacy>=2.2.2->es_core_news_md==2.2.5) (2.10)
✔ Download and installation successful
You can now load the model via spacy.load('es_core_news_md')
import es_core_news_md
nlp = es_core_news_md.load()
texto= ('Este es un tutorial acerca de Procesamiento de lenguaje usando Python con spaCy')
doc = nlp(texto)
#tokenizar
print([token.text for token in doc])
['Este', 'es', 'un', 'tutorial', 'acerca', 'de', 'Procesamiento', 'de', 'lenguaje', 'usando', 'Python', 'con', 'spaCy']
text=('Gus es un desarrollador en Python actualmente trabajando para una compañia Fintech en Londres Inglaterra. Se encuentra interesado en aprender NLP.')
t=nlp(text)
oraciones= list(t.sents)
print(len(oraciones))
for x in oraciones:
print(x)
2 Gus es un desarrollador en Python actualmente trabajando para una compañia Fintech en Londres Inglaterra. Se encuentra interesado en aprender NLP.
for token in t:
print(token, token.idx)
Gus 0 es 4 un 7 desarrollador 10 en 24 Python 27 actualmente 34 trabajando 46 para 57 una 62 compañia 66 Fintech 75 en 83 Londres 86 Inglaterra 94 . 104 Se 106 encuentra 109 interesado 119 en 130 aprender 133 NLP 142 . 145
for token in t:
print(token, token.idx, token.text_with_ws,
token.is_alpha, token.is_punct, token.is_space,
token.shape_, token.is_stop)
Gus 0 Gus True False False Xxx False es 4 es True False False xx True un 7 un True False False xx True desarrollador 10 desarrollador True False False xxxx False en 24 en True False False xx True Python 27 Python True False False Xxxxx False actualmente 34 actualmente True False False xxxx True trabajando 46 trabajando True False False xxxx False para 57 para True False False xxxx True una 62 una True False False xxx True compañia 66 compañia True False False xxxx False Fintech 75 Fintech True False False Xxxxx False en 83 en True False False xx True Londres 86 Londres True False False Xxxxx False Inglaterra 94 Inglaterra True False False Xxxxx False . 104 . False True False . False Se 106 Se True False False Xx True encuentra 109 encuentra True False False xxxx True interesado 119 interesado True False False xxxx False en 130 en True False False xx True aprender 133 aprender True False False xxxx False NLP 142 NLP True False False XXX False . 145 . False True False . False
En este ejemplo tenemos:
import spacy
spacy_stopwords = spacy.lang.es.stop_words.STOP_WORDS
print(len(spacy_stopwords))
for stop_word in list(spacy_stopwords)[:10]:
print(stop_word)
551 sea siguiente informo tampoco dan estuvo enseguida habia ninguno ocho
for token in t:
if not token.is_stop:
print(token)
Gus desarrollador Python trabajando compañia Fintech Londres Inglaterra . interesado aprender NLP .
# Creacion adicional de stopwrods
documento_sin_stopword = [token for token in t if not token.is_stop]
print(documento_sin_stopword)
[Gus, desarrollador, Python, trabajando, compañia, Fintech, Londres, Inglaterra, ., interesado, aprender, NLP, .]
for token in t:
print(token, '-', token.lemma_)
Gus - Gus es - ser un - uno desarrollador - desarrollador en - en Python - Python actualmente - actualmente trabajando - trabajar para - parir una - uno compañia - compañia Fintech - Fintech en - en Londres - Londres Inglaterra - Inglaterra . - . Se - Se encuentra - encontrar interesado - interesar en - en aprender - aprender NLP - NLP . - .
texto= '''
La FIFA responde así a una denuncia interpuesta por la Federación de Chile ante esa Comisión Disciplinaria, en la que presentaba alegaciones sobre la posible falsificación de los documentos que conceden la nacionalidad ecuatoriana Byron Castillo.
La selección de Ecuador se clasificó de forma directa para el Mundial, junto con las de Brasil, Argentina y Uruguay, al contrario que las de Chile y Perú. El combinado peruano, que terminó quinto por detrás del ecuatoriano, disputará una repesca.
El defensa fue alineado por el seleccionador ecuatoriano Gustavo Alfaro para los dos partidos contra Paraguay y Chile y en una ocasión ante Uruguay, Bolivia, Venezuela y Argentina, partidos clave para que el equipo lograse uno de los cupos directos para el Mundial.
"Innumerables pruebas de que nació en Colombia"
La Federación de Chile denunció el pasado día 5 que hay "innumerables pruebas de que el jugador nació en Colombia".
"Las investigaciones realizadas en Ecuador, entre ellas, un informe jurídico de la Dirección Nacional de Registro Civil, declararon la existencia de inconsistencias en el certificado de nacimiento presentado por el jugador", afirmó este organismo, que acusó a la Federación Ecuatoriana de tener "total conocimiento" de las irregularidades.
Una posible sanción de la FIFA podría implicar la resta de puntos a Ecuador por los partidos que Castillo jugó, lo que alteraría la nómina de clasificados.
Un informe técnico jurídico de la dirección nacional del registro civil de Ecuador afirma que la inscripción de nacimiento de Byron Castillo en la ciudad ecuatoriana de Guayas no consta en el tomo, la página y el acta solicitado, según un documento oficial.
'''
type(texto)
str
import re
texto1 = re.sub('\n', '', texto) #remover saltos de linea
print(type(texto))
str(texto1)
<class 'str'>
'La FIFA responde así a una denuncia interpuesta por la Federación de Chile ante esa Comisión Disciplinaria, en la que presentaba alegaciones sobre la posible falsificación de los documentos que conceden la nacionalidad ecuatoriana Byron Castillo.La selección de Ecuador se clasificó de forma directa para el Mundial, junto con las de Brasil, Argentina y Uruguay, al contrario que las de Chile y Perú. El combinado peruano, que terminó quinto por detrás del ecuatoriano, disputará una repesca.El defensa fue alineado por el seleccionador ecuatoriano Gustavo Alfaro para los dos partidos contra Paraguay y Chile y en una ocasión ante Uruguay, Bolivia, Venezuela y Argentina, partidos clave para que el equipo lograse uno de los cupos directos para el Mundial."Innumerables pruebas de que nació en Colombia"La Federación de Chile denunció el pasado día 5 que hay "innumerables pruebas de que el jugador nació en Colombia"."Las investigaciones realizadas en Ecuador, entre ellas, un informe jurídico de la Dirección Nacional de Registro Civil, declararon la existencia de inconsistencias en el certificado de nacimiento presentado por el jugador", afirmó este organismo, que acusó a la Federación Ecuatoriana de tener "total conocimiento" de las irregularidades.Una posible sanción de la FIFA podría implicar la resta de puntos a Ecuador por los partidos que Castillo jugó, lo que alteraría la nómina de clasificados.Un informe técnico jurídico de la dirección nacional del registro civil de Ecuador afirma que la inscripción de nacimiento de Byron Castillo en la ciudad ecuatoriana de Guayas no consta en el tomo, la página y el acta solicitado, según un documento oficial.'
doc= nlp(texto1)
# Remover stopwrods
words= [token.text for token in doc if not token.is_stop and not token.is_punct]
from collections import Counter
word_freq= Counter(words)
# Sacar las 5 mas frecuentes y sus frecuencias
common_words= word_freq.most_common(5)
print(common_words)
unique_words = [word for (word, freq) in word_freq.items() if freq == 1]
print(unique_words)
[('y', 6), ('Chile', 4), ('Ecuador', 4), ('a', 3), ('Federación', 3)] ['responde', 'denuncia', 'interpuesta', 'Comisión', 'Disciplinaria', 'presentaba', 'alegaciones', 'falsificación', 'documentos', 'conceden', 'nacionalidad', 'selección', 'clasificó', 'forma', 'directa', 'Brasil', 'contrario', 'Perú', 'combinado', 'peruano', 'terminó', 'quinto', 'disputará', 'repesca', 'defensa', 'alineado', 'seleccionador', 'Gustavo', 'Alfaro', 'Paraguay', 'ocasión', 'Bolivia', 'Venezuela', 'clave', 'equipo', 'lograse', 'cupos', 'directos', '"Innumerables', 'Colombia"La', 'denunció', '5', 'innumerables', 'Colombia"', '"Las', 'investigaciones', 'realizadas', 'Dirección', 'Nacional', 'Registro', 'Civil', 'declararon', 'existencia', 'inconsistencias', 'certificado', 'presentado', 'organismo', 'acusó', 'Ecuatoriana', 'conocimiento', 'irregularidades', 'sanción', 'implicar', 'resta', 'puntos', 'jugó', 'alteraría', 'nómina', 'clasificados', 'técnico', 'dirección', 'nacional', 'registro', 'civil', 'afirma', 'inscripción', 'ciudad', 'Guayas', 'consta', 'tomo', 'página', 'acta', 'solicitado', 'documento', 'oficial']
for token in doc:
print(token,' -', token.tag_, ' -', token.pos_,' -' ,spacy.explain(token.tag_))
La - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None FIFA - PROPN___ - PROPN - None responde - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Pres|VerbForm=Fin - VERB - None así - ADV___ - ADV - None a - ADP__AdpType=Prep - ADP - None una - DET__Definite=Ind|Gender=Fem|Number=Sing|PronType=Art - DET - None denuncia - NOUN__Gender=Fem|Number=Sing - NOUN - None interpuesta - ADJ__Gender=Fem|Number=Sing|VerbForm=Part - ADJ - None por - ADP__AdpType=Prep - ADP - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None Federación - PROPN___ - PROPN - None de - ADP__AdpType=Prep - ADP - None Chile - PROPN___ - PROPN - None ante - ADP__AdpType=Prep - ADP - None esa - DET__Gender=Fem|Number=Sing|PronType=Dem - DET - None Comisión - PROPN___ - PROPN - None Disciplinaria - PROPN___ - PROPN - None , - PUNCT__PunctType=Comm - PUNCT - None en - ADP__AdpType=Prep - ADP - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None que - PRON__PronType=Rel - PRON - None presentaba - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Imp|VerbForm=Fin - VERB - None alegaciones - NOUN__Gender=Fem|Number=Plur - NOUN - None sobre - ADP__AdpType=Prep - ADP - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None posible - ADJ__Number=Sing - ADJ - None falsificación - NOUN__Gender=Fem|Number=Sing - NOUN - None de - ADP__AdpType=Prep - ADP - None los - DET__Definite=Def|Gender=Masc|Number=Plur|PronType=Art - DET - None documentos - NOUN__Gender=Masc|Number=Plur - NOUN - None que - PRON__PronType=Rel - PRON - None conceden - VERB__Mood=Ind|Number=Plur|Person=3|Tense=Pres|VerbForm=Fin - VERB - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None nacionalidad - NOUN__Gender=Fem|Number=Sing - NOUN - None ecuatoriana - ADJ__Gender=Fem|Number=Sing - ADJ - None Byron - PROPN___ - PROPN - None Castillo - PROPN___ - PROPN - None . - PUNCT__PunctType=Peri - PUNCT - None La - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None selección - NOUN__Gender=Fem|Number=Sing - NOUN - None de - ADP__AdpType=Prep - ADP - None Ecuador - PROPN___ - PROPN - None se - PRON__Person=3 - PRON - None clasificó - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin - VERB - None de - ADP__AdpType=Prep - ADP - None forma - NOUN__Gender=Fem|Number=Sing - NOUN - None directa - ADJ__Gender=Fem|Number=Sing - ADJ - None para - ADP__AdpType=Prep - ADP - None el - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None Mundial - PROPN___ - PROPN - None , - PUNCT__PunctType=Comm - PUNCT - None junto - ADJ__AdpType=Prep - ADJ - None con - ADP__AdpType=Prep - ADP - None las - DET__Definite=Def|Gender=Fem|Number=Plur|PronType=Art - DET - None de - ADP__AdpType=Prep - ADP - None Brasil - PROPN___ - PROPN - None , - PUNCT__PunctType=Comm - PUNCT - None Argentina - PROPN___ - PROPN - None y - CCONJ___ - CONJ - None Uruguay - PROPN___ - PROPN - None , - PUNCT__PunctType=Comm - PUNCT - None al - ADP__AdpType=Preppron|Gender=Masc|Number=Sing - ADP - None contrario - NOUN___ - NOUN - None que - SCONJ___ - SCONJ - None las - DET__Definite=Def|Gender=Fem|Number=Plur|PronType=Art - DET - None de - ADP__AdpType=Prep - ADP - None Chile - PROPN___ - PROPN - None y - CCONJ___ - CONJ - None Perú - PROPN___ - PROPN - None . - PUNCT__PunctType=Peri - PUNCT - None El - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None combinado - NOUN__Gender=Masc|Number=Sing - NOUN - None peruano - ADJ__Gender=Masc|Number=Sing - ADJ - None , - PUNCT__PunctType=Comm - PUNCT - None que - PRON__PronType=Rel - PRON - None terminó - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin - VERB - None quinto - ADJ__Gender=Masc|Number=Sing|NumType=Ord - ADJ - None por - ADP__AdpType=Prep - ADP - None detrás - ADV__AdpType=Preppron|Gender=Masc|Number=Sing - ADV - None del - ADP__AdpType=Preppron|Gender=Masc|Number=Sing - ADP - None ecuatoriano - NOUN__Gender=Masc|Number=Sing - NOUN - None , - PUNCT__PunctType=Comm - PUNCT - None disputará - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Fut|VerbForm=Fin - VERB - None una - DET__Definite=Ind|Gender=Fem|Number=Sing|PronType=Art - DET - None repesca - NOUN__Gender=Fem|Number=Sing - NOUN - None . - PUNCT__PunctType=Peri - PUNCT - None El - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None defensa - NOUN__Number=Sing - NOUN - None fue - AUX__Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin - AUX - None alineado - VERB__Gender=Masc|Number=Sing|Tense=Past|VerbForm=Part - VERB - None por - ADP__AdpType=Prep - ADP - None el - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None seleccionador - NOUN__Gender=Masc|Number=Sing - NOUN - None ecuatoriano - ADJ__Gender=Masc|Number=Sing - ADJ - None Gustavo - PROPN___ - PROPN - None Alfaro - PROPN___ - PROPN - None para - ADP__AdpType=Prep - ADP - None los - DET__Definite=Def|Gender=Masc|Number=Plur|PronType=Art - DET - None dos - NUM__Number=Plur|NumType=Card - NUM - None partidos - NOUN__Gender=Masc|Number=Plur - NOUN - None contra - ADP__AdpType=Prep - ADP - None Paraguay - PROPN___ - PROPN - None y - CCONJ___ - CONJ - None Chile - PROPN___ - PROPN - None y - CCONJ___ - CONJ - None en - ADP__AdpType=Prep - ADP - None una - DET__Definite=Ind|Gender=Fem|Number=Sing|PronType=Art - DET - None ocasión - NOUN__Gender=Fem|Number=Sing - NOUN - None ante - ADP__AdpType=Prep - ADP - None Uruguay - PROPN___ - PROPN - None , - PUNCT__PunctType=Comm - PUNCT - None Bolivia - PROPN___ - PROPN - None , - PUNCT__PunctType=Comm - PUNCT - None Venezuela - PROPN___ - PROPN - None y - CCONJ___ - CONJ - None Argentina - PROPN___ - PROPN - None , - PUNCT__PunctType=Comm - PUNCT - None partidos - NOUN__Gender=Masc|Number=Plur - NOUN - None clave - NOUN__Gender=Fem|Number=Sing - NOUN - None para - ADP__AdpType=Prep - ADP - None que - SCONJ___ - SCONJ - None el - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None equipo - NOUN__Gender=Masc|Number=Sing - NOUN - None lograse - VERB__Mood=Sub|Number=Sing|Person=3|Tense=Imp|VerbForm=Fin - VERB - None uno - PRON__Gender=Masc|Number=Sing|PronType=Ind - PRON - None de - ADP__AdpType=Prep - ADP - None los - DET__Definite=Def|Gender=Masc|Number=Plur|PronType=Art - DET - None cupos - NOUN__Gender=Masc|Number=Plur - NOUN - None directos - ADJ__Gender=Masc|Number=Plur - ADJ - None para - ADP__AdpType=Prep - ADP - None el - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None Mundial - PROPN___ - PROPN - None . - PUNCT__PunctType=Peri - PUNCT - None "Innumerables - ADJ__Number=Plur - ADJ - None pruebas - NOUN__Gender=Fem|Number=Plur - NOUN - None de - ADP__AdpType=Prep - ADP - None que - SCONJ___ - SCONJ - None nació - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin - VERB - None en - ADP__AdpType=Prep - ADP - None Colombia"La - PROPN___ - PROPN - None Federación - PROPN___ - PROPN - None de - ADP__AdpType=Prep - ADP - None Chile - PROPN___ - PROPN - None denunció - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin - VERB - None el - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None pasado - ADJ__Gender=Masc|Number=Sing|VerbForm=Part - ADJ - None día - NOUN___ - NOUN - None 5 - NUM__NumForm=Digit|NumType=Card - NUM - None que - PRON__PronType=Rel - PRON - None hay - AUX__Mood=Ind|Number=Sing|Person=3|Tense=Pres|VerbForm=Fin - AUX - None " - PUNCT__PunctType=Quot - PUNCT - None innumerables - ADJ__Number=Plur - ADJ - None pruebas - NOUN__Gender=Fem|Number=Plur - NOUN - None de - ADP__AdpType=Prep - ADP - None que - SCONJ___ - SCONJ - None el - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None jugador - NOUN__Gender=Masc|Number=Sing - NOUN - None nació - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin - VERB - None en - ADP__AdpType=Prep - ADP - None Colombia" - PROPN___ - PROPN - None . - PUNCT__PunctType=Peri - PUNCT - None "Las - NUM__NumForm=Digit - NUM - None investigaciones - NOUN__Gender=Fem|Number=Plur - NOUN - None realizadas - ADJ__Gender=Fem|Number=Plur|VerbForm=Part - ADJ - None en - ADP__AdpType=Prep - ADP - None Ecuador - PROPN___ - PROPN - None , - PUNCT__PunctType=Comm - PUNCT - None entre - ADP__AdpType=Prep - ADP - None ellas - PRON__Gender=Fem|Number=Plur|Person=3|PronType=Prs - PRON - None , - PUNCT__PunctType=Comm - PUNCT - None un - DET__Definite=Ind|Gender=Masc|Number=Sing|PronType=Art - DET - None informe - NOUN__Gender=Masc|Number=Sing - NOUN - None jurídico - ADJ__Gender=Masc|Number=Sing - ADJ - None de - ADP__AdpType=Prep - ADP - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None Dirección - PROPN___ - PROPN - None Nacional - PROPN___ - PROPN - None de - ADP__AdpType=Prep - ADP - None Registro - PROPN___ - PROPN - None Civil - PROPN___ - PROPN - None , - PUNCT__PunctType=Comm - PUNCT - None declararon - VERB__Mood=Ind|Number=Plur|Person=3|Tense=Past|VerbForm=Fin - VERB - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None existencia - NOUN__Gender=Fem|Number=Sing - NOUN - None de - ADP__AdpType=Prep - ADP - None inconsistencias - NOUN__Gender=Fem|Number=Plur - NOUN - None en - ADP__AdpType=Prep - ADP - None el - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None certificado - NOUN__Gender=Masc|Number=Sing - NOUN - None de - ADP__AdpType=Prep - ADP - None nacimiento - NOUN__Gender=Masc|Number=Sing - NOUN - None presentado - ADJ__Gender=Masc|Number=Sing|VerbForm=Part - ADJ - None por - ADP__AdpType=Prep - ADP - None el - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None jugador - NOUN__Gender=Masc|Number=Sing - NOUN - None " - PUNCT__PunctType=Quot - PUNCT - None , - PUNCT__PunctType=Comm - PUNCT - None afirmó - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin - VERB - None este - DET__Gender=Masc|Number=Sing|PronType=Dem - DET - None organismo - NOUN__Gender=Masc|Number=Sing - NOUN - None , - PUNCT__PunctType=Comm - PUNCT - None que - PRON__PronType=Rel - PRON - None acusó - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin - VERB - None a - ADP__AdpType=Prep - ADP - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None Federación - PROPN___ - PROPN - None Ecuatoriana - PROPN___ - PROPN - None de - ADP__AdpType=Prep - ADP - None tener - VERB__VerbForm=Inf - VERB - None " - PUNCT__PunctType=Quot - PUNCT - None total - ADJ__Number=Sing - ADJ - None conocimiento - NOUN__Gender=Masc|Number=Sing - NOUN - None " - PUNCT__PunctType=Quot - PUNCT - None de - ADP__AdpType=Prep - ADP - None las - DET__Definite=Def|Gender=Fem|Number=Plur|PronType=Art - DET - None irregularidades - NOUN__Gender=Fem|Number=Plur - NOUN - None . - PUNCT__PunctType=Peri - PUNCT - None Una - DET__Definite=Ind|Gender=Fem|Number=Sing|PronType=Art - DET - None posible - ADJ__Number=Sing - ADJ - None sanción - NOUN__Gender=Fem|Number=Sing - NOUN - None de - ADP__AdpType=Prep - ADP - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None FIFA - PROPN___ - PROPN - None podría - AUX__Mood=Cnd|Number=Sing|Person=3|VerbForm=Fin - AUX - None implicar - VERB__VerbForm=Inf - VERB - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None resta - NOUN__Gender=Fem|Number=Sing - NOUN - None de - ADP__AdpType=Prep - ADP - None puntos - NOUN__Gender=Masc|Number=Plur - NOUN - None a - ADP__AdpType=Prep - ADP - None Ecuador - PROPN___ - PROPN - None por - ADP__AdpType=Prep - ADP - None los - DET__Definite=Def|Gender=Masc|Number=Plur|PronType=Art - DET - None partidos - NOUN__Gender=Masc|Number=Plur - NOUN - None que - PRON__PronType=Rel - PRON - None Castillo - PROPN___ - PROPN - None jugó - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Past|VerbForm=Fin - VERB - None , - PUNCT__PunctType=Comm - PUNCT - None lo - DET__Definite=Def|Number=Sing|PronType=Art - DET - None que - PRON__PronType=Rel - PRON - None alteraría - VERB__Mood=Cnd|Number=Sing|Person=3|VerbForm=Fin - VERB - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None nómina - NOUN__Gender=Fem|Number=Sing - NOUN - None de - ADP__AdpType=Prep - ADP - None clasificados - NOUN__Gender=Masc|Number=Plur - NOUN - None . - PUNCT__PunctType=Peri - PUNCT - None Un - DET__Definite=Ind|Gender=Masc|Number=Sing|PronType=Art - DET - None informe - NOUN__Gender=Masc|Number=Sing - NOUN - None técnico - ADJ__Gender=Masc|Number=Sing - ADJ - None jurídico - ADJ__Gender=Masc|Number=Sing - ADJ - None de - ADP__AdpType=Prep - ADP - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None dirección - PROPN___ - PROPN - None nacional - PROPN___ - PROPN - None del - ADP__AdpType=Preppron|Gender=Masc|Number=Sing - ADP - None registro - PROPN___ - PROPN - None civil - ADJ__Number=Sing - ADJ - None de - ADP__AdpType=Prep - ADP - None Ecuador - PROPN___ - PROPN - None afirma - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Pres|VerbForm=Fin - VERB - None que - SCONJ___ - SCONJ - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None inscripción - NOUN__Gender=Fem|Number=Sing - NOUN - None de - ADP__AdpType=Prep - ADP - None nacimiento - NOUN__Gender=Masc|Number=Sing - NOUN - None de - ADP__AdpType=Prep - ADP - None Byron - PROPN___ - PROPN - None Castillo - PROPN___ - PROPN - None en - ADP__AdpType=Prep - ADP - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None ciudad - NOUN__Gender=Fem|Number=Sing - NOUN - None ecuatoriana - ADJ__Gender=Fem|Number=Sing - ADJ - None de - ADP__AdpType=Prep - ADP - None Guayas - PROPN___ - PROPN - None no - ADV__Polarity=Neg - ADV - None consta - VERB__Mood=Ind|Number=Sing|Person=3|Tense=Pres|VerbForm=Fin - VERB - None en - ADP__AdpType=Prep - ADP - None el - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None tomo - NOUN__Gender=Masc|Number=Sing - NOUN - None , - PUNCT__PunctType=Comm - PUNCT - None la - DET__Definite=Def|Gender=Fem|Number=Sing|PronType=Art - DET - None página - NOUN__Gender=Fem|Number=Sing - NOUN - None y - CCONJ___ - CONJ - None el - DET__Definite=Def|Gender=Masc|Number=Sing|PronType=Art - DET - None acta - NOUN__Gender=Masc|Number=Sing - NOUN - None solicitado - ADJ__Gender=Masc|Number=Sing|VerbForm=Part - ADJ - None , - PUNCT__PunctType=Comm - PUNCT - None según - ADP__AdpType=Prep - ADP - None un - DET__Definite=Ind|Gender=Masc|Number=Sing|PronType=Art - DET - None documento - NOUN__Gender=Masc|Number=Sing - NOUN - None oficial - ADJ__Number=Sing - ADJ - None . - PUNCT__PunctType=Peri - PUNCT - None
nouns=[]
adjectives=[]
for token in doc:
if token.pos_ =='NOUN':
nouns.append(token)
if token.pos_ =='ADJ':
adjectives.append(token)
print(nouns)
print(adjectives)
[denuncia, alegaciones, falsificación, documentos, nacionalidad, selección, forma, contrario, combinado, ecuatoriano, repesca, defensa, seleccionador, partidos, ocasión, partidos, clave, equipo, cupos, pruebas, día, pruebas, jugador, investigaciones, informe, existencia, inconsistencias, certificado, nacimiento, jugador, organismo, conocimiento, irregularidades, sanción, resta, puntos, partidos, nómina, clasificados, informe, inscripción, nacimiento, ciudad, tomo, página, acta, documento] [interpuesta, posible, ecuatoriana, directa, junto, peruano, quinto, ecuatoriano, directos, "Innumerables, pasado, innumerables, realizadas, jurídico, presentado, total, posible, técnico, jurídico, civil, ecuatoriana, solicitado, oficial]
from spacy import displacy
texto = ('el se encuentra interesado en aprender Procesamiento de Lenguaje Natural')
t = nlp(texto)
displacy.render(t, style='dep',jupyter=True)
import os
from collections import Counter
import matplotlib.pyplot as plt
import nltk
import numpy as np
import pandas as pd
from pylab import rcParams
from wordcloud import WordCloud
from nltk import word_tokenize
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.feature_extraction.text import TfidfVectorizer
rcParams['figure.figsize'] = 30, 60
%matplotlib inline
Después de trabajar en este caso, debería poder aplicar el análisis de sentimientos a los problemas comerciales. Comprenderá cómo utilizar técnicas de clasificación de texto para crear un modelo de análisis de opiniones y podrá aplicar modelos de opiniones a datos del mundo real.
Habrá adquirido experiencia en el uso de varias técnicas diferentes de vectorización y creación de modelos, utilizando bibliotecas populares de Python como scikit-learn y gensim. Habrá utilizado tanto vectorizaciones basadas en conteo como incrustaciones de palabras, y comprenderá los pros y los contras de cada una.
Contexto empresarial. Eres un científico de datos para una gran empresa de comercio electrónico. Tienes decenas de miles de clientes que escriben reseñas sobre productos cada día. Cada revisión contiene comentarios textuales junto con un sistema de calificación de 1 a 5 estrellas (siendo 1 la menos satisfecha y 5 la más satisfecha).
También tiene un equipo de atención al cliente que interactúa con los clientes a través de servicios de llamadas y mensajes. Su empresa también recopila comentarios sobre las experiencias de sus clientes con la interacción del sitio web después de cada compra. Ni este comentario ni el servicio de mensajería tienen un número de calificación. La empresa quiere cuantificar la satisfacción del cliente proveniente de estas interacciones no calificadas para ayudar con futuras decisiones comerciales (por ejemplo, determinar qué tan bien se están desempeñando sus diversos agentes de servicio al cliente).
Problema de negocio Su tarea es construir modelos que puedan identificar el sentimiento (positivo o negativo) de cada una de estas interacciones no calificadas.
Contexto analitico Los datos son un conjunto de reseñas en formato de archivo CSV. Combinaremos lo que aprendimos sobre el procesamiento de texto y los modelos de clasificación para desarrollar algoritmos capaces de clasificar las interacciones por sentimiento.
El caso está estructurado de la siguiente manera: 1) leerá y analizará los datos del texto de entrada y las variables de respuesta correspondientes (calificaciones); 2) realizara un preprocesamiento básico para preparar los datos para el modelado; 3) aprendera y aplicara varias formas de caracterizar el texto de reseñas; y finalmente 4) construira modelos de aprendizaje automático para clasificar el texto como mostrando un sentimiento positivo o negativo (1 o 0).
from google.colab import drive
import os
drive.mount('/content/gdrive')
# Establecer ruta de acceso en dr
import os
print(os.getcwd())
os.chdir("/content/gdrive/My Drive")
Mounted at /content/gdrive /content
import pandas as pd
amazon_reviews = pd.read_csv('Reviews.csv')
# Seleccionando solo los primeros 10,000 registros para calculo mas rapido
#amazon_reviews = amazon_reviews[:10000]
amazon_reviews.head()
Id | ProductId | UserId | ProfileName | HelpfulnessNumerator | HelpfulnessDenominator | Score | Time | Summary | Text | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | B001E4KFG0 | A3SGXH7AUHU8GW | delmartian | 1 | 1 | 5 | 1303862400 | Good Quality Dog Food | I have bought several of the Vitality canned d... |
1 | 2 | B00813GRG4 | A1D87F6ZCVE5NK | dll pa | 0 | 0 | 1 | 1346976000 | Not as Advertised | Product arrived labeled as Jumbo Salted Peanut... |
2 | 3 | B000LQOCH0 | ABXLMWJIXXAIN | Natalia Corres "Natalia Corres" | 1 | 1 | 4 | 1219017600 | "Delight" says it all | This is a confection that has been around a fe... |
3 | 4 | B000UA0QIQ | A395BORC6FGVXV | Karl | 3 | 3 | 2 | 1307923200 | Cough Medicine | If you are looking for the secret ingredient i... |
4 | 5 | B006K2ZZ7K | A1UQRSCLF8GW1T | Michael D. Bigham "M. Wassir" | 0 | 0 | 5 | 1350777600 | Great taffy | Great taffy at a great price. There was a wid... |
amazon_reviews.shape
(568454, 10)
Columnas del dataset:
Miremos la distribucion del numero de palabras por review
words_per_review = amazon_reviews.Text.apply(lambda x: len(x.split(" ")))
words_per_review.hist(bins = 100)
plt.title('Numero de palabras por revision')
plt.xlabel('Palabras')
plt.ylabel('Frecuencia')
Text(0, 0.5, 'Frecuencia')
words_per_review.mean()
82.00552199474363
Distrobucion de los ratings
amazon_reviews.Score.value_counts()
5 363122 4 80655 1 52268 3 42640 2 29769 Name: Score, dtype: int64
percent_val = 100 * amazon_reviews.Score.value_counts()/amazon_reviews.shape[0]
percent_val
5 63.878871 4 14.188483 1 9.194763 3 7.501047 2 5.236835 Name: Score, dtype: float64
percent_val.plot.bar()
plt.title('Revisiones por scores')
plt.xlabel('Score')
plt.ylabel('Porcentaje (%)')
Text(0, 0.5, 'Porcentaje (%)')
la distribucion es asimetrica, con un gran valor para los ratings de 5s y pocos par 3,2 y 1
word_cloud_text = ''.join(amazon_reviews.Text)
print(len(word_cloud_text))
wordcloud = WordCloud(
max_font_size=100,
max_words=100,
background_color="white",
scale=10,
width=800,
height=400
).generate(word_cloud_text)
plt.figure(figsize=(12,6))
plt.imshow(wordcloud, interpolation="bilinear")
plt.axis("off")
plt.show()
247972188
El wordcloud muestra que hat muchos reviews que hablan sobre temas de comida (cafe, sabores, sabor, bebidas), tambien se observan otras palabras como bueno, amor y el mejor
Para propositos del analisis de sentimiento convertiremos todos los ratings en valores binarios con las siguientes reglas:
amazon_reviews['Sentiment_rating'] = np.where(amazon_reviews.Score > 3, 1, 0)
amazon_reviews['Sentiment_rating'].value_counts()
1 443777 0 124677 Name: Sentiment_rating, dtype: int64
# removiendo neutrales
amazon_reviews = amazon_reviews[amazon_reviews.Score != 3]
#rcParams['figure.figsize'] = 8, 5
amazon_reviews.Sentiment_rating.value_counts().plot.bar()
plt.title('Scores luego de estandarizacion')
plt.xlabel('Score')
plt.ylabel('Frecuencia')
Text(0, 0.5, 'Frecuencia')
Recordemos que el preprocesamiento de texto y la normalizacion es crucial antes de desarrollar un modelo de NLP, algunos pasos importantes son:
Procedamos con la primera fase
amazon_reviews['reviews_text_new'] = amazon_reviews.Text.apply(lambda x: x.lower())
Las siguientes fases:
from nltk import word_tokenize
import nltk
nltk.download('punkt')
token_lists = [word_tokenize(each) for each in amazon_reviews.Text]
tokens = [item for sublist in token_lists for item in sublist]
print("Numero de tokens unicos antes: ", len(set(tokens)))
token_lists_lower = [word_tokenize(each) for each in amazon_reviews.reviews_text_new]
tokens_lower = [item for sublist in token_lists_lower for item in sublist]
print("Numero de tokens unicos nuevos: ", len(set(tokens_lower)))
[nltk_data] Downloading package punkt to /root/nltk_data... [nltk_data] Unzipping tokenizers/punkt.zip. Numero de tokens unicos antes: 27899 Numero de tokens unicos nuevos: 22865
(22865-27899)/27899
-0.18043657478762679
El numero de tokens han bajado en cerca del 18% con la normalizacion
¿Es la eliminación de caracteres especiales incluso una buena idea? ¿Cuáles son algunos ejemplos de caracteres que probablemente sería seguro eliminar y cuáles no?
Eliminar caracteres especiales es una decisión subjetiva, especialmente en casos como este. Las personas a menudo usan caracteres especiales para expresar sus emociones y pueden dejar una reseña como "¡¡¡Este producto es el peor!!!", mientras que una reseña positiva podría ser "Este producto es el mejor". ¡Me encantó!' Aquí, la presencia de signos de exclamación indica claramente algo sobre el sentimiento subyacente, por lo que eliminarlos puede no ser una buena idea.
Por otro lado, eliminar la puntuación sin carga emocional, como las comas, los puntos y el punto y coma, probablemente sea seguro.
En aras de la simplicidad, procederemos eliminando todos los caracteres especiales; sin embargo, vale la pena tener en cuenta que esto es algo para revisar dependiendo de los resultados que obtengamos más adelante. Lo siguiente da una lista de todos los caracteres especiales en nuestro conjunto de datos:
# Seleccionando los caracteres no alfa numericos que no son espacios
special_chars = amazon_reviews.reviews_text_new.apply(lambda x: [each for each in list(x) if not each.isalnum() and each != ' '])
# obtener una lista de listas
flat_list = [item for sublist in special_chars for item in sublist]
# caracteres especiales unicos
print(set(flat_list))
{')', '`', '§', '^', '~', "'", '[', ';', '.', '"', '<', '{', '(', '#', '}', '®', '&', '@', '-', ':', '>', '=', '_', '!', '%', ',', '?', ']', '+', '$', '*', '/'}
Ahora removamos los caracteres especiales de los reviews
import re
review_backup = amazon_reviews.reviews_text_new.copy()
amazon_reviews.reviews_text_new = amazon_reviews.reviews_text_new.apply(
lambda x: re.sub('[^A-Za-z0-9 ]+', ' ', x)
)
Miremos como se ven algunos de los resultados luego de remover esto
print("Review anterior:")
review_backup.values[6]
Review anterior:
"this saltwater taffy had great flavors and was very soft and chewy. each candy was individually wrapped well. none of the candies were stuck together, which did happen in the expensive version, fralinger's. would highly recommend this candy! i served it at a beach-themed party and everyone loved it!"
print("Review nuevo:")
amazon_reviews.reviews_text_new[6]
Review nuevo:
'this saltwater taffy had great flavors and was very soft and chewy each candy was individually wrapped well none of the candies were stuck together which did happen in the expensive version fralinger s would highly recommend this candy i served it at a beach themed party and everyone loved it '
El numero de tokens unicos que se han borrado son
token_lists = [word_tokenize(each) for each in amazon_reviews.Text]
tokens = [item for sublist in token_lists for item in sublist]
print("Numero de token unicos antes: ", len(set(tokens)))
token_lists = [word_tokenize(each) for each in amazon_reviews.reviews_text_new]
tokens = [item for sublist in token_lists for item in sublist]
print("Numero de tokens unicos despues: ", len(set(tokens)))
Numero de token unicos antes: 27899 Numero de tokens unicos despues: 18039
Vamos a remover estas palabras
import nltk
nltk.download('stopwords')
noise_words = []
stopwords_corpus = nltk.corpus.stopwords
eng_stop_words = stopwords_corpus.words('english')
noise_words.extend(eng_stop_words)
print(len(noise_words))
noise_words
[nltk_data] Downloading package stopwords to /root/nltk_data... [nltk_data] Unzipping corpora/stopwords.zip. 179
['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', "you're", "you've", "you'll", "you'd", 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', "she's", 'her', 'hers', 'herself', 'it', "it's", 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', "that'll", 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', "don't", 'should', "should've", 'now', 'd', 'll', 'm', 'o', 're', 've', 'y', 'ain', 'aren', "aren't", 'couldn', "couldn't", 'didn', "didn't", 'doesn', "doesn't", 'hadn', "hadn't", 'hasn', "hasn't", 'haven', "haven't", 'isn', "isn't", 'ma', 'mightn', "mightn't", 'mustn', "mustn't", 'needn', "needn't", 'shan', "shan't", 'shouldn', "shouldn't", 'wasn', "wasn't", 'weren', "weren't", 'won', "won't", 'wouldn', "wouldn't"]
Encontremos las palabras de alta y baja frecuencia, que definiremos como el 1 % de las palabras que aparecen con más frecuencia en las reseñas, así como el 1 % de las palabras que aparecen con menos frecuencia en las reseñas (después de ajustar por mayúsculas y minúsculas y caracteres especiales).
one_percentile = int(len(set(tokens)) * 0.01)
top_1_percentile = Counter(tokens).most_common(one_percentile)
top_1_percentile[:10]
[('the', 28122), ('i', 25705), ('and', 19980), ('a', 18505), ('it', 16143), ('to', 15137), ('of', 12067), ('is', 11063), ('this', 10530), ('br', 9361)]
pd.DataFrame(top_1_percentile[:10], columns=['Palabras','Frecuencia']).set_index('Palabras').plot(kind='bar')
plt.title('Percentil 1 de palabras mas frecuentes')
plt.xlabel('Palabras')
plt.ylabel('Frecuencia')
Text(0, 0.5, 'Frecuencia')
bottom_1_percentile = Counter(tokens).most_common()[-one_percentile:]
bottom_1_percentile[:10]
[('pruchase', 1), ('slick', 1), ('cloured', 1), ('innocuous', 1), ('espensive', 1), ('marketer', 1), ('strofoam', 1), ('destroyers', 1), ('ruth', 1), ('gleaning', 1)]
noise_words.extend([word for word,val in top_1_percentile])
noise_words.extend([word for word,val in bottom_1_percentile])
Las stopwords y las palabras de alta/baja frecuencia ahora se han agregado a noise_words, que se eliminarán de las revisiones antes de entrenar los modelos de aprendizaje automático.
Es poco probable que las stopwords sean tan útiles, ya que esperamos que aparezcan con la misma frecuencia en las críticas positivas y negativas. Las palabras poco comunes pueden ser más significativas y, en teoría, podrían indicar el sentimiento de la revisión
Si quieren profundizar en los conceptos de stemming, ylemmatization y otros tipos de normalizaciones pueden encontrar una buena introduccion en: https://nlp.stanford.edu/IR-book/html/htmledition/stemming-and-lemmatization-1.html.
from nltk.stem import PorterStemmer, WordNetLemmatizer, LancasterStemmer
nltk.download('wordnet')
from nltk.corpus import wordnet
porter = PorterStemmer()
lancaster = LancasterStemmer()
lemmatizer = WordNetLemmatizer()
[nltk_data] Downloading package wordnet to /root/nltk_data... [nltk_data] Unzipping corpora/wordnet.zip.
Los algoritmos de Stemming funcionan cortando el final o el principio de la palabra, teniendo en cuenta una lista de prefijos y sufijos comunes que se pueden encontrar.
Por otro lado, la lematización toma en consideración el análisis morfológico de las palabras. Por lo tanto, la lematización tiene en cuenta la gramática de la palabra e intenta encontrar la palabra raíz en lugar de simplemente llegar a la palabra raíz mediante métodos de fuerza bruta.
print("Lancaster Stemmer")
print(lancaster.stem("trouble"))
print(lancaster.stem("troubling"))
print(lancaster.stem("troubled"))
# Proveer una palabra que sera lemantizada
print("WordNet Lemmatizer")
print(lemmatizer.lemmatize("trouble", wordnet.NOUN))
print(lemmatizer.lemmatize("troubling", wordnet.VERB))
print(lemmatizer.lemmatize("troubled", wordnet.VERB))
Lancaster Stemmer troubl troubl troubl WordNet Lemmatizer trouble trouble trouble
Se puede ver que obtenemos una raíz de significado de Lemmatizer, mientras que Stemmer simplemente recorta y extrae la primera parte importante de la palabra.
amazon_reviews[['Text','Score','Sentiment_rating']].head(5)
Text | Score | Sentiment_rating | |
---|---|---|---|
0 | I have bought several of the Vitality canned d... | 5 | 1 |
1 | Product arrived labeled as Jumbo Salted Peanut... | 1 | 0 |
2 | This is a confection that has been around a fe... | 4 | 1 |
3 | If you are looking for the secret ingredient i... | 2 | 0 |
4 | Great taffy at a great price. There was a wid... | 5 | 1 |
Las variables independientes o características del modelo se derivan del texto de revisión. Previamente, discutimos cómo podemos usar n-grams para crear características, y específicamente cómo la bolsa de palabras es la interpretación más simple de estos n-gramas, sin tener en cuenta el orden y el contexto por completo y solo enfocándonos en la frecuencia/recuento. Usemos eso como punto de partida.
CountVectorizer es una clase de Python que da cuenta automáticamente de ciertos pasos de preprocesamiento, como la eliminación de palabras vacías, la derivación, la creación de n-gramas y la tokenización de palabras:
# Creacion de metodo para stemming
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
analyzer = CountVectorizer().build_analyzer()
def stemmed_words(doc):
return (stemmer.stem(w) for w in analyzer(doc))
Usemos esto para crear una bolsa de palabras de las reseñas, excluyendo las palabras irrelevantes que identificamos anteriormente:
# Creacion de un objeto tipo CountVectorizer
bow_counts = CountVectorizer(
tokenizer=word_tokenize,
stop_words=noise_words,
ngram_range=(1, 1)
)
Una vez que se prepara la bolsa de palabras, el conjunto de datos debe dividirse en conjuntos de entrenamiento y de prueba. También podríamos dividir los datos después de vectorizarlos, pero es útil dividir los datos lo antes posible en el proceso. Esto significa que una vez que hemos generado nuestras predicciones, podemos compararlas más fácilmente con los textos originales, antes de que hayan sido preprocesadas y vectorizadas.
reviews_train, reviews_test = train_test_split(amazon_reviews, test_size=0.2, random_state=0)
X_train_bow = bow_counts.fit_transform(reviews_train.reviews_text_new)
X_test_bow = bow_counts.transform(reviews_test.reviews_text_new)
/usr/local/lib/python3.7/dist-packages/sklearn/feature_extraction/text.py:401: UserWarning: Your stop_words may be inconsistent with your preprocessing. Tokenizing the stop words generated tokens ["'d", "'ll", "'re", "'s", "'ve", 'might', 'must', "n't", 'need', 'sha', 'wo'] not in stop_words. % sorted(inconsistent)
Tengan en cuenta que llamamos a fit_transform
para vectorizar nuestro conjunto de entrenamiento y transform
para vectorizar nuestro conjunto de prueba. Esto genera las asignaciones de vectorización solo en los datos del conjunto de entrenamiento, que es una restricción a la que nos enfrentaríamos en un problema del mundo real (no tener acceso a los datos de prueba durante el tiempo de entrenamiento).
Por lo tanto, puede haber algunas palabras en el conjunto de prueba que no sabemos cómo vectorizar y se omitirán.
y_train_bow = reviews_train['Sentiment_rating']
y_test_bow = reviews_test['Sentiment_rating']
y_test_bow.value_counts() / y_test_bow.shape[0]
1 0.847921 0 0.152079 Name: Sentiment_rating, dtype: float64
Los datos de prueba contienen 84% de opiniones positivas. El modelo de predicción más simple que podríamos pensar sería uno que prediga "positivo" para cada entrada. Llamaríamos a esto un modelo "ingenuo", y constituye una línea de base útil. En este caso, dicho modelo obtendría un 84 % de precisión, por lo que podemos considerarlo como una puntuación de referencia que nuestro modelo de aprendizaje automático debe superar.
# Entrenar el modelo
lr_model_all = LogisticRegression(C=1, solver="liblinear")
lr_model_all.fit(X_train_bow, y_train_bow)
# Predecir el output
test_pred_lr_prob = lr_model_all.predict_proba(X_test_bow)
test_pred_lr_all = lr_model_all.predict(X_test_bow)
print("F1 score: ", f1_score(y_test_bow, test_pred_lr_all))
print("Accuracy: ", accuracy_score(y_test_bow, test_pred_lr_all) * 100)
F1 score: 0.942550505050505 Accuracy: 90.04376367614879
test_pred_lr_prob
array([[7.97531898e-01, 2.02468102e-01], [2.70697065e-09, 9.99999997e-01], [5.15753584e-02, 9.48424642e-01], ..., [1.23713200e-04, 9.99876287e-01], [9.96585322e-01, 3.41467779e-03], [9.18040817e-02, 9.08195918e-01]])
probabilities = [each[1] for each in test_pred_lr_prob]
predictions = pd.DataFrame()
predictions['Text'] = reviews_test['Text']
predictions['Actual_Score'] = reviews_test['Score']
predictions['Sentiment_rating'] = reviews_test['Sentiment_rating']
predictions['Predicted_sentiment'] = test_pred_lr_all
predictions['Predicted_probability'] = probabilities
predictions.head(5)
Text | Actual_Score | Sentiment_rating | Predicted_sentiment | Predicted_probability | |
---|---|---|---|---|---|
7419 | These truffles are not as great as they are ma... | 2 | 0 | 0 | 0.202468 |
4315 | Ok--I just bought BIsquick GF and the first th... | 4 | 1 | 1 | 1.000000 |
1781 | I love these chips so much... and I can't get ... | 5 | 1 | 1 | 0.948425 |
962 | I purchased this because I read that it was a ... | 2 | 0 | 0 | 0.363864 |
7289 | I do like the idea of these crackers for glute... | 2 | 0 | 1 | 0.954062 |
accuracy_score(predictions['Sentiment_rating'], predictions['Predicted_sentiment'])
0.9004376367614879
En la columna Predicted_probability
, puede ver la confianza que tenía el modelo en sus predicciones, siendo las probabilidades muy cercanas a 0 predicciones de sentimientos negativos muy confiables y las probabilidades muy cercanas a 1, predicciones de sentimientos positivos muy confiables.
Utilice esta información para encontrar el caso en el que el modelo tenía más confianza al predecir que una reseña tenía un sentimiento negativo cuando la puntuación real era positiva.
Mire el texto y escriba algunas oraciones de análisis sobre por qué cree que el modelo se equivocó
predictions[
predictions['Predicted_sentiment'] != predictions['Sentiment_rating']
].sort_values(by=["Predicted_probability"]).head(3)
Text | Actual_Score | Sentiment_rating | Predicted_sentiment | Predicted_probability | |
---|---|---|---|---|---|
7692 | My little girl (6 months) can't stomach any fo... | 5 | 1 | 0 | 0.000719 |
4166 | The first time I tried this product to make pa... | 4 | 1 | 0 | 0.002114 |
6612 | When I opened the package, I only found 11 pac... | 4 | 1 | 0 | 0.006040 |
predictions.loc[7692].values
array(["My little girl (6 months) can't stomach any formula. Even Nutramigen, at $35 per can made her colicky and sick (accompanied with the worst smelling spit up). The expensive formulas smelled bad, and tasted worse, I hated giving them to her. Goats milk was recommended to me. Her doctor's only objection was that it was low in Folic Acid and tended to dehydrate babies. Well, Meyenberg Powdered Milk is fortified with Folic Acid, so dilute it with a little extra water and you're in business! I now have the happiest infant you've ever seen. Enfamil can take that nasty chemical soup they call formula and shove it, I'll never go back.<br /><br />You can get the formula Recipe at:<br />[...]<br /><br />There have been concerns about arsenic in rice syrup lately, so I have been using Lyle's Golden Syrup, and she likes it fine (Also it's the best pancake syrup you'll ever have). I am going to try barley malt syrup next, it is chemically more similar to rice syrup.", 5, 1, 0, 0.0007189017742100002], dtype=object)
Podemos ver que la reseña tiene un tono muy negativo y utiliza palabras que la modelo probablemente ha aprendido a asociar fuertemente con malas reseñas como "caro", "malo", "peor", "odiado", "desagradable", " nunca". Sin embargo, todos estos en realidad están dirigidos a un producto diferente. El autor dice que después de comprar este producto tiene el "bebé más feliz", pero eso no es lo suficientemente fuerte como para contrarrestar todos los aspectos negativos de la reseña.
Modifique el conjunto de características en el modelo para incluir bigramas, trigramas y 4-grams. No elimine las palabras irrelevantes definidas anteriormente antes de presentarlas. (Sugerencia: establece ngram_range=(1,4).)
Al mismo tiempo, experimente con el ajuste de hiperparámetros. Cambie el valor C del clasificador de regresión logística a 0,9.
# Cambios con respecto al código anterior
# 1. Aumentar los n-gramas de solo tener 1 gramo a (1 gramo, 2 gramos, 3 gramos y 4 gramos)
# 2. Incluir las palabras vacías en las características de la bolsa de palabras
bow_counts = CountVectorizer(
tokenizer=word_tokenize,
ngram_range=(1,4)
)
X_train_bow = bow_counts.fit_transform(reviews_train.reviews_text_new)
X_test_bow = bow_counts.transform(reviews_test.reviews_text_new)
# Observe el aumento de funciones con la inclusión de palabras vacías
X_train_bow
<7310x1054259 sparse matrix of type '<class 'numpy.int64'>' with 2036674 stored elements in Compressed Sparse Row format>
# Cambios en la regresión logística
# Cambio de la sanción de regularización por defecto de l2 a l1
# Cambiando el parámetro de costo C a 0.9
lr_model_all_new = LogisticRegression(C=0.9, solver="liblinear")
# Entrenar el modelo
lr_model_all_new.fit(X_train_bow, y_train_bow)
# Predecir resultados
test_pred_lr_prob = lr_model_all_new.predict_proba(X_test_bow)
test_pred_lr_all = lr_model_all_new.predict(X_test_bow)
print("F1 score: ", f1_score(y_test_bow, test_pred_lr_all))
print("Accuracy: ", accuracy_score(y_test_bow, test_pred_lr_all) * 100)
F1 score: 0.9554579673776662 Accuracy: 92.23194748358861
La precisión ha saltado del 90% al 92,2%. Este es un ejemplo de lo que el simple ajuste de hiperparámetros y la modificación de características de entrada pueden hacer en el rendimiento general. Incluso podemos obtener características interpretables de esto en términos de lo que más contribuyó al sentimiento positivo y negativo.
lr_weights = pd.DataFrame(list(
zip(
bow_counts.get_feature_names(),
lr_model_all_new.coef_[0])
),
columns=['words','weights']
)
lr_weights.sort_values(['weights'],ascending = False)[:15]
/usr/local/lib/python3.7/dist-packages/sklearn/utils/deprecation.py:87: FutureWarning: Function get_feature_names is deprecated; get_feature_names is deprecated in 1.0 and will be removed in 1.2. Please use get_feature_names_out instead. warnings.warn(msg, category=FutureWarning)
words | weights | |
---|---|---|
374800 | great | 1.382526 |
256072 | delicious | 0.979694 |
366512 | good | 0.864971 |
677656 | perfect | 0.844286 |
299760 | excellent | 0.838121 |
855190 | the best | 0.826370 |
534066 | love | 0.819486 |
143456 | best | 0.813387 |
593253 | nice | 0.780972 |
536985 | loves | 0.660368 |
1034499 | wonderful | 0.653160 |
777859 | smooth | 0.642421 |
309549 | favorite | 0.615238 |
605055 | not too | 0.587947 |
315915 | find | 0.585533 |
lr_weights.sort_values(['weights'],ascending = False)[-15:]
words | weights | |
---|---|---|
1037701 | worst | -0.526030 |
1007881 | were | -0.529210 |
982174 | very disappointed | -0.540921 |
120594 | away | -0.546831 |
730653 | return | -0.547383 |
801742 | stick | -0.561657 |
121444 | awful | -0.570031 |
997363 | waste | -0.591713 |
265649 | disappointing | -0.598279 |
820835 | t | -0.654783 |
124042 | bad | -0.663200 |
1003928 | weak | -0.702415 |
294693 | even | -0.782674 |
599002 | not | -0.975730 |
265330 | disappointed | -1.121489 |
rf_model_all = RandomForestClassifier(n_estimators=100)
# Entrenamiento
rf_model_all.fit(X_train_bow, y_train_bow)
# predicciones
test_pred_lr_prob = rf_model_all.predict_proba(X_test_bow)
test_pred_lr_all = rf_model_all.predict(X_test_bow)
print("F1 score: ", f1_score(y_test_bow,test_pred_lr_all))
print("Accuracy: ", accuracy_score(y_test_bow,test_pred_lr_all)* 100)
F1 score: 0.9281437125748503 Accuracy: 86.87089715536105
Esto no es tan bueno como la regresión logística. Podemos obtener los n-gramas que fueron más importantes para las predicciones de la siguiente manera:
feature_importances = pd.DataFrame(
rf_model_all.feature_importances_,
index=bow_counts.get_feature_names(),
columns=['importance']
)
feature_importances.sort_values(['importance'], ascending=False)[:10]
Por supuesto, la bolsa de palabras no es la única forma de caracterizar el texto. Otro método, que mencionamos brevemente antes, es el método TF-IDF. Esto evalúa qué tan importante es una palabra para un documento dentro de una gran colección de documentos (es decir, corpus). La importancia aumenta proporcionalmente en función del número de veces que aparece una palabra en el documento, pero se compensa con la frecuencia de la palabra en el corpus.
La TF-IDF es el producto de dos términos. El primero calcula la frecuencia de término normalizada (TF); es decir, el número de veces que aparece una palabra en un documento dividido por el número total de palabras en ese documento. El segundo término es la Frecuencia Inversa de Documentos (IDF), calculada como el logaritmo del número de documentos en el corpus dividido por el número de documentos
Menos formalmente, ¿qué significa esto?
TF-IDF no solo cuenta cada palabra, sino que aplica una ponderación para que las palabras comunes reciban menos atención y las palabras raras reciban más.
Volvamos a presentar nuestro conjunto original de revisiones basado en TF-IDF y dividamos las funciones resultantes en conjuntos de entrenamiento y prueba:
# Cree un vectorizador: aún alimentamos nuestras stopwords, aunque
# estos son menos relevantes ahora ya que TF-IDF los ponderaría menos
# de todas formas.
tfidf_counts = TfidfVectorizer(
tokenizer=word_tokenize,
stop_words=noise_words,
ngram_range=(1,1)
)
X_train_tfidf = tfidf_counts.fit_transform(reviews_train.reviews_text_new)
X_test_tfidf = tfidf_counts.transform(reviews_test.reviews_text_new)
/usr/local/lib/python3.7/dist-packages/sklearn/feature_extraction/text.py:401: UserWarning: Your stop_words may be inconsistent with your preprocessing. Tokenizing the stop words generated tokens ["'d", "'ll", "'re", "'s", "'ve", 'might', 'must', "n't", 'need', 'sha', 'wo'] not in stop_words. % sorted(inconsistent)
# Crear el clasificador
lr_model_tf_idf = LogisticRegression(solver="liblinear")
# Entrenar
lr_model_tf_idf.fit(X_train_tfidf, y_train_bow)
# Predecir
test_pred_lr_prob = lr_model_tf_idf.predict_proba(X_test_tfidf)
test_pred_lr_all = lr_model_tf_idf.predict(X_test_tfidf)
## Evaluar el modelo
print("F1 score: ",f1_score(y_test_bow, test_pred_lr_all))
print("Accuracy: ", accuracy_score(y_test_bow, test_pred_lr_all) * 100)
F1 score: 0.9343419062027231 Accuracy: 88.12910284463895
Aquí hemos logrado una precisión del 88 % con TF-IDF en comparación con el 90 % con 1-gram. Es difícil saber exactamente por qué este algoritmo de vectorización más sofisticado conduce a peores resultados, pero podría ser que penalizar palabras que son comunes en todo el corpus genere una desventaja para este conjunto de datos en particular. TF-IDF suele ser útil cuando los datos de prueba son muy diferentes de los datos de entrenamiento, lo que permite que se desprioricen las palabras que solo son comunes en el conjunto de entrenamiento.
Trate de aumentar la precisión del modelo por
ngram_range=(1,4)
en el Vectorizadortfidf_counts = TfidfVectorizer(
tokenizer=word_tokenize,
ngram_range=(1,4)
)
X_train_tfidf = tfidf_counts.fit_transform(reviews_train.reviews_text_new)
X_test_tfidf = tfidf_counts.transform(reviews_test.reviews_text_new)
# definiendo la clase del modelo
lr_model_tf_idf_new = LogisticRegression(solver="liblinear", penalty='l1', C=10)
# Entrenar
lr_model_tf_idf_new.fit(X_train_tfidf, y_train_bow)
# Predecir
test_pred_lr_prob = lr_model_tf_idf_new.predict_proba(X_test_tfidf)
test_pred_lr_all = lr_model_tf_idf_new.predict(X_test_tfidf)
# Evaluar el modelo
print("F1 score: ",f1_score(y_test_bow, test_pred_lr_all))
print("Accuracy: ", accuracy_score(y_test_bow, test_pred_lr_all)*100)
F1 score: 0.9470967741935484 Accuracy: 91.02844638949672
Esta es una mejora con respecto a nuestro resultado anterior, pero hicimos cuatro cambios al mismo tiempo, por lo que no sabemos cuáles ayudaron y cuánto.
Probar diferentes hiperparámetros para mejorar su modelo se denomina ajuste de hiperparámetros y es un campo enorme por sí solo. Puede imaginar cómo ejecutar este modelo 16 veces, una vez con cada posible configuración de hiperparámetros que hemos probado, sería un poco complicado de seguir, ¡y esto es solo con 4 hiperparámetros y dos valores para cada uno! Con 100 o 1000 de hiperparámetros y 100 o 1000 de valores para cada uno, las combinaciones totales crecen muy rápidamente.
Para ayudar con esto, scikit-learn proporciona la llamada funcionalidad de "GridSearch", donde puede configurar una canalización y especificar los rangos de hiperparámetros que desea "buscar". Scikit-learn probará cada combinación y entrenará y evaluará el modelo para cada caso, diciéndole cuál funcionó mejor.
También podemos encontrar nuestras características más importantes nuevamente, como se muestra a continuación:
lr_weights = pd.DataFrame(
list(
zip(tfidf_counts.get_feature_names(), lr_model_tf_idf_new.coef_[0])
),
columns=['words','weights']
)
lr_weights.sort_values(['weights'],ascending = False)[:10]
/usr/local/lib/python3.7/dist-packages/sklearn/utils/deprecation.py:87: FutureWarning: Function get_feature_names is deprecated; get_feature_names is deprecated in 1.0 and will be removed in 1.2. Please use get_feature_names_out instead. warnings.warn(msg, category=FutureWarning)
words | weights | |
---|---|---|
374800 | great | 51.682173 |
677656 | perfect | 46.173501 |
855190 | the best | 44.691956 |
256072 | delicious | 39.746053 |
63261 | amazing | 38.903587 |
131810 | be disappointed | 38.802913 |
299760 | excellent | 37.935929 |
777859 | smooth | 36.007994 |
685128 | pleased | 33.385577 |
593253 | nice | 32.073194 |
lr_weights.sort_values(['weights'],ascending = False)[-10:]
words | weights | |
---|---|---|
730653 | return | -34.726053 |
599002 | not | -35.237744 |
603641 | not recommend | -35.535403 |
843696 | than this | -36.479892 |
1037701 | worst | -37.469519 |
601259 | not for | -38.382928 |
1053388 | yuck | -41.851058 |
605704 | not worth | -44.629182 |
265330 | disappointed | -45.957117 |
265649 | disappointing | -49.420275 |
El tipo final de caracterización que cubriremos son las incrustaciones de palabras (Word Embeddings). Este es un tipo de representación de palabras que permite que palabras con un significado similar tengan una representación similar. Al ser entrenados previamente en datos externos, como Wikipedia, las incrustaciones de palabras saben cuándo los conceptos están relacionados semánticamente; por ejemplo, los vectores para "rey" y "reina" se ubicarían uno cerca del otro, aunque no hay sintaxis ni similitud en ortografía entre estas palabras.
Es este enfoque para representar palabras y documentos el que puede considerarse uno de los avances clave del aprendizaje profundo en los desafiantes problemas de procesamiento del lenguaje natural.
Hay muchos conjuntos de datos de incrustaciones de palabras previamente entrenadas que están disponibles gratuitamente, o puede entrenar las suyas propias. Algunos de los avances más importantes en esta área incluyen Word2Vec, que se convirtió en el modelo a seguir de la NPL, y otros enfoques integrados como Glove, ELMo y BERT.
Existen diferentes métodos para aprender incrustaciones de palabras: Word2Vec, GloVe, FastText. Word2Vec utiliza una red neuronal superficial y es de dos tipos; CBOW y Skip Gram. GloVe es un algoritmo de aprendizaje no supervisado para obtener representaciones vectoriales de palabras. El entrenamiento se realiza en estadísticas globales agregadas de coocurrencia palabra-palabra de un corpus, y las representaciones resultantes muestran subestructuras lineales interesantes del espacio vectorial de palabras. fastText es una biblioteca para aprender incrustaciones de palabras y clasificación de texto creada por el laboratorio de investigación de IA de Facebook.
Porque usar Word Embeddings en lugar de Bag of words o TF-IDF Cada palabra está representada por un vector de valor real, que generalmente tiene decenas o cientos de dimensiones. Esto contrasta con los miles o millones de dimensiones requeridas para las representaciones de palabras dispersas. Por lo tanto, las incrustaciones de palabras pueden reducir drásticamente la cantidad de dimensiones requeridas para representar un documento de texto:
import gensim
# Cargar una incrustación de palabras de guante preentrenada que se entrena en un conjunto de datos de Twitter
# Esta palabra incrustada tiene 200 dimensiones, lo que significa que cada palabra está representada
# por un vector de 200 dimensiones.
model = gensim.models.KeyedVectors.load_word2vec_format(
os.path.join(os.getcwd(), 'glove.twitter.27B.200d_out.txt'),
binary=False,
unicode_errors='ignore'
)
Teníamos aproximadamente 18000 tokens distintos para características de 1-grams en la representación de la bolsa de palabras, pero solo tendrán 200 dimensiones en esta inserción de palabras. ¡Esta es una gran diferencia!
Además, las incrustaciones de palabras capturan el contexto y la semántica de las oraciones, ya que cada representación de vector de palabra se basa en su significado contextual.
A continuación se muestra la representación vectorial de "comida" y "genial":
print("El embedding para food es", len(model['food']), "dimensional")
model['food']
El embedding para food es 200 dimensional
array([-6.9175e-01, -1.4259e-01, 3.8653e-01, -2.3141e-01, -2.0408e-01, -2.1565e-01, 7.7839e-01, 2.2689e-03, -7.2446e-02, -6.0134e-01, -4.2400e-01, -5.7140e-01, -8.4249e-01, 1.5947e-01, -1.2899e-01, 5.9032e-01, -1.3632e-01, -6.6478e-01, -1.9557e-01, -8.2453e-01, -1.3177e-01, 1.3514e-01, -7.3214e-01, 4.8200e-01, 4.3505e-01, 1.6676e+00, -1.8275e-01, -1.0007e-01, 3.7003e-01, 1.0411e-01, -8.8115e-01, -9.7733e-04, -2.9459e-01, -7.3869e-02, -4.0103e-01, -4.6626e-01, 2.3253e-01, 2.7776e-01, 4.0754e-01, -4.5051e-02, -1.9468e-01, -2.9230e-01, -3.4642e-01, -4.9286e-01, 1.0467e-01, 7.2143e-01, 5.9596e-01, 5.3495e-01, 3.8788e-02, -1.4406e-01, -5.2248e-02, -6.8292e-01, -1.0080e-01, -1.2961e-01, -2.6006e-02, 1.4836e-01, 3.2417e-02, 1.3997e-01, 8.3943e-03, -2.3139e-01, -1.8000e-01, -3.1689e-01, 2.3606e-01, 1.8237e-01, 4.3933e-01, -3.2313e-01, -2.1512e-03, -4.4172e-01, 4.1011e-01, 1.7174e-01, -8.6405e-01, -3.9674e-01, 4.4175e-01, 5.9300e-01, 1.8982e-01, -2.9646e-02, -3.4041e-01, -3.3708e-02, 7.3449e-01, 4.5300e-01, -2.7855e-02, -1.8993e-02, 3.8107e-01, -5.6606e-02, 1.4864e-02, 3.1518e-01, -3.2304e-01, -2.7439e-01, 6.1900e-02, 3.2886e-01, 1.5138e-01, 5.3268e-01, -1.6616e-01, -2.3076e-01, -9.6515e-02, 4.5991e-01, -5.1475e-01, 1.0297e-01, -4.0225e-02, 5.6679e-01, 3.1027e-01, 1.5679e-01, -2.5897e-01, 4.6312e-01, 2.2561e-01, -3.9300e-01, -3.9593e-01, 4.4001e-01, 3.7176e-01, 1.4747e-02, -1.9193e-01, -2.2478e-01, -1.2665e-01, -3.4982e-01, 5.0847e-01, 3.1720e-01, 1.2942e-01, -6.2695e-01, 5.8675e-01, 4.1040e-02, 1.8835e-01, -2.2626e-01, -1.1744e-01, 5.1429e-03, 7.2058e-02, -4.9525e-01, 4.4159e-01, 8.6225e-01, 7.6765e-02, -9.7908e-02, 6.8383e-02, 3.0596e-01, 3.7980e-01, 1.1563e-01, -6.1020e-01, -6.8107e-01, 3.2723e-02, 2.5346e-01, 3.5334e-01, 2.5407e-01, -4.6516e-01, 4.8858e-01, 3.9032e-01, -8.1296e-01, -6.9780e-01, -1.2542e-01, 7.9234e-02, 1.2918e-01, -1.1048e-01, 8.9312e-03, 3.6999e-01, 3.0116e-01, -4.6578e+00, -4.4493e-03, 2.0313e-02, -5.0215e-02, -2.0646e-01, -3.7321e-02, -5.1779e-02, 6.6986e-02, -5.8853e-01, 7.1753e-01, 4.2784e-02, 1.6667e-03, -2.6193e-01, 5.8214e-01, -1.0513e+00, -3.0341e-02, 7.3892e-01, -1.8003e-01, -1.1104e-01, 3.0846e-01, 4.4027e-01, -8.4080e-02, -2.6251e-01, -3.8733e-01, -2.6630e-01, 1.9655e-01, 5.3812e-02, -2.4456e-01, -7.8868e-01, -7.1843e-01, 7.0593e-02, -1.9051e-01, 2.5553e-01, -1.3786e-01, 1.2942e-01, 4.5864e-01, 5.5462e-01, 8.2104e-01, -2.5049e-01, -3.3623e-01, 1.8491e-01, -4.8235e-01, 3.1425e-01, 2.4499e-01, -2.4404e-01, 8.0309e-02, 3.4060e-01, 7.0451e-01], dtype=float32)
print("El embedding para great es", len(model['great']), "dimensional")
model['great']
El embedding para great es 200 dimensional
array([ 1.0751e-01, 1.5958e-01, 1.3332e-01, 1.6642e-01, -3.2737e-02, 1.7592e-01, 7.2395e-01, 1.1713e-01, -3.5036e-01, -4.2937e-01, -4.0925e-01, -2.5761e-01, -1.0264e+00, -1.0014e-01, 5.5390e-02, 2.0413e-01, 1.2807e-01, -2.6337e-02, -6.9719e-02, -3.6193e-02, -1.9917e-01, 3.9437e-02, -9.2358e-02, 2.6981e-01, -2.0951e-01, 1.5455e+00, -2.8123e-01, 3.2046e-01, 4.5545e-01, -3.8841e-02, -1.7369e-01, -2.3251e-01, -5.9551e-02, 2.3250e-01, 4.4214e-01, 3.3666e-01, 3.9352e-02, -1.2462e-01, -2.9317e-01, -4.8857e-02, 6.9021e-01, 7.1279e-02, 1.0252e-01, 1.6122e-01, -2.3536e-01, 6.2724e-02, 2.0222e-01, 5.0234e-02, -1.1611e-01, 2.8909e-02, -1.1109e-01, -5.0241e-02, -5.9063e-01, -8.8747e-02, 5.1444e-01, -1.3715e-01, 1.7194e-01, -8.3657e-02, 9.6333e-02, -9.7063e-02, 3.4003e-03, -7.0180e-02, -5.9588e-01, -2.8264e-01, 1.2529e-01, 2.4359e-01, -4.9082e-01, -4.2533e-02, 2.2158e-01, -2.1491e-01, -4.2101e-02, 2.3359e-01, 3.1978e-01, 3.5063e-01, 6.1748e-01, -1.0197e-01, 5.3357e-01, -3.6005e-01, -1.7212e-02, 1.6645e-01, 8.9432e-01, 2.7322e-02, 3.0683e-01, 1.9715e-02, 6.0516e-01, 4.1085e-01, 5.5945e-01, -8.4501e-02, 3.5933e-01, 1.0216e-01, 2.6675e-01, -6.0445e-01, -1.0513e-01, -1.9248e-01, 2.9150e-01, -1.0537e-01, 5.2671e-01, 2.3763e-01, -1.3640e-01, -6.1029e-02, 1.0081e-01, 7.4541e-02, -1.4899e-01, -2.2301e-01, -1.3653e-02, 4.0192e-02, 5.5821e-03, -2.9936e-02, 2.7338e-02, 5.9412e-01, -1.0302e-01, 9.0319e-02, 3.1055e-01, 6.3336e-01, 2.9762e-01, -8.4671e-02, -1.2552e-01, -6.3930e-01, 3.8613e-01, 6.6371e-01, 5.1345e-01, 2.0719e-01, 2.1100e-01, 1.4579e-01, -7.3321e-02, -7.0593e-01, -6.2578e-02, -2.5470e-01, 1.1986e-01, 1.6102e-01, 3.2958e-02, -2.4159e-01, -2.5708e-01, 3.2051e-01, -1.1569e-01, 6.7540e-03, -1.1688e-01, -3.6158e-02, -6.5320e-01, 4.9560e-01, -3.9429e-02, -1.8395e-01, 2.3295e-01, 5.4128e-01, 2.4568e-02, -1.9862e-01, 2.1041e-01, 9.3798e-02, 8.3096e-03, -6.1551e-02, 2.3262e-01, -4.2756e-02, -5.3511e+00, 3.0604e-01, 3.3578e-01, -3.6771e-01, 5.6225e-01, -8.2341e-02, 2.9809e-01, 2.5189e-01, -4.6203e-01, 1.0452e-01, -3.9540e-01, 3.6961e-01, 1.3093e-01, 1.6653e-01, -3.1915e-01, 1.6974e-01, 4.2575e-01, 3.6420e-01, 3.7175e-01, -1.9450e-01, 6.2702e-02, 4.9775e-01, 3.1842e-02, -6.4072e-02, 7.6183e-02, -5.9534e-01, 3.1731e-01, -2.8254e-01, 1.5987e-01, -9.2750e-02, -4.1426e-02, 7.5799e-02, 9.5740e-03, -2.1532e-01, -3.1419e-01, -1.5144e-01, -4.6584e-01, -1.1069e-01, -4.0130e-01, 3.9266e-02, 8.1880e-01, -4.2955e-02, 2.1698e-01, -6.0347e-02, 3.3431e-01, -9.9549e-02, -1.8156e-01, -8.5143e-02], dtype=float32)
Como discutimos, el poder de las incrustaciones de palabras es que las palabras que tienen un significado similar están más juntas en el espacio vectorial. Podemos demostrar esto mirando la distancia del coseno entre algunos pares de palabras de la siguiente manera:
def print_similarity(word1, word2, model):
v1 = model[word1]
v2 = model[word2]
similarity = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
print(f"{word1} y {word2} son {round(similarity * 100)}% similar")
print_similarity("cat", "dog", model)
print_similarity("good", "bad", model)
print_similarity("great", "good", model)
print_similarity("grass", "model", model)
cat y dog son 83% similar good y bad son 80% similar great y good son 87% similar grass y model son 11% similar
Aquí, "significado similar" se define vagamente como "usado en contextos similares". Debido a que hay muchos ejemplos como "Acaricié a mi gato" y "Acaricié a mi perro" donde estas palabras se usan en contextos similares, se consideran muy similares. También hay muchas oraciones en las que "bueno" y "malo" pueden intercambiarse y la oración puede seguir siendo válida, por lo que, aunque las consideremos "opuestas", nuestro modelo las considerará similares. "hierba" y "modelo" no tienen casi nada que ver entre sí, por lo que están muy separados.
Para encontrar el vector de una reseña completa, obtenemos el vector de cada palabra de la reseña por separado y tomamos un promedio simple
Calcule el vector para cada revisión individual en el conjunto de datos.
review_embeddings = []
for each_review in amazon_reviews.reviews_text_new:
review_average = np.zeros(model.vector_size)
count_val = 0
for each_word in word_tokenize(each_review):
# Cambiar a "if True" para remover stopwords del promedio de embeddings
if False:
if(each_word.lower() in noise_words):
print(each_word.lower())
continue
if(each_word.lower() in model):
review_average += model[each_word.lower()]
count_val += 1
review_embeddings.append(list(review_average/count_val))
Cargar sus propios vectores de palabras es excelente para comprender cómo funcionan, pero también existen bibliotecas de nivel superior que abstraen código como el que se muestra arriba. En la industria, una biblioteca de NLP ampliamente utilizada es [SpaCy] (https://spacy.io/). Esta biblioteca le permite extraer de manera eficiente las incrustaciones de palabras de los textos y realizar operaciones de alto nivel sobre ellos.
Convirtamos la lista de representaciones vectoriales para cada revisión en un DataFrame y dividámoslo en conjuntos de entrenamiento y prueba:
embedding_data = pd.DataFrame(review_embeddings)
embedding_data = embedding_data.fillna(0)
X_train_embed, X_test_embed, y_train_embed, y_test_embed = train_test_split(
embedding_data,
amazon_reviews.Sentiment_rating,
test_size=0.2,
random_state=0
)
Ahora apliquemos el modelo de regresion logistica a nuestras word embeddings
lr_model = LogisticRegression(penalty="l1", C=10, solver="liblinear")
lr_model.fit(X_train_embed, y_train_embed)
test_pred_lr_prob = lr_model.predict_proba(X_test_embed)
test_pred_lr_all = lr_model.predict(X_test_embed)
print("F1 score: ", f1_score(y_test_embed, test_pred_lr_all))
print("Accuracy: ", accuracy_score(y_test_embed, test_pred_lr_all)*100)
F1 score: 0.9381703470031545 Accuracy: 89.27789934354486
Desafortunadamente, esto no es tan bueno como las representaciones de bolsa de palabras o TF-IDF. Además, aunque las incrustaciones de palabras fueron realmente efectivas para reducir el número total de dimensiones, adolece del problema de la interpretabilidad. Esto significa que es muy difícil para nosotros incluso diagnosticar qué está causando su bajo rendimiento.
Sin embargo, ¿recuerda cómo "bueno" y "malo" estaban juntos en el espacio vectorial? Esta es una de las razones por las que las incrustaciones de palabras pueden no funcionar tan bien para el análisis de sentimientos en conjuntos de datos más pequeños: las incrustaciones de palabras son buenas para usar "conocimiento" del mundo externo (latente en las incrustaciones preentrenadas) para inferir información adicional sobre un conjunto de datos más pequeño, pero en el caso del análisis de sentimientos, esto podría hacer más daño que bien al combinar palabras "similares" que en realidad están muy separadas para una tarea de análisis de sentimientos.
En nuestro caso, la creación de características usando TF-IDF nos dio una precisión del 92 % con características muy interpretables. Esta es una buena combinación, por lo que consideramos que este es el mejor modelo para nosotros aquí.
Tenga en cuenta que para un experimento real, habríamos dividido nuestro conjunto de datos en tres partes, no solo en dos. Cuando se ejecuta un experimento varias veces con diferentes parámetros, es casi seguro que algunos resultados serán mejores simplemente por casualidad, y es mala ciencia seleccionar el modelo con mejor desempeño después de docenas o cientos de ejecuciones.
Para evitar este problema, los datos deben dividirse en conjuntos de "entrenamiento", "prueba" y "validación". El conjunto de "prueba" debe reservarse al comienzo del experimento y nunca mirarse. El modelo debe ajustarse utilizando el conjunto de "validación".
Solo una vez que el experimentador esté satisfecho con el modelo al mejorar el rendimiento en el conjunto de validación, se debe ejecutar el modelo en el conjunto de prueba y esos resultados finales se toman como los resultados finales del experimento.
En este caso, limpiamos y destacamos un conjunto de datos de reseñas de Amazon y construimos algunos modelos de clasificación en estas características para predecir el sentimiento. Vimos que la bolsa de palabras y TF-IDF brindaban características interpretables, mientras que las incrustaciones de palabras realmente no. Al aumentar el conjunto de n-gramas que usamos de 1-gram a 4-grams, pudimos obtener la precisión de nuestro modelo de regresión logística hasta en un 92 %.
La construcción de modelos de aprendizaje automático en texto es una disciplina muy complicada. Algunas cosas importantes a tener en cuenta son las siguientes:
Aunque existen diferentes tipos de preprocesamiento involucrados en los datos textuales, no todo tiene que aplicarse en cada caso. Por ejemplo, cuando se trata de mensajes de texto, los caracteres especiales pueden representar información importante y no es necesario eliminarlos. Además, las mayúsculas pueden significar que alguien está enojado y representan gritos, por lo que las mayúsculas y las minúsculas pueden representar información valiosa. En otras situaciones, es más valioso normalizarlas.
El ajuste de hiperparámetros en los modelos de aprendizaje automático es un paso muy importante y, si bien los hiperparámetros predeterminados funcionan bien en muchos casos, a menudo se puede obtener un rendimiento adicional al ajustarlos. Se deben probar diferentes conjuntos de parámetros para ver qué contribuye al mejor modelo.
Cada tarea de clasificación en NLP es diferente, pero el proceso a seguir es similar al que hicimos en este caso: discutir los datos -> crear características a partir del texto -> entrenar modelos -> evaluar modelos.