Generaci贸n de texto con Python y TensorFlow/Keras

    Introducci贸n

    驴Est谩 interesado en utilizar una red neuronal para generar texto? TensorFlow y Keras se pueden usar para algunas aplicaciones asombrosas de t茅cnicas de procesamiento de lenguaje natural, incluida la generaci贸n de texto.

    En este tutorial, cubriremos la teor铆a detr谩s de la generaci贸n de texto usando redes neuronales recurrentes, espec铆ficamente una red de memoria a corto y largo plazo, implementaremos esta red en Python y la usaremos para generar texto.

    Definici贸n de t茅rminos

    Para empezar, comencemos por definir nuestros t茅rminos. Puede resultar dif铆cil entender por qu茅 se ejecutan ciertas l铆neas de c贸digo a menos que tenga una comprensi贸n decente de los conceptos que se est谩n reuniendo.

    TensorFlow

    TensorFlow es una de las bibliotecas de Machine Learning m谩s utilizadas en Python, y se especializa en la creaci贸n de redes neuronales profundas. Las redes neuronales profundas se destacan en tareas como el reconocimiento de im谩genes y el reconocimiento de patrones en el habla. TensorFlow fue dise帽ado por Google Brain y su poder radica en su capacidad para unir muchos nodes de procesamiento diferentes.

    Dificultad

    Mientras tanto, Keras es una interfaz de programaci贸n de aplicaciones o API. Keras hace uso de las funciones y habilidades de TensorFlow, pero agiliza la implementaci贸n de las funciones de TensorFlow, lo que hace que la construcci贸n de una red neuronal sea mucho m谩s simple y f谩cil. Los principios fundamentales de Keras son la modularidad y la facilidad de uso, lo que significa que, si bien Keras es bastante poderoso, es f谩cil de usar y escalar.

    Procesamiento natural del lenguaje

    El procesamiento del lenguaje natural (PNL) es exactamente lo que parece, las t茅cnicas utilizadas para permitir que las computadoras comprendan el lenguaje humano natural, en lugar de tener que interactuar con las personas a trav茅s de lenguajes de programaci贸n. El procesamiento del lenguaje natural es necesario para tareas como la clasificaci贸n de documentos de Word o la creaci贸n de un chatbot.

    Corpus

    Un corpus es una gran colecci贸n de texto y, en el sentido del Machine Learning, un corpus puede considerarse como los datos de entrada de su modelo. El corpus contiene el texto que desea que aprenda el modelo.

    Es com煤n dividir un corpus grande en conjuntos de entrenamiento y prueba, utilizando la mayor parte del corpus para entrenar el modelo y una parte invisible del corpus para probar el modelo, aunque el conjunto de prueba puede ser un conjunto de datos completamente diferente. Por lo general, el corpus requiere un procesamiento previo para que sea apto para su uso en un sistema de Machine Learning.

    Codificaci贸n

    La codificaci贸n a veces se conoce como representaci贸n de palabras y se refiere al proceso de convertir datos de texto en una forma que un modelo de Machine Learning pueda comprender. Las redes neuronales no pueden trabajar con datos de texto sin procesar, los caracteres / palabras deben transformarse en una serie de n煤meros que la red pueda interpretar.

    El proceso real de convertir palabras en vectores num茅ricos se conoce como “tokenizaci贸n”, porque obtienes tokens que representan las palabras reales. Hay varias formas de codificar palabras como valores num茅ricos. Los m茅todos principales de codificaci贸n son la codificaci贸n one-hot y la creaci贸n de vectores densamente integrados .

    Analizaremos la diferencia entre estos m茅todos en la secci贸n de teor铆a a continuaci贸n.

    Red neuronal recurrente

    Una red neuronal b谩sica une una serie de neuronas o nodes, cada uno de los cuales toma algunos datos de entrada y los transforma con alguna funci贸n matem谩tica elegida. En una red neuronal b谩sica, los datos deben tener un tama帽o fijo, y en cualquier capa dada en la red neuronal, los datos que se pasan son simplemente las salidas de la capa anterior en la red, que luego se transforman por los pesos para esa capa.

    Por el contrario, una red neuronal recurrente se diferencia de una red neuronal “b谩sica” gracias a su capacidad para recordar entradas anteriores de capas anteriores en la red neuronal.

    Para decirlo de otra manera, las salidas de las capas en una red neuronal recurrente no est谩n influenciadas solo por los pesos y la salida de la capa anterior como en una red neuronal regular, sino que tambi茅n est谩n influenciadas por el “contexto” hasta ahora, que se deriva de entradas y salidas anteriores.

    Las redes neuronales recurrentes son 煤tiles para el procesamiento de texto debido a su capacidad para recordar las diferentes partes de una serie de entradas, lo que significa que pueden tener en cuenta las partes anteriores de una oraci贸n para interpretar el contexto.

    Memoria a corto plazo

    Las redes de memoria a largo y corto plazo (LSTM) son un tipo espec铆fico de redes neuronales recurrentes. Los LSTM tienen ventajas sobre otras redes neuronales recurrentes. Si bien las redes neuronales recurrentes generalmente pueden recordar palabras anteriores en una oraci贸n, su capacidad para preservar el contexto de entradas anteriores se degrada con el tiempo.

    Cuanto m谩s larga es la serie de entrada, m谩s “olvida” la red. Los datos irrelevantes se acumulan a lo largo del tiempo y bloquean los datos relevantes necesarios para que la red haga predicciones precisas sobre el patr贸n del texto. Esto se conoce como el problema del gradiente de desaparici贸n .

    No es necesario que comprenda los algoritmos que se ocupan del problema del gradiente de desaparici贸n (aunque puede leer m谩s al respecto aqu铆 ), pero sepa que un LSTM puede lidiar con este problema “olvidando” selectivamente la informaci贸n que se considera no esencial para la tarea en cuesti贸n. . Al suprimir la informaci贸n no esencial, el LSTM puede enfocarse solo en la informaci贸n que realmente importa, resolviendo el problema del gradiente que se desvanece. Esto hace que los LSTM sean m谩s robustos al manejar largas cadenas de texto.

    Teor铆a / Enfoque de Generaci贸n de Texto

    Codificaci贸n revisada

    Codificaci贸n One-Hot

    Como se mencion贸 anteriormente, hay dos formas principales de codificar datos de texto. Un m茅todo se denomina codificaci贸n one-hot, mientras que el otro m茅todo se denomina incrustaci贸n de palabras.

    El proceso de codificaci贸n one-hot se refiere a un m茅todo de representar texto como una serie de unos y ceros. Se crea un vector que contiene todas las palabras posibles que le interesan, a menudo todas las palabras del corpus, y una sola palabra se representa con un valor “uno” en su posici贸n respectiva. Mientras tanto, todas las dem谩s posiciones (todas las dem谩s palabras posibles) reciben un valor cero. Se crea un vector como este para cada palabra del conjunto de caracter铆sticas, y cuando los vectores se unen, el resultado es una matriz que contiene representaciones binarias de todas las palabras caracter铆sticas.

    Aqu铆 hay otra forma de pensar sobre esto: cualquier palabra dada est谩 representada por un vector de unos y ceros, con un valor de uno en una posici贸n 煤nica. El vector se ocupa esencialmente de responder a la pregunta: “驴Es esta la palabra objetivo?” Si la palabra en la lista de palabras caracter铆sticas es el objetivo, se ingresa un valor positivo (uno) all铆 y, en todos los dem谩s casos, la palabra no es el objetivo, por lo que se ingresa un cero. Por lo tanto, tiene un vector que representa solo la palabra objetivo. Esto se hace para cada palabra en la lista de caracter铆sticas.

    Las codificaciones one-hot son 煤tiles cuando necesita crear una bolsa de palabras o una representaci贸n de palabras que tenga en cuenta su frecuencia de aparici贸n. Los modelos de bolsa de palabras son 煤tiles porque, aunque son modelos simples, a煤n conservan mucha informaci贸n importante y son lo suficientemente vers谩tiles como para usarse en muchas tareas diferentes relacionadas con la PNL.

    Un inconveniente de usar codificaciones one-hot es que no pueden representar el significado de una palabra, ni pueden detectar f谩cilmente similitudes entre palabras. Si le preocupan el significado y la similitud, a menudo se utilizan incrustaciones de palabras.

    Incrustaciones de palabras

    La incrustaci贸n de palabras se refiere a representar palabras o frases como un vector de n煤meros reales, al igual que lo hace la codificaci贸n one-hot. Sin embargo, una palabra incrustada puede usar m谩s n煤meros que simplemente unos y ceros y, por lo tanto, puede formar representaciones m谩s complejas. Por ejemplo, el vector que representa una palabra ahora puede estar compuesto por valores decimales como 0.5. Estas representaciones pueden almacenar informaci贸n importante sobre palabras, como la relaci贸n con otras palabras, su morfolog铆a, su contexto, etc.

    Las incrustaciones de palabras tienen menos dimensiones que los vectores codificados en caliente, lo que obliga al modelo a representar palabras similares con vectores similares. Cada vector de palabra en una incrustaci贸n de palabras es una representaci贸n en una dimensi贸n diferente de la matriz, y la distancia entre los vectores se puede usar para representar su relaci贸n. Las incrustaciones de palabras pueden generalizarse porque las palabras sem谩nticamente similares tienen vectores similares. Los vectores de palabras ocupan una regi贸n similar de la matriz, lo que ayuda a capturar el contexto y la sem谩ntica.

    En general, los vectores one-hot son de alta dimensi贸n pero escasos y simples, mientras que las incrustaciones de palabras son de baja dimensi贸n pero densas y complejas.

    Generaci贸n a nivel de palabra vs generaci贸n a nivel de personaje

    Hay dos formas de abordar una tarea de procesamiento del lenguaje natural como la generaci贸n de texto. Puede analizar los datos y hacer predicciones sobre ellos al nivel de las palabras en el corpus o al nivel de los caracteres individuales. Tanto la generaci贸n a nivel de personaje como la generaci贸n a nivel de palabra tienen sus ventajas y desventajas.

    En general, los modelos de lenguaje a nivel de palabra tienden a mostrar una mayor precisi贸n que los modelos de lenguaje a nivel de car谩cter. Esto se debe a que pueden formar representaciones m谩s cortas de oraciones y preservar el contexto entre palabras m谩s f谩cilmente que los modelos de lenguaje a nivel de car谩cter. Sin embargo, se necesitan grandes corporaciones para entrenar suficientemente los modelos de lenguaje a nivel de palabra, y la codificaci贸n one-hot no es muy factible para los modelos a nivel de palabra.

    Por el contrario, los modelos de lenguaje a nivel de car谩cter suelen ser m谩s r谩pidos de entrenar, requieren menos memoria y tienen una inferencia m谩s r谩pida que los modelos basados 鈥嬧媏n palabras. Esto se debe a que es probable que el “vocabulario” (el n煤mero de funciones de entrenamiento) del modelo sea mucho menor en general, limitado a algunos cientos de caracteres en lugar de cientos de miles de palabras.

    Los modelos basados 鈥嬧媏n caracteres tambi茅n funcionan bien al traducir palabras entre idiomas porque capturan los caracteres que componen las palabras, en lugar de intentar capturar las cualidades sem谩nticas de las palabras. Usaremos un modelo a nivel de personaje aqu铆, en parte debido a su simplicidad y r谩pida inferencia.

    Usando un RNN / LSTM

    Cuando se trata de implementar un LSTM en Keras, el proceso es similar a implementar otras redes neuronales creadas con el modelo secuencial. Empiece declarando el tipo de estructura de modelo que va a utilizar y luego agregue capas al modelo de una en una. Las capas LSTM son f谩cilmente accesibles para nosotros en Keras, solo tenemos que importar las capas y luego agregarlas con model.add.

    Entre las capas primarias del LSTM, usaremos capas de deserci贸n , lo que ayuda a prevenir el problema del sobreajuste. Finalmente, la 煤ltima capa de la red ser谩 una capa densamente conectada que utilizar谩 una funci贸n de activaci贸n sigmoidea y probabilidades de salida.

    Secuencias y caracter铆sticas

    Es importante comprender c贸mo manejaremos nuestros datos de entrada para nuestro modelo. Dividiremos las palabras de entrada en partes y las enviaremos a trav茅s del modelo de una en una.

    Las caracter铆sticas de nuestro modelo son simplemente las palabras que nos interesa analizar, representadas con la bolsa de palabras. Los fragmentos en los que dividimos el corpus ser谩n secuencias de palabras, y puede pensar en cada secuencia como una instancia / ejemplo de entrenamiento individual en una tarea tradicional de Machine Learning.

    Implementaci贸n de un LSTM para la generaci贸n de texto

    Ahora implementaremos un LSTM y generaremos texto con 茅l. Primero, necesitaremos obtener algunos datos de texto y preprocesar los datos. Despu茅s de eso, crearemos el modelo LSTM y lo entrenaremos con los datos. Finalmente, evaluaremos la red.

    Para la generaci贸n de texto, queremos que nuestro modelo aprenda las probabilidades sobre qu茅 car谩cter vendr谩 despu茅s, cuando se le da un car谩cter inicial (aleatorio). Luego, encadenaremos estas probabilidades para crear una salida de muchos caracteres. Primero necesitamos convertir nuestro texto de entrada en n煤meros y luego entrenar el modelo en secuencias de estos n煤meros.

    Comencemos importando todas las bibliotecas que vamos a usar. Necesitamos numpytransformar nuestros datos de entrada en matrices que nuestra red pueda usar, y obviamente usaremos varias funciones de Keras.

    Tambi茅n necesitaremos usar algunas funciones del Kit de herramientas de lenguaje natural (NLTK) para preprocesar nuestro texto y prepararlo para entrenar. Finalmente, necesitaremos la sysbiblioteca para manejar la impresi贸n de nuestro texto:

    import numpy
    import sys
    from nltk.tokenize import RegexpTokenizer
    from nltk.corpus import stopwords
    from keras.models import Sequential
    from keras.layers import Dense, Dropout, LSTM
    from keras.utils import np_utils
    from keras.callbacks import ModelCheckpoint
    

    Para empezar, necesitamos tener datos para entrenar nuestro modelo. Puede usar cualquier archivo de texto que desee para esto, pero usaremos parte del Frankenstein de Mary Shelley, que est谩 disponible para descargar en Project Gutenburg , que aloja textos de dominio p煤blico.

    Entrenaremos a la red sobre el texto de los primeros 9 cap铆tulos:

    file = open("frankenstein-2.txt").read()
    

    Comencemos cargando nuestros datos de texto y haciendo un preprocesamiento de los datos. Necesitaremos aplicar algunas transformaciones al texto para que todo est茅 estandarizado y nuestro modelo pueda funcionar con 茅l.

    Vamos a poner todo en min煤sculas y no nos preocuparemos por las may煤sculas en este ejemplo. Tambi茅n usaremos NLTK para hacer tokens a partir de las palabras del archivo de entrada. Creemos una instancia del tokenizador y us茅mosla en nuestro archivo de entrada.

    Finalmente, vamos a filtrar nuestra lista de tokens y solo mantendremos los tokens que no est谩n en una lista de Stop Words, o palabras comunes que brindan poca informaci贸n sobre la oraci贸n en cuesti贸n. Haremos esto usando lambdapara hacer una funci贸n r谩pida de usar y tirar y solo asignaremos las palabras a nuestra variable si no est谩n en una lista de Stop Words proporcionada por NLTK.

    Creemos una funci贸n para manejar todo eso:

    def tokenize_words(input):
        # lowercase everything to standardize it
        input = input.lower()
    
        # instantiate the tokenizer
        tokenizer = RegexpTokenizer(r'w+')
        tokens = tokenizer.tokenize(input)
    
        # if the created token isn't in the stop words, make it part of "filtered"
        filtered = filter(lambda token: token not in stopwords.words('english'), tokens)
        return " ".join(filtered)
    

    Ahora llamamos a la funci贸n en nuestro archivo:

    # preprocess the input data, make tokens
    processed_inputs = tokenize_words(file)
    

    Una red neuronal funciona con n煤meros, no con caracteres de texto. As铆 que necesitamos convertir los caracteres en nuestra entrada a n煤meros. Ordenaremos la lista del conjunto de todos los caracteres que aparecen en nuestro texto de entrada, luego usaremos la enumeratefunci贸n para obtener n煤meros que representan los caracteres. Luego creamos un diccionario que almacena las claves y valores, o los caracteres y n煤meros que los representan:

    chars = sorted(list(set(processed_inputs)))
    char_to_num = dict((c, i) for i, c in enumerate(chars))
    

    Necesitamos la longitud total de nuestras entradas y la longitud total de nuestro conjunto de caracteres para la preparaci贸n de datos posterior, por lo que los almacenaremos en una variable. Para tener una idea de si nuestro proceso de conversi贸n de palabras en caracteres ha funcionado hasta ahora, imprimamos la longitud de nuestras variables:

    input_len = len(processed_inputs)
    vocab_len = len(chars)
    print ("Total number of characters:", input_len)
    print ("Total vocab:", vocab_len)
    

    Aqu铆 est谩 el resultado:

    Total number of characters: 100581
    Total vocab: 42
    

    Ahora que hemos transformado los datos en la forma en que deben estar, podemos comenzar a hacer un conjunto de datos con ellos, que alimentaremos a nuestra red. Necesitamos definir cu谩nto tiempo queremos que sea una secuencia individual (un mapeo completo de caracteres de entrada como n煤meros enteros). Estableceremos una longitud de 100 por ahora y declararemos listas vac铆as para almacenar nuestros datos de entrada y salida:

    seq_length = 100
    x_data = []
    y_data = []
    

    Ahora debemos revisar toda la lista de entradas y convertir los caracteres en n煤meros. Haremos esto con un forbucle. Esto crear谩 un mont贸n de secuencias donde cada secuencia comienza con el siguiente car谩cter en los datos de entrada, comenzando con el primer car谩cter:

    # loop through inputs, start at the beginning and go until we hit
    # the final character we can create a sequence out of
    for i in range(0, input_len - seq_length, 1):
        # Define input and output sequences
        # Input is the current character plus desired sequence length
        in_seq = processed_inputs[i:i + seq_length]
    
        # Out sequence is the initial character plus total sequence length
        out_seq = processed_inputs[i + seq_length]
    
        # We now convert list of characters to integers based on
        # previously and add the values to our lists
        x_data.append([char_to_num[char] for char in in_seq])
        y_data.append(char_to_num[out_seq])
    

    Ahora tenemos nuestras secuencias de entrada de caracteres y nuestra salida, que es el car谩cter que debe aparecer despu茅s de que finalice la secuencia. Ahora tenemos nuestras caracter铆sticas y etiquetas de datos de entrenamiento, almacenadas como x_datay y_data.Guardemos nuestro n煤mero total de secuencias y verifiquemos cu谩ntas secuencias de entrada totales tenemos:

    n_patterns = len(x_data)
    print ("Total Patterns:", n_patterns)
    

    Aqu铆 est谩 el resultado:

    Total Patterns: 100481
    

    Ahora continuaremos y convertiremos nuestras secuencias de entrada en una matriz num茅rica procesada que nuestra red puede usar. Tambi茅n necesitaremos convertir los valores de la matriz num茅rica en flotantes para que la funci贸n de activaci贸n sigmoidea que usa nuestra red pueda interpretarlos y generar probabilidades de 0 a 1:

    X = numpy.reshape(x_data, (n_patterns, seq_length, 1))
    X = X/float(vocab_len)
    

    Ahora codificaremos en caliente nuestros datos de etiqueta:

    y = np_utils.to_categorical(y_data)
    

    Dado que nuestras caracter铆sticas y etiquetas ya est谩n listas para que las utilice la red, sigamos adelante y creemos nuestro modelo LSTM. Especificamos el tipo de modelo que queremos hacer ( sequentialuno) y luego agregamos nuestra primera capa.

    Haremos abandonos para evitar el sobreajuste, seguido de otra capa o dos. Luego agregaremos la capa final, una capa densamente conectada que generar谩 una probabilidad sobre cu谩l ser谩 el siguiente car谩cter de la secuencia:

    model = Sequential()
    model.add(LSTM(256, input_shape=(X.shape[1], X.shape[2]), return_sequences=True))
    model.add(Dropout(0.2))
    model.add(LSTM(256, return_sequences=True))
    model.add(Dropout(0.2))
    model.add(LSTM(128))
    model.add(Dropout(0.2))
    model.add(Dense(y.shape[1], activation='softmax'))
    

    Compilamos el modelo ahora y est谩 listo para entrenar:

    model.compile(loss="categorical_crossentropy", optimizer="adam")
    

    El modelo tarda bastante en entrenarse, por eso guardaremos los pesos y los recargaremos cuando termine el entrenamiento. Configuraremos un checkpointpara guardar los pesos y luego los convertiremos en devoluciones de llamada para nuestro modelo futuro.

    filepath = "model_weights_saved.hdf5"
    checkpoint = ModelCheckpoint(filepath, monitor="loss", verbose=1, save_best_only=True, mode="min")
    desired_callbacks = [checkpoint]
    

    Ahora ajustaremos el modelo y lo dejaremos entrenar.

    model.fit(X, y, epochs=4, batch_size=256, callbacks=desired_callbacks)
    

    Una vez que haya terminado el entrenamiento, especificaremos el nombre del archivo y cargaremos los pesos. Luego recompile nuestro modelo con los pesos guardados:

    filename = "model_weights_saved.hdf5"
    model.load_weights(filename)
    model.compile(loss="categorical_crossentropy", optimizer="adam")
    

    Como convertimos los caracteres a n煤meros anteriormente, necesitamos definir una variable de diccionario que convertir谩 la salida del modelo nuevamente en n煤meros:

    num_to_char = dict((i, c) for i, c in enumerate(chars))
    

    Para generar personajes, necesitamos proporcionar a nuestro modelo entrenado un car谩cter semilla aleatorio que pueda generar una secuencia de caracteres a partir de:

    start = numpy.random.randint(0, len(x_data) - 1)
    pattern = x_data[start]
    print("Random Seed:")
    print(""", ''.join([num_to_char[value] for value in pattern]), """)
    

    Aqu铆 hay un ejemplo de una semilla aleatoria:

    " ed destruction pause peace grave succeeded sad torments thus spoke prophetic soul torn remorse horro "
    

    Ahora, para finalmente generar texto, vamos a iterar a trav茅s de nuestro n煤mero elegido de caracteres y convertir nuestra entrada (la semilla aleatoria) en floatvalores.

    Le pediremos al modelo que prediga lo que sigue a partir de la semilla aleatoria, convierta los n煤meros de salida en caracteres y luego lo anexe al patr贸n, que es nuestra lista de caracteres generados m谩s la semilla inicial:

    for i in range(1000):
        x = numpy.reshape(pattern, (1, len(pattern), 1))
        x = x / float(vocab_len)
        prediction = model.predict(x, verbose=0)
        index = numpy.argmax(prediction)
        result = num_to_char[index]
        seq_in = [num_to_char[value] for value in pattern]
    
        sys.stdout.write(result)
    
        pattern.append(index)
        pattern = pattern[1:len(pattern)]
    

    Veamos qu茅 gener贸.

    "er ed thu so sa fare ver ser ser er serer serer serer serer serer serer serer serer serer serer serer serer serer serer serer serer serer serer...."
    

    驴Parece esto algo decepcionante? S铆, el texto que se gener贸 no tiene ning煤n sentido y parece que simplemente comienza a repetir patrones despu茅s de un rato. Sin embargo, cuanto m谩s entrenes a la red, mejor ser谩 el texto que se genere.

    Por ejemplo, cuando el n煤mero de 茅pocas de entrenamiento se increment贸 a 20, el resultado se parec铆a m谩s a esto:

    "ligther my paling the same been the this manner to the forter the shempented and the had an ardand the verasion the the dears conterration of the astore"
    

    El modelo ahora genera palabras reales, incluso si la mayor铆a todav铆a no tiene sentido. A煤n as铆, por solo alrededor de 100 l铆neas de c贸digo, no est谩 mal.

    Ahora puede jugar con el modelo usted mismo e intentar ajustar los par谩metros para obtener mejores resultados.

    Conclusi贸n

    Querr谩 aumentar la cantidad de per铆odos de entrenamiento para mejorar el rendimiento de la red. Sin embargo, es posible que tambi茅n desee utilizar una red neuronal m谩s profunda (agregar m谩s capas a la red) o una red m谩s amplia (aumentar la cantidad de neuronas / unidades de memoria) en las capas.

    Tambi茅n puede intentar ajustar el tama帽o del lote, codificar en caliente las entradas, rellenar las secuencias de entrada o combinar cualquier n煤mero de estas ideas.

    Si desea obtener m谩s informaci贸n sobre c贸mo funcionan los LSTM, puede leer m谩s sobre el tema aqu铆 . Aprender c贸mo los par谩metros del modelo influyen en el rendimiento del modelo le ayudar谩 a elegir qu茅 par谩metros o hiperpar谩metros ajustar. Tambi茅n puede leer sobre t茅cnicas y herramientas de procesamiento de texto como las proporcionadas por NLTK.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *