Este documento presenta el diseño de un sistema para indexar un repositorio de documentos y realizar consultas ranqueadas utilizando el método LSI (Latent Semantic Indexing). El diseño incluye la división del repositorio en bloques, la extracción y procesamiento de términos, la construcción de archivos auxiliares y matrices, y la reducción dimensional utilizando descomposición en valores singulares. El sistema permitirá indexar múltiples repositorios y realizar consultas sobre los índices parciales generados para cada bloque.
1. Facultad de Ingeniería
Organización de Datos (75.06)
Entrega de diseño
Federico Farina <federicofarina22@gmail.com>
Nicolás Vazquez <nickva1988@gmail.com>
Sergio Zelechowski <sergiozz123@gmail.com>
1 de octubre de 2012
2. Organización de datos 75.06 Entrega de diseño
Índice
1. Introducción 1
1.1. Indexar el Repositorio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Realizar consultas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2. Primer Etapa: Registro de Documentos a Indexar 2
2.1. Identificación de los diferentes repositorios . . . . . . . . . . . . . . . . . . . . . . . . 2
3. Definición del criterio de Parseo de los términos 3
3.1. Reducción morfológica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4. Construcción de índices y realización de consultas sobre un repositorio 5
4.1. Proceso de indexación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
4.2. Proceso de consultas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
5. Construcción de los archivos auxiliares 6
5.1. Extracción de términos y construcción de archivos auxiliares . . . . . . . . . . . . . . . 6
6. Front Coding 7
7. Fórmula elegida para cada elemento de la matriz 8
7.1. Teoría de George Zipf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
7.2. Log Entropy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
8. Merge de los archivos auxiliares 9
8.1. Construcción del índice de cada bloque . . . . . . . . . . . . . . . . . . . . . . . . . . 9
8.2. Almacenamiento del léxico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
9. Front Coding Parcial 10
10. Descomposición en valores singulares 11
10.1. Reducción de las dimensiones de la matriz . . . . . . . . . . . . . . . . . . . . . . . . . 11
11. Etapa de procesamiento de la consulta 12
12. Método del coseno 13
I
3. Organización de datos 75.06 Entrega de diseño
1 Introducción
El objetivo de este trabajo práctico es construir un sistema que permita realizar búsquedas ranqueadas
sobre una colección de documentos usando para ello el método conocido como LSI (Latent Semantic
Indexing). La colección de documentos será un conjunto de archivos en formato texto. El TP deberá
procesar los documentos construyendo una matriz de términos x documentos y luego reducir las dimen-
siones de la matriz a un número indicado. Las operaciones posibles serán:
1.1. Indexar el Repositorio
Por línea de comandos un programa indexador recibe un nombre para el repositorio, un número y un
directorio.
indexar nombreIndice -200 /home/TP/datos
El TP deberá entonces indexar todos los archivos que se encuentran en el directorio indicado, cons-
truir la matriz y reducir sus dimensiones al número indicado (en este caso 200).
1.2. Realizar consultas
El TP recibe como parámetros el nombre del repositorio a consultar y una lista de términos (palabras).
consultar -r nombreIndice -q términos
La consulta debe devolver una lista con todos los documentos de la colección ordenados por relevan-
cia, por lo que el documento más relevante para la consulta estará primero.
1
4. Organización de datos 75.06 Entrega de diseño
2 Primer Etapa: Registro de Documentos a Indexar
En esta etapa se procederá a registrar los documentos que se van indexando. Como primera medida,
se les asignará un identificador. Sin esto sería imposible devolver documentos como resultados, ya que
se perdería la referencia a los mismos.
Se tuvo en cuenta que todos comparten un directorio raíz (el path ingresado por línea de comandos),
simplificado a que no existirán ABM de otros directorios.
La organización del archivo será secuencial con registros de longitud variable, con el siguiente formato:
NumBytes Path raíz NroDOC NumBytes II Path restante
uint8_t string uint32_t uint8_t string
Los primeros dos campos forman parte del Header de este archivo. Significa que sólo se encuentran
una vez al comienzo del mismo. El resto de los campos conforman un registro X.
NumBytes indica cuántos caracteres se deben leer para formar el string que se encuentra en el campo
contiguo; Path Restante concatenado al Path Raíz conforma el nombre completo del archivo; y NroDOC
es el identificador que se le asignará al archivo, por orden de procesamiento.
Por otro lado, se contará con otro archivo secuencial, pero esta vez con registros de longitud fija, que
se puede cargar en memoria de ser necesario y operar como un índice, básicamente para evitar tener
que recorrer secuencialmente el archivo anterior en busca de los nombres de los documentos que son
resultados de las query’s, reduciendo accesos a disco. El formato será el siguiente:
NroDOC Offset
uint32_t uint32_t
Donde NroDoc servirá de clave para relacionar ambos archivos y el siguiente campo es el offset al
archivo antes detallado.
2.1. Identificación de los diferentes repositorios
Para la identificación de los diferentes repositorios indexados se decidió generar un archivo secuen-
cial con registros de longitud variable con el siguiente formato:
NumBytes Nombre Dimensión
uint8_t string uint16_t
NumBytes indica cuántos caracteres leer para formar el nombre del repositorio, el cual pese a ser
de longitud variable se lo validará con un tope en longitud por prolijidad. Ese nombre, de ser necesario
concatenado a un número, representará los distintos nombres de los archivos relacionados al repositorio
que se generarán a lo largo del proceso de indexación, permitiendo distinguir las distintas estructuras
de archivos generados en el tiempo por los diferentes repositorios, y servirá además para advertir de la
duplicación de nombres de repositorios.
2
5. Organización de datos 75.06 Entrega de diseño
3 Definición del criterio de Parseo de los términos
La estrategia para decidir que se procesa como un término y que caracteres se descartan durante el
parseo se detalla a continuación:
• Números: Los términos constituidos por números no serán incluidos en el listado de términos que
conformarán el índice, a excepción de aquellos que puedan resultar importantes en colecciones con-
teniendo documentos técnicos, como por ejemplo una base de datos de medicina que contenga in-
formación sobre vitaminas. Una posible solución para este caso podría ser considerar como términos
aquellos que no comiencen con un dígito, por ejemplo, B6 y B12. Otro caso de sumo interés se pre-
senta cuando los términos representan años. Para resolverlo se tendrán en cuenta aquellos términos
que posean 4 dígitos, realizando las validaciones correspondientes para cada caso. El resto de los
casos serán descartados.
• Signos de puntuación: Se descartarán todo tipo de signos de puntuación que puedan contener los
términos, tales como coma, punto y coma, signos de admiración, etc.
• Palabras compuestas: Se dividirán en sus palabras componentes y se almacenarán como términos
individuales, por ejemplo, MS-DOS, el cual se dividirá en dos términos; a saber, MS y DOS. Es
decir, en este caso ’-’ se utilizará como delimitador de términos.
• Capitalización: Se convertirán los términos que contengan mayúsculas en términos conteniendo úni-
camente minúsculas. Por lo tanto, se tratará de igual manera a términos como Facultad y facultad.
Una ventaja obvia es evitar la repetición de términos semejantes que contengan los mismos caracteres
pero difieran en la capitalización de éstos.
• Stop words: Se tomó como criterio que el uso de las stop words podría llegar a perjudicar la precisión
sobre las consultas, y siendo ésto más importante que el espacio ganado por reducción del tamaño
del índice, que de todas forma no iba a resultar tan significativo, hemos decidido no utilizarlas. Pero
en contrapartida, se tomarán otras medidas más efectivas:
• No se indexarán términos de longitud uno.
• Los términos que resulten ser ampliamente probables en toda la colección no serán incluidos en el
índice final. Esta decisión será explicada más adelante; básicamente cuando se realice un merge de
todos los archivos auxiliares generados durante la extracción de términos, ya se conocerá la totalidad
de documentos y términos tratados y por lo tanto se podrá calcular la frecuencia global de cada
término, si ésta supera un valor dado, por ejemplo el 80 %, ese término será ignorado por el merge,
tratándolo como un término irrelevante y discontinuando su procesamiento. El porcentaje puede ser
modificado luego de realizar pruebas y determinarlo de manera más eficiente.
• Finalmente, se aplicará un proceso conocido como reducción morfólogica o "stemming", en inglés,
a cada término de la colección y también a los términos de las consultas.
3.1. Reducción morfológica
Consiste en lograr llevar un término cualquiera a su forma base o raíz siguiendo una serie de reglas
lingüísticas, es decir, elimina pluralismos, sufijos, conjugaciones simples, etc. Por ejemplo, reloj, relo-
jero, relojes, relojitos, relojería; todas derivan de reloj y esta es la palabra que devuelve la reducción en
todos los casos de este ejemplo. Otro ejemplo, pero en inglés, podría ser: "fishing", "fished", "fish.a la
palabra raíz, "fish".
3
6. Organización de datos 75.06 Entrega de diseño
Más ejemplos:
triplicate ->triplic
replacement ->replac
adjustment ->adjust
adjustable ->adjust
caresses ->caress
Esto sin duda reduce el número de términos a indexar por el motivo explicado recientemente, y por
sobre todo mejora la recall en las consultas, es decir, que más documentos van a matchear con los térmi-
nos de la query, obteniéndose mayores resultados; además reduce el tamaño del índice favorablemente lo
que aumenta la velocidad de procesamiento. Se podrán obtener resultados incluso para ciertos términos,
que tal vez sin aplicar esta técnica, no devolverían documentos relevantes.
Para esta etapa vamos a utilizar una librería Open source conocida como Snowball que implementa
el algoritmo de Porter y una serie de reglas para tal proceso similares a las utilizadas en los ejemplos
comentados. Se puede obtener más información en las referencias citadas al final de este informe. Tam-
bién, somos conscientes de que el algoritmo no es perfecto para la derivación de determinadas palabras y
la página del autor lo aclara, pero aún así consideramos que el beneficio es mayor al prejuicio, son casos
más aislados.
4
7. Organización de datos 75.06 Entrega de diseño
4 Construcción de índices y realización de consultas sobre un re-
positorio
Con el objetivo de crear una aplicación escalable y considerando que el repositorio a indexar puede
tener una cantidad desmedida de documentos, ideamos una estrategia para que las limitaciones físicas,
sobre todo la memoria principal, no sean un impedimento.
4.1. Proceso de indexación
Para afrontar el problema anterior se decidió dividir la cantidad de documentos totales en N bloques a
los cuales se les aplicará el mismo proceso de indexación. Cada uno de estos bloques se tratarán como un
repositorio individual y estarán sujetos a todas las operaciones que esto implica, incluyendo la creación
de archivos auxiliares, el mergeo posterior de estos, el armado de la matriz de términos x documentos y
la indexación semántica latente incluyendo todos los pasos intermedios. De esta forma, la estrategia dará
por resultado un índice parcial por cada bloque, los cuales se asociarán todos al mismo repositorio y a
un mismo nombre.
Al indexar un repositorio y especificar el nombre del índice resultante se guardarán también todos
los índices parciales asociados a ese nombre.
4.2. Proceso de consultas
Al estar sujeto este proceso a la etapa de indexación, se debe considerar que para hacer una consulta
se la debe realizar en cada índice parcial. Al ser los índices todos distintos, cada query también lo
será e inherentemente los resultados. Al realizar una búsqueda ranqueada se obtendrán N resultados,
donde N es el número de índices parciales y cada uno tendrá un ranking parcial de acuerdo a su lista de
documentos; por lo que se deberán unir todos los resultados y ordenar el ranking de acuerdo a la lista
total de documentos.
5
8. Organización de datos 75.06 Entrega de diseño
5 Construcción de los archivos auxiliares
En esta etapa se construirán los archivos auxiliares que luego se utilizarán en la etapa de merge
para formar las matrices de términos x documentos correspondientes a cada uno de los N bloques de
documentos contenidos en el repositorio, a las cuales luego se le aplicará SVD.
5.1. Extracción de términos y construcción de archivos auxiliares
En esta etapa se procesan los documentos del repositorio para generar archivos auxiliares ordenados
alfabéticamente por término y luego por documento. Se usará un buffer ordenado para ir almacenando
los términos en memoria y así evitar repeticiones de términos dentro del mismo documento, lo que im-
plicaría ocupar espacio en disco innecesariamente. Cada vez que se llene el buffer se guardará un archivo
al disco y se seguirá con este procedimiento hasta haber procesado todos los documentos del repositorio.
Un archivo auxiliar podría ser de la forma:
Termino Frecuencia Documento
DAY 4 1
MIDNIGHT 7 1
POLICE 5 1
NEWS 9 2
NIGHT 3 2
RAINBOW 1 2
SUN 5 3
YESTERDAY 9 4
WELCOME 7 4
Cuadro 1: Formato del archivo auxiliar
Como el archivo auxiliar está ordenado por término y sólo se procesará secuencialmente para realizar
el merge consideramos que es ideal para comprimir los términos usando Front Coding.
Los archivos serán secuenciales con registros de longitud variable y los representaremos de la forma:
Repetidos Distintos Caracteres Frecuencia NroDocumento
unsigned char unsigned char string uint16_t uint32_t
Cuadro 2: Estructura del archivo secuencial en disco
De esta forma, dado que unsigned char se representa con 8 bits, estamos limitando los términos a una
longitud perteneciente al intervalo [2 ; 512] bytes en el mejor caso (términos de un caracter se descartan
y no existen términos sin ningún caracter); lo cual nos parece algo razonable ya que la longitud promedio
de un término es de aproximadamente 8 bytes.
6
9. Organización de datos 75.06 Entrega de diseño
6 Front Coding
Este método es aprovechado cuando se comprimen palabras que están ordenadas alfabéticamente,
puesto que éstas comparten un prefijo al estar en esta disposición.
La longitud del prefijo compartido por las palabras ronda los 3 caracteres y además se cumple que a
medida que las palabras son más largas crece la probabilidad de que se compartan más caracteres.
Los caracteres omitidos se reemplazan por un número que indica cuántos caracteres de la palabra anterior
deben tomarse para formar la siguiente palabra con el sufijo que resta. Este número suele ser pequeño,
con lo cual con 3 o 4 bits podríamos estar reemplazando un promedio de 3 bytes. Esto nos da un promedio
de 2,5 bytes menos por palabra [Managing Gygabytes, cap 4].
7
10. Organización de datos 75.06 Entrega de diseño
7 Fórmula elegida para cada elemento de la matriz
El caso más sencillo consiste en colocar en cada celda Aij de la matriz un 1 en caso que el término i
aparezca en el documento j y un 0 en caso contrario. Este método le asigna el mismo peso a términos que
aparezcan una vez en un documento y a aquellos que aparezcan muchas veces, además de favorecer a
los documentos largos; los cuales al tener mayor cantidad de términos tienen más probabilidad de match
con los términos de la consulta. Para solucionar esto se podría pensar en lugar de colocar un 1 o un 0,
colocar la cantidad de veces que aparece el término i en el documento j (frecuencia del término i en
el documento j). Pero de esta manera seguirían favoreciéndose los documentos largos, además que no
tiene en cuenta aquellos términos que aparecen en pocos documentos, los cuales sin duda son de suma
importancia.
7.1. Teoría de George Zipf
La ley enunciada por George Zipf mide la importancia de un determinado objeto en un conjunto de
la siguiente manera:
N
wtd = ftd x log10
ft
En nuestro caso N sería el número total de documentos que contiene cada bloque, ftd la frecuencia
del término dentro del documento y ft la cantidad de documentos donde aparece el término. Usando esta
forma de asignar los pesos a cada término se puede observar que se favorece notablemente a aquellos
términos que aparecen en menor cantidad de documentos. Además, en este caso, se está asignando tanto
un peso local (basado en la frecuencia del término dentro de un documento) como global (basado en
la frecuencia de un término dentro de la colección de documentos) a cada término y el resultado es la
multiplicación de ambos. En nuestro caso, vamos a usar la combinación Log-Entropy, basada también
en la multiplicación de un peso local por un peso global, debido a que varios estudios experimentales
han demostrado el buen funcionamiento de ésta en diversas colecciones de datos.
7.2. Log Entropy
Para calcular el valor a colocar en cada celda Aij de la matriz (peso del término i en el documento j)
a aplicarle SVD se usarán las siguientes fórmulas:
lij = log10 (tfij + 1)
pij x log10 (pij )
gi = 1 + Σ
log10 n
Donde:
• tfij: es la frecuencia de aparición de cada término en cada documento.
• Gi: es la frecuencia total de aparición del término en todos los documentos.
• n: es la cantidad de documentos que contiene la colección.
tfij
• pij = gfi
8
11. Organización de datos 75.06 Entrega de diseño
8 Merge de los archivos auxiliares
En esta etapa se realizará el merge de los n archivos auxiliares formados en la etapa de parseo de
cada bloque de documentos, es decir, se tendrán al final del proceso de parseo N x n archivos auxiliares.
Se recorrerán secuencialmente los archivos auxiliares y se irán generando simultáneamente la matriz que
será el índice de cada bloque de documentos y 2 archivos por cada bloque.
Se usará una organización indexada con registros de longitud variable con el siguiente formato:
Repetidos Distintos Caracteres NroTermino PesoGlobal
unsigned char unsigned char string uint32_t float
Cuadro 3: Primer archivo
Repetidos Distintos Caracteres Offset
unsigned char unsigned char string uint32_t
Cuadro 4: Segundo archivo
8.1. Construcción del índice de cada bloque
A medida que se vayan procesando los registros de los archivos auxiliares, dado que éstos se encuen-
tran ordenados alfabéticamente por término y dentro de éste ordenamiento por número de documento,
se irán almacenando en cada celda de la matriz en memoria los pesos locales y se seguirá leyendo hasta
encontrar un nuevo término. Una vez que esto ocurra, se calculará el valor del peso global de dicho tér-
mino y se multiplicará la fila completa de la matriz por este valor, con lo cual se obtendrá el valor final
que corresponderá a cada celda. Una vez formada la matriz completa se procederá a realizar la SVD.
8.2. Almacenamiento del léxico
Simultáneamente a la construcción de la matriz, se ingresará en el Primer archivo (Cuadro 3) la entra-
da correspondiente al término que se terminó de procesar en el merge. El número de término comenzará
desde 0 y se irá incrementando a medida que se vaya leyendo un término distinto.
El Segundo archivo (Cuadro 4) es el índice del Primer archivo. Será utilizado para la búsqueda del nú-
mero de término para armar el vector de consulta en la etapa de procesamiento de la consulta.
El Primer archivo será dividido lógicamente en tantos bloques como entradas tenga el índice. El campo
correspondiente al término será el inicio de un bloque y el campo offset la referencia a la cantidad de
bytes relativa al inicio del Primer archivo en que se encuentra dicho término.
Una vez obtenido el bloque en el que se encuentra el término que se está buscando, se levantará a
memoria y se realizará una búsqueda binaria. El tamaño de bloque será fijado de manera convenien-
te. Estimamos que estará aproximadamente rondando los 2 Mb por bloque (para 100.000 términos), de
manera tal que si el índice no presenta ninguna entrada, es decir, se encuentra vacío por ser el tamaño
del Primer archivo menor al tamaño de bloque fijado, se levantará el archivo entero a memoria. Estos
archivos serán comprimidos mediante Front Coding Parcial.
9
12. Organización de datos 75.06 Entrega de diseño
9 Front Coding Parcial
Esta variación del método implica guardar cada N palabras comprimidas, 1 palabra sin comprimir.
Como es lógico, esto implica que la compresión no es tan buena como el Front Coding, pero al tener
palabras descomprimidas podemos realizar consultas eficientemente por medio de una búsqueda binaria.
A esta búsqueda se le sumaría el coste que si no se encuentra la palabra debemos recorrer N palabras
descomprimiéndolas para ver si la palabra buscada es alguna de estas. Pero el coste de recorrer secuen-
cialmente estas palabras no es prohibitivo, puesto que tomaremos un valor de N = 10.
10
13. Organización de datos 75.06 Entrega de diseño
10 Descomposición en valores singulares
Una vez formada la matriz A perteneciente a cada bloque de documentos, se procederá a realizar la
descomposición en valores singulares (SVD).
Usaremos para esta operación la biblioteca de algebra lineal open source Armadillo C++ que se ha
desarrollado con el objetivo de obtener un buen equilibrio entre velocidad y facilidad de uso. Es compa-
tible con números enteros, reales y complejos y posee un conjunto de funciones trigonométricas y esta-
dísticas. Las comparaciones de rendimiento sugieren que la biblioteca es considerablemente más rápida
que Matlab y Octave, como así también frente a anteriores bibliotecas de C, tales como IT andNewmat.
La biblioteca se encuentra bajo la licencia Lesser GPL(LGPL), por lo que es útil en el desarrollo de
proyectos Open source y Propietary software.
Armadillo puede descargarse desde:
• http://arma.sourceforge.net
10.1. Reducción de las dimensiones de la matriz
Una vez aplicado el proceso de SVD, se obtendrán las matrices T, S y DT , reducidas al rango k que
se le impuso a la entrada del proceso.
Se obtendrá algo de la forma:
Las matrices reducidas se almacenarán en el directorio correspondiente a cada bloque de documentos
con el siguiente formato:
• T se guardará en un archivo secuencial con registros de longitud fija y contendrá como primer valor
el número de filas y luego una lista de valores que representan a cada celda.
nColumnas Lista de valores
uint32_t Lista de floats
Cuadro 5: Matriz T
11
14. Organización de datos 75.06 Entrega de diseño
• S se guardará en un archivo secuencial con registros de longitud fija, como una lista de valores donde
cada uno es un valor singular.
Lista de valores
Lista de floats
Cuadro 6: Matriz S
• D se guardará en un archivo secuencial con registros de longitud fija y contendrá como primer valor
el número de columnas y luego una lista de valores que representan a cada celda.
nFilas Lista de valores
uint32_t Lista de floats
Cuadro 7: Matriz D
12
15. Organización de datos 75.06 Entrega de diseño
11 Etapa de procesamiento de la consulta
En esta etapa se procederá al parseo y determinación de los términos que se ingresaron en la consulta,
con los mismos criterios adoptados en el parseo de documentos en la etapa de indexación.
Una vez obtenidos los términos, para armar el vector de la consulta se realizará una búsqueda binaria en
memoria sobre el Segundo archivo (Cuadro 4). Luego, se levantará a memoria el bloque correspondiente
y se realizará una búsqueda binaria sobre éste para determinar el número de término (fila del vector
de consulta) correspondiente a cada término de la consulta. Los pesos asignados a los términos de la
consulta serán los pesos globales que contenga cada término, los cuales serán tomados también del
campo correspondiente al registro encontrado en el Primer archivo (Cuadro 3).
Para adaptar las dimensiones del vector de consulta a las adecuadas luego de la reducción de la matriz
original mediante el proceso de SVD se utilizará la siguiente fórmula:
−1
qk = q T Tk Sk
Para el ranqueado de los documentos, se aplicará el método del coseno entre el vector de la consulta
reducido en dimensiones y el vector correspondiente a cada documento Di , que consiste en tomar la
i-ésima columna de VkT .
13
16. Organización de datos 75.06 Entrega de diseño
12 Método del coseno
Se utilizará para determinar el puntaje de cada documento en la consulta, para luego poder ordenar
por relevancia los resultados.
Una vez obtenido el vector de consulta reducido en dimensiones, el puntaje de cada documento se calcu-
lará como el producto interno entre el vector de consulta y el vector de cada documento Di , dividiendo
el resultado por el módulo de cada vector. Esto representa la similitud en dirección entre los dos vectores
en el espacio de k dimensiones, de manera que cuanto menor sea el ángulo entre éstos, mayor será el
coseno y por lo tanto el puntaje del documento.
qK ∗ Di
cos φ =
|q| |Di |
Finalmente, se ordenarán los documentos en base al puntaje obtenido en forma decreciente.
14
17. Organización de datos 75.06 Entrega de diseño
Referencias
[1] http://snowball.tartarus.org/. Snowball, an Open Source small string processing language designed
for creating stemming algorithms for use in Information Retrieval.
[2] http://snowball.tartarus.org/algorithms/english/stemmer.html. Snowball Steeming Explicación del
funcionamiento de stemming algorithm.
[3] http://publication.wilsonwong.me/paper/233281449.pdf: Managing Gigabytes, Compressing and
Indexing Documents and Images by Ian H. Witten, Alistair Moffat, and Timothy C. Bell.
[4] http://arma.sourceforge.net. Conrad Sanderson. Armadillo: An Open Source C++ Linear Algebra
Library for Fast Prototyping and Computationally Intensive Experiments. Technical Report, NIC-
TA, 2010.
[5] http://www.miislita.com/information-retrieval-tutorial/latent-semantic-indexing-fast-track-
tutorial.pdf. Latent Semantic Indexing (LSI) A Fast Track Tutorial.
15