#!/usr/bin/env python
# coding: utf-8

# # Clasificación de textos con deep learning

# In[1]:


import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings("ignore")


# In[2]:


df = pd.read_csv('spam.csv', encoding = "ISO-8859-1")


# In[3]:


df


# Borramos columnas innecesarias

# In[4]:


df = df.iloc [:,0:2]


# In[5]:


df.head()


# In[6]:


df.columns = ['Target','Email']


# In[7]:


from nltk.corpus import stopwords
from nltk import *
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.stem import WordNetLemmatizer
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split


# Eliminamos stoprwords

# In[8]:


stop = stopwords.words('english')
df['Email'] = df['Email'].apply(lambda x: " ".join(x for x in x.split() if x not in stop))


# In[9]:


df


# Eliminar símbolos puntuación...

# In[10]:


df['Email'] = df['Email'].apply(lambda x: re.sub('[!@#$:).;,?&]',' ', x.lower()))


# In[11]:


df


# In[12]:


df['Email'] = df['Email'].apply(lambda x:  re.sub('[áéíóúå]','', x))


# In[13]:


df


# Separamos los datos de la variable clase

# In[14]:


df2=df[['Email', 'Target']]


# Dividimos los datos en train y test en una proporción de 80:20

# In[15]:


train, test = train_test_split(df2, test_size=0.2)
# Define the sequence lengths, max number of words and embedding dimensions
# Sequence length of each sentence. If more, truncate. If less, pad with zeros


# Definimos las variables longuitud máxima de la sentencia y las n palabras más frecuentes

# In[16]:


long_max_sentencia = 300
num_palabras = 20000


# Keras nos permite usar Redes Neuronales con datos de texto.
# Para ello necesitamos que los datos de entrada, esteń codificados, de manera que cada palabra esté representada como un número entero único.

# In[17]:


from keras.preprocessing.text import Tokenizer

# Tokenizamos las 20000 palabras más frecuentes
tokenizer = Tokenizer(num_palabras)

# Creamos un índice de vocabulario basado en la frecuencia de las palabras
tokenizer.fit_on_texts(train.Email)

# Transformamos cada texto en los textos en una secuencia de números.
# Básicamente cogemos cada palabra y la sustitímos por el valor entero
# generado en fit_on_texts
train_sequences = tokenizer.texts_to_sequences(train.Email)
test_sequences = tokenizer.texts_to_sequences(test.Email)


# In[18]:


tokenizer.index_word 


# In[19]:


train_sequences [0:5]


# In[20]:


# Creamos un diccionario que contenga las palabras y su índice (el opuesto a index_word)
word_index = tokenizer.word_index
print('Tenemos %s tokens únicos.' % len(word_index))


# In[21]:


from tensorflow.keras.preprocessing.sequence import pad_sequences

# con pad_sequences conseguimos que todas las secuencias de datos tengan la misma longuitud
train_data = pad_sequences(train_sequences, maxlen=long_max_sentencia)
test_data = pad_sequences(test_sequences, maxlen=long_max_sentencia)


# In[22]:


print(train_data.shape)
print(test_data.shape)


# In[23]:


train_labels = train['Target']
test_labels = test['Target']


# In[206]:


train_labels.head()


# In[24]:


from sklearn.preprocessing import LabelEncoder


# Convertimos la matriz de caracteres en un matriz numérica.  Asignamos los niveles a etiquetas únicas.  (Similar a OneHot Encoding)

# In[25]:


le = LabelEncoder()
le.fit(train_labels)
train_labels = le.transform(train_labels)
test_labels = le.transform(test_labels)


# In[26]:


print(le.classes_)
print(np.unique(train_labels, return_counts=True))
print(np.unique(test_labels, return_counts=True))


# Convertimos los vectores train_labels y test_labels en una matriz binaria

# In[27]:


from tensorflow.keras.utils import to_categorical

labels_train = to_categorical(np.asarray(train_labels))
labels_test = to_categorical(np.asarray(test_labels))
print('Dimensiones del tensor con los datos de entrenamiento:', train_data.shape)
print('Dimensiones del tensor con las etiquetas de entrenamiento:', labels_train.shape)
print('Dimensiones del tensor con los datos de test:', test_data.shape)
print('Dimensiones del tensor con las etiquetas de test:', labels_test.shape)


# In[28]:


train_data[0:10]


# In[29]:


labels_train


# In[30]:


test_data[0:10]


# In[31]:


labels_test


# Podemos entrenar un modelo en Keras para que acepte texto codificado como lo hemos hecho, a través de una capa **Embedding** (incrustación).
# Se trata de una capa flexible que puede utilizarse de diversas maneras:
# 
# * Se puede utilizar sola para aprender una incrustación de palabras que se puede guardar y utilizar en otro modelo más adelante.
# * Puede utilizarse como parte de un modelo de aprendizaje profundo en el que la incrustación se aprende junto con el propio modelo.
# * Puede utilizarse para cargar un modelo de incrustación de palabras preentrenado, un tipo de aprendizaje por transferencia.
# 
# La capa de incrustación se define como la primera capa oculta de una red. Debe especificar 3 argumentos:
# 
# * **input_dim**: Es el tamaño del vocabulario en los datos de texto. Por ejemplo, si los datos están codificados en números enteros con valores entre 0 y 10, el tamaño del vocabulario sería de 11 palabras.
# * **output_dim**: Es el tamaño del espacio vectorial en el que se incrustarán las palabras. Define el tamaño de los vectores de salida de esta capa para cada palabra. Por ejemplo, puede ser 32 o 100 o incluso más grande. Aquí tenemos que probar diferentes valores para resolver el problema.
# * **input_length**: Esta es la longitud de las secuencias de entrada. Por ejemplo, si todos los documentos de entrada están compuestos por 1000 palabras, ésto sería 1000.

# In[32]:


tamanio_incrustacion = 100
print(long_max_sentencia)


# # Modelado y predicción

# ## Clasificador con una Red Neuronal Convolucional

# In[33]:


import sys, os, re, csv, codecs
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.layers import Dense, Input, LSTM, Embedding, Dropout, Activation
from keras.layers import Bidirectional, GlobalMaxPool1D, Conv1D, SimpleRNN
from keras.models import Model
from keras.models import Sequential
from keras import initializers, regularizers, constraints, optimizers, layers
from keras.layers import Dense, Input, Flatten, Dropout, BatchNormalization
from keras.layers import Conv1D, MaxPooling1D, Embedding
from keras.models import Sequential


# In[63]:


model = Sequential()
model.add(Embedding(num_palabras, tamanio_incrustacion, input_length=long_max_sentencia))
model.add(Dropout(0.5))
model.add(Conv1D(128, 5, activation='relu'))
model.add(MaxPooling1D(5))
model.add(Dropout(0.5))
model.add(BatchNormalization())
model.add(Conv1D(128, 5, activation='relu'))
model.add(MaxPooling1D(5))
model.add(Dropout(0.5))
model.add(BatchNormalization())
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dense(2, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['acc'])


# In[67]:


from keras.callbacks import TensorBoard
# para gpu
tensorboard_cb = TensorBoard(os.getcwd())
model.fit(train_data, 
          labels_train,
          batch_size=64,
          epochs=5,
          validation_data=(test_data, labels_test),
          # para gpu
          callbacks=[tensorboard_cb]
          )


# In[76]:


prediccion1=model.predict(test_data)
prediccion1


# ## Evaluamos...

# In[80]:


import sklearn
from sklearn.metrics import precision_recall_fscore_support as score 
precision, recall, fscore, support = score(labels_test, prediccion1.round())
print('precision: {}'.format(precision))
print('recall: {}'.format(recall))
print('fscore: {}'.format(fscore))
print('support: {}'.format(support))
print("############################")
print(sklearn.metrics.classification_report(labels_test,prediccion1.round()))


# ## Clasificador con una Red Neuronal Recurrente

# In[41]:


from keras.layers.recurrent import SimpleRNN


# In[70]:


model = Sequential()
model.add(Embedding(num_palabras, tamanio_incrustacion, input_length=long_max_sentencia))
model.add(SimpleRNN(2, input_shape=(None,1)))
model.add(Dense(2,activation='softmax'))
model.compile(loss = 'binary_crossentropy', optimizer='adam',metrics = ['accuracy'])


# In[72]:


# para gpu
tensorboard_cb = TensorBoard(os.getcwd())

model.fit(train_data, 
          labels_train,
          batch_size=16,
          epochs=5,
          validation_data=(test_data, labels_test),
          # para gpu
          callbacks=[tensorboard_cb]
          )


# In[73]:


prediccion_Srnn=model.predict(test_data)
prediccion_Srnn


# ## Evaluamos...

# In[78]:


precision, recall, fscore, support = score(labels_test, prediccion_Srnn.round())


# In[79]:


print('precision: {}'.format(precision))
print('recall: {}'.format(recall))
print('fscore: {}'.format(fscore))
print('support: {}'.format(support))
print("############################")
print(sklearn.metrics.classification_report(labels_test, prediccion_Srnn.round()))


# ## Clasificador con una Red Neuronal LSTM (LongShort-Term Memory)

# Las LSTM son un tipo especial de redes recurrentes. La característica principal de las redes recurrentes es que la información puede persistir introduciendo bucles en el diagrama de la red, por lo que, básicamente, pueden «recordar» estados previos y utilizar esta información para decidir cuál será el siguiente. Esta característica las hace muy adecuadas para manejar series cronológicas. Mientras las redes recurrentes estándar pueden modelar dependencias a corto plazo (es decir, relaciones cercanas en la serie cronológica), las LSTM pueden aprender dependencias largas, por lo que se podría decir que tienen una «memoria» a más largo plazo.

# En el campo del internet de las cosas (IoT), el mantenimiento predictivo es otro ámbito en el que los datos de serie cronológica son muy importantes. Entre otros temas, el mantenimiento predictivo incluye la gestión de averías: predicción, clasificación y diagnóstico. Una rutina de mantenimiento predictivo para un dispositivo o conjunto de dispositivos determinados (ordenadores, generadores eléctricos, máquinas de fabricación, etc.) registra una serie de lecturas de datos a lo largo del tiempo y utiliza estos datos para hallar patrones que sirvan para predecir averías o comportamientos anómalos. Esta predicción se puede utilizar para activar medidas correctivas y así evitar efectos indeseados en el sistema.

# In[81]:


model = Sequential()
model.add(Embedding(num_palabras, tamanio_incrustacion, input_length=long_max_sentencia))
# para usar con cpu
# model.add(LSTM(16, activation='relu', return_sequences=True))
# para usar con gpu
model.add(LSTM(16, activation='tanh', unroll=True, return_sequences=True))
model.add(Dropout(0.2))
model.add(BatchNormalization())
model.add(Flatten())
model.add(Dense(2,activation='softmax'))
model.compile(loss = 'binary_crossentropy', optimizer='adam',metrics = ['accuracy'])

# para gpu
tensorboard_cb = TensorBoard(os.getcwd())

model.fit(train_data, 
          labels_train,
          batch_size=16,
          epochs=5,
          validation_data=(test_data, labels_test),
          # para gpu
          callbacks=[tensorboard_cb]
          )


# In[82]:


prediccion_lstm=model.predict(test_data)
prediccion_lstm


# ## Evaluamos...

# In[83]:


precision, recall, fscore, support = score(labels_test, prediccion_lstm.round())
print('precision: {}'.format(precision))
print('recall: {}'.format(recall))
print('fscore: {}'.format(fscore))
print('support: {}'.format(support))
print("############################")
print(sklearn.metrics.classification_report(labels_test, prediccion_lstm.round()))


# In bidirectional LSTMs, inputs are fed in two ways: one
# from previous to future and the other going backward from future to
# past, helping in learning future representation as well. Bidirectional
# LSTMs are known for producing very good results as they are capable of
# understanding the context better.

# ## Clasificación con Red Neuronal Bidireccional LSTM

# Este tipo de redes, permiten correlacionar no sólo los datos pasados, sino también los futuros.

# ![lstm bidireccional.jpeg](attachment:a77be5a9-25a3-49b5-811a-a6904d9e8871.jpeg)

# In[84]:


model = Sequential()
model.add(Embedding(num_palabras,tamanio_incrustacion,input_length=long_max_sentencia))
# para cpu
# model.add(Bidirectional(LSTM(16, return_sequences=True, dropout=0.1, recurrent_dropout=0.1)))
# para gpu
model.add(Bidirectional(LSTM(16, return_sequences=True, dropout=0.1, recurrent_dropout=0)))
model.add(Conv1D(16, kernel_size = 3, padding = "valid", kernel_initializer = "glorot_uniform"))
model.add(GlobalMaxPool1D())
model.add(Dense(50, activation="relu"))
model.add(Dropout(0.1))
model.add(Dense(2,activation='softmax'))
model.compile(loss = 'binary_crossentropy', optimizer='adam',metrics = ['accuracy'])

# para gpu
tensorboard_cb = TensorBoard(os.getcwd())

model.fit(train_data, 
          labels_train,
          batch_size=16,
          epochs=3,
          validation_data=(test_data, labels_test),
          # para gpu
          callbacks=[tensorboard_cb]
          )


# In[85]:


prediccion_blstm=model.predict(test_data)
prediccion_blstm


# In[86]:


precision, recall, fscore, support = score(labels_test, prediccion_blstm.round())
print('precision: {}'.format(precision))
print('recall: {}'.format(recall))
print('fscore: {}'.format(fscore))
print('support: {}'.format(support))
print("############################")
print(sklearn.metrics.classification_report(labels_test, prediccion_blstm.round()))

