<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Aprendizaje out-of-core
Problemas de escalabilidad
Las clases sklearn.feature_extraction.text.CountVectorizer y sklearn.feature_extraction.text.TfidfVectorizer tienen una serie de problemas de escalabilidad que provienen de la forma en que se utiliza, a nivel interno, el atributo vocabulary_ (que es un diccionario Python) para convertir los nombres de las características (cadenas) a índices enteros de características.
Los principales problemas de escalabilidad son
Step1:  El vocabulario se utiliza en la fase transform para construir la matriz de ocurrencias
Step2:  Vamos a realizar un nuevo fit con un corpus algo más grande
Step3:  El atributo vocabulary_ crece (en escala logarítmica) con respecto al tamaño del conjunto de entrenamiento. Observa que no podemos construir los vocabularios en paralelo para cada documento de texto ya que hay algunas palabras que son comunes y necesitaríamos alguna estructura compartida o barrera de sincronización (aumentando la complejidad de implementar el entrenamiento, sobre todo si queremos distribuirlo en un cluster).
Con este nuevo vocabulario, la dimensionalidad del espacio de salida es mayor
Step4:  El dataset de películas IMDb
Para mostrar los problemas de escalabilidad con los vocabularios basados en vectorizadores, vamos a cargar un dataset realista que proviene de una tarea típica de clasificación de textos
Step5:  Ahora, vamos a cargarlos en nuestra sesión activa usando la función load_files de scikit-learn
Step6:  <div class="alert alert-warning">
    <b>NOTA</b>
Step7:  En particular, solo estamos interesados en los arrays data y target.
Step8:  Como puedes comprobar, el array 'target' consiste en valores 0 y 1, donde el 0 es una revisión negativa y el 1 representa una positiva.
El truco del hashing
Recuerda la representación bag-of-words que se obtenía usando un vectorizador basado en vocabulario
Step9:  La conversión no tiene estado y la dimensionalidad del espacio de salida se fija a priori (aquí usamos módulo 2 ** 20, que significa aproximadamente que tenemos un millón de dimensiones, $2^{20}$). Esto hace posible evitar las limitaciones del vectorizador de vocabulario, tanto a nivel de paralelización como de poder aplicar aprendizaje online.
La clase HashingVectorizer es una alternativa a CountVectorizer (o a TfidfVectorizer si consideramos use_idf=False) que aplica internamente la función de hash llamada murmurhash
Step10:  Comparte la misma estructura de preprocesamiento, generación de tokens y análisis
Step11:  Podemos vectorizar nuestros datasets en matriz dispersa de scipy de la misma forma que hubiéramos hecho con CountVectorizer o TfidfVectorizer, excepto que podemos llamar directamente al método transform. No hay necesidad de llamar a fit porque el HashingVectorizer no se entrena, las transformaciones están prefijadas.
Step12:  La dimensión de salida se fija de antemano a n_features=2 ** 20 (valor por defecto) para minimizar la probabilidad de colisión en la mayoría de problemas de clasificación (1M de pesos en el atributo coef_)
Step13:  Ahora vamos a comparar la eficiencia computacional de HashingVectorizer con respecto a CountVectorizer
Step14:  Como puedes observar, HashingVectorizer es mucho más rápido que Countvectorizer.
Por último, vamos a entrenar un clasificador LogisticRegression en los datos de entrenamiento de IMDb
Step15:  Aprendizaje Out-of-Core
El aprendizaje Out-of-Core consiste en entrenar un modelo de aprendizaje automático usando un dataset que no cabe en memoria RAM. Requiere las siguientes condiciones
Step16:  Ahora vamos a crear el array de etiquetas
Step17:  Ahora vamos a implementar la función batch_train function
Step18:  Ahora vamos a utilizar la clase un SGDClassifier con un coste logístico en lugar de LogisticRegression. SGD proviene de stochastic gradient descent, un algoritmo de optimización que optimiza los pesos de forma iterativa ejemplo a ejemplo, lo que nos permite pasarle los datos en grupos.
Como empleamos el SGDClassifier con la configuración por defecto, entrenará el clasificador en 25*1000=25000 documentos (lo que puede llevar algo de tiempo).
Step19:  Al terminar, evaluemos el rendimiento
<ASSISTANT_TASK:>
Python Code:
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer(min_df=1)
vectorizer.fit([
    "The cat sat on the mat.",
])
vectorizer.vocabulary_
Explanation: Aprendizaje out-of-core
Problemas de escalabilidad
Las clases sklearn.feature_extraction.text.CountVectorizer y sklearn.feature_extraction.text.TfidfVectorizer tienen una serie de problemas de escalabilidad que provienen de la forma en que se utiliza, a nivel interno, el atributo vocabulary_ (que es un diccionario Python) para convertir los nombres de las características (cadenas) a índices enteros de características.
Los principales problemas de escalabilidad son:
Uso de memoria del vectorizador de texto: todas las representaciones textuales de características se cargan en memoria.
Problemas de paralelización para extracción de características: el atributo vocabulary_ es compartido, lo que conlleva que sea difícil la sincronización y por tanto que se produzca una sobrecarga.
Imposibilidad de realizar aprendizaje online, out-of-core o streaming: el atributo vocabulary_ tiene que obtenerse a partir de los datos y su tamaño no se puede conocer hasta que no realizamos una pasada completa por toda la base de datos de entrenamiento.
Para entender mejor estos problemas, analicemos como trabaja el atributo vocabulary_. En la fase de fit se identifican los tokens del corpus de forma unívoca, mediante un índice entero, y esta correspondencia se guarda en el vocabulario:
End of explanation
X = vectorizer.transform([
    "The cat sat on the mat.",
    "This cat is a nice cat.",
]).toarray()
print(len(vectorizer.vocabulary_))
print(vectorizer.get_feature_names())
print(X)
Explanation: El vocabulario se utiliza en la fase transform para construir la matriz de ocurrencias:
End of explanation
vectorizer = CountVectorizer(min_df=1)
vectorizer.fit([
    "The cat sat on the mat.",
    "The quick brown fox jumps over the lazy dog.",
])
vectorizer.vocabulary_
Explanation: Vamos a realizar un nuevo fit con un corpus algo más grande:
End of explanation
X = vectorizer.transform([
    "The cat sat on the mat.",
    "This cat is a nice cat.",
]).toarray()
print(len(vectorizer.vocabulary_))
print(vectorizer.get_feature_names())
print(X)
Explanation: El atributo vocabulary_ crece (en escala logarítmica) con respecto al tamaño del conjunto de entrenamiento. Observa que no podemos construir los vocabularios en paralelo para cada documento de texto ya que hay algunas palabras que son comunes y necesitaríamos alguna estructura compartida o barrera de sincronización (aumentando la complejidad de implementar el entrenamiento, sobre todo si queremos distribuirlo en un cluster).
Con este nuevo vocabulario, la dimensionalidad del espacio de salida es mayor:
End of explanation
import os
train_path = os.path.join('datasets', 'IMDb', 'aclImdb', 'train')
test_path = os.path.join('datasets', 'IMDb', 'aclImdb', 'test')
Explanation: El dataset de películas IMDb
Para mostrar los problemas de escalabilidad con los vocabularios basados en vectorizadores, vamos a cargar un dataset realista que proviene de una tarea típica de clasificación de textos: análisis de sentimientos en texto. El objetivo es discernir entre revisiones positivas y negativas a partir de la base de datos de Internet Movie Database (IMDb).
En las siguientes secciones, vamos a usar el siguiente dataset subset de revisiones de películas de IMDb, que has sido recolectado por Maas et al. 
A. L. Maas, R. E. Daly, P. T. Pham, D. Huang, A. Y. Ng, and C. Potts. Learning Word Vectors for Sentiment Analysis. In the proceedings of the 49th Annual Meeting of the Association for Computational Linguistics: Human Language Technologies, pages 142–150, Portland, Oregon, USA, June 2011. Association for Computational Linguistics. 
Este dataset contiene 50,000 revisiones de películas, divididas en 25,000 ejemplos de entrenamiento y 25,000 ejemplos de test. Las revisiones se etiquetan como negativas (neg) o positivas (pos). De hecho, las negativas recibieron $\le 4$ estrellas en IMDb; las positivas recibieron $\ge 7$ estrellas. Las revisiones neutrales no se incluyeron en el dataset.
Asumiendo que ya habéis ejecutado el script fetch_data.py, deberías tener disponibles los siguientes ficheros:
End of explanation
from sklearn.datasets import load_files
train = load_files(container_path=(train_path),
                   categories=['pos', 'neg'])
test = load_files(container_path=(test_path),
                  categories=['pos', 'neg'])
Explanation: Ahora, vamos a cargarlos en nuestra sesión activa usando la función load_files de scikit-learn:
End of explanation
train.keys()
Explanation: <div class="alert alert-warning">
    <b>NOTA</b>:
     <ul>
      <li>
      Ya que el dataset de películas contiene 50,000 ficheros individuales de texto, ejecutar el código anterior puede llevar bastante tiempo.
      </li>
    </ul>
</div>
La función load_files  ha cargado los datasets en objetos sklearn.datasets.base.Bunch, que son diccionarios de Python:
End of explanation
import numpy as np
for label, data in zip(('ENTRENAMIENTO', 'TEST'), (train, test)):
    print('\n\n%s' % label)
    print('Número de documentos:', len(data['data']))
    print('\n1er documento:\n', data['data'][0])
    print('\n1era etiqueta:', data['target'][0])
    print('\nNombre de las clases:', data['target_names'])
    print('Conteo de las clases:', 
          np.unique(data['target']), ' -> ',
          np.bincount(data['target']))
Explanation: En particular, solo estamos interesados en los arrays data y target.
End of explanation
from sklearn.utils.murmurhash import murmurhash3_bytes_u32
# Codificado para compatibilidad con Python 3
for word in "the cat sat on the mat".encode("utf-8").split():
    print("{0} => {1}".format(
        word, murmurhash3_bytes_u32(word, 0) % 2 ** 20))
Explanation: Como puedes comprobar, el array 'target' consiste en valores 0 y 1, donde el 0 es una revisión negativa y el 1 representa una positiva.
El truco del hashing
Recuerda la representación bag-of-words que se obtenía usando un vectorizador basado en vocabulario:
<img src="figures/bag_of_words.svg" width="100%">
Para solventar las limitaciones de los vectorizadores basados en vocabularios, se puede utilizar el truco del hashing. En lugar de construir y almacenar una conversión explícita de los nombres de las características a los índices de las mismas dentro de un diccionario Python, podemos aplicar una función de hash y el operador módulo:
<img src="figures/hashing_vectorizer.svg" width="100%">
Podemos acceder a más información y a las referencias a los artículos originales en el siguiente sitio web, y una descripción más sencilla en este otro sitio.
End of explanation
from sklearn.feature_extraction.text import HashingVectorizer
h_vectorizer = HashingVectorizer(encoding='latin-1')
h_vectorizer
Explanation: La conversión no tiene estado y la dimensionalidad del espacio de salida se fija a priori (aquí usamos módulo 2 ** 20, que significa aproximadamente que tenemos un millón de dimensiones, $2^{20}$). Esto hace posible evitar las limitaciones del vectorizador de vocabulario, tanto a nivel de paralelización como de poder aplicar aprendizaje online.
La clase HashingVectorizer es una alternativa a CountVectorizer (o a TfidfVectorizer si consideramos use_idf=False) que aplica internamente la función de hash llamada murmurhash:
End of explanation
analyzer = h_vectorizer.build_analyzer()
analyzer('Esta es una frase de prueba.')
Explanation: Comparte la misma estructura de preprocesamiento, generación de tokens y análisis:
End of explanation
docs_train, y_train = train['data'], train['target']
docs_valid, y_valid = test['data'][:12500], test['target'][:12500]
docs_test, y_test = test['data'][12500:], test['target'][12500:]
Explanation: Podemos vectorizar nuestros datasets en matriz dispersa de scipy de la misma forma que hubiéramos hecho con CountVectorizer o TfidfVectorizer, excepto que podemos llamar directamente al método transform. No hay necesidad de llamar a fit porque el HashingVectorizer no se entrena, las transformaciones están prefijadas.
End of explanation
h_vectorizer.transform(docs_train)
Explanation: La dimensión de salida se fija de antemano a n_features=2 ** 20 (valor por defecto) para minimizar la probabilidad de colisión en la mayoría de problemas de clasificación (1M de pesos en el atributo coef_):
End of explanation
h_vec = HashingVectorizer(encoding='latin-1')
%timeit -n 1 -r 3 h_vec.fit(docs_train, y_train)
count_vec =  CountVectorizer(encoding='latin-1')
%timeit -n 1 -r 3 count_vec.fit(docs_train, y_train)
Explanation: Ahora vamos a comparar la eficiencia computacional de HashingVectorizer con respecto a CountVectorizer:
End of explanation
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
h_pipeline = Pipeline([
    ('vec', HashingVectorizer(encoding='latin-1')),
    ('clf', LogisticRegression(random_state=1)),
])
h_pipeline.fit(docs_train, y_train)
print('Accuracy de entrenamiento', h_pipeline.score(docs_train, y_train))
print('Accuracy de validación', h_pipeline.score(docs_valid, y_valid))
import gc
del count_vec
del h_pipeline
gc.collect()
Explanation: Como puedes observar, HashingVectorizer es mucho más rápido que Countvectorizer.
Por último, vamos a entrenar un clasificador LogisticRegression en los datos de entrenamiento de IMDb:
End of explanation
train_path = os.path.join('datasets', 'IMDb', 'aclImdb', 'train')
train_pos = os.path.join(train_path, 'pos')
train_neg = os.path.join(train_path, 'neg')
fnames = [os.path.join(train_pos, f) for f in os.listdir(train_pos)] +\
         [os.path.join(train_neg, f) for f in os.listdir(train_neg)]
fnames[:3]
Explanation: Aprendizaje Out-of-Core
El aprendizaje Out-of-Core consiste en entrenar un modelo de aprendizaje automático usando un dataset que no cabe en memoria RAM. Requiere las siguientes condiciones:
Una capa de extracción de características con una dimensionalidad de salida fija.
Saber la lista de clases de antemano (en este caso, sabemos que hay tweets positivos y negativos).
Un algoritmo de aprendizaje automático que soporte aprendizaje incremental (método partial_fit en scikit-learn).
En la siguientes secciones, vamos a configurar una función simple de entrenamiento iterativo de un SGDClassifier.
Pero primero cargamos los nombres de los ficheros en una lista de Python:
End of explanation
y_train = np.zeros((len(fnames), ), dtype=int)
y_train[:12500] = 1
np.bincount(y_train)
Explanation: Ahora vamos a crear el array de etiquetas:
End of explanation
from sklearn.base import clone
def batch_train(clf, fnames, labels, iterations=25, batchsize=1000, random_seed=1):
    vec = HashingVectorizer(encoding='latin-1')
    idx = np.arange(labels.shape[0])
    c_clf = clone(clf)
    rng = np.random.RandomState(seed=random_seed)
    
    for i in range(iterations):
        rnd_idx = rng.choice(idx, size=batchsize)
        documents = []
        for i in rnd_idx:
            with open(fnames[i], 'r') as f:
                documents.append(f.read())
        X_batch = vec.transform(documents)
        batch_labels = labels[rnd_idx]
        c_clf.partial_fit(X=X_batch, 
                          y=batch_labels, 
                          classes=[0, 1])
      
    return c_clf
Explanation: Ahora vamos a implementar la función batch_train function:
End of explanation
from sklearn.linear_model import SGDClassifier
sgd = SGDClassifier(loss='log', random_state=1)
sgd = batch_train(clf=sgd,
                  fnames=fnames,
                  labels=y_train)
Explanation: Ahora vamos a utilizar la clase un SGDClassifier con un coste logístico en lugar de LogisticRegression. SGD proviene de stochastic gradient descent, un algoritmo de optimización que optimiza los pesos de forma iterativa ejemplo a ejemplo, lo que nos permite pasarle los datos en grupos.
Como empleamos el SGDClassifier con la configuración por defecto, entrenará el clasificador en 25*1000=25000 documentos (lo que puede llevar algo de tiempo).
End of explanation
vec = HashingVectorizer(encoding='latin-1')
sgd.score(vec.transform(docs_test), y_test)
Explanation: Al terminar, evaluemos el rendimiento:
End of explanation
<END_TASK>