from sentiment.tass import InterTASSReader
reader = InterTASSReader('TASS/InterTASS/tw_faces4tassTrain1000rc.xml')
tweets = list(reader.tweets()) # iterador sobre los tweets
X = list(reader.X()) # iterador sobre los contenidos de los tweets
y = list(reader.y()) # iterador sobre las polaridades de los tweets
print(tweets[0])
#print(X[0])
#print(y[0])
{'user': 'OnceBukowski', 'sentiment': 'NONE', 'tweetid': '768213876278165504', 'date': '2016-08-23 22:30:35', 'content': '-Me caes muy bien \n-Tienes que jugar más partidas al lol con Russel y conmigo\n-Por qué tan Otako, deja de ser otako\n-Haber si me muero', 'lang': 'es'}
%run sentiment/scripts/train.py -m clf -c maxent -o clf_maxent
%run sentiment/scripts/eval.py -i clf_maxent
Sentiment P: Precision: 52.89% (119/225) Recall: 76.28% (119/156) F1: 62.47% Sentiment N: Precision: 60.65% (131/216) Recall: 59.82% (131/219) F1: 60.23% Sentiment NEU: Precision: 15.79% (3/19) Recall: 4.35% (3/69) F1: 6.82% Sentiment NONE: Precision: 28.26% (13/46) Recall: 20.97% (13/62) F1: 24.07% Accuracy: 52.57% (266/506) Macro-Precision: 39.40% Macro-Recall: 40.35% Macro-F1: 39.87% P N NEU NONE P 119 27 5 5 N 60 131 7 21 NEU 30 29 3 7 NONE 16 29 4 13
pipeline = model._pipeline
x = X[0]
# x = '@noseashetero 1000/10 de verdad a ti que voy a decir petarda que te quiero más que a mí mismo ✨'
y = pipeline.predict([x])
P = pipeline.predict_proba([x])
print(x)
print(y)
print(P)
# print(type(P)) P es un array de numpy (o sea, una matriz)
print(pipeline.classes_)
@noseashetero 1000/10 de verdad a ti que voy a decir petarda que te quiero más que a mí mismo ✨ ['P'] [[0.03945864 0.07788631 0.11067046 0.77198459]] ['N' 'NEU' 'NONE' 'P']
Para el clasificador maxent podemos consultar las características que más favorecen o desfavorecen cada clase:
# el model ya quedó cargado al haber corrido eval.py
pipeline = model._pipeline
vect = pipeline.named_steps['vect']
clf = pipeline.named_steps['clf']
from sentiment.analysis import print_maxent_features
print_maxent_features(vect, clf)
N: portada buena enhorabuena gracias besos ([-1.57715354 -1.43077936 -1.42759822 -1.41947779 -1.36450292]) odio déficit recortes muertos triste ([1.6724519 1.68052657 1.78226641 2.04207167 2.42096484]) NEU: parados cree enhorabuena cuenta puedes ([-1.11954982 -0.99833537 -0.93920145 -0.90387799 -0.8670128 ]) decidirán broma palomacervilla expectación huelga ([1.26041253 1.26806314 1.30022578 1.32740269 1.34231887]) NONE: feliz gracias interesante gran enhorabuena ([-1.95280773 -1.93897273 -1.84154676 -1.79116806 -1.70262616]) periódico sesión jugar reunión portada ([1.21813152 1.30945322 1.38032478 1.45742904 2.20772094]) P: portada triste culpa urdangarin griñan ([-1.66217174 -1.54530836 -1.42478139 -1.31705283 -1.27911123]) genial homenaje gracias felicidades enhorabuena ([1.85082741 2.00512272 2.14249476 2.27528371 2.44819998])
El script de evaluación también calcula una matriz de confusión detallada "cm_items" para ver en qué instancias falla. Veamos las instancias que son negativas y fueron marcadas como positivas:
# print(cm_items['N', 'P'])
X2 = [X[i] for i in cm_items['N', 'P']] # obtenemos los contenidos
# print(X2[:10])
Usando predict_proba podemos calcular los "peores" errores, esto es, los que más favorecieron P por encima de N:
P = pipeline.predict_proba(X2) # calculamos las probabilidades para todas las clases
# print(P.shape)
# print(P[0])
# print(pipeline.classes_)
delta = P[:,3] - P[:,0] # diferencia entre prob de P y prob de N
# print(delta[0])
# print(delta.shape)
sorted_X2 = sorted(zip(X2, delta), key=lambda x: x[1], reverse=True) # ordenamos de mayor a menor
sorted_X2[:3]
[('@sport JA JA JA JA JA Teneis el ojino como la bandera de Japón ,hijos de la gran puta ', 0.8688556947282048), ('@carlachan Ja ja, hay gente muy cansina, sobre todo a partir de cierta edad, paciencia... ', 0.8102357071340037), ('@LovNaty Tu vida ha parido a un grandisimo hijo de la gran p... , un maravilloso hombre!!. ', 0.7749119307900135)]
print(pipeline.predict_proba(['JA JA JA JA JA']))
# print(pipeline.decision_function(['JA JA JA JA JA']))
[[0.00652915 0.06819939 0.02362334 0.90164813]]
Acá parece que las repeticiones afectan mucho!
También se puede ver para una instancia particular, qué features
from sentiment.analysis import print_feature_weights_for_item
x = sorted_X2[0][0]
print_feature_weights_for_item(vect, clf, x)
bandera [-0.21837896 -0.06875349 -0.18659394 0.40750473] como [ 0.28427726 -0.02419825 -0.30479818 -0.02135281] de [ 0.14313646 -0.05570942 -0.17630761 -0.05663444] el [ 0.12529257 0.14119337 -0.35910615 -0.01771959] gran [-0.77341637 0.16377957 -1.79116806 1.43934252] hijos [-0.24858589 0.46061136 0.04537216 -0.41909758] ja [-0.67403068 0.09710408 -0.70453954 0.80007021] japón [ 0.07483029 -0.04713823 -0.05095741 0.03802967] la [ 0.18916709 0.03099955 -0.39759961 -0.00821768] puta [ 1.14006978 -0.41941094 -0.58078613 -0.61462698] teneis [ 0.09455496 0.19996237 0.17334524 -0.23311611]
Veamos por ejemplo cómo eliminar URLs y menciones de usuarios usando expresiones regulares. Tomemos un tweet de ejemplo que tenga ambas cosas:
x = [x for x in X if 'http' in x and '@' in x][0]
x
'Ha ardido una caravana aparcada al lado de la playa \nFuego ya sofocado por los bomberos @… https://t.co/LgbW9ryGWy'
mentions = r'(?:@[^\s]+)' # una arroba seguida de uno o más caracteres que no son de espaciado
urls = r'(?:https?\://t.co/[\w]+)' # una URL http o https. \w acepta letras, números y '_'.
import re
# re.sub(mentions, '', x)
re.sub(urls, '', x)
'Ha ardido una caravana aparcada al lado de la playa \nFuego ya sofocado por los bomberos @… '
Veamos una forma simple de manejar las negaciones:
# x = [x for x in X if 'no' in x.split()][0]
# x
x = 'las tengo pero aún no las he leído . Caerán prontito'
tokens = x.split()
new_tokens = []
negate = False
for token in tokens:
if token in ['no', 'tampoco']:
negate = True
elif token == '.':
negate = False
elif negate:
token = 'NOT_' + token
new_tokens.append(token)
' '.join(new_tokens)
'las tengo pero aún no NOT_las NOT_he NOT_leído . Caerán prontito'
Los emojis no deben ser filtrados ya que expresan sentimiento. Veamos
# pip install emoji
import emoji
emojis = set(emoji.UNICODE_EMOJI)
x = [x for x in X if emojis & set(x.split())][0] # buscamos algún ejemplo con emojis
print(x)
@Sakura_Abril Ow Bueno, no pasa nada, cuando puedas confirmarlo, estoy aquí 😊 Y si no pudieras de cosplay pero sí a la expo, +
import re
token_pattern = r"(?u)\b\w\w+\b" # este es el patrón de tokenización que usa el count vectorizer
re.findall(token_pattern, x)
['Sakura_Abril', 'Ow', 'Bueno', 'no', 'pasa', 'nada', 'cuando', 'puedas', 'confirmarlo', 'estoy', 'aquí', 'si', 'no', 'pudieras', 'de', 'cosplay', 'pero', 'sí', 'la', 'expo']
Podemos ver que este tokenizador elimina los emojis y la puntuación. Veamos el tokenizador de NLTK:
from nltk import word_tokenize
word_tokenize(x)
['@', 'Sakura_Abril', 'Ow', 'Bueno', ',', 'no', 'pasa', 'nada', ',', 'cuando', 'puedas', 'confirmarlo', ',', 'estoy', 'aquí', '😊', 'Y', 'si', 'no', 'pudieras', 'de', 'cosplay', 'pero', 'sí', 'a', 'la', 'expo', ',', '+']
¡Mejor!