Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
LA CATEGORIZACIÓN AUTOMÁTICA DE
DOCUMENTOS
FACULTAD DE INGENIERIA
UNIVERSIDAD DE BUENOS AIRES
FACULTAD DE INGENIERIA
UNIVERSIDAD DE BUENOS AIRES
Tesista Director
ABRIL 2003
Resumen
Abstract
Indice
CAPÍTULO 1 ...............................................................................................................................................................7
INTRODUCCIÓN ......................................................................................................................................................7
CAPÍTULO 2 .............................................................................................................................................................15
CAPÍTULO 3 .............................................................................................................................................................55
CAPÍTULO 4 .............................................................................................................................................................61
4.1.4 Selección..................................................................................................................................................64
4.1.5 Cruza ........................................................................................................................................................64
4.1.5.1 Cruza monopunto y multipunto......................................................................................................... 64
4.1.5.2 Cruza basada en las aristas................................................................................................................. 66
4.1.6 Mutación..................................................................................................................................................67
4.2 A LGORITMO PROPUESTO................................................................................................................................ 67
4.2.1 Representación.......................................................................................................................................67
4.2.2 Estructura del cromosoma....................................................................................................................68
4.2.3 Generacion de la población inicial.....................................................................................................68
4.2.4 Funcion de adaptación..........................................................................................................................69
4.2.5 Seleccion..................................................................................................................................................69
4.2.6 Cruza ........................................................................................................................................................70
4.2.6.1 Cruza Pasa Grupo............................................................................................................................... 70
4.2.6.2 Análisis del operador Cruza Pasa Grupo........................................................................................... 72
4.2.7 Mutación..................................................................................................................................................73
4.2.7.1 Mutación RefinarKM......................................................................................................................... 73
4.2.7.2 Mutación Refinar Selectivo ............................................................................................................... 74
4.2.7.3 Mutación Join..................................................................................................................................... 74
4.2.7.4 Mutación Split.................................................................................................................................... 75
4.2.8 Inserción de los hijos en la población................................................................................................75
4.2.9 Tamaño de la población........................................................................................................................76
4.2.10 Criterio de terminación.......................................................................................................................76
4.2.11 Algoritmo “Genético con refinamiento”.........................................................................................77
CAPÍTULO 5 .............................................................................................................................................................79
PRUEBA EXPERIMENTAL................................................................................................................................79
CAPÍTULO 6 .............................................................................................................................................................97
CONCLUSIONES ....................................................................................................................................................97
A2.3.1.2 “Bisecting K-Means con refinamiento” contra “Genético con refinamiento”.............................. 126
A2.3.2 Entropía............................................................................................................................................. 127
A2.3.2.1 “Bisecting K-Means con refinamiento” contra “Genético” .......................................................... 127
A2.3.2.2 “Bisecting K-Means con refinamiento” contra “Genético con refinamiento”.............................. 127
A2.3.3 Cantidad de operaciones................................................................................................................. 128
A2.3.3.1 “Bisecting K-Means con refinamiento” contra “Genético” .......................................................... 128
A2.3.3.2 “Bisecting K-Means con refinamiento” contra “Genético con refinamiento”.............................. 128
Capítulo 1
Introducción
La Categorización de Documentos (Document Clustering) puede definirse como
la tarea de separar documentos en grupos. El criterio de agrupamiento se basa en las
similitudes existentes entre ellos [Kaufmann et al., 1990]. En la bibliografía consultada,
los términos clasificación (“classification”), y categorización o agrupamiento
(“categorization” ó “clustering”), son utilizados con distinto significado [Lewis, 1991;
Yang, 1997; Maarek et. al., 2000; Clerking et. al., 2001]. El concepto de clasificación
de documentos refiere al problema de encontrar para cada documento la clase a la que
pertenece, asumiendo que las clases están predefinidas y que se tienen documentos
preclasificados para utilizar como ejemplos. En la presente tesis, se estudia la
categorización o agrupamiento de documentos, entendiéndose por ésto el proceso de
encontrar grupos dentro de una colección de documentos basándose en las similitudes
existentes entre ellos, sin un conocimiento a priori de sus características.
Un criterio de agrupamiento utilizado es dividir los documentos en una jerarquía
de temas. Un ejemplo de esto último son las categorías que presenta el buscador
Yahoo® [Rüger et. al., 2000]. Un documento en particular se podría encontrar, por
ejemplo, dentro de “Tecnología ? Informática ? Internet ? Buscadores”. El problema
que presenta esta técnica es la dificultad de encontrar la categoría que mejor describa a
un documento [Hearst et. al., 1996]. Los documentos no tratan un sólo tema, y aunque
lo hicieran, el enfoque con el que el tema es tratado puede hacer que el documento
encuadre en otra categoría. Esto hace que la categorización de documentos sea una tarea
compleja y subjetiva, ya que dos personas podrían asignar el mismo documento a
categorías diferentes, cada una aplicando un criterio válido [Macskassy et.al., 2001].
Las siguientes secciones de este capítulo dan una introducción al contenido,
objetivo y estructura de esta tesis. La sección 1.1 presenta el problema de la
categorización automática de documentos, describiendo su utilidad y las causas que han
despertado el interés por resolverlo. En la sección 1.2 se muestra la imposibilidad de
encontrar la solución al problema de categorización automática mediante una búsqueda
exhaustiva, lo que ha llevado a la creación de algoritmos que encuentran soluciones
aproximadas al problema. La sección 1.3 describe en forma breve qué son los
algoritmos genéticos, y por qué se cree que pueden aplicarse al problema en estudio. La
sección 1.4 presenta el objetivo de la tesis, y la sección 1.5, la forma en la que se
estructura su contenido en los capítulos que la componen.
eficacia se basa en la relevancia de los resultados encontrados [Raghavan et. al., 1989];
en el ejemplo del párrafo anterior, una búsqueda eficaz debería retornar todos los
documentos que trataran sobre la búsqueda de información en internet, aún si la palabra
“buscador” no estuviera en el contenido de los mismos.
En el contexto del recupero de información, Van Rijsbergen [Van Rijsbergen,
1979] formula la denominada “Hipótesis del Agrupamiento” (en inglés, “Cluster
Hypothesis”); básicamente, la hipótesis del agrupamiento sostiene que “los documentos
fuertemente asociados tienden a ser relevantes para la misma consulta”, lo cual ha sido
verificado experimentalmente [Cutting et. al., 1992; Schütze et. al.,1997; Zamir et. al.,
1999]; basándose en dicha hipótesis, la categorización automática tiene en cuenta el
contenido de los documentos para agruparlos, ya que documentos similares contendrán
palabras (términos) similares.
1.1.2 Aplicaciones
lista se presenta sin ningún tipo de procesamiento previo, el usuario del sistema se verá
obligado a revisar todos los documentos descartando aquellos que no son relevantes
para él. Si los resultados se agrupan, la hipótesis del agrupamiento indica que los
documentos del mismo grupo serán relevantes para una subconsulta más específica que
la original. De esta forma, los documentos quedarán separados en grupos temáticos (los
grupos son una división de los documentos originales, y cada grupo responde a una
subconsulta más específica dentro del tema buscado) [Rüger et. al., 2000]. Asi, con sólo
revisar uno o dos documentos de cada grupo, el usuario podrá determinar cuál es el
subtema al que responden todos los documentos del grupo, pudiendo descartar
rápidamente los documentos irrelevantes para su búsqueda.
1 K K
agrupar n objetos en K grupos, está dada por: S ( n, K ) = ∑ (−1)i (K − i )n .
K ! i =0 i
Utilizando esta fórmula puede calcularse, por ejemplo: S (25,5) = 2 x 1016 , o,
dicho en otras palabras, que hay más de dos mil millones de millones de formas de
agrupar 25 objetos en 5 grupos. Los 25 objetos del ejemplo, representan un problema de
dimensiones demasiado pequeñas para ser de utilidad. En la práctica, se querría aplicar
la categorización automática, por lo menos, a varios cientos de documentos. Sin
embargo, haciendo algunos cálculos sobre este ejemplo de dimensiones pequeñas,
puede comprenderse la complejidad del problema. Supóngase un algoritmo que intente
probar todas las posibles categorizaciones para encontrar la mejor de ellas, asignandoles
puntajes de acuerdo a un criterio predefinido. Suponiendo que el algoritmo consiga
calcular el puntaje de 100 mil soluciones por segundo (el algoritmo tendría que ser muy
rápido, pero supóngase que lo es), para probar las 2 x 1016 posibilidades, el algoritmo
tomaría más de 6 mil años en dar el resultado.
Los algoritmos genéticos son una técnica del área de Sistemas Inteligentes que
constituye una abstracción del concepto biológico de evolución natural y tiene
numerosas aplicaciones en problemas de optimización [Davis, 1991; Falkenauer, 1999].
Su funcionamiento está basado en los mecanismos de selección natural, combinando la
supervivencia del más apto con un intercambio de información entre miembros de una
población de posibles soluciones.
Se estima que el uso de algoritmos géneticos puede contribuir a acortar
significativamente los tiempos de resolución del problema [Raghavan y Birchard, 1978;
Johnson y Fotouhi, 1996; Cole, 1998; Painho y Bação, 2000], ya que se caracterizan por
su capacidad de explorar el espacio de búsqueda amplia y eficientemente [Goldberg,
1989; Koza, 1997].
En este contexto, el objetivo de esta tesis es estudiar cómo pueden aplicarse los
algoritmos genéticos, que han demostrado ser útiles para ayudar a resolver problemas de
optimización, al problema de encontrar en forma automática la mejor categorización de
documentos. Se estudiará de qué forma las características de los algoritmos genéticos
pueden utilizarse para diseñar un algoritmo que supere el rendimiento de los algoritmos
que actualmente se aplican al problema, y se evaluarán los resultados de acuerdo a la
calidad de las soluciones obtenidas.
propuesto. Este algoritmo emplea algunas de las técnicas descriptas en la sección 4.1,
pero su característica principal es la utilización de los nuevos operadores que se
presentan en esta tesis.
El capítulo 5 describe las pruebas que se realizaron para evaluar la efectividad de
la solución propuesta en el capítulo 4. La sección 5.1 describe los conjuntos de datos
utilizados. Los mismos fueron extraídos de una colección reconocida como un estándar
dentro de la comunidad de investigadores dedicados a la categorización automática de
documentos. En las secciones 5.2 y 5.3 se detalla la metodología seguida en la
experimentación. Se enumeran las variables que intervienen en los experimentos y los
distintos tipos de experimentos realizados. La sección 5.4 presenta los resultados de la
experimentación. La presentación se hace en forma de gráficos y para cada variable se
incluye además el resultado del test estadístico realizado sobre los resultados. En la
sección 5.5 se analizan brevemente los resultados, que luego se tratan con más detalle
en el capítulo 6.
El capítulo 6 presenta las conclusiones extraídas a partir de la investigación
realizada y los resultados experimentales obtenidos. En esta tesis se propone una
adaptación de un algoritmo genético al problema de la categorización automática de
documentos, que incluye el diseño de un nuevo operador de cruza y cuatro operadores
de mutación. Los resultados experimentales obtenidos confirman la tesis que los
algoritmos genéticos son una poderosa herramienta para la resolución de problemas en
los cuales el espacio de soluciones es amplio y la función de optimización es compleja.
En el apéndice 1 se describen las pruebas realizadas para obtener los parámetros
utilizados en el algoritmo propuesto.
El apéndice 2 detalla el análisis estadístico realizado sobre los resultados que se
exponen en el capítulo 5, y que soportan las afirmaciones realizadas en la sección 5.4
(“Resultados”) de ese capítulo.
El apéndice 3 contiene la documentación de los programas y conjuntos de datos
que se incluyen en el CD que acompaña la presente tesis. Estos programas hacen
posible la realización de los experimentos y la evaluación de los resultados.
El apéndice 4 detalla la forma en la que se programaron los algoritmos
comparados en la prueba experimental. Para realizar la prueba experimental, se
desarrolló una aplicación de categorización automática de documentos que permite
Capítulo 2
Este capítulo describe el estado actual de los campos de estudio relacionados con
esta tesis. La sección 2.1 define formalmente el problema de la categorización
automática de documentos.
La categorización automática de documentos se vincula con las ciencias de la
“Recuperación de Información” y de la “Minería de Datos”. Del campo de la
Recuperación de Información toma los conceptos y métodos utilizados para el
procesamiento de documentos. Estas técnicas son las que permiten transformar la
información no estructurada que contienen los documentos (llamados “documentos de
texto libre”, o en inglés “free text documents”), a estructuras de datos manejables
mediante algoritmos computacionales. Las secciones 2.2 y 2.3 describen estas técnicas.
Del campo de la Minería de Datos toma las técnicas que se ocupan de los
problemas de categorización de objetos. Los algoritmos utilizados para la
categorización automática de documentos son adaptaciones de los que se utilizan para el
problema más general de categorizar objetos de cualquier tipo. La sección 2.4 muestra
la ubicación de la categorización automática de documentos dentro de este área, y
detalla los algoritmos utilizados actualmente para categorizar documentos en forma
automática.
En la sección 2.6 se describe un algoritmo presentado recientemente, que supera
a los métodos clásicos, y contra el cual se comparará la solución propuesta en esta tesis.
La sección 2.7 presenta los principios básicos de los algoritmos genéticos, en los que se
basará el algoritmo de categorización presentado en esta tesis.
Para poder definir medidas de semejanza entre los objetos a agrupar, éstos se
representan mediante vectores v = (a1 , a2 , ... , am ), donde cada componente del vector
es el valor de un atributo del objeto. De esta forma, cada uno de los objetos a agrupar es
un punto en un Espacio Euclideano de m dimensiones, Rm. Los investigadores
dedicados a las ramas de la Recuperación de Información adoptaron tempranamente una
representación vectorial para el manejo de documentos [Van Rijsbergen, 1979]; en este
modelo, cada documento es considerado un vector d en el espacio de términos (el
conjunto de palabras que aparecen en por lo menos uno de los documentos de la
colección).
Una forma aceptada [Van Rijsbergen, 1979; Faloutsos et.al., 1995; Jones et.al.,
1995] de representar los documentos es asignar a cada atributo del vector la presencia o
ausencia del término correspondiente. De esta manera, para m términos el vector
consistiría en m atributos binarios que pueden tomar valores de cero o uno, según los
términos que aparezcan o no en el documento. Otra manera es representar a cada
documento por su vector de frecuencia de términos [Steinbach et.al., 2000]: dtf = ( tf1 ,
tf 2 , ... , tf m ), donde tf i es la frecuencia del término número i en el documento (cantidad de
veces que aparece esa palabra en el documento). De esta forma, se considera que los
documentos quedan caracterizados por la cantidad de veces que aparece cada término
dentro del mismo.
Un refinamiento de este modelo consiste en multiplicar a la frecuencia de cada
término por su “frecuencia documental inversa” (en inglés, “inverse document
frecuency”). Ésta técnica, denominada “tf-idf”, se basa en la premisa que si un término
aparece en gran parte de los documentos, es poco discriminante, y por lo tanto, debe
restársele importancia [Faloutsos et.al., 1995; Zhao et.al., 2001]. Asi, la representación
del documento resultaría: dt f-idf = ( tf1 log(N / df1 ) , tf2 log(N / df2 ) , ... , tfm log(N / dfm) ),
donde N es la cantidad total de documentos de la colección y df i es la cantidad de
documentos que contienen al término i.
La normalización de los vectores (|| d || = 1) asegura que los documentos se
evalúen por la composición de su contenido, sin tener en cuenta su tamaño, ya que en
los documentos más extensos, las frecuencias de aparición de los términos serán
mayores.
∑s i
promedio de los vectores que componen el grupo: Cs = i =1
. Cada componente del
h
vector centroide es el promedio del valor de esa componente para los miembros del
grupo [Steinbach et.al., 2000; Zhao et.al., 2001]; su propiedad más importante es que la
distancia promedio desde un punto cualquiera del espacio hasta cada elemento del grupo
es igual a la distancia entre ese punto y el centroide del grupo.
Para reducir el número de términos distintos con los que se trabaja, el primer
paso es reducir todas las palabras a su raíz (en inglés “steeming”). Por ejemplo, las
palabras “medicina”, “médico” y “medicinal” se reducen a la forma “medic”. Esta
técnica es llamada “remoción de sufijos” (en inglés “suffix stripping”). Si bien es
posible que de esta manera se lleven a la misma raíz palabras que en realidad tienen
significados distintos, en general las palabras que tienen la misma raíz refieren al mismo
concepto, y la pérdida de precisión es compensada por la reducción de la
dimensionalidad del espacio [Van Rijsbergen, 1979].
Las aplicaciones de agrupamiento de documentos usan técnicas de remoción de
sufijos [Krovetz, 1993; Zamir et.al., 1998; Steinbach et.al., 2000], existiendo consenso
en cuanto a la conveniencia de su aplicación. La técnica de remoción de sufijos más
utilizada es la llamada “regla de Porter” [Porter, 1980]; esta técnica se basa en un
algoritmo que aplica una serie de reglas que determinan si las últimas sílabas de una
palabra deben ser removidas.
El análisis comparativo [Yang et al., 1997], usando cada uno de los métodos de
remoción de términos para diferentes algoritmos de agrupamiento, llega a la conclusión
de que las técnicas de Umbral de frecuencia, Ganancia de información y Estadística X2
obtienen resultados similares, notando que el método de Umbral de frecuencia no
requiere documentos previamente clasificados, lo que amplía el rango de situaciones en
las que puede ser aplicado.
d1 ∩ d 2
d1 ∗ d2
d1 • d 2
cos(d1 , d 2 ) =
d1 ∗ d 2
cos(d1 , d 2 ) = d1 • d2
d1 • d 2
jac( d 1 , d 2 ) =
+ d 2 − ( d1 • d 2 )
2 2
d1
Es una extensión del coeficiente de Jaccard del modelo binario para el caso de
atributos con valores reales. Los valores posibles de similitud se encuentran en el rango
[0,1]. Esta medida tiene propiedades intermedias entre el coeficiente del coseno
extendido y la distancia Euclideana, que se detalla a continuación.
- Distancia Euclideana
dist _ euc( d 1 , d 2 ) = d 1 − d 2
Es la fórmula tradicional para calcular el tamaño del segmento que une dos
puntos. La semejanza de dos documentos queda definida en forma inversa, ya que los
documentos más similares serán los que estén a menor distancia. La fórmula
comunmente utilizada es:
1
euc( d 1 , d 2 ) =
1 − d1 − d 2
Clasificaciones
No exclusivas Exclusivas
Extrínsecas Intrínsecas
Fig 2.1
División de las formas de
Jerárquicas Particionales
clasificar objetos
- Extrínsecas (supervisadas) : Las clases a las que pertenecen los objetos están
predefinidas, y se conocen ejemplos de cada una, ó algunos de los objetos ya
están clasificados y son utilizados por el algoritmo para aprender a clasificar a
los demás.
- Intrínsecas (no supe rvisadas) : La clasificación se realiza en base a las
características propias de los objetos, sin conocimiento previo sobre las clases a
las que pertenecen.
de la colección [Jain et.al., 1999]. Cada vértice (nodo) del árbol es un grupo de
elementos. La raíz del árbol (primer nivel) se compone de un sólo grupo que contiene
todos los elementos. Cada hoja del último nivel del árbol es un grupo compuesto por un
sólo elemento (hay tantas hojas como objetos tenga la colección). En los niveles
intermedios, cada nodo del nivel n es dividido para formar sus hijos del nivel n + 1.
Las figuras 2.2 y 2.3 ilustran estos conceptos mediante un ejemplo simple.
F
C
A
D
E
Fig 2.2
B
Posible colección de objetos en
un espacio de 2 dimensiones
ABCDEF
A C E F B D
A C E F B D
A E F C B D
A E F C B D
A E F C B D
A E F C B D
Fig 2.3. Posible dendograma para la colección de la figura 2.2 y agrupamientos que representa
cada nivel.
Los métodos aglomerativos parten de las hojas del árbol, ubicando a cada
elemento en su propio grupo, y en cada paso buscan los dos grupos más cercanos para
juntarlos. Los divisivos, por su parte, hacen el camino inverso. Comenzando en la raíz,
en cada paso seleccionan un grupo para dividirlo en dos, buscando que el agrupamiento
resultante sea el mejor de acuerdo a un criterio predeterminado. El análisis necesario
para pasar de un nivel a otro (decidir qué grupo dividir o cuales juntar) es más sencillo
para los métodos aglomerativos [Dash et.al., 2001], y esto hace que éstos sean más
utilizados que los divisivos [Fasulo, 1999; Steinbach et.al., 2000]. En adelante, cuando
se hable de métodos jerárquicos, se hará referencia únicamente a los algoritmos de
Agrupamiento Jerárquico Aglomerativo (HAC, su sigla en inglés).
Las distintas variantes de algoritmos jerárquicos aglomerativos difieren
únicamente en la manera de determinar la semejanza entre los grupos al seleccionar los
dos grupos más cercanos [Qin He, 1996; Willet, 1998; Cole, 1998; Jain et.al., 1999;
Fasulo 1999]. Debe notarse la diferencia entre medidas de semejanza entre documentos
y medidas de semejanza entre grupos. La similitud de dos grupos se calcula en base a
los valores de semejanza existentes entre sus documentos, pero la forma de hacer este
cálculo no es única. Dada una medida de semejanza entre documentos, que puede
considerarse la misma para todos, los distintos algoritmos jerárquicos aglomerativos se
distinguen por la medida de semejanza entre grupos que utiliza cada uno.
La figura 2.4 presenta un espacio bidimensional en el cual se han colocado 4
grupos de objetos. Los objetos de un mismo grupo se han representado mediante el
mismo símbolo (x, *, # ó +).
x
*
+ x *
+ *
+ x ** *
*
** *
x *
*
#
#
Fig 2.4
#
# Cuatro grupos de objetos en un
#
espacio de 2 dimensiones
Las figuras 2.5, 2.6 y 2.7 muestran cómo los métodos pueden diferir entre ellos
al seleccionar cuáles son los grupos más semejantes. En las figuras se utiliza la distancia
Euclideana como medida de semejanza entre los documentos. La disposición presentada
de los grupos se ha elegido especialmente para provocar las diferencias, si los grupos
están suficientemente separados y son compactos todos los métodos coinciden en la
selección de los grupos más cercanos.
x
*
+ x *
+ *
+ x ** *
*
** *
x * Fig 2.5
*
# Distancias al grupo de las “ x “
x
*
+ x *
+ *
+ x ** *
*
** * Fig 2.6
x *
* Distancias al grupo de las “ x “
#
según el método de enlace
#
completo. La distancia entre los
#
# grupos más cercanos está
#
remarcada.
x
*
+ x *
+ *
+ x ** *
*
** * Fig 2.7
x *
* Distancias al grupo de las “ x “
#
según el método de enlace
#
promedio. La distancia entre los
#
# grupos más cercanos está
#
remarcada.
h
Ws = ∑ || si − C s || 2
i =1
k
W = ∑ Wi
i =1
c) Son deterministas
Al aplicar dos veces un algoritmo jerárquico a una colección de documentos, las
dos veces seguirá el mismo camino hacia la solución. Hay algunos agrupamientos
que el algoritmo nunca considerará, sin importar la cantidad de veces que se lo
ejecute [Steinbach et.al.,2000].
Este ciclo se repite hasta que no sea posible mejorar el criterio de optimización.
Criterios internos
Los criterios internos dan una medida de la cohesión interna de los grupos. Para
cada grupo se calcula un valor en base a los objetos que lo componen (sin tener en
cuenta elementos de otros grupos), y luego se suman los valores de cohesión de cada
uno.
1
sim _ prom s = 2 ∑ sim ( d , d ' )
n s d ∈s
d '∈s
k
sim _ prom = ∑ n k .sim _ prom k
i=1
Este criterio toma valores más altos cuando los elementos de cada grupo
son más similares entre sí.
k
sim _ cent = ∑ sim _ centk
i =1
Los valores más altos se alcanzan cuando cada objeto se encuentra cerca
del centro de su grupo.
Criterios externos
Los criterios externos tienen en cuenta la separación que existe entre los
distintos grupos. Se considera que un agrupamiento es mejor que otro cuando sus
grupos están más separados del centro de la colección.
k
ext _ sim = ∑ sim (Ci , C )
i=1
En [Zhao et.al., 2001] se evalúa cada uno de los criterios detallados, aplicados al
agrupamiento de colecciones de documentos. El criterio interno de maximización de la
suma de similitudes con el centroide (que es el que se utiliza más comunmente en la
bibliografía), obtiene los mejores resultados al aplicar cada uno de los criterios en forma
individual. El trabajo propone una combinación de éste criterio con el criterio externo
de minimización de la similitud de los centroides con el centro de la colección, que
mejora el rendimiento del algoritmo de agrupamiento, produciendo grupos de tamaños
más balanceados.
Algoritmo “K-means”
2.10. En la figura 2.11 puede verse la situación inicial para el paso N+1. En éste paso, el
algoritmo encontrará los 3 grupos claramente definidos que existen en la colección. La
disposición de los objetos se ha elegido especialmente para que la mejora en el
agrupamiento sea evidente.
+ X
+
+
+
+ +
+
X
+ X
+ Fig 2.8
+
Objetos en una colección y los 3
centroides del paso N
+ X
+
+
+
+ +
+
X
+ X
Fig 2.9
+
+ Los objetos de la colección se
asignan al grupo del centroide
más cercano
+
X +
+
+
+ +
X +
+ X
+ Fig 2.10
+
Se calculan los centroides para
el paso N + 1
+
X +
+
+
+ +
X +
+ X
+ Fig 2.11
+
Situación inicial para el paso
N+1
Codificación
Espacio de cromosomas Espacio de soluciones
C1
S2
C4 C2
C3 S1
S3
SN
CM
Generación de la
población inicial
Evaluación de la
función de adaptación
Selección de los
individuos a reproducir
Mutación
Decodificación
Mejor cromosoma Solución
2.7.1 Representación
0 1 1 0 0 0 1 1 0 1
3 lados 13 mm
1 0 1 0 0 1 0 0 1 1
5 lados 19 mm
Fig 2.14 - Ejemplo de un esquema de
representación para polígonos regulares
3 13
3 13 mm
5 19
5 19 mm
2.7.4 Selección
Cromosoma C1 C2 C3 C4 C5 Totales
Aptitud 2,8 10 22,5 3,1 6 44,4
Porcentaje 0,07 0,23 0,5 0,07 0,13 1
C1 C2 C3 C4 C5
2.7.5 Reproducción
Padre 1 A1 A2 A3 A4 A5 A6 A7 A8
Padre 2 B1 B2 B3 B4 B5 B6 B7 B8
Recombinación
Hijo A1 A2 B3 A4 B5 A6 A7 B8
Los operadores de reproducción más típicos generan dos hijos a partir de dos
padres. A continuación se describen los más utilizados.
C=3
Padre 1 A1 A2 A3 A4 A5 A6 A7 A8
Padre 2 B1 B2 B3 B4 B5 B6 B7 B8
Cruza monopunto
Hijo 1 A1 A2 A3 B4 B5 B6 B7 B8
Hijo 2 B1 B2 B3 A4 A5 A6 A7 A8
C 1= 2 C 2= 5
Padre 1 A1 A2 A3 A4 A5 A6 A7 A8
Padre 2 B1 B2 B3 B4 B5 B6 B7 B8
Cruza multipunto
Hijo 1 A1 A2 B3 B4 B5 A6 A7 A8
Hijo 2 B1 B2 A3 A4 A5 B6 B7 B8
2.7.6 Mutación
Inserción uniforme
Los cromosomas a ser reemplazados se eligen al azar entre los miembros de la
población. Se corre el riesgo de eliminar buenas soluciones, ya que no se tiene en cuenta
la aptitud de los cromosomas.
Inserción elitista
Se eligen los cromosomas menos aptos para ser reemplazados. Este método
asegura que los mejores cromosomas pasarán siempre a la siguiente generación, pero
puede restringir la amplitud de la búsqueda que realiza el algoritmo genético.
Capítulo 3
Descripción del problema
En este capítulo se plantean las cuestiones que esta tesis apunta a responder, y se
analiza el trabajo previo que existe acerca de la aplicación de los Algoritmos Genéticos
a este problema. En la sección 3.1 se exponen las limitaciones que presentan los
algoritmos actuales, relacionados con la forma en la que exploran el espacio de posibles
soluciones. La sección 3.2 describe los problemas encontrados en los trabajos previos
que aplicaron los algoritmos genéticos a la categorización automática.
Algoritmo K-Means
El algoritmo K-Means es mucho más eficiente (los tiempos de cómputo
requeridos son lineales con la cantidad documentos a agrupar [Han et.al., 2001]), pero
es dependiente de la selección inicial de centroides [Bradley, 1998]. Sus resultados
pueden ser bastante pobres y suelen variar mucho si se aplica varias veces a la misma
colección de documentos, ya que si la selección de centroides al azar es mala, la
solución encontrada también lo será.
Estos problemas de los algoritmos actuales dan lugar a las siguientes cuestiones:
que explorar todo el espacio de búsqueda, ya que lo guía por aquellos sectores en los
cuales las soluciones son significativas.
Sin la aplicación de conocimiento específico del problema que se está
resolviendo en los operadores, es casi imposible que el algoritmo genético supere en
términos de eficiencia a los algoritmos diseñados ad hoc para el problema [Goldberg,
1989; Estivill-Castro, 2000]. Sin embargo, solamente un trabajo hace uso de este
conocimiento [Estivill-Castro, 2000], aunque falla en reconocer la importancia de la
sensibilidad al contexto del operador de cruza.
Capítulo 4
Solución propuesta
En este capítulo se presenta la solución propuesta en esta tesis, que apunta a
responder a las cuestiones planteadas en el capítulo 3. En la sección 4.1 se describen las
técnicas utilizadas en trabajos previos que aplican los algoritmos genéticos a problemas
de categorización automática. La sección 4.2 detalla el algoritmo de categorización
propuesto. Este algoritmo emplea algunas de las técnicas descriptas en la sección 4.1,
pero su característica principal es la utilización de los nuevos operadores que se
presentan en esta tesis.
4.1.1 Representación
1 1 2 1 2
2 2 1 2 1
0 0 1 0 1
1 1 0 1 0
1 2 4 6 3 5
3 5 6 1 2 4
4 1 2 6 3 5
1 3 4 5 2
Suponiendo que el agrupamiento usado como ejemplo sea el máximo local para
esa búsqueda.
Estos métodos tienen ciertas particularidades que deben tenerse en cuenta:
- Puede suceder que haya agrupamientos imposibles de codificar con estos
esquemas (que no se pueda encontrar una permutación para la cual ese
agrupamiento sea el máximo local).
- Los algoritmos de búsqueda local más comunes son del orden de (nk) o de
(n2 k) [Cole, 1998].
4.1.4 Selección
4.1.5 Cruza
Estos son los operadores de cruza clásicos, que suelen aplicarse a los
cromosomas que usan representación binaria en los algoritmos genéticos más sencillos
[Goldberg, 1989]. Sin embargo, aplicados a la representación de Numeración de grupo,
1 1 2 1 2
2 2 1 2 1
1 1 1 2 1
Como los dos padres representan el mismo agrupamiento, con los grupos {1,2,4}
y {3,5}, se esperaría que el hijo represente el mismo agrupamiento. Sin embargo
representa un agrupamiento distinto, que contiene los grupos {1,2,3,5} y {4}.
Una forma de intentar resolver el problema es renumerar los grupos de cada
padre en forma canónica (por ejemplo, de forma ascendente, de izquierda a derecha)
antes de realizar la cruza [Jones et.al., 1991]. En ese caso, los dos cromosomas padres
del ejemplo anterior serían idénticos. Sin embargo, hay casos en los cuales esto no es
suficiente:
1 2 3 3 3 3 1
1 1 2 2 2 2 3
1 2 3 3 2 2 3
4.1.6 Mutación
4.2.1 Representación
CantElementos : es la cantidad de
elementos que tiene el grupo
Vector de
grupos NroGrupo : es el número que identifica al
grupo
VecCentroide : es el centroide del grupo
4.2.5 Seleccion
4.2.6 Cruza
Hijo
Los pasos que sigue el algoritmo para generar cada uno de los hijos son:
1. Crear un cromosoma con el mismo agrupamiento que el primer padre (el
padre que pasa el agrupamiento),
2. Seleccionar un grupo fuente del segundo padre (el padre que pasa el grupo),
3. Seleccionar un grupo destino en el hijo, que va a transformarse en el grupo
del padre,
4. Reacomodar los elementos del hijo según sea necesario para que el grupo
destino contenga los elementos que contiene el grupo fuente.
Sensibilidad al contexto
El operador copia el agrupamiento de uno de los padres al hijo, y después le pasa
un grupo del otro padre. El hijo siempre heredará grupos de ambos padres. En el caso
de que ambos padres sean iguales, la forma de seleccionar el grupo destino asegura que
en el 96% de los casos el grupo destino será el mismo que el grupo origen, y el hijo será
igual a los padres. En las pruebas realizadas se observó que si no se dejaba ningún caso
librado al azar, el algoritmo exploraba el espacio de búsqueda en forma incompleta,
convergiendo en forma demasiado rápido a soluciones subóptimas.
4.2.7 Mutación
Capítulo 5
Prueba experimental
Este capítulo describe las pruebas que se realizaron para evaluar la efectividad
de la solución propuesta en el capítulo 4. La sección 5.1 describe los conjuntos de datos
utilizados. Los mismos fueron extraídos de una colección reconocida como un estándar
dentro de la comunidad de investigadores dedicados a la categorización automática de
documentos. En las secciones 5.2 y 5.3 se detalla la metodología seguida en la
experimentación. Se enumeran las variables que intervienen en los experimentos y los
distintos tipos de experimentos realizados. La sección 5.4 presenta los resultados de la
experimentación. La presentación se hace en forma de gráficos y para cada variable se
incluye además el resultado del test estadístico realizado sobre los resultados. En la
sección 5.5 se analizan brevemente los resultados, que luego se tratan con más detalle
en el capítulo 6.
- En los subconjuntos re0 y re1 extraidos de esta colección [Zhao et.al., 2001]
ya se han filtrado aquellos documentos sin clasificar, o con más de una
clasificación (que complican innecesariamente la evaluación de los
resultados) y son más fáciles de manipular que la colección completa.
- Los documentos de los subconjuntos re0 y re1 ya se han preprocesado
utilizando técnicas estándar. Primero, se han removido de cada documento
las palabras comunes que no sirven para el proceso de categorización,
utilizando una “stop list”, y el resto de las palabras fueron llevadas a su raíz
utilizando el algoritmo de Porter [Porter, 1980].
Una vez que se obtienen los agrupamientos de documentos que arroja cada
algoritmo como salida, debe medirse cuantitativamente la calidad de cada uno para
poder compararlos.
Los siguientes son los parámetros que pueden variarse con cada muestra de
datos y cada corrida de los algoritmos:
- Cantidad de documentos (N): es la cantidad de documentos que contiene la
muestra, y que se van a categorizar.
- Cantidad de grupos (K): es la cantidad de grupos que va a formar cada
algoritmo con los documentos de la muestra.
1 n j di • d j
sim _ prom j = 2 ∑
n j i =1 d i * d j
j= 1
k
∑ n j * sim _ prom j
j =i
sim _ prom =
N
5.2.2.2 Entropía
La entropía es una medida “externa” (para calcularla es necesario conocer a qué
categoría “real” pertenece cada documento). El concepto de entropía tiene su origen en
el campo de la física (más especificamente, en el area de la termodinámica), y es
utilizada como medida del grado de desorden de un sistema [Carter, 2000]. En 1948,
Shannon [Shannon, 1948] incorpora el término a la teoría de la información como una
función que mide la cantidad de información generada por un proceso.
Un ejemplo simple permitirá entender qué es lo que mide la entropía y cómo
puede utilizarse para medir la calidad de un agrupamiento dado. Supóngase que se tiene
un grupo con 1000 documentos de 8 categorías distintas. Una persona debe tomar cada
documento, y escribir un código (en forma binaria) para describir la categoría a la cual
pertenece. La forma más simple de hacerlo sería escribir, para cada documento, el
número de la categoría (de 0 a 7), en base 2. Para esto, necesitaría 3 bits por cada
documento (en base 2 se requieren 3 bits para codificar los números 0 a 7).
Supóngase ahora que los documentos del grupo no pertenecen a cada categoría
en cantidades iguales, sino que el 42% de los documentos son de la primer categoría, el
40% son de la segunda categoría, y el 18% restante pertenece a las otras 6 categorías en
cantidades iguales. La persona encargada de anotar la categoría de cada documento
podría desarrollar una codificación más eficiente (la forma simple, descrita
anteriormente le insumía 3 bits por cada uno). Por ejemplo, podría adoptar el siguiente
código:
0 00 42%
1 01 40%
2 1000 3%
3 1001 3%
4 1010 3%
5 1011 3%
6 1100 3%
7 1101 3%
pertenecen en cantidades iguales a las categorías (en ese caso, H = 3, que es el caso de
la codificación en base 2 del número de categoría). En el caso que se describe en la
tabla, el valor de H es aproximadamente 1,96, lo que indica que la codificación elegida
no es la óptima. Cuanto más desparejas sean las probabilidades, el valor de la entropía
irá disminuyendo. Si todos los documentos del grupo fueran de una sóla categoría, el
sistema estará totalmente ordenado, y la entropía será igual a 0 (no hará falta escribir de
qué categoría es cada documento).
Para medir la calidad de un agrupamiento utilizando la entropía, primero se
calcula entropía de cada grupo. Para cada categoría “real” i, se calcula la probabilidad
nij
pij de que un miembro del grupo j sea de la categoría i, pij = n j , donde nij es la
fórmula: H j = −∑ pij * log( pij ) , donde i recorre todas las categorías “reales” de los
i
documentos.
La entropía total del agrupamiento se calcula luego ponderando la entropía de
cada grupo de acuerdo a su tamaño,
k
∑n j * H j
j =1
H=
N
conjuntos de resultados, lo que hubiera hecho muy dificil un análisis estadístico de los
mismos. El rendimiento de los algoritmos se hubiera comparado basándose solamente
en la categorización de dos muestras, que podrían haber sido casos favorables para
alguno de ellos, quitándole validez a las conclusiones.
Con el objetivo de obtener resultados que fueran válidos desde un punto de vista
estadístico, tratando de minimizar las probabilidades de que un caso particular favorable
a uno u otro algoritmo influyera en los resultados en forma excesiva, se prepararon las
muestras de datos de la siguiente manera: de cada subconjunto de datos (re0 y re1) se
extrajeron 10 muestras de 500 documentos al azar, y se realizaron 5 corridas de cada
algoritmo sobre cada muestra. De esta manera, los resultados particulares que no
reflejan el comportamiento estadístico de los algoritmos tienen menos posibilidades de
distorsionar los resultados.
Para cada una de las muestras de datos, se obtuvieron agrupamientos de 5, 10, 15
y 20 grupos con cada algoritmo y se calcularon las medidas de evaluación para cada
uno. El procedimiento se repitió 5 veces, y se promediaron las medidas de evaluación de
las 5 iteraciones. Esto dió como resultado 20 tablas (una para cada muestra de datos),
con las medidas de evaluación de cada algoritmo para los agrupamientos de 5, 10, 15 y
20 grupos. Promediando los valores de las 20 tablas, se construyó una tabla de
promedios generales, con el valor promedio de cada medida de evaluación para cada
algoritmo, para los agrupamientos de 5, 10, 15 y 20 grupos.
Para la confección de los gráficos, se utilizó la tabla de promedios generales.
Para la realización del test de Wilcoxon, se utilizaron los valores cada una de las 20
tablas, ya que cada una de ellas se corresponde a una muestra de datos.
5.4 Resultados
5.4.1 Experimentos variando la cantidad de grupos
Similitud Promedio
0,2
0,18
0,16
0,14
Bisecting KM Ref
Genetico
0,12
Genetico Ref
0,1
0,08
5 10 15 20
Cant. Grupos
Fig 5.1
Similitud promedio de los agrupamientos encontrados en función de la cantidad de grupos.
Entropía
Bisecting KM Ref
Genetico
1,9
Genetico Ref
1,8
1,7
1,6
1,5
1,4
1,3
1,2
5 10 15 20
Cant. Grupos
Fig 5.2
Entropía de los agrupamientos encontrados en función de la cantidad de grupos.
Cant. Operaciones
Bisecting KM Ref
Genetico
200000
Genetico Ref
180000
160000
140000
120000
100000
80000
60000
5 10 15 20
Cant. Grupos
Fig 5.3
Cantidad de operaciones realizadas por los algoritmos en función de la cantidad de grupos.
Similitud promedio
Bisecting KM Ref
0,172 Genetico Ref
0,17 Genetico
0,168
0,166
0,164
0,162
0,16
0,158
0,156
0,154
300 500 700 900 1100 1300
Fig 5.4
Similitud promedio de los agrupamientos encontrados en función de la cantidad de documentos.
Fig 5.5
Entropía de los agrupamientos encontrados en función de la cantidad de documentos.
Cant. Operaciones
Bisecting KM Ref
550000 Genetico Ref
500000 Genetico
450000
400000
350000
300000
250000
200000
150000
100000
50000
300 500 700 900 1100 1300
Fig 5.6
Cantidad de operaciones realizadas por los algoritmos en función de la cantidad de documentos.
Capítulo 6
Conclusiones
La figura muestra con símbolos cuadrados dos posibles agrupamientos entre los
cuales debe elegir el algoritmo “Bisecting K-Means”. Este algoritmo elegirá la solución
con mayor valor para el criterio de optimización, y esto puede tener como consecuencia
que se encuentre una solución subóptima, ya que tal vez la región con el máximo más
alto nunca se explore. Los símbolos triangulares representan los posibles miembros de
la población del algoritmo genético. Si los operadores del algoritmo genético generaran
el agrupamiento graficado con línea punteada, este agrupamiento pasaría a formar parte
de la población (aún cuando existan soluciones mejores), dándole al algoritmo genético
la posibilidad de explorar esa región.
Fig 6.1
Comparación de la exploración de los algoritmos “Bisecting K-Means con refinamiento” y “Genético”.
Cuestión 4: ¿De qué manera pueden diseñarse los operadores del algoritmo genético
para hacer uso del conocimiento específico del dominio?
Los 5 nuevos operadores presentados como parte de la solución propuesta (el
operador de cruza y los 4 operadores de mutación, que se detallan en las secciones
4.2.6.1 y 4.7) dan respuesta a esta cuestión. Estos operadores hacen uso de esta
información para guiar la búsqueda del algoritmo genético por regiones donde haya más
posibilidades de encontrar soluciones de buena calidad.
Referencias
ACM SIGIR. (1996). Proceedings of the 19th annual international ACM SIGIR
conference on Research and development in information retrieval, ACM
Press, New York, USA.
Anderberg, Michael R. (1973). Cluster analysis for Applications. Academic Press, New
York.
Boley, D., Gini, M., Gross, R., Eui Hong, H., Hastings, K., Karypis, G., Kumar, V.,
Mobasher, B., Moore, J. (1999). Partitioning-based clustering for Web
Document Categorization. Decision Support Systems, volumen 27, número
3, páginas 329-341.
Box, G.E.P., Hunter, W.G., Hunter, J.S. (1978). Statistics for experimenters: An
introduction to design, data analysis and model building. John Wiley and
Sons, New York.
Bradley, P.S. y Fayyad, U.M. (1998). Refining initial points for k-means clustering. In
J. Shavlik, editor, Proceedings of the Fifteenth International Conference on
Machine Learning (ICML '98), pages 91--99, San Francisco, CA, 1998.
Morgan Kaufmann
Clerking, P., Cunningham, P., Hayes, C. (2001). Ontology Discovery for the Semantic
Web Using Hierarchical Clustering. Department of Computer Science,
Trinity College, Dublin.
Citeseer (Research Index). The NEC Research Institute Digital Library. Sitio dedicado a
la difusión de literatura científica. http://citeseer.nj.nec.com/.
Cole, Rowena M. (1998). Clustering with Genetic Algorithms. Thesis for the degree of
Master of Science, Department of Computer Science, University of Western
Australia.
Croft, W. B. (1978). Organizing and searching large files of documents, Ph.D. Thesis,
University of Cambridge.
Davis, L. (1991). Handbook of Genetic Algorithms. New York. Van Nostrand Reihold.
Dunlop, M.D. y Van Rijsbergen, C. J. (1991). Hypermedia and free text retrieval.
RIA091 Conference, Barcelona.
Estivill-Castro, V. (2000). Hybrid Genetic Algorithms Are Better for Spatial Clustering.
Pacific Rim International Conference on Artificial Intelligence, pages 424-
434.
Guha, S., Rastogi, R., y Shim, K. (1998). CURE: An efficient clustering algorithm for
large databases. In Proceedings of 1998 ACM-SIGMOD International
Conference on Management of Data.
Hall, L.O., Ozyurt, B. y Bezdek, J.C. (1999). Clustering with a genetically optimized
approach. IEEE Trans. On Evolutionaty Computation, 3, 2, 103-112.
Han, J., Kamber, M. y Tung, A.K.H. (2001). Spatial clustering methods in data mining:
A survey. Geographic Data Mining and Knowledge Discovery, H. Miller
and J. Han, editors, Taylor and Francis.
Holland, J. H. (1975). Adaptation in natural and artificial systems. Ann Arbor: The
University of Michigan Press.
Honkela, T., Kaski, S., Lagus, K., y Kohonen, T. (1996). Newsgroup exploration with
WEBSOM method and browsing interface. Technical Report A32, Helsinky
University of Technology, Laboratory of Computer and Information
Science.
Jain, A. K., Murty, M.N., y Flinn, P.J. (1999). Data Clustering: A review. ACM
Computing Surveys, Vol. 31, Nro 3, Septiembre 1999.
Joachims, T. (1998). Text Categorization with Support Vector Machines: Learning with
Many Relevant Features. Proceedings of ECML-98, 10th European
Conference on Machine Learning, Springer Verlag, Heidelberg, DE.
Maarek, Yoelle S., Fagin, Ronald, Ben-Shaul, Israel Z. y Pelleg, Dan. (2000).
Ephemeral Document Clustering for Web Applications. IBM Research
Report RJ 10186.
Macskassy, S.A., Banerjee, A., Davison, B.D., Hirsh, H. (2001). Human performance
on clustering web pages. Technical Report DCS-TR-355, Department of
computer Science, Rutgers, State University of New Jersey.
Mahfouz, S.Y., Toropov, V.V., Westbrook, R.K. (2000). Modification, tunning and
testing of a GA for structural optimization problems. Department of Civil
and Environmental Engineering, University of Bradford, UK.
Miller, B.L., Goldberg, D.E. (1995). Genetic algorithms, Selection Schemes and the
Varying Effects of Noise, IlliGAL report No. 95009.
Myers, R.H., Montgomery, D.C. (1995). Response surface methodology: process and
product optimization using designed experiments. John Wiley and Sons,
New York.
Porter, M. F., (1980). An Algorithm for Suffix Stripping, Program, vol.14, no. 3, 130-
137, 1980
Raghavan, V., Bollmann, P., y Jung, G. (1989). A critical investigation of recall and
precision as measures of retrieval system performance. ACM Transactions
on Information Systems, 7(3):205--229.
Sarle, W.S., ed. (1997) Neural Network FAQ, part 1 of 7: Introduction, actualización
periódica al grupo de noticias de Usenet comp.ai.neural-nets,
ftp://ftp.sas.com/pub/neural/FAQ.html
Trocine, L., Malone, L.C. (2000). Finding important independent variables through
screening designs: a comparison of methods. Proceedings of the 2000
Winter Simulation Conference, University of Central Florida, Orlando,
U.S.A.
Unal, R., Dean E.B. (1991). Taguchi approach to design optimization for quality and
cost: an overview, 1991 International Conference of the International
Society of Parametric Analysts.
Zamir, Oren y Etzioni, Oren. (1999). Grouper: A Dynamic Clustering Interface to Web
Search Results. Proceedings of the Eighth International World Wide Web
Conference, Computer Networks and ISDN Systems.
Apéndice 1
Determinación de parámetros para el algoritmo
Este apéndice detalla las pruebas realizadas para determinar los valores de cada
uno de los parámetros utilizados por el algoritmo genético.
A1.1.1 generacionesMáximo
Este parámetro especifica el número fijo de generaciones que se ejecutará el
algoritmo genético. El valor de este parámetro debe ser dependiente de la cantidad de
documentos que se van a agrupar. Lo que debe determinarse es la fórmula para calcular
el número de generaciones en función de la cantidad de documentos.
A1.1.2 poblacionTamaño
Este parámetro indica el número de cromosomas en la población. El posible
rango de valores es de 10 a 50 cromosomas.
A1.1.3 torneoTamaño
Este parámetro determina el tamaño del torneo para el operador de selección.
Los valores para este parámetro pueden variar entre 2 y 4.
A1.1.4 torneoProbMejor
Este parámetro especifica la probabilidad con la cual se elige al mejor individuo
del torneo en el operador de selección. Su valor puede variar entre 0.6 y 1.
A1.1.5 cruzaPasaGrupoProbMejor
Este parámetro indica la probabilidad de que, en el operador de cruza, el grupo
con mayor similitud promedio del padre que pasa el grupo se incluya en el hijo. Su
valor puede variar entre 0.6 y 1.
A1.1.6 mutacionRefinarKMProb
Este parametro especifica la probabilidad de aplicar el operador de mutación
“Refinar KM” a cada hijo generado por el operador de cruza. El posible rango de
valores es de 0 a 0.15.
A1.1.7 mutacionRefinarSelectivoProb
Este parametro especifica la probabilidad de aplicar el operador de mutación
“Refinar Selectivo” a cada hijo generado por el operador de cruza. El posible rango de
valores es de 0 a 0.1.
A1.3 Resultados
A1.3.1 generacionesMáximo
Se realizaron experimentos con muestras de datos tomadas al azar conteniendo
400, 700, 1000 y 1300 documentos, y se graficó la evolución de la aptitud promedio de
la población en función del número de generación para cada tamaño de muestra.
Cantidad de documentos
0,17
0,15
Aptitud promedio
0,13
0,11
0,09
0,07
0,05
0 50 100 150 200
Generación
Fig A1.1
Aptitud promedio en función del número de generación para distintas cantidades de documentos.
convergencia, ya que esta toma demasiado tiempo. En el gráfico se incluye una línea
que corta a cada curva en el punto donde se observa que el crecimiento comienza a ser
lento. Se encuentra experimentalmente que la función:
generacionesMaximo = (N / 20) + 25
donde N es la cantidad de documentos a categorizar, es una buena aproximación
de esta línea.
A1.3.2 poblacionTamaño
poblacionTamaño
10 20 30 40
0,18
0,16
Aptitud promedio
0,14
0,12
0,1
0,08
0,06
0 50 100 150 200
Generación
Fig A1.2
Aptitud promedio en función del número de generación para distintos tamaños de población.
(aunque muy lentamente), mientras que las poblaciones de pocos individuos convergen
rapidamente.
Para elegir el tamaño de la población a utilizar, se tuvieron en cuenta los
siguientes puntos:
- El algoritmo genético se detendrá antes de la convergencia de la población, y
en esa parte de la curva la evolución es similar para todos los tamaños de
población.
- El tamaño de la población impacta significativamente en la cantidad de
operaciones que requiere el algoritmo para llegar a una solución, ya que la
generación de cada individuo de la población inicial insume una cantidad de
operaciones considerable.
En el algoritmo propuesto, se comienza con un tamaño de población de 12
individuos, que se reduce a 10 una vez que han transcurrido el 40% de las generaciones
previstas. Se encontró experimentalmente que esta reducción en el tamaño de la
población aceleraba ligeramente la convergencia del algoritmo sin afectar la calidad de
la solución obtenida.
A1.3.3 torneoTamaño
torneoTamaño
2 3 4
0,18
0,16
Aptitud promedio
0,14
0,12
0,1
0,08
0,06
0 50 100 150 200
Generación
Fig A1.3
Aptitud promedio en función del número de generación para distintos tamaños de torneo.
A1.3.4 torneoProbMejor
torneoProbMejor
0.6 0.8 1
0,18
0,16
Aptitud promedio
0,14
0,12
0,1
0,08
0,06
0 50 100 150 200
Generación
Fig A1.4
Aptitud promedio en función del número de generación para distintos valores del parámetro
probTorneoMejor.
Se observa que este parámetro tiene un impacto sobre la presión selectiva, que es
aún mayor que el del parámetro anterior (tamaño del torneo). Una presión selectiva
demasiado pequeña (valor igual a 0.6) no hace evolucionar a la población, mientras que
si es demasiado alta (valor igual a 1), se produce la convergencia prematura. El valor de
0.8 produce los mejores resultados. El algoritmo propuesto utiliza para este parámetro
un valor igual a 0.75 hasta que transcurre el 40% de las generaciones previstas, luego se
aumenta el valor a 0.8 hasta llegar al 80% de las generaciones, donde se vuelve a
aumentar a 0.85 para las generaciones restantes. De esta manera, se va acelerando
progresivamente la velocidad de evolución del algoritmo.
A1.3.5 cruzaPasaGrupoProbMejor
cruzaPasaGrupoProbMejor
0.6 0.8 1
0,18
0,16
Aptitud promedio
0,14
0,12
0,1
0,08
0,06
0 50 100 150 200
Generación
Fig A1.5
Aptitud promedio en función del número de generación para distintos valores del parámetro
cruzaPasaGrupoProbMejor.
Un valor de 0.6 para este parámetro resulta demasiado pequeño y provoca una
involución de la población, ya que en un 40% de los casos el grupo que pasa al hijo es
seleccionado al azar, lo que puede ocasionar que los hijos sean soluciones de peor
calidad que los padres. Los valores de 0.8 y 1 presentan curvas de evolución similares.
En el algoritmo propuesto se utiliza un valor de 0.8 hasta que transcurre el 80% de las
generaciones previstas, y luego se aumenta este valor a 0.9, para acelerar la
convergencia en la fase final.
A1.3.6 mutacionRefinarKMProb
mutacionRefinarKMProb
0,18
0,16
Aptitud promedio
0,14
0,12
0,1
0,08
0,06
0 50 100 150 200
Generación
Fig A1.6
Aptitud promedio en función del número de generación para distintos valores del parámetro
mutacionRefinarKMProb.
A1.3.7 mutacionRefinarSelectivoProb
mutacionRefinarSelectivoProb
0 0.05 0.1
0,18
0,16
Aptitud promedio
0,14
0,12
0,1
0,08
0,06
0 50 100 150 200
Generación
Fig A1.7
Aptitud promedio en función del número de generación para distintos valores del parámetro
mutacionRefinarSelectivoProb.
Puede observarse que la aplicación de este operador puede ser nociva para la
evolución de la población. Para un valor de probabilidad de 0.1, el algoritmo genético
no alcanza soluciones aceptables. Tal como se describe en el capítulo 4 este operador
suele formar unos pocos grupos con gran similitud promedio, aunque reduciendo la
calidad general de la solución mutada. Si se lo utiliza desde el inicio de la evolución,
cada vez que se aplica reduce la calidad del agrupamiento que muta, impidiendo que el
algoritmo alcance soluciones de buena calidad. En el algoritmo propuesto, se lo utiliza
solamente sobre el final del algoritmo (en el último 20% de las generaciones) con
probabilidad 0.3. En esta fase, casi todas las soluciones son de la misma calidad y este
operador permite obtener material genético no presente en las soluciones.
Apéndice 2
Análisis estadístico de los resultados
Este apéndice detalla el análisis estadístico hecho sobre los resultados que se
exponen en el capítulo 5, y que soportan las afirmaciones realizadas en la sección 5.4
(“Resultados”) de ese capítulo. Las nociones teóricas de probabilidad y estadística
fueron extraídas principalmente de [Canavos, 1984], pero son las mismas que pueden
encontrarse en cualquier texto relativo al tema.
Cuando H0 es
Cuando H0 es
verdadera
verdadera No poder
(error Tipo I) Rechazar H0
Rechazar H0
Cuando H0
Cuando H0 es falsa (error
es falsa Tipo II)
A2.2.1 Introducción
Como se ve, los valores pueden tener grandes variaciones de muestra a muestra,
pero lo que importa es la diferencia entre los valores de cada algoritmo para cada
muestra, ya que eso es lo que indicará el mejor o peor rendimiento de cada uno.
La hipótesis nula que es puesta a prueba es que el promedio de los valores es
igual para los dos algoritmos (es decir, que las calidades de los agrupamientos
producidos es equivalente). Se plantean dos hipótesis alternativas, una de ellas afirma
que el promedio de los valores es mayor para el algoritmo 1, y la otra que el promedio
de los valores es mayor para el algoritmo 2.
La metodología del test es la siguiente:
- Se calculan las diferencias de los valores para cada muestra,
- Luego, se asigna a cada diferencia un valor en un ranking (de menor a
mayor) en base a su valor absoluto,
- Por último, a cada valor del ranking se le asigna el signo de la diferencia que
le dió origen.
Se denomina T+ a la suma de los valores positivos y T- a la suma de los
negativos. Si no hubiera diferencias entre los algoritmos, es de esperar que T+ resulte
igual a T- (en valor absoluto). Para muestras lo suficientemente grandes (N > 15), la
variable T+ puede aproximarse por medio de una distribución normal con media E(T+)
y varianza Var(T+), donde
N ( N + 1) N ( N + 1)( 2 N + 1)
E (T + ) = Var(T + ) =
4 24
(T + ) − E (T +)
zT + =
Var(T + )
Debe recordarse que valores más grandes para la similitud promedio indican una
mejor calidad del agrupamiento.
A2.3.2 Entropía
Debe recordarse que valores más chicos para la entropía indican una mejor
calidad del agrupamiento.
Apéndice 3
Manual de Uso
Este apéndice contiene la documentación de los programas y conjuntos de datos
que se incluyen en el CD que acompaña el presente trabajo. Estos programas hacen
posible la realización de los experimentos y la evaluación de los resultados.
La sección A3.1 describe el esquema de procesamiento, explicando la función de
cada componente en el proceso de experimentación. La sección A3.2 identifica la
ubicación de cada uno de los componentes en la estructura de directorios del CD. La
sección A3.3 explica cómo debe configurarse el origen de datos ODBC para que los
programas puedan acceder a la base de datos de datasets. La sección A3.4 detalla el
formato de los archivos que contienen los conjuntos de datos utilizados. En la sección
A3.5 se explica cómo utilizar el programa que genera subconjuntos de datos a partir de
los archivos que contienen la colección completa. La sección A3.6 contiene la
documentación del programa de agrupamiento. Éste programa es el que implementa los
algoritmos de agrupamiento descriptos durante el trabajo y permite utilizarlos para
agrupar los documentos de los subconjuntos de datos. La sección A3.7 describe el
programa que, a partir de la salida generada por el programa de agrupamiento, evalúa
las soluciones generadas y proporciona los valores para las distintas medidas de calidad.
Programa
“AGDocClus”
Base de datos de
Agrupamientos
datasets (MDB)
Programa
“Evaluar Agrupamientos”
Ø Directorio Raiz
Ø Tesis
Este directorio contiene los documentos que componen el presente
trabajo.
Ø Conjuntos de datos
Este directorio contiene los archivos de los conjuntos re0 y re1.
Ø Subconjuntos de datos
Este directorio contiene subdirectorios para cada uno de los subconjuntos
utilizados. En cada subdirectorio se encuentran los archivos del
subconjunto, y los agrupamientos encontrados por cada uno de los
algoritmos evaluados.
Ø Base de datos
Este directorio contiene la base de datos de datasets.
Ø Armar Datasets
Este directorio contiene el código fuente y el ejecutable compilado del
programa que genera los subconjuntos de datos al azar.
Ø AGDocClus
Este directorio contiene el código fuente y el ejecutable compilado del
programa que implementa los algoritmos de agrupamiento estudiados en
el trabajo.
Ø Evaluar Agrupamientos
Este directorio contiene el código fuente y el ejecutable compilado del
programa que calcula las medidas de calidad para los agrupamientos
generados.
Ø Resultados
Este directorio contiene las planillas excel con los resultados de los
experimentos realizados.
Luego debe configurarse el origen de datos ODBC para utilizar el archivo MDB
que se copió al disco en el paso descripto en la sección A3.3.1, de la forma que muestra
la figura A3.5. En la figura se utiliza el nombre “bdReuters2” para el origen de datos. Se
recomienda el uso de este nombre, ya que es el que tienen configurado por defecto los
programas que acceden a la base de datos.
Fig. A3.5:
Configuración del
origen de datos
Este programa es el que se utiliza para extraer muestras de datos al azar a partir
de los conjuntos de datos re0 y re1. En la realización de los experimentos, este
programa se utilizó para generar las 10 muestras de datos re0_1 a re0_10 con 500
documentos al azar del conjunto de datos re0, y las 10 muestras de datos re1_1 a
re1_10, con 500 documentos al azar del conjunto de datos re1.
Al ejecutar el programa, se presenta la siguiente pantalla:
Fig. A3.6:
Configuración del
programa “Armar
Datasets”
datos re0 y re1. En este directorio se ubicarán tambien los archivos de las
nuevas muestras de datos generadas por el programa.
- Dataset fuente : Este parámetro permite seleccionar el conjunto de datos del
cual se quiere extraer la muestra de datos al azar. Este parámetro puede
tomar los valores “re0” y “re1”.
- Cantidad elementos dataset fuente : En este parámetro debe colocarse la
cantidad de documentos que contiene el conjunto de datos ingresado en el
parámetro anterior. Los conjuntos de datos re0 y re1 contienen 1504 y 1657
documentos, respectivamente.
- Cantidad elementos nuevos datasets : Este parámetro indica la cantidad de
documentos que van a contener las muestras de datos extraídas. Debe ser
menor o igual a la cantidad de documentos del conjunto de datos origen.
- Nuevos datasets, desde : Este parámetro indica el número de la primera
muestra de datos a generar. Si el parámetro tiene el valor “1”, y el conjunto
de datos origen es “re0”, la primera muestra de datos será “re0_1”.
- Nuevos datasets, hasta : Este parámetro indica el número de la última
muestra de datos a generar. Si el conjunto de datos origen es “re0”, el
parámetro “Nuevos datasets, desde” toma el valor “1”, y el parámetro
“Nuevos datasets, hasta” toma el valor “3”, las muestras de datos generadas
serán “re0_1”, “re0_2” y “re0_3”.
Una vez configurados los valores de los parámetros, debe presionarse el botón
“Procesar”. El programa generará los archivos correspondientes a las muestras de datos,
y guardará tambien la información de los mismos en la base de datos de datasets.
Las opciones (1) a (3) permiten cambiar parámetros que afectarán la realización
de los experimentos.
La opción (1) permite cambiar la cantidad de grupos que se formarán.
La opción (2) permite cambiar la cantidad de corridas del algoritmo
seleccionado que se realizarán.
La opción (3) permite cambiar la cantidad de generaciones que se ejecutarán en
el algoritmo genético.
Las opciones (a) y (b) permiten realizar los agrupamientos con cada uno de los
algoritmos.
Al seleccionar la opción (a) se agruparán los elementos de la muestra de datos
utilizando el algoritmo “Bisecting K-Means con refinamiento”.
Al seleccionar la opción (b) se agruparán los elementos de la muestra de datos
utilizando los algoritmos “Genético” y “Genético con refinamiento”.
El programa generará los archivos de salida para cada corrida de los algoritmos
en el subdirectorio “salida”. Estos archivos de salida son utilizados por el programa
“Evaluar Agrupamientos”.
Este programa toma los agrupamientos generados como salida por el programa
“AGDocClus” y calcula las medidas de calidad utilizadas en el trabajo.
Una vez configurados los valores de los parámetros, debe presionarse el botón
“Procesar”. El programa buscará los archivos de salida generados por el programa
“AGDocClus”, obtendrá la información del dataset de la base de datos de datasets, y
generará una planilla en el programa Microsoft Excel con las medidas de calidad para
cada uno de los agrupamientos procesados.
Para cada corrida de cada uno de los algoritmos se muestran sus medidas de
calidad, con los promedios agrupados para las corridas de cada algoritmo.
Apéndice 4
Programación de los algoritmos
En éste apéndice se detalla la forma en la que se programaron los algoritmos
comparados en la prueba experimental. Para realizar la prueba experimental, se
desarrolló una aplicación de categorización automática de documentos que permite
seleccionar qué algoritmo se quiere utilizar. El desarrollo de la aplicación se realizó en
el lenguaje C++. La sección A4.1 contiene la descripción de los distintos módulos en
los que se separó la lógica del algoritmo y la interacción que existe entre los mismos. En
la sección A4.2 se incluye el código fuente de los módulos más significativos.
Este módulo implementa las funciones necesarias para leer los subconjuntos de
documentos que se categorizarán con la aplicación. Los subconjuntos de documentos
deben estar compuestos por archivos con el formato que se describe en el apéndice 3
(“Manual de uso”), sección A3.4. Este módulo se compone de los siguientes archivos de
código fuente:
• ArchivosTexto.h: Declaración de las funciones para leer los
subconjuntos de documentos.
• ArchivosTexto.cpp: Implementación de las funciones que leen los
subconjuntos de documentos.
Este módulo implementa la interfaz con el usuario, que permite seleccionar qué
subconjunto de datos se quiere agrupar, qué algoritmo se desea utilizar, y cuántos
grupos se van a formar. El módulo contiene procedimientos que, utilizando las
funciones y clases definidas en los otros módulos, ejecutan las acciones seleccionadas.
Este módulo se compone de los siguientes archivos de código fuente:
• Def.h: Definición de tipos de datos y de constantes utilizadas en la
aplicación.
• Sis.h, SisDos.h y SisDos.cpp: Declaración e implementación de
funciones auxiliares.
• Ppal.cpp: Implementación de las funciones de interfaz con el usuario, y
de los procedimientos que ejecutan las acciones seleccionadas.
A4.2.1 ArchivosTexto.h
#ifndef __ArchivosTextoH__
#define __ArchivosTextoH__
#endif
A4.2.2 ArchivosTexto.cpp
#include "def.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "skipl.h"
#include "archtxt.h"
#include "filtrarpalabras.h"
#include "steeming.h"
#include "vecArchivos.h"
d = PrimeroDir(dir,arch);
while (d!=-1)
/* mientras haya archivos en el directorio */
{
strcpy(archlwr,arch);
strlwr(archlwr);
if (strstr(archlwr,".txt"))
/* si es un .txt */
{
/* paso cant a long */
auxlong = (long)cant;
ltxt.Buscar_Insertar((ubyte*)arch,auxlong);
cant++;
//xxxdebug
//printf("Arch : %d - %s\n",cant-1,arch);
}
d = SiguienteDir(d,arch);
}
return (cant-1);
}
char arch[MAX_LONG_NOMARCH];
/* empiezo a contar desde 1 */
uent cant,i;
uword auxlong;
FILE *fMat;
/* cargamos la lista */
for (i=1;i <= cant; i++)
/* para cada "archivo" */
{
sprintf((char*)&arch,"_D_%d.txt",i);
/* paso "i" a long */
auxlong = (long)i;
ltxt.Buscar_Insertar((ubyte*)arch,auxlong);
//xxxdebug
//printf("Arch : %d - %s\n",i,arch);
}
return cant;
}
cantlex = 1;
if (ltxt.MoverPrimero() == ERROR)
/* la lista estaba vacia, no hay archivos */
return OK;
if (atxt.CrearBuffer(tbufftxt) == ERROR_MEMORIA)
return ERROR_MEMORIA;
do
{
ltxt.ObtenerClave(nomtxt);
nroarch = ltxt.ObtenerValor();
lLexArchivos[nroarch] = new c_SkipList();
if (lLexArchivos[nroarch] == NULL)
return ERROR_MEMORIA;
/* obtenemos los datos del archivo de texto */
if (atxt.Abrir((char*)nomtxt) == ERROR_ARCHIVO)
return ERROR_ARCHIVO;
lexico.ResetFrecs();
/* reseteamos las frecuencias de la lista de lexico */
while (atxt.LeerPalabra(pal))
/* leemos todas las palabras y las metemos en el lexico si
es que no estan */
{
/* procesamos la palabra */
codRet = ProcesarPalabra(pal);
if (codRet != ERROR)
/* Hay que guardar esta palabra */
{
/* le aplicamos las reglas de steeming */
steeming_porter((char*)pal);
nropal = cantlex;
/* pasamos el nropal a long */
auxlong = nropal;
if (lexico.Buscar_Insertar(pal,auxlong) == ERROR_MEMORIA)
return ERROR_MEMORIA;
nropal = (uent)auxlong;
if (nropal == cantlex)
{
cantlex++;
}
}
}
atxt.Cerrar();
/* no hay mas palabras, completemos la lista de ese archivo */
if (lexico.MoverPrimero() == OK)
{
/* vamos a trabajar con la lista de ese archivo */
lLexA = lLexArchivos[nroarch];
do
{
if (lexico.ObtenerFrec() != 0)
/* si la palabra estaba en el archivo que procesamos */
{
/* la insertamos en la lista del archivo */
lexico.ObtenerClave(pal);
nroTerm = lexico.ObtenerValor();
frecTerm = lexico.ObtenerFrec();
if (lLexA->Buscar_Insertar(pal,nroTerm) ==
ERROR_MEMORIA)
return ERROR_MEMORIA;
/* seteamos la frecuencia del termino */
lLexA->SetearFrecuencia(frecTerm);
}
} while (lexico.MoverSiguiente() == OK);
}
} while (ltxt.MoverSiguiente() == OK);
lexico.ResetFrecs();
/* cantlex habia quedado con 1 de mas */
cantlex--;
return OK;
lexico.ResetFrecs();
/* vamos leyendo los pares (palabra,frecuencia) de ese archivo */
do
{
fscanf(fp,"%f %f",&fNroTerm,&fFrecTerm);
nroTerm = (uword)fNroTerm;
frecTerm = (uent)fFrecTerm;
/* buscamos esa palabra en el lexico */
lexico.Buscar(pal,nroTerm);
/* la insertamos el la lista del archivo */
if (lLexA->Buscar_Insertar(pal,nroTerm) == ERROR_MEMORIA)
return ERROR_MEMORIA;
/* asignamos la frecuencia en ambas listas */
lexico.SetearFrecuencia(frecTerm);
lLexA->SetearFrecuencia(frecTerm);
/* leemos un caracter, a ver si viene el fin de linea */
fscanf(fp,"%c",&dummyChar);
} while (dummyChar != '\n');
}
lexico.ResetFrecs();
fclose(fp);
return OK;
}
A4.2.3 VecArchivos.h
/*
Clase de vector de archivos
*/
#ifndef __vecArchivosH__
#define __vecArchivosH__
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "def.h"
#include "sis.h"
#define NRO_ENT_KEYWORDS 5
class c_VecArchivo {
private:
uent cantEntradas; /* numero de entradas en el vector */
t_RegVecArchivo *entradas; /* vector con una entrada por cada palabra del
archivo */
t_RegVecArchivo keywords[NRO_ENT_KEYWORDS]; /* palabras clave que
describen al archivo */
real normaValor; /* valor de la norma del vector */
bool normaActualizada; /* true si el valor de la norma está actualizado */
uent SumarPriv(c_VecArchivo &otroVec, real signo, bool
soloEntradasEnComun);
public:
static uword contMultiplicaciones; /* cuenta las veces que se llamó a la funcion
multiplicacion */
static uword contCalculoNorma; /* cuenta la cantidad de veces que se llamó a la
funcion calcular norma */
c_VecArchivo();
uent CrearVector(uent parCantEntradas);
uent Duplicar(c_VecArchivo &otroVec);
void ModificarEntrada(uent nroEntrada, uent parValEnt, real parValReal, bool
usarKeywords);
void ConseguirEntrada(uent nroEntrada, uent &parValEnt, real &parValReal, bool
usarKeywords);
#endif
A4.2.4 VecArchivos.cpp
#include "vecArchivos.h"
uword c_VecArchivo::contMultiplicaciones = 0;
uword c_VecArchivo::contCalculoNorma = 0;
c_VecArchivo::c_VecArchivo()
/* constructor */
{
cantEntradas = 0;
entradas = NULL;
normaValor = (real)0.0;
normaActualizada = false;
}
c_VecArchivo::~c_VecArchivo()
/* destructor */
{
if (cantEntradas != 0)
free((void*)entradas);
}
cantEntradas = parCantEntradas;
if (cantEntradas == 0)
return OK;
return OK;
}
memcpy((void*)keywords,(void*)otroVec.keywords,NRO_ENT_KEYWORDS *
sizeof(t_RegVecArchivo));
return OK;
}
parValEnt = 0;
parValReal = 0;
}
}
}
contMultiplicaciones++;
ent1 = ConseguirCantEntradas(usarKeywords);
ent2 = otroVec.ConseguirCantEntradas(usarKeywords);
i1 = 0;
i2 = 0;
ConseguirEntrada(i1,valEnt1,valReal1,usarKeywords);
otroVec.ConseguirEntrada(i2,valEnt2,valReal2,usarKeywords);
rAux = 0;
return rAux;
}
real valReal1,valReal2;
uent i1,i2,valEnt1,valEnt2,entAux;
uent ent1,ent2;
ent1 = ConseguirCantEntradas(usarKeywords);
ent2 = otroVec.ConseguirCantEntradas(usarKeywords);
i1 = 0;
i2 = 0;
ConseguirEntrada(i1,valEnt1,valReal1,usarKeywords);
otroVec.ConseguirEntrada(i2,valEnt2,valReal2,usarKeywords);
entAux = 0;
return entAux;
}
usarKeywords = false;
ent1 = ConseguirCantEntradas(usarKeywords);
ent2 = otroVec.ConseguirCantEntradas(usarKeywords);
if (soloEntradasEnComun == false)
{
entComun = EnComun(otroVec,usarKeywords);
nuevasEntradas =
(t_RegVecArchivo*)malloc(nuevoTam*sizeof(t_RegVecArchivo));
if (nuevasEntradas == NULL)
return ERROR_MEMORIA;
}
else
{
nuevasEntradas = entradas;
}
i1 = 0;
i2 = 0;
iNuevo = 0;
normaVec = (real)0.0;
ConseguirEntrada(i1,valEnt1,valReal1,usarKeywords);
otroVec.ConseguirEntrada(i2,valEnt2,valReal2,usarKeywords);
}
i2++;
otroVec.ConseguirEntrada(i2,valEnt2,valReal2,usarKeywords);
}
if (ignoramosEntrada == false)
{
normaVec += nuevasEntradas[iNuevo].valReal *
nuevasEntradas[iNuevo].valReal;
iNuevo++;
}
}
/* alguno de los vectores se terminó */
while (i1 < ent1) {
/* se terminó el otro vector */
nuevasEntradas[iNuevo].valEnt = valEnt1;
nuevasEntradas[iNuevo].valReal = valReal1;
i1++;
ConseguirEntrada(i1,valEnt1,valReal1,usarKeywords);
normaVec += nuevasEntradas[iNuevo].valReal *
nuevasEntradas[iNuevo].valReal;
iNuevo++;
}
while ((i2 < ent2) && (soloEntradasEnComun == false)) {
/* se terminó este vector */
nuevasEntradas[iNuevo].valEnt = valEnt2;
nuevasEntradas[iNuevo].valReal = signo * valReal2;
i2++;
otroVec.ConseguirEntrada(i2,valEnt2,valReal2,usarKeywords);
normaVec += nuevasEntradas[iNuevo].valReal *
nuevasEntradas[iNuevo].valReal;
iNuevo++;
}
if (soloEntradasEnComun == false)
{
/* liberamos el viejo vector de entradas */
free((void*)entradas);
/* asignamos el nuevo vector de entradas */
entradas = nuevasEntradas;
}
cantEntradas = iNuevo;
/* guardamos la norma y marcamos que está actualizada */
normaValor = (real)sqrt((double)normaVec);
normaActualizada = true;
return OK;
real normaVec;
uent i;
if (incContador)
contCalculoNorma++;
if (normaActualizada == false)
{
/* la norma no está actualizada, hay que calcularla */
normaVec = (real)0.0;
for (i=0; i < cantEntradas; i++)
{
normaVec += entradas[i].valReal * entradas[i].valReal;
}
normaVec = (real)sqrt((double)normaVec);
/* la guardamos, y marcamos que está actualizada */
normaValor = normaVec;
normaActualizada = true;
}
else
{
normaVec = normaValor;
}
return normaVec;
}
void c_VecArchivo::Normalizar()
{
/* divide las entradas del vector por su norma, de forma tal
que la norma del vector sea igual a 1 */
real normaVec,unoSobre;
normaVec = CalcularNorma(false);
/* dividimos los valores de todas las entradas por la norma */
unoSobre = (real)1.0 / normaVec;
MultiplicarPorReal(unoSobre);
}
void c_VecArchivo::ActualizarPalabrasClave()
{
/* actualiza la lista de palabras clave del vector
*/
uent iEntradas,i,j;
{
keywords[j].valReal = (real)0.0;
keywords[j].valEnt = 0;
}
i = 0;
A4.2.5 ClsAgrupamiento.h
/*
Clase que implementa un agrupamiento
*/
#ifndef __clsAgrupamiento__
#define __clsAgrupamiento__
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "def.h"
#include "vecArchivos.h"
#define AGRUP_MIN_GRUPOS 5
#define AGRUP_INC_CANT_GRUPOS 5
class c_Agrupamiento {
friend class c_KMeans;
friend class c_Agrup_Imprime;
friend class c_Genetico;
private:
tRegVecGrupos *vecGrupos;
uent *vecElementos;
uent capacidadVecGrupos;
void CopiarRegVecGrupos(uent i, uent j, tRegVecGrupos *nuevoVecGrupos);
public:
uent cantGrupos;
uent cantElementos;
uent IndiceDeGrupo(uent nroGrupo, bool puedeNoEstar);
uent SiguienteNroGrupo();
c_Agrupamiento();
~c_Agrupamiento();
uent Inicializar(uent parCantElementos);
uent Duplicar(c_Agrupamiento &agrup);
uent AgruparAlAzar(uent cantGrupMin, uent cantGrupMax);
uent AgregarGrupos(uent cantidad);
void RenumerarGrupos();
void MoverElemento(uent nroElem, uent indGrupNuevo, uent indGrupAnterior);
void EliminarRegVecGrupos(uent indGrupo);
};
#endif
A4.2.6 ClsAgrupamiento.cpp
#include "clsAgrupamiento.h"
c_Agrupamiento::c_Agrupamiento()
{
cantElementos = 0;
capacidadVecGrupos = 0;
cantGrupos = 0;
}
c_Agrupamiento::~c_Agrupamiento()
{
uent i;
for (i=0 ; i < cantGrupos ; i++)
{
if (vecGrupos[i].vecCentroide != NULL)
delete(vecGrupos[i].vecCentroide);
}
if (vecGrupos != NULL)
free((void*)vecGrupos);
if (vecElementos != NULL)
free((void*)vecElementos);
uent c_Agrupamiento::SiguienteNroGrupo()
{
/* devuelve el siguiente número de grupo que se puede
utilizar para un grupo nuevo
*/
uent i;
uent max;
max = 1;
for (i=0; i < cantGrupos; i++)
if (vecGrupos[i].nroGrupo > max)
max = vecGrupos[i].nroGrupo;
/* veamos si no hay un numero mayor, en ese caso,
devolvemos 0 */
if (max >= MAX_UENT)
return 0;
return (max + 1);
}
{
if (puedeNoEstar)
return RANDOM(cantGrupos);
else
return MAX_UENT; /* para que de error y detectemos el problema */
}
else
return i;
}
}
else
{
vecGrupos[i].vecCentroide = NULL;
}
}
return OK;
}
{
/* buscamos el grupo del elemento */
grupAsig = IndiceDeGrupo(vecElementos[i],false);
}
/* sumamos un elemento al grupo */
vecGrupos[grupAsig].cantElementos++;
/* vemos si este elemento es el menor nro de elementos del grupo */
if (vecGrupos[grupAsig].menorNroElem > i)
vecGrupos[grupAsig].menorNroElem = i;
}
return OK;
}
siguienteNro = SiguienteNroGrupo();
if ((siguienteNro + cantidad) > MAX_UENT) /* renumeramos los grupos */
{
RenumerarGrupos();
siguienteNro = SiguienteNroGrupo();
}
/* agregamos los nuevos grupos al vector */
cantGrupOld = cantGrupos;
for (i=0; i < cantidad; i++)
{
vecGrupos[cantGrupOld + i].cantElementos = 0;
/* en el campo "menorNroElemento" ponemos la cantidad de elementos
del agrupamiento, para que sea mayor a cualquier numero de elemento */
vecGrupos[cantGrupOld + i].menorNroElem = cantElementos;
vecGrupos[cantGrupOld + i].nroGrupo = siguienteNro;
vecGrupos[cantGrupOld + i].vecCentroide = NULL;
siguienteNro++;
cantGrupos++;
}
return OK;
}
void c_Agrupamiento::RenumerarGrupos()
{
/* renumera los grupos */
/* esta función debe utilizarse en el improbable caso de que se
hayan usado más de 65536 números de grupo, por agregar y borrar
grupos muchas veces */
uent i,j, nroAnterior;
if (indGrupNuevo != indGrupAnterior)
{
vecElementos[nroElem] = vecGrupos[indGrupNuevo].nroGrupo;
vecGrupos[indGrupNuevo].cantElementos++;
/* veamos si el que agregamos al nuevo grupo es el menor nro de elemento */
if (nroElem < vecGrupos[indGrupNuevo].menorNroElem)
vecGrupos[indGrupNuevo].menorNroElem = nroElem;
if (vecGrupos[indGrupAnterior].cantElementos <= 1)
EliminarRegVecGrupos(indGrupAnterior);
else
{
vecGrupos[indGrupAnterior].cantElementos--;
/* veamos si el que le sacamos era el menor numero de elemento */
if (nroElem == vecGrupos[indGrupAnterior].menorNroElem)
{
/* buscamos el nuevo menor numero de elemento */
i = nroElem + 1;
while (i < cantElementos)
{
if (vecElementos[i] ==
vecGrupos[indGrupAnterior].nroGrupo)
break;
i++;
}
vecGrupos[indGrupAnterior].menorNroElem = i;
}
}
}
}
A4.2.7 ClsAgrupImprime.h
/*
Clase que imprime agrupamientos, grupos y documentos
para ser desplegados al usuario
*/
#ifndef __clsAgrupImprime__
#define __clsAgrupImprime__
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "def.h"
#include "sis.h"
#include "vecArchivos.h"
#include "clsAgrupamiento.h"
#include "skipl.h"
#include "clsKMeans.h"
class c_Agrup_Imprime {
private:
public:
static bool salidaCompacta;
c_Agrup_Imprime() {};
~c_Agrup_Imprime() {};
static uent ImprimirVecArchivo(FILE *fp, c_VecArchivo &vecArchivo, c_SkipList
&lexico);
static uent ImprimirTodo(FILE *fp, c_Agrupamiento &agrup, c_VecArchivo
*vVecArchivos[], c_SkipList &lexico, c_SkipList <xt);
static uent ImprimirGrupo(FILE *fp, uent indGrupo, c_Agrupamiento &agrup,
c_VecArchivo *vVecArchivos[], c_SkipList &lexico, c_SkipList <xt);
static uent ImprimirDocumento(FILE *fp, uent nroElem, c_VecArchivo
*vVecArchivos[], c_SkipList &lexico);
};
#endif
A4.2.8 ClsAgrupImprime.cpp
#include "clsAgrupImprime.h"
cantKeywords = vecArchivo.ConseguirCantEntradas(true);
/* recorremos la lista de atras para adelante porque las ultimas
entradas son las de mayor peso */
for (i=cantKeywords; i > 0; i--)
{
vecArchivo.ConseguirEntrada(i-1,nroPalEnt,auxReal,true);
if (nroPalEnt > 0)
{
/* la buscamos en la lista de lexico */
nroPalLong = (uword)nroPalEnt;
lexico.Buscar(palabra,nroPalLong);
/* la imprimimos */
fprintf(fp,"%s",(char*)palabra);
}
if (i > 1)
fprintf(fp," ; ");
}
return OK;
}
if (salidaCompacta)
{
fprintf(fp,"%d",nroElem);
}
else
{
codRet = ImprimirVecArchivo(fp,*(vVecArchivos[nroElem]),lexico);
if (codRet != OK)
return codRet;
}
return OK;
}
uent j;
uword jLong;
ubyte nomArch[MAX_LONG_NOMARCH];
real similPromGrupo;
real simElemCent;
ImprimirVecArchivo(fp,*(agrup.vecGrupos[indGrupo].vecCentroide),lexico);
}
//xxxdebug
else
ImprimirVecArchivo(fp,*(vVecArchivos[agrup.vecGrupos[indGrupo].menorNroElem +
1]),lexico);
fprintf(fp,"\n");
}
/* imprimimos los elementos del grupo */
for (j=0; j < agrup.cantElementos; j++)
if (agrup.vecElementos[j] == agrup.vecGrupos[indGrupo].nroGrupo)
if (salidaCompacta)
{
jLong = (uword)j + 1;
ltxt.Buscar(nomArch,jLong);
fprintf(fp,"%s;",(char*)nomArch);
//ImprimirDocumento(fp,j+1,vVecArchivos,lexico);
//fprintf(fp,";");
}
else
{
jLong = (uword)j + 1;
ltxt.Buscar(nomArch,jLong);
simElemCent =
c_KMeans::ValorCriterioActual(agrup,vVecArchivos,j,indGrupo);
fprintf(fp,"\t%s (%f)\n",(char*)nomArch,simElemCent);
fprintf(fp,"\t\t");
ImprimirDocumento(fp,j+1,vVecArchivos,lexico);
fprintf(fp,"\n");
}
fprintf(fp,"\n");
return OK;
}
return OK;
}
A4.2.9 ClsKMeans.h
/*
Clase que implementa las funciones del algoritmo kmeans
*/
#ifndef __clsKMeans__
#define __clsKMeans__
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "def.h"
#include "sis.h"
#include "vecArchivos.h"
#include "clsAgrupamiento.h"
//xxxdebug
#include "skipl.h"
#include "clsAgrupImprime.h"
class c_KMeans {
private:
static uent ElegirKCentroides(c_Agrupamiento &agrup, uent nroGrupo, uent
*indGrupos, c_VecArchivo *vVecArchivos[], bool bOptimizarSeparacion, uent cantMuestras);
public:
static uword contMoverYRecalcular; /* cuenta las veces que se llamó a la
función de mover y recalcular */
static uent k;
static uent codCriterio; /* el criterio de optimizacion que se utiliza para refinar */
/* 1 : minima distancia de cada elemento al centroide del grupo */
/* 2 : ... */
c_KMeans() {};
~c_KMeans() {};
static uent FaseInicial(c_Agrupamiento &agrup, uent nroGrupo, c_VecArchivo
*vVecArchivos[], uent *indGrupos, bool bOptimizarSeparacion, uent cantMuestras);
static uent FaseInicialLite(c_Agrupamiento &agrup, uent nroGrupo,
c_VecArchivo *vVecArchivos[], uent *indGrupos, bool bOptimizarSeparacion, uent
cantMuestras);
static uent CalcularCentroides(c_Agrupamiento &agrup,c_VecArchivo
*vVecArchivos[],uent cantGrup,uent *vecIndGrupos);
static uent FaseRefinar(c_Agrupamiento &agrup, c_VecArchivo
*vVecArchivos[], uent *indGrupos,bool iteracionesFijas, uent &cantIteraciones, uent
&cantCambiosTotal);
static uent FaseRefinarLite(c_Agrupamiento &agrup, c_VecArchivo
*vVecArchivos[], uent *indGrupos,bool iteracionesFijas, uent &cantIteraciones, uent
&cantCambiosTotal);
static bool NroGrupoEstaEnVectorIndices(uent nro, uent *vector,uent
cantEntradas, c_Agrupamiento &agrup, uent &indGrupo);
static real ValorCriterioActual(c_Agrupamiento &agrup, c_VecArchivo
*vVecArchivos[], uent nroElemAnalisis, uent indGrupoAnalisis);
static real ValorCriterioAlternativa(c_Agrupamiento &agrup, c_VecArchivo
*vVecArchivos[], uent nroElemAnalisis, uent indGrupoCandidato, real valCriterioActual);
#endif
A4.2.10 ClsKMeans.cpp
#include "clsKMeans.h"
/* por defecto k = 5 */
uent c_KMeans::k = 5;
uword c_KMeans::contMoverYRecalcular = 0;
indGrupo = agrup.IndiceDeGrupo(nroGrupo,false);
cantElemGrupo = agrup.vecGrupos[indGrupo].cantElementos;
cantGruposAnterior = agrup.cantGrupos;
free((void*)nrosAzar);
free((void*)vNroElem);
return OK;
}
if (ordenProceso == NULL)
return ERROR_MEMORIA;
/* armamos un vector con los numeros de elemento del grupo */
nrosElementos = (uent*)malloc(cantElemGrupo * SINT);
indElemGrupo = 0;
for (i = 0; i < agrup.cantElementos; i++)
{
if (agrup.vecElementos[i] == nroGrupo)
{
//xxxdebug
//printf("c_KMeans::FI::orAzar : %d ;; nroElem :
%d\n",ordenProceso[indElemGrupo]-1,i);
nrosElementos[ordenProceso[indElemGrupo]-1] = i;
indElemGrupo++;
}
}
/* el centroide de cada grupo es el elemento que figura en el campo "menorNroElem" */
for (i = 0; i < k; i++)
{
if (i == 0)
j = indGrupo;
else
j = cantGruposAnterior + i - 1;
if (agrup.vecGrupos[j].vecCentroide != NULL)
delete(agrup.vecGrupos[j].vecCentroide);
agrup.vecGrupos[j].vecCentroide = new c_VecArchivo;
agrup.vecGrupos[j].vecCentroide-
>Duplicar(*(vVecArchivos[agrup.vecGrupos[j].menorNroElem + 1]));
}
grupGanador = cantGruposAnterior + j;
}
else
{
similAux = vecArchivoElem-
>Multiplicar(*(agrup.vecGrupos[cantGruposAnterior + j].vecCentroide),false);
ganoEmpate = false;
if (similAux == similGanador)
{
empates++;
ganoEmpate = (RANDOM(empates+1)==0);
}
if ((similAux > similGanador) || ganoEmpate)
{
similGanador = similAux;
grupGanador = cantGruposAnterior + j;
}
}
}
}
if (bOptimizarSeparacion)
{
c_KMeans::MoverElementoyRecalcular(agrup,vVecArchivos,nroElem,indGrupo,grupGa
nador,false);
}
else
{
/* si hace falta, lo movemos */
if (grupGanador != indGrupo)
{
agrup.MoverElemento(nroElem, grupGanador, indGrupo);
}
}
//xxxdebug
//if (esCentroide)
//printf("c_KMeans:FI: Elem : %d ;; Es centroide de :
%d\n",nroElem,grupGanador + 1);
//else
//printf("c_KMeans:FI: Elem : %d ;; SimGan : %f ;; GrupGan :
%d\n",nroElem,similGanador,grupGanador + 1);
}
free((void*)ordenProceso);
free((void*)nrosElementos);
return OK;
}
{
similGanador = vecArchivoElem-
>Multiplicar(*(vVecArchivos[agrup.vecGrupos[vecIndGruposMuestra[0]].menorNroElem +
1]),false);
grupGanador = vecIndGruposMuestra[0];
}
/* ahora con los otros */
for (j = 1; j < cantGruposMuestra-1; j++)
{
if (!esCentroide)
{
if (nroElem ==
agrup.vecGrupos[vecIndGruposMuestra[j]].menorNroElem)
{
esCentroide = true;
grupGanador = vecIndGruposMuestra[j];
}
else
{
similAux = vecArchivoElem-
>Multiplicar(*(vVecArchivos[agrup.vecGrupos[vecIndGruposMuestra[j]].menorNroElem +
1]),false);
ganoEmpate = false;
if (similAux == similGanador)
{
empates++;
ganoEmpate = (RANDOM(empates+1)==0);
}
if ((similAux > similGanador) || ganoEmpate)
{
similGanador = similAux;
grupGanador = vecIndGruposMuestra[j];
}
}
}
}
/* si hace falta, lo movemos */
if (grupGanador != indGrupo)
{
agrup.MoverElemento(nroElem, grupGanador, indGrupo);
}
free((void*)vecIndGruposMuestra);
//xxxdebug
//if (esCentroide)
//printf("c_KMeans:FI: Elem : %d ;; Es centroide de :
%d\n",nroElem,grupGanador + 1);
//else
//printf("c_KMeans:FI: Elem : %d ;; SimGan : %f ;; GrupGan :
%d\n",nroElem,similGanador,grupGanador + 1);
}
free((void*)ordenProceso);
free((void*)nrosElementos);
return OK;
}
return OK;
}
cantElementos = agrup.cantElementos;
iIteraciones = 0;
cantCambiosTotal = 0;
bParar = false;
while (!bParar)
{
cantCambios = 0;
/* buscamos un orden al azar para procesar los elementos */
ordenProceso = kNumerosRandom(cantElementos,0,cantElementos-1,false);
if (ordenProceso == NULL)
return ERROR_MEMORIA;
/* vamos iterando por todos los elementos pero procesamos
solamente los de los grupos que nos interesan */
for (iElem=0; iElem<cantElementos; iElem++)
{
/* tomamos el elemento */
nroElem = ordenProceso[iElem];
///xxxdebug
//printf("KMRefinar:Proc.Elem=%d\n",nroElem);
nroGrupoElem = agrup.vecElementos[nroElem];
/* veamos si esta en uno de los grupos que nos interesan */
if
(NroGrupoEstaEnVectorIndices(nroGrupoElem,indGrupos,k,agrup,indGrupoOrig))
{
///xxxdebug
//printf(" nos interesa(grupo=%d)\n",nroGrupoElem);
{
valorAux =
ValorCriterioAlternativa(agrup,vVecArchivos,nroElem,iGrupos,valorActual);
if ((valorAux - valorActual) > incGanador)
{
/* este incremento va ganando por
ahora */
indGrupoGanador = iGrupos;
incGanador = (valorAux - valorActual);
}
//xxxdebug
//printf("%f;;;",valorAux - valorActual);
}
}
//xxxdebug
//printf("\n");
/* veamos si hubo un ganador */
if (indGrupoGanador != indGrupoOrig)
{
/* hay que mover el elemento */
codRet =
MoverElementoyRecalcular(agrup,vVecArchivos,nroElem,indGrupoOrig,indGrupoGanador,true)
;
if (codRet != OK)
return codRet;
cantCambios++; /* sumamos un cambio esta vuelta */
//xxxdebug
//printf("KMRefinar::Cambio.Iter=%d
Elem=%d;Orig=%d;Dest=%d\n",iIteraciones,nroElem,indGrupoOrig,indGrupoGanador);
}
}
}
cantCambiosTotal += cantCambios;
//xxxdebug
//printf("KMRefinar::FinIter=%d Cambios=%d\n",iIteraciones,cantCambios);
//xxxdebug
printf("KMRefinar::Fin TotalIter=%d
TotalCambios=%d\n",iIteraciones,cantCambiosTotal);
return OK;
cantElementos = agrup.cantElementos;
iIteraciones = 0;
cantCambiosTotal = 0;
bParar = false;
while (!bParar)
{
cantCambios = 0;
/* buscamos un orden al azar para procesar los elementos */
ordenProceso = kNumerosRandom(cantElementos,0,cantElementos-1,false);
if (ordenProceso == NULL)
return ERROR_MEMORIA;
/* vamos iterando por todos los elementos pero procesamos
solamente los de los grupos que nos interesan */
for (iElem=0; iElem<cantElementos; iElem++)
{
/* tomamos el elemento */
nroElem = ordenProceso[iElem];
///xxxdebug
//printf("KMRefinar:Proc.Elem=%d\n",nroElem);
nroGrupoElem = agrup.vecElementos[nroElem];
/* veamos si esta en uno de los grupos que nos interesan */
if
(NroGrupoEstaEnVectorIndices(nroGrupoElem,indGrupos,k,agrup,indGrupoOrig))
{
///xxxdebug
//printf(" nos interesa(grupo=%d)\n",nroGrupoElem);
getchar(); getchar();
return ERROR_MEMORIA;
}
cantCambiosTotal += cantCambios;
//xxxdebug
//printf("KMRefinar::FinIter=%d Cambios=%d\n",iIteraciones,cantCambios);
//xxxdebug
printf("KMRefinarLite::Fin TotalIter=%d
TotalCambios=%d\n",iIteraciones,cantCambiosTotal);
return OK;
}
uent i;
return false;
if (agrup.vecGrupos[indGrupoAnalisis].cantElementos <= 1)
return MAX_VALOR_CRITERIO;
real valorCriterio;
real normaCent;
switch (codCriterio)
{
case 1 :
{
c_VecArchivo *vecCent, *vecElem;
real simil;
/* este criterio es igual a la similitud entre
el centroide del grupo y el elemento */
vecCent = agrup.vecGrupos[indGrupoAnalisis].vecCentroide;
vecElem = vVecArchivos[nroElemAnalisis+1];
simil = vecCent->Multiplicar(*vecElem,false);
normaCent = vecCent->CalcularNorma(false);
valorCriterio = simil / normaCent;
break;
}
default :
{
/* error : no estaba definido el criterio */
valorCriterio = 0.0;
}
}
return valorCriterio;
}
switch (codCriterio)
{
case 1 :
{
c_VecArchivo *vecCent, *vecElem;
real simil;
/* este criterio es igual a la similitud entre
return valorCriterio;
uent codRet;
contMoverYRecalcular++;
switch (codCriterio)
{
case 1 :
{
c_VecArchivo *vecCentOrig, *vecCentGanador, *vecElem;
real unoSobre;
uent cantElemOrig, cantElemGanador;
/* recalculamos los centroides de los 2 grupos */
vecCentOrig = agrup.vecGrupos[indGrupoOrig].vecCentroide;
vecCentGanador = agrup.vecGrupos[indGrupoGanador].vecCentroide;
vecElem = vVecArchivos[nroElem+1];
cantElemOrig = agrup.vecGrupos[indGrupoOrig].cantElementos;
cantElemGanador =
agrup.vecGrupos[indGrupoGanador].cantElementos;
/* al original le restamos el elemento que le sacamos */
/* primero lo multiplicamos por la cantidad de elementos anterior */
if (restarACentroide == true)
{
vecCentOrig->MultiplicarPorReal((real)cantElemOrig);
codRet = vecCentOrig->Restar(*vecElem);
if (codRet != OK)
return codRet;
unoSobre = (real)1.0 / (real)(cantElemOrig - 1);
vecCentOrig->MultiplicarPorReal(unoSobre);
}
/* al ganador le sumamos el elemento que le agregamos */
/* primero lo multiplicamos por la cantidad de elementos anterior */
vecCentGanador->MultiplicarPorReal((real)cantElemGanador);
codRet = vecCentGanador->Sumar(*vecElem);
if (codRet != OK)
return codRet;
unoSobre = (real)1.0 / (real)(cantElemGanador + 1);
vecCentGanador->MultiplicarPorReal(unoSobre);
/* movemos el elemento de un grupo al otro */
agrup.MoverElemento(nroElem,indGrupoGanador,indGrupoOrig);
break;
}
default :
{
/* error : no estaba definido el criterio */
}
}
return OK;
if (vecAgrups == NULL)
return ERROR_MEMORIA;
/* este vector va a contener los kBisect indices de los grupos que vamos
a ir refinando */
vec2Ind = (uent*)malloc(k * SINT);
if (vec2Ind == NULL)
return ERROR_MEMORIA;
/* en este vector vamos a tener los indices de los grupos que vamos
generando */
vecIndices = (uent*)malloc(kOriginal * SINT);
if (vecIndices == NULL)
return ERROR_MEMORIA;
while (iCantGrupos < kOriginal) /* hasta que no tengamos la cantidad de grupos que
queremos */
{
/* buscamos el grupo que tenga la mayor cantidad de elementos */
cantElemMax = 0;
for (i=0; i < agrup->cantGrupos; i++)
{
if (NroGrupoEstaEnVectorIndices(agrup-
>vecGrupos[i].nroGrupo,vecIndices,iCantGrupos,*agrup,indiceAux))
{
if (agrup->vecGrupos[i].cantElementos > cantElemMax)
{
nroADividir = agrup->vecGrupos[i].nroGrupo;
cantElemMax = agrup->vecGrupos[i].cantElementos;
}
}
}
//xxxdebug
printf("BisKM:SimProm[%d] = %f\n",i,simProm / k);
//c_Agrup_Imprime::salidaCompacta = false;
//c_Agrup_Imprime::ImprimirTodo(stdout,agrup,vVecArchivos,lLex,lTxt);
//printf ("Otra Iter \n");
}
//xxxdebug
printf("BisKM:SimPromGanador[%d] = %f\n",indMejor,maxSimProm / k);
/* restauramos el valor de k */
k = kOriginal;
return OK;
}
A4.2.11 ClsGenetico.h
/*
Clase que implementa las funciones del algoritmo genetico
*/
#ifndef __clsGenetico__
#define __clsGenetico__
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "def.h"
#include "sis.h"
#include "vecArchivos.h"
#include "clsAgrupamiento.h"
#include "clsKMeans.h"
//xxxdebug
#include "skipl.h"
#include "clsAgrupImprime.h"
class c_Genetico {
private:
uent AsignarFormaElegirGrupoDestino();
uent AsignarFormaElegirGrupoSplit();
tCromosoma **poblacion;
real FitnessDeCromosoma(tCromosoma &cromosoma);
uent privCruzaPasaGrupo(tCromosoma *padre1, tCromosoma *padre2,
tCromosoma **hijo1, c_VecArchivo *vVecArchivos[]);
void ActualizarParametros(uent genActual,uent genLimite);
FILE *DebugFP();
public:
uent ElegirPorTorneo(uent &indElegido, bool invertido);
tRegConfigGenetico RegConfig;
c_Genetico();
~c_Genetico();
#endif
A4.2.12 ClsGenetico.cpp
#include "clsGenetico.h"
c_Genetico::c_Genetico()
/* constructor */
{
/* seteos por defecto */
RegConfig.generacionesMaximo = 200;
RegConfig.k = 5; /* por defecto k=5 */
RegConfig.generacionInicialModo = 3;
RegConfig.fitnessCriterio = 1;
RegConfig.poblacionTamanio = 12;
RegConfig.poblacionTamanioDeseado = RegConfig.poblacionTamanio;
RegConfig.torneoTamanio = 2;
RegConfig.torneoProbMejor = (real)0.7;
RegConfig.cruzaOperador = 1;
RegConfig.cruzaPasaGrupoProbMejor = (real)0.7;
RegConfig.cruzaPasaGrupoProbMejorDestino = (real)0.95;
/* las siguientes 4 probabilidades deben sumar 1 */
RegConfig.cruzaPasaGrupoProbMasElemComun = (real)0.4;
RegConfig.cruzaPasaGrupoProbMasSimilar = (real)0.56;
RegConfig.cruzaPasaGrupoProbMenorSimilProm = (real)0.02;
RegConfig.cruzaPasaGrupoProbAzar = (real)0.02;
/* */
RegConfig.mutacionRefinarKMCantIter = 1;
RegConfig.mutacionRefinarKMProb = (real)0.3;
RegConfig.fpSalidaDebug = NULL;
RegConfig.mutacionJoinProb = (real)0.1;
/* las siguientes 3 probabilidades deben sumar 1 */
RegConfig.mutacionJoinProbAzar = (real)0.00;
RegConfig.mutacionJoinProbMenorSimilProm = (real)0.30;
RegConfig.mutacionJoinProbMasChico = (real)0.70;
/* */
RegConfig.mutacionJoinProbMasSimilar = (real)0.95;
RegConfig.mutacionSplitProb = (real)0.1;
/* las siguientes 3 probabilidades deben sumar 1 */
RegConfig.mutacionSplitProbAzar = (real)0.05;
RegConfig.mutacionSplitProbMasGrande = (real)0.9;
RegConfig.mutacionSplitProbMenorSimilProm = (real)0.05;
RegConfig.mutacionSplitCantMuestras = 6;
/* */
RegConfig.mutacionRefinarSelectivoProb = (real)0.0;
}
/*
RegConfig.torneoProbMejor = (real)(0.6 + (0.2 * pctCompleto));
RegConfig.cruzaPasaGrupoProbMejor = (real)(0.60 + (0.3 * pctCompleto));
RegConfig.mutacionRefinarKMProb = (real)(0.4 - (0.35 * sin((PI/2)*pctCompleto)));
*/
if (pctCompleto == 0.0) {
RegConfig.poblacionTamanioDeseado =
RegConfig.poblacionTamanioDeseado;
}
if (pctCompleto < 0.2) /* 0 a 20% */ {
RegConfig.torneoProbMejor = (real)(0.75);
RegConfig.cruzaPasaGrupoProbMejor = (real)(0.8);
RegConfig.mutacionRefinarKMProb = (real)(0.1);
RegConfig.mutacionJoinProb = (real)0.1;
RegConfig.mutacionSplitProb = (real)0.1;
RegConfig.torneoTamanio = 2;
} else if (pctCompleto < 0.4) /* 20 a 40% */ {
RegConfig.torneoProbMejor = (real)(0.75);
RegConfig.cruzaPasaGrupoProbMejor = (real)(0.8);
RegConfig.mutacionRefinarKMProb = (real)(0.1);
RegConfig.mutacionJoinProb = (real)0.1;
RegConfig.mutacionSplitProb = (real)0.1;
RegConfig.torneoTamanio = 3;
} else if (pctCompleto < 0.8) /* 40 a 80% */ {
RegConfig.poblacionTamanioDeseado = 10;
RegConfig.torneoProbMejor = (real)(0.8);
RegConfig.cruzaPasaGrupoProbMejor = (real)(0.8);
RegConfig.mutacionRefinarKMProb = (real)(0.1);
RegConfig.mutacionJoinProb = (real)0.15;
RegConfig.mutacionSplitProb = (real)0.1;
RegConfig.torneoTamanio = 3;
RegConfig.mutacionRefinarSelectivoProb = (real)0.1;
} else if (pctCompleto < 0.9) /* 80 a 90% */ {
RegConfig.torneoProbMejor = (real)(0.85);
RegConfig.cruzaPasaGrupoProbMejor = (real)(0.9);
RegConfig.mutacionRefinarKMProb = (real)(0.25);
RegConfig.torneoTamanio = 3;
RegConfig.mutacionJoinProb = (real)0.05;
RegConfig.mutacionSplitProb = (real)0.05;
RegConfig.mutacionRefinarSelectivoProb = (real)0.3;
} else /* 90 a 100% */ {
RegConfig.mutacionRefinarSelectivoProb = (real)0.0;
}
}
FILE* c_Genetico::DebugFP()
{
return (RegConfig.fpSalidaDebug ? RegConfig.fpSalidaDebug : stdout);
}
c_Genetico::~c_Genetico()
/* destructor */
{
uent i;
for (i=0; i < RegConfig.poblacionTamanio; i++)
{
if (poblacion[i] != NULL)
{
if (poblacion[i]->agrup != NULL)
{
delete (poblacion[i]->agrup);
}
free((void*)poblacion[i]);
}
}
free((void*)poblacion);
}
switch (RegConfig.fitnessCriterio)
{
case 1:
/* este criterio evalua el promedio de similitudes entre
los elementos de cada grupo */
simSuma = (real)0.0;
vecCent = agrup->vecGrupos[iGrupos].vecCentroide;
/* calculamos la norma del centroide al cuadrado */
normaCent = vecCent->CalcularNorma(true);
normaCent *= normaCent;
/* sumamos la norma */
simSuma += (real)(normaCent);
}
/* el criterio a devolver es la suma dividida la cantidad total de grupos */
return ((simSuma / cantGrupos));
break;
default: /* criterio desconocido ... */
return((real)0.0);
break;
}
switch (RegConfig.generacionInicialModo)
{
case 1:
/* lo agrupamos al azar */
codRet = poblacion[i]->agrup-
>AgruparAlAzar(RegConfig.k,RegConfig.k);
if (codRet != OK)
return codRet;
break;
case 2:
/* lo inicializamos con la fase inicial (lite) del kmeans */
codRet = poblacion[i]->agrup->AgruparAlAzar(1,1);
if (codRet != OK)
return codRet;
c_KMeans::k = RegConfig.k;
codRet = c_KMeans::FaseInicialLite(*(poblacion[i]-
>agrup),1,vVecArchivos,vecIndGruposAux,false,0);
if (codRet != OK)
return codRet;
break;
case 3:
/* fase inicial, pero solamente log k grupos */
codRet = poblacion[i]->agrup->AgruparAlAzar(1,1);
if (codRet != OK)
return codRet;
c_KMeans::k = (uent)(log(RegConfig.k)/log(2));
codRet = c_KMeans::FaseInicial(*(poblacion[i]-
>agrup),1,vVecArchivos,vecIndGruposAux,false,0);
if (codRet != OK)
return codRet;
break;
}
/* calculamos los centroides */
for (j=0; j<RegConfig.k; j++)
vecIndGruposAux[j] = j;
codRet = c_KMeans::CalcularCentroides(*(poblacion[i]-
>agrup),vVecArchivos,RegConfig.k,vecIndGruposAux);
if (codRet != OK)
return codRet;
/* le asociamos el fitness */
poblacion[i]->fitness = FitnessDeCromosoma(*(poblacion[i]));
/* esta disponible para ser seleccionado */
poblacion[i]->disponible = true;
/* no hubo un join */
poblacion[i]->ultimoJoin = -1;
fprintf(DebugFP(),"GenInic : %d; Fitness : %f \n",i,poblacion[i]->fitness);
}
free((void*)vecIndGruposAux);
return OK;
}
iMax = 0;
fitMax = (real)-1.0;
*solucion = poblacion[iMax]->agrup;
return OK;
}
if (invertido == false)
fprintf(DebugFP(),"Torneo : ");
else
fprintf(DebugFP(),"Torneo invertido : ");
/* iElem avanza mas rapido que iElemTorneo cuando hay cromosomas no disponibles
*/
iElem = 0;
for (iElemTorneo=0;iElemTorneo<RegConfig.torneoTamanio;iElemTorneo++)
{
/* iElem saltea los cromosomas no disponibles */
while(poblacion[elemAzar[iElem]]->disponible == false)
iElem++;
fprintf(DebugFP(),"(%d) %f ; ",elemAzar[iElem],poblacion[elemAzar[iElem]]-
>fitness);
/* ahora veamos en que posicion del vector "ordenTorneo" tiene que ir */
/* empezamos a evaluar por el ultimo del vector (el de mas fitness) */
j = iElemTorneo;
while (j > 0)
{
/* veamos si el que acabamos de elegir tiene mas fitness */
if (poblacion[elemAzar[iElem]]->fitness < poblacion[ordenTorneo[j-1]]-
>fitness)
{
/* movemos el elemento del vector hacia arriba */
ordenTorneo[j] = ordenTorneo[j-1];
}
else
{
break; /* salimos del while */
}
j--;
}
/* el elemento va en la posicion j */
ordenTorneo[j] = elemAzar[iElem];
iElem++;
}
fprintf(DebugFP()," --> ");
/* ya tenemos los elementos en el vector "ordenTorneo" ordenados
por fitness (primero el de menor fitness) */
/* ahora elegimos al elemento con la probabilidad que corresponde */
bSalir = false;
j = 0;
while ((j < RegConfig.torneoTamanio) && (bSalir == false))
{
if (TirarMonedaCargada(RegConfig.torneoProbMejor))
{
bSalir = true;
}
else
{
fprintf(DebugFP()," salteo - ");
j++;
}
}
if (bSalir == false) /* j se pasó 1 */
j--;
/* de acuerdo al valor de "invertido" devolvemos el que corresponde */
if (invertido == true)
indElegido = ordenTorneo[j];
else
indElegido = ordenTorneo[RegConfig.torneoTamanio - 1 - j];
fprintf(DebugFP(),"[%f]\n",poblacion[indElegido]->fitness);
free((void*)elemAzar);
free((void*)ordenTorneo);
return (0);
}
/* generamos el hijo 1 */
fprintf(DebugFP(),"Cruza PasaGrup 1 : fitness padre1(%d) = %f ; fitness padre2(%d) =
%f\n",padre1->agrup->cantGrupos,padre1->fitness,padre2->agrup->cantGrupos,padre2-
>fitness);
codRet = privCruzaPasaGrupo(padre1,padre2,hijo1,vVecArchivos);
if (codRet != OK)
return codRet;
/* generamos el hijo 2 */
fprintf(DebugFP(),"Cruza PasaGrup 2 : fitness padre1 = %f ; fitness padre2 =
%f\n",padre2->fitness,padre1->fitness);
codRet = privCruzaPasaGrupo(padre2,padre1,hijo2,vVecArchivos);
return codRet;
return OK;
}
uent c_Genetico::AsignarFormaElegirGrupoDestino()
/* calcula la forma de elegir el grupo destino de acuerdo a las probabilidades
(usado por el operador de cruza)
Devuelve : 1 = Mas elementos en comun
2 = Mas similar
3 = Menor similitud promedio
4 = Al Azar
*/
{
real totalProbRestante;
totalProbRestante = (real)1.0;
if (TirarMonedaCargada(RegConfig.cruzaPasaGrupoProbMasElemComun * (real)(1.0 /
totalProbRestante)))
{
return 1;
}
totalProbRestante -= RegConfig.cruzaPasaGrupoProbMasElemComun;
if (TirarMonedaCargada(RegConfig.cruzaPasaGrupoProbMasSimilar * (real)(1.0 /
totalProbRestante)))
{
return 2;
}
totalProbRestante -= RegConfig.cruzaPasaGrupoProbMasSimilar;
if (TirarMonedaCargada(RegConfig.cruzaPasaGrupoProbMenorSimilProm * (real)(1.0 /
totalProbRestante)))
{
return 3;
}
/* si llegamos hasta aca, es al azar */
return 4;
}
/* creamos el cromosoma */
*hijo1 = (tCromosoma*)malloc(sizeof(tCromosoma));
if (*hijo1 == NULL)
return ERROR_MEMORIA;
/* creamos el agrupamiento del cromosoma */
(*hijo1)->agrup = new c_Agrupamiento();
if ((*hijo1)->agrup == NULL)
return ERROR_MEMORIA;
agrupHijo = (*hijo1)->agrup;
/* hacemos el agrupamiento del hijo igual al del padre 2 */
codRet = agrupHijo->Duplicar(*(padre2->agrup));
if (codRet != OK)
return codRet;
/* le tenemos que pasar un grupo del padre 1 */
agrupPadre = padre1->agrup;
/* veamos si tenemos que pasar el mejor grupo */
if (TirarMonedaCargada(RegConfig.cruzaPasaGrupoProbMejor))
{
/* buscamos el grupo del padre con mejor similitud promedio */
maxSimProm = (real)-1.0;
for (i=0; i < agrupPadre->cantGrupos; i++)
{
simProm = agrupPadre->vecGrupos[i].vecCentroide-
>CalcularNorma(false);
if (simProm > maxSimProm)
{
indGrupoPasar = i;
maxSimProm = simProm;
}
}
}
else
{
/* el grupo a pasar se elige al azar */
indGrupoPasar = RANDOM(agrupPadre->cantGrupos);
maxSimProm = 0;
}
nroGrupoPasar = agrupPadre->vecGrupos[indGrupoPasar].nroGrupo;
{
/* sumamos uno al grupo del hijo que tiene ese elemento */
nroGrupoElemHijo = agrupHijo->vecElementos[i];
vecNroElemComun[agrupHijo-
>IndiceDeGrupo(nroGrupoElemHijo,false)]++;
}
}
/* buscamos la mayor entrada en el vector */
maxElemComun = 0;
for (i=0; i < agrupHijo->cantGrupos; i++)
{
if (vecNroElemComun[i] > maxElemComun)
{
maxElemComun = vecNroElemComun[i];
indGrupoDestino = i;
}
}
free((void*)vecNroElemComun);
maxSimPromDest = (real)maxElemComun;
break;
case 2: /* mas similar */
maxSimPromDest = vMaxSimProm[1];
indGrupoDestino = vIndGrupoDestino[1];
break;
case 3: /* menor similitud promedio */
maxSimPromDest = (real)-10000.0;
for (i=0; i < agrupHijo->cantGrupos; i++)
{
vecCent2 = agrupHijo->vecGrupos[i].vecCentroide;
simProm = (real)-1.0 * vecCent2->CalcularNorma(false);
if (simProm > maxSimPromDest)
{
maxSimPromDest = simProm;
indGrupoDestino = i;
}
}
break;
case 4: /* al azar */
maxSimPromDest = (real)0.0;
indGrupoDestino = RANDOM(agrupHijo->cantGrupos);
break;
}
nroGrupoDestino = agrupHijo->vecGrupos[indGrupoDestino].nroGrupo;
return OK;
}
if (RegConfig.cruzaOperador == 1)
{
codRet =
CruzaPasaGrupo(poblacion[indPadre1],poblacion[indPadre2],&hijo1,&hijo2,vVecArchivos);
if (codRet != OK)
return (codRet);
}
else
{
/* ... no definido aun */
hijo1->ultimoJoin = -1;
hijo2->ultimoJoin = -1;
hijo1->ultimoJoin = -1;
hijo2->ultimoJoin = -1;
return OK;
}
if (hijo1 != NULL)
poblacion[indReemp1] = hijo1;
if (hijo2 != NULL)
poblacion[indReemp2] = hijo2;
return OK;
}
if (generacionesFijas == true)
limiteGeneraciones = cantGeneraciones;
else
limiteGeneraciones = RegConfig.generacionesMaximo;
iGeneraciones = 0;
while (iGeneraciones < limiteGeneraciones)
{
//xxxdebug
printf("Gcion : %d\n",iGeneraciones);
fprintf(DebugFP(),"Gcion : %d\n",iGeneraciones);
ActualizarParametros(iGeneraciones,limiteGeneraciones);
codRet = OperadorCruza(vVecArchivos);
if (codRet != OK)
return (codRet);
iGeneraciones++;
}
cantGeneraciones = iGeneraciones;
return OK;
}
cantIteraciones = RegConfig.mutacionRefinarKMCantIter;
c_KMeans::k = cromosoma->agrup->cantGrupos;
codRet = c_KMeans::FaseRefinarLite(*(cromosoma-
>agrup),vVecArchivos,indGrupos,true,cantIteraciones,cantCambiosTotal);
if (codRet != OK)
return codRet;
fprintf(DebugFP()," %d\n",cantCambiosTotal);
free((void*)indGrupos);
return OK;
}
agrup = cromosoma->agrup;
h = vecOrdenTamano[i];
if (vecPuntaje[h] == 0)
{
vecPuntaje[h] = i + 1;
}
else
{
if (vecPuntaje[h] < (i+1))
{
vecPuntaje[h] *= 2;
vecPuntaje[h] += i + 1;
}
else
{
vecPuntaje[h] += 2 * (i+1);
}
// fprintf(DebugFP(),"Grupo : %d, Puntaje : %d\n",h,vecPuntaje[h]);
}
h = vecOrdenSimilProm[i];
if (vecPuntaje[h] == 0)
{
vecPuntaje[h] = i + 1;
}
else
{
if (vecPuntaje[h] < (i+1))
{
vecPuntaje[h] *= 2;
vecPuntaje[h] += i + 1;
}
else
{
vecPuntaje[h] += 2 * (i+1);
}
// fprintf(DebugFP(),"Grupo : %d, Puntaje : %d\n",h,vecPuntaje[h]);
}
}
if (j > 0)
{
/* va en la posicion j - 1 */
/* hay que correr los demas */
j--;
for (h=0; h < j; h++)
vecMejores[h] = vecMejores[h+1];
vecMejores[j] = i;
}
/* ahora en el de peores */
j = 0;
while (j < cantPeores)
{
if (vecPeores[j] == agrup->cantGrupos)
j++;
else if (vecPuntaje[i] > vecPuntaje[vecPeores[j]])
j++;
else
break;
}
if (j > 0)
{
/* va en la posicion j - 1 */
/* hay que correr los demas */
j--;
for (h=0; h < j; h++)
vecPeores[h] = vecPeores[h+1];
vecPeores[j] = i;
}
}
indGrupoDestino = vecPeores[h];
simDestino = simDestinoAux;
}
}
/* veamos si la mejor similitud esta dentro de lo tolerado */
//fprintf(DebugFP()," Mejor Sim Alternativa : %f, Grupo :
%d, Desvio : %f\n",simDestino,indGrupoDestino,desvioSim);
if (simDestino > (simProm - (desvioSim *
factorDesvioTolerado)))
{
cantCambios++;
c_KMeans::MoverElementoyRecalcular(*agrup,vVecArchivos,i,indGrupoOrigen,indGrup
oDestino,true);
}
}
}
}
fprintf(DebugFP()," %d\n",cantCambios);
free((void*)vecMejores);
free((void*)vecPeores);
free((void*)vecOrdenSimilProm);
free((void*)vecOrdenTamano);
free((void*)vecPuntaje);
free((void*)vecSimilPromAux);
return OK;
}
agrup = cromosoma->agrup;
else
formaElegirGrupoOrigen = 3; /* menor similitud promedio */
}
switch (formaElegirGrupoOrigen)
{
case 2: /* mas chico */
minCantElem = MAX_UENT;
for (i=0; i < agrup->cantGrupos; i++)
{
cantElem = agrup->vecGrupos[i].cantElementos;
if (cantElem < minCantElem)
{
minCantElem = cantElem;
indGrupoOrigen = i;
}
}
maxSimProm = (real)(0 - minCantElem);
break;
case 3: /* menor similitud promedio */
maxSimProm = (real)-10000.0;
for (i=0; i < agrup->cantGrupos; i++)
{
vecCent2 = agrup->vecGrupos[i].vecCentroide;
simProm = (real)-1.0 * vecCent2->CalcularNorma(false);
if (simProm > maxSimProm)
{
maxSimProm = simProm;
indGrupoOrigen = i;
}
}
break;
case 4: /* al azar */
maxSimProm = (real)0.0;
indGrupoOrigen = RANDOM(agrup->cantGrupos);
break;
}
nroGrupoOrigen = agrup->vecGrupos[indGrupoOrigen].nroGrupo;
switch (formaElegirGrupoDestino)
{
case 2: /* mas similar */
maxSimPromDest = (real)-1.0;
vecCent1 = agrup->vecGrupos[indGrupoOrigen].vecCentroide;
for (i=0; i < agrup->cantGrupos; i++)
{
if (i != indGrupoOrigen)
{
vecCent2 = agrup->vecGrupos[i].vecCentroide;
simPromDest = vecCent1->Multiplicar(*vecCent2,false);
if (simPromDest > maxSimPromDest)
{
maxSimPromDest = simPromDest;
indGrupoDestino = i;
}
}
}
break;
case 4: /* al azar */
maxSimPromDest = (real)0.0;
indGrupoDestino = RANDOM(agrup->cantGrupos);
break;
}
nroGrupoDestino = agrup->vecGrupos[indGrupoDestino].nroGrupo;
cromosoma->ultimoJoin = indGrupoDestino;
if (indGrupoDestino != indGrupoOrigen)
/* puede pasar que sean iguales cuando se elige al azar */
{
/* juntamos los 2 grupos */
for (i=0; i < agrup->cantElementos; i++)
{
if (agrup->vecElementos[i] == nroGrupoOrigen)
{
/* lo pasamos al otro */
cantGrupos = agrup->cantGrupos; /* para ver si cambia */
codRet =
c_KMeans::MoverElementoyRecalcular(*agrup,vVecArchivos,i,indGrupoOrigen,indGrupoDestin
o,true);
if (codRet != OK)
return codRet;
if (cantGrupos != agrup->cantGrupos)
{
indGrupoOrigen = agrup-
>IndiceDeGrupo(nroGrupoOrigen,false);
indGrupoDestino = agrup-
>IndiceDeGrupo(nroGrupoDestino,true);
}
}
}
}
return OK;
}
uent c_Genetico::AsignarFormaElegirGrupoSplit()
/* calcula la forma de elegir el grupo a partir de acuerdo a las probabilidades
(usado por el operador de mutacion split)
Devuelve : 1 = (... no definido)
2 = Mas grande
3 = Menor similitud promedio
4 = Al Azar
*/
{
real totalProbRestante;
totalProbRestante = (real)1.0;
if (TirarMonedaCargada(RegConfig.mutacionSplitProbMasGrande * (real)(1.0 /
totalProbRestante)))
{
return 2;
}
totalProbRestante -= RegConfig.mutacionSplitProbMasGrande;
if (TirarMonedaCargada(RegConfig.mutacionSplitProbMenorSimilProm * (real)(1.0 /
totalProbRestante)))
{
return 3;
}
/* si llegamos hasta aca, es al azar */
return 4;
}
agrup = cromosoma->agrup;
if (cromosoma->ultimoJoin == -1)
{
switch (formaElegirGrupoOrigen)
{
case 2: /* mas grande */
maxCantElem = 0;
for (i=0; i < agrup->cantGrupos; i++)
{
cantElem = agrup->vecGrupos[i].cantElementos;
if (cantElem > maxCantElem)
{
maxCantElem = cantElem;
indGrupoOrigen = i;
}
}
maxSimProm = (real)maxCantElem;
break;
case 3: /* menor similitud promedio */
maxSimProm = (real)-10000.0;
for (i=0; i < agrup->cantGrupos; i++)
{
vecCent2 = agrup->vecGrupos[i].vecCentroide;
simProm = (real)-1.0 * vecCent2->CalcularNorma(false);
if (simProm > maxSimProm)
{
maxSimProm = simProm;
indGrupoOrigen = i;
}
}
break;
case 4: /* al azar */
maxSimProm = (real)0.0;
indGrupoOrigen = RANDOM(agrup->cantGrupos);
break;
}
}
else //tomamos el grupo donde se hizo el ultimo join
{
indGrupoOrigen = cromosoma->ultimoJoin;
maxSimProm = (real)-1.0;
}
nroGrupoOrigen = agrup->vecGrupos[indGrupoOrigen].nroGrupo;
/* lo partimos */
/* guardamos el valor actual de k de la clase kmeans */
kAux = c_KMeans::k;
/* seteamos el k del kmeans igual a 2 */
c_KMeans::k = 2;
/* creamos un vector para contener el indice del nuevo grupo */
vec2Ind = (uent*)malloc(SINT * 2);
if (vec2Ind == NULL)
return ERROR_MEMORIA;
/* separamos el grupo en 2 usando la fase inicial del kmeans */
codRet =
c_KMeans::FaseInicial(*agrup,nroGrupoOrigen,vVecArchivos,vec2Ind,true,RegConfig.mutacion
SplitCantMuestras);
if (codRet != OK)
return codRet;
/* calculamos los centroides */
codRet = c_KMeans::CalcularCentroides(*agrup,vVecArchivos,2,vec2Ind);
if (codRet != OK)
return codRet;
/* reponemos el valor de k de la clase kmeans */
c_KMeans::k = kAux;
free((void*)vec2Ind);
return OK;
}
A4.2.13 Def.h
/*
En este archivo se hacen las definiciones generales
que se utilizaran en el resto del programa
*/
#ifndef __NuestraDefH__
/* si no esta definido todo esto... */
/* lo definimos ahora */
#define __NuestraDefH__
#include "limits.h"
/* estamos en windows */
#define midefDOS
#define TRUE 1
#define FALSE 0
#define OK 0
#define ERROR 1
#define ERROR_MEMORIA 2
#define ERROR_ARCHIVO 3
#define ERROR_DIRECTORIO 4
#define MODO_LECTURA 0
#define MODO_ESCRITURA 1
#endif
A4.2.14 Sis.h
/*
En vez de incluir sisdos o sislinux debe incluirse
este archivo para poder tener definidas las funciones de sistema
*/
#include "def.h"
#ifdef midefDOS
/* dos */
#ifndef __SisDosH__
#include "sisdos.h"
#endif
#endif
#ifndef midefDOS
/* linux */
#ifndef __SisLinuxH__
#include "sislinux.h"
#endif
#endif
A4.2.15 SisDos.h
/*
Va a contener las rutinas que dependen del sistema operativo en
version DOS
*/
#include <io.h>
#include <malloc.h>
#include "def.h"
#include <string.h>
#include <stdlib.h>
#define __SisDosH__
#define PI (real)3.14159
A4.2.16 SisDos.cpp
/*
Va a contener las rutinas que dependen del sistema operativo en
version DOS
*/
#include "sisdos.h"
ent random(int n)
{
/*
float f;
return (int)f;
*/
if (!ordenados)
{
/* los desordenamos */
for (i=0; i < k; i++)
{
nroAzar = RANDOM(k);
nroAzar2 = RANDOM(k);
j = punt[nroAzar];
punt[nroAzar] = punt[nroAzar2];
punt[nroAzar2] = j;
}
}
return punt;
}
nroAzar = RANDOM(100);
A4.2.17 Ppal.cpp
#include "def.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <conio.h>
#include "skipl.h"
#include "archtxt.h"
#include "vecArchivos.h"
#include "archivostexto.h"
#include "filtrarpalabras.h"
#include "clsAgrupamiento.h"
#include "clsKMeans.h"
#include "clsAgrupImprime.h"
#include "clsGenetico.h"
/* VARIABLES GLOBALES */
uent generacionesGenetico;
char szSalida[300] = "salida\\salida_clustering.txt";
c_SkipList **lLexArchivos;
c_SkipList lTxt;
c_SkipList lLex;
c_VecArchivo **vVectoresArchivos;
time_t startTime, elapsedTime, endTime;
uent codRet;
uent kBisect;
char szMat[300] = "";
uent cantGrupos;
char szAlgoritmo[300];
uent iCorridas;
fpSalida = fopen(szSalida,"wt");
fprintf(fpSalida,"CantMultip=%d\n",c_VecArchivo::contMultiplicaciones);
fprintf(fpSalida,"CantCalcNorma=%d\n",c_VecArchivo::contCalculoNorma);
fprintf(fpSalida,"CantMovimientos=%d\n",c_KMeans::contMoverYRecalcular);
c_Agrup_Imprime::salidaCompacta = true;
c_Agrup_Imprime::ImprimirTodo(fpSalida,agrup,vVectoresArchivos,lLex,lTxt);
fclose(fpSalida);
//xxxdebug
fpSalida = fopen("salida_no_compacta.txt","wt");
c_Agrup_Imprime::salidaCompacta = false;
c_Agrup_Imprime::ImprimirTodo(fpSalida,agrup,vVectoresArchivos,lLex,lTxt);
fclose(fpSalida);
}
uent AgruparRandom(uent k)
/* Realiza un agrupamiento al azar de los documentos en
k grupos */
{
c_Agrupamiento agrup;
uent codRet;
startTime = time(NULL);
codRet = agrup.Inicializar((uent)lTxt.ObtenerCantElementos());
if (codRet != OK)
return codRet;
codRet = agrup.AgruparAlAzar(k,k);
if (codRet != OK)
return codRet;
codRet = c_KMeans::CalcularCentroides(agrup,vVectoresArchivos,0,NULL);
if (codRet != OK)
return codRet;
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin agrupar al azar : %d segundos\n",elapsedTime);
ImprimirAgrupamiento(agrup);
return OK;
}
uent AgruparKMeansComun(uent k)
/* realiza un agrupamiento k-means en k grupos */
{
c_Agrupamiento agrup;
uent codRet;
uent *vecIndGrupos;
uent cantIteraciones = 100, cantCambiosTotal = 0;
startTime = time(NULL);
c_KMeans::k = k;
codRet = agrup.Inicializar((uent)lTxt.ObtenerCantElementos());
if (codRet != OK)
return codRet;
codRet = agrup.AgruparAlAzar(1,1);
if (codRet != OK)
return codRet;
codRet = c_KMeans::FaseInicial(agrup,1,vVectoresArchivos,vecIndGrupos,false,0);
if (codRet != OK)
return codRet;
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin fase inicial : %d segundos\n",elapsedTime);
startTime = time(NULL);
c_KMeans::CalcularCentroides(agrup,vVectoresArchivos,0,NULL);
//c_Agrup_Imprime::ImprimirTodo(stdout,agrup,vVectoresArchivos,lLex,lTxt);
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin calcular centroides : %d segundos\n",elapsedTime);
startTime = time(NULL);
printf("Vamos a refinar...\n");
c_KMeans::FaseRefinar(agrup,vVectoresArchivos,vecIndGrupos,true,cantIteraciones,c
antCambiosTotal);
printf("Refinar : %d iteraciones.\n",cantIteraciones);
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin refinar : %d segundos\n",elapsedTime);
startTime = time(NULL);
ImprimirAgrupamiento(agrup);
return OK;
}
startTime = time(NULL);
c_KMeans::k = k;
codRet = agrup->Inicializar((uent)lTxt.ObtenerCantElementos());
if (codRet != OK)
return codRet;
codRet = agrup->AgruparAlAzar(1,1);
if (codRet != OK)
return codRet;
codRet = c_KMeans::BisectingKMeans(agrup,1,vVectoresArchivos,kBisect,refinar);
if (codRet != OK)
return codRet;
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin bisecting k-means : %d segundos\n",elapsedTime);
startTime = time(NULL);
ImprimirAgrupamiento(*agrup);
delete(agrup);
return OK;
}
uent AgruparBisectingKMeansSinRef(uent k)
{
return AgruparBisectingKMeans(k,false);
}
uent AgruparBisectingKMeansConRef(uent k)
{
return AgruparBisectingKMeans(k,true);
}
uent AgruparGenetico(uent k)
/* Realiza un agrupamiento de los documentos en k grupos
usando el algoritmo genetico */
{
c_Genetico objGenetico;
uent codRet;
c_Agrupamiento *agrup = NULL;
uent cantGeneraciones, i, cantIteraciones, cantCambiosTotal;
FILE *fp;
uent *vecIndGrupos;
char szDebug[255];
//fp = NULL;
sprintf(szAlgoritmo,"Gen");
sprintf(szSalida,"salida\\salida_%s_%s_%d_%d.txt",szMat,szAlgoritmo,cantGrupos,iCor
ridas);
sprintf(szDebug,"%s_d.asc",szSalida);
fp = fopen(szDebug,"wt");
objGenetico.RegConfig.fpSalidaDebug = fp;
uent a = 0;
startTime = time(NULL);
objGenetico.RegConfig.k = k;
codRet =
objGenetico.GenerarPoblacionInicial((uent)lTxt.ObtenerCantElementos(),vVectoresArchivos);
if (codRet != OK)
return codRet;
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin generar poblacion inicial : %d segundos\n",elapsedTime);
startTime = time(NULL);
cantGeneraciones = generacionesGenetico;
codRet = objGenetico.Evolucionar(vVectoresArchivos,true,cantGeneraciones);
if (codRet != OK)
return codRet;
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin evolucion : %d segundos\n",elapsedTime);
startTime = time(NULL);
codRet = objGenetico.DevolverMejorSolucion(&agrup);
if (codRet != OK)
return codRet;
if (fp != NULL)
fclose(fp);
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin refinar : %d segundos\n",elapsedTime);
startTime = time(NULL);
/* lo imprimimos refinado */
sprintf(szAlgoritmo,"Gen(ref)");
sprintf(szSalida,"salida\\salida_%s_%s_%d_%d.txt",szMat,szAlgoritmo,cantGrupos,iCor
ridas);
ImprimirAgrupamiento(*agrup);
return OK;
}
int main(void)
{
int i;
//int j;
//uent valEnt;
//real valReal;
char szDir[300] = "d:\\Tesis_Docs";
char szPathMat[300] = "";
uent cantLex;
uent cantArchivos;
char opcion;
uent codRet;
bool bCorrerAlgoritmo;
uent (*funcionAgrupar)(uent); /* puntero a la funcion */
uent cantCorridas;
uent cantGruposLoc;
if (strcmp((char*)&szMat,"txt") == 0)
/* ***************************************** */
/* LEEMOS LOS ARCHIVOS .TXT DEL DISCO */
/* Se leen los archivos y se arman las listas de
palabras */
startTime = time(NULL);
codRet = ProcesarArchivosTexto(lTxt,lLex,TBUFFTXT,cantLex,lLexArchivos);
if (codRet != OK)
return codRet;
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin leer archivos : %d segundos\n",elapsedTime);
startTime = time(NULL);
/* ******************************************** */
/* VAMOS A FILTRAR LAS LISTAS DE LEXICO */
codRet =
FiltrarListasDeLexico(lLex,lLexArchivos,lTxt.ObtenerCantElementos());
if (codRet != OK)
return codRet;
}
else
{
/* Leemos un dataset en formato .mat */
sprintf((char*)&szPathMat,"%s\\%s.mat",szDir,szMat);
startTime = time(NULL);
cantArchivos = CargarListaArchivosMAT(szPathMat,lTxt);
codRet = ProcesarArchivosTextoMAT(szPathMat,lLex,cantLex,lLexArchivos);
if (codRet != OK)
return codRet;
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin leer .mat : %d segundos\n",elapsedTime);
startTime = time(NULL);
/* ******************************************** */
/* VAMOS A CREAR LOS VECTORES DE ARCHIVOS */
codRet =
ArmarVectoresArchivos(lLex,lTxt.ObtenerCantElementos(),lLexArchivos,vVectoresArchivos);
if (codRet != OK)
return codRet;
endTime = time(NULL);
elapsedTime = endTime - startTime;
printf("Fin inicializacion : %d segundos\n",elapsedTime);
startTime = time(NULL);
opcion = 0;
/* valores por default */
cantGrupos = 15;
cantCorridas = 5;
kBisect = 2;
generacionesGenetico = 50;
switch (opcion)
{
case 'a':
funcionAgrupar = &AgruparBisectingKMeansConRef;
sprintf(szAlgoritmo,"BisKM(ref)%d",kBisect);
bCorrerAlgoritmo = true;
break;
case 'b':
funcionAgrupar = &AgruparGenetico;
sprintf(szAlgoritmo,"Gen");
bCorrerAlgoritmo = true;
break;
case 'c':
funcionAgrupar = &AgruparRandom;
sprintf(szAlgoritmo,"Azar");
bCorrerAlgoritmo = true;
break;
case 'd':
funcionAgrupar = &AgruparKMeansComun;
sprintf(szAlgoritmo,"KMCom");
bCorrerAlgoritmo = true;
break;
case 'e':
funcionAgrupar = &AgruparBisectingKMeansSinRef;
sprintf(szAlgoritmo,"BisKM%d",kBisect);
bCorrerAlgoritmo = true;
break;
case '1':
printf("Grupos : ");
scanf("%d",&cantGruposLoc); getchar();
cantGrupos = cantGruposLoc;
break;
case '2':
printf("Corridas : ");
scanf("%d",&cantCorridas); getchar();
break;
case '3':
printf("Generaciones : ");
scanf("%d",&generacionesGenetico); getchar();
break;
case '4':
printf("K de bisecting k-means : ");
scanf("%d",&kBisect); getchar();
break;
}
if (bCorrerAlgoritmo)
{
for (iCorridas = 0; iCorridas < cantCorridas; iCorridas++)
{
/* reseteamos los contadores de las clases */
c_KMeans::contMoverYRecalcular = 0;
c_VecArchivo::contCalculoNorma = 0;
c_VecArchivo::contMultiplicaciones = 0;
sprintf(szSalida,"salida\\salida_%s_%s_%d_%d.txt",szMat,szAlgoritmo,cantGrupos,iCor
ridas);
codRet = (*funcionAgrupar)(cantGrupos);
if (codRet != OK)
return codRet;
}
}
return 0;
}