File size: 6,061 Bytes
a87c588
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import numpy as np
import matplotlib.pyplot as plt
from collections import Counter
from string import punctuation

class Tokenizer():
  def __init__(self):

    self.vocab = None
    self.pad_idx = 0
    self.unk_idx = 1

  def preprocessing(self, texts: list):

    '''
    Método que pre procesa un documento, transformando las palabras a minúsculas, eliminando puntuaciones y caracterés "\n". Devuelve los textos pre procesados.
    texts: documentos a ser procesados (list)
    '''

    texts = [text.lower() for text in texts] # lowercase
    texts = [''.join([c for c in text if c not in punctuation]) for text in texts] # delete punctuation
    texts = [text.split('\n') for text in texts] # eliminate \n
    texts = [' '.join(text) for text in texts]

    return texts

  def train(self, texts: list):

    '''
    Método que entrena el tokenizador, construyendo un vocabulario de tokens y su codificación respectiva.
    texts: documentos que el tokenizador usa para construir el vocabulario (list)
    '''

    # preprocessing
    texts = self.preprocessing(texts)

    # joint text
    megadoc = ' '.join(texts)
    words = megadoc.split()

    self.counts = Counter(words) # Construye un diccionario de palabras. Las claves son las palabras y los valores son la frecuencia
    self.vocab = sorted(self.counts, key = self.counts.get, reverse = True) # Ordenamos la palabras por frecuencia
    self.vocab_to_int = {word: ii for ii, word in enumerate(self.vocab, 2)} # Construimos diccionario para mapear palabra a número entero. Empezamos los índices en 2
    self.vocab_to_int[self.unk_idx] = '<unk>' # token para palabras no reconocidas
    self.vocab_to_int[self.pad_idx] = '<pad>' # token para padding

    self.int_to_vocab = {value: key for key, value in self.vocab_to_int.items()}

  def encode(self, texts: list):

    '''
    Método que usa el vocabulario construido para codificar textos. Devuelve los textos codificados.
    texts: Documentos a ser codificados (list)
    '''

    if self.vocab_to_int is None:
      raise ValueError('Debes entrenar el tokenizador primero')

    encoded_text = [[self.vocab_to_int[word] if word in self.vocab_to_int.keys() else 1 for word in text.split()] for text in texts]

    return encoded_text

  def decode(self, texts: list):

    '''
    Método que usa el vocabulario construido para decodificar textos. Devuelve los textos decodificados.
    texts: Documentos a ser decodificados (list)
    '''

    if self.vocab is None:
      raise ValueError('Debes entrenar el tokenizador primero')
    
    decoded_text = [[self.int_to_vocab[word] if word in self.int_to_vocab.keys() else 'unk' for word in text] for text in texts]

    return decoded_text

  def filter_text(self, encoded_text: list, encoded_labels: list, min_tokens = 1, max_tokens = 1e6):

    '''
    Método que filtra una colección de documentos en función de la cantidad de tokens. Devuelve la coleccion de documentos filtrados.
    encoded_text: Textos codificados a ser filtrados (list)
    encoded_labels: Etiquetas a filtrar en función del texto (list)
    min_tokens: Cantidad mínima de tokens permitida (int)
    max_tokens: Cantidad máxima de tokens permitida (int)
    '''

    print('Documentos antes de eliminación:', len(encoded_text))

    #Extraemos los índices de todos los reviews que tienen longitud cumpliendo los filtros
    filter_idx = [ii for ii, text in enumerate(encoded_text) if min_tokens <= len(text) <= max_tokens]

    #Nos quedamos solo con los reviews con longitud que cumplen los filtros
    encoded_text = [encoded_text[ii] for ii in filter_idx]

    #Lo mismo con los labels
    encoded_labels = np.array([encoded_labels[ii] for ii in filter_idx])

    print('Documentos después de eliminación:', len(encoded_text))

    return encoded_text, encoded_labels

  def padding(self, encoded_text: list, vector_size: int):
    
    '''
    Método que hace padding a una secuencia, fijando el largo de las secuencias en un número determinado vector_size:
      Las secuencias con largo mayor a vector_size, son acortadas por la derecha hasta ese valor.
      Las secuencias con largo menor a vector_size, son llenadas con 0s por la izquierda hasta completar ese valor.
    Retorna la secuencia modificada.

    encoded_text: lista con los textos a modificar (list)
    vector_size: largo de los documentos a fijar (int)
    '''

    features = np.zeros((len(encoded_text), vector_size), dtype = int)

    for i, row in enumerate(encoded_text):
      features[i, -len(row):] = np.array(row)[:vector_size]

    return features

  def tokenize(self, texts: list, 
               #vector_size: int
               ):

    '''
    Método que tokeniza documentos a partir del vocabulario construido. Devuelve los documentos codificados en un largo de tamaño vector_size
    texts: Textos a ser tokenizados (list)
    vector_size: Largo de los textos de salida (int)
    '''

    if self.vocab is None:
      raise ValueError('Debes entrenar el tokenizador primero')

    if self.vector_size is None:
      raise ValueError('Debes especificar vector_size en objeto Tokenizer (tokenizer.vector_size = x)')

    # preprocessing
    texts = self.preprocessing(texts)

    # encode
    encoded_text = self.encode(texts)

    # padding
    features = self.padding(encoded_text, self.vector_size)
  
    return features

  def graph_distribution(self, encoded_text):

    '''
    Método que grafica la distribución del largo de los documentos de entrenamiento.
    '''
    
    if self.vocab is None:
      raise ValueError('Debes entrenar el tokenizador primero')

    text_lens = Counter([len(text) for text in encoded_text]) #Contamos cuantas palabras hay en cada review

    plt.figure(figsize = (12, 6))
    plt.bar(text_lens.keys(), text_lens.values())
    plt.title('Distribución del largo de documentos en el Dataset')
    plt.xlabel('Cantidad de tokens')
    plt.ylabel('Frecuencia')
    plt.show()

  def __len__(self):
    return len(self.vocab_to_int) if self.vocab is not None else 0