Agrupación y clasificación de datos de Big Text con Java Machine Learning. Artículo # 2 - Algoritmos

imagen



¡Hola, Habr! Hoy habrá una continuación del tema Agrupación y clasificación de Big Text Data usando Machine Learning en Java. Este artículo es una continuación del primer artículo .





El artículo contendrá la teoría y la implementación de los algoritmos que utilicé.





1. Tokenización



Teoría:



‒ . (, ). , , , , , , . . . (), . ‒ ; , . , - . , , . , , .



, . .



, «». (, , , ), , . , , . , .



, PDF-, , , . , . .



. , , , , . , , , , , . , . . . , , , . , .



, , . , , . , . . , , , , , , . , , . , . , .





:



Iterator<String> finalIterator = new WordIterator(reader);


private final BufferedReader br;
String curLine;
public WordIterator(BufferedReader br) {
        this.br = br;
        curLine = null;
        advance();
    }
    private void advance() {
        try {
            while (true) {
                if (curLine == null || !matcher.find()) {
                    String line = br.readLine();
                    if (line == null) {
                        next = null;
                        br.close();
                        return;
                    }
                    matcher = notWhiteSpace.matcher(line);
                    curLine = line;
                    if (!matcher.find())
                        continue;                    
                }
                next = curLine.substring(matcher.start(), matcher.end());
                break;
            }
        } catch (IOException ioe) {
            throw new IOError(ioe);
        }
    }


2. -



:



, , «-», «-». , . - . -. - 1958 .. . - ‒ , , . , , , , , , , , , , , , , , , , , , , , , . . , . - , , , . , « », -, “”, “”, “ ”, “”. , «” “”, , , “” „“ . , , , : “”, “ ”, “”, , . , . - , , .



. -, . , » ", «», «», . -, , , , . , . .

- :



  • - ‒ .
  • , -, , , , -.
  • - - . .
  • , .
  • , -, , .
  • - .
  • - :
  • : -, -. .
  • , («—»): - -. (TF-High), , , . . (TF1), (IDF).
  • (MI): , (, , ), , . , , .


Muestreo aleatorio de términos (TBRS): método en el que las palabras vacías se detectan manualmente en los documentos. Esta técnica se utiliza iterando sobre fragmentos de datos individuales seleccionados al azar y clasificando las características en cada fragmento según sus valores en un formato utilizando la medida de divergencia de Kullback-Leibler como se muestra en la siguiente ecuación:



d_x (t) = Px (t) .log_2⁡ 〖( Px (t)) ⁄ (P (t))〗



donde Px (t) es la frecuencia normalizada del término t dentro del peso x

P (t) es la frecuencia normalizada del término t en toda la colección.

La lista de detención final se construye adoptando los términos menos informativos en todos los documentos, eliminando todos los posibles duplicados.





El código:

TokenFilter filter = new TokenFilter().loadFromResource("stopwords.txt")
if (!filter.accept(token)) continue;


private Set<String> tokens;
private boolean excludeTokens;
private TokenFilter parent;

public TokenFilter loadFromResource(String fileName) {
		try {
			ClassLoader classLoader = getClass().getClassLoader();
			String str = IOUtils.toString(
					classLoader.getResourceAsStream(fileName),
					Charset.defaultCharset());
			InputStream is = new ByteArrayInputStream(str.getBytes());
			BufferedReader br = new BufferedReader(new InputStreamReader(is));

			Set<String> words = new HashSet<String>();
			for (String line = null; (line = br.readLine()) != null;)
				words.add(line);
			br.close();

			this.tokens = words;
			this.excludeTokens = true;
			this.parent = null;
		} catch (Exception e) {
			throw new IOError(e);
		}
		return this;
	}
public boolean accept(String token) {
		token = token.toLowerCase().replaceAll("[\\. \\d]", "");
		return (parent == null || parent.accept(token))
				&& tokens.contains(token) ^ excludeTokens && token.length() > 2 && token.matches("^[-]+");
	}


Archivo:



















....


3. Lematización



Teoría:



. , . , .



‒ , , . , . , , ( ). , working, works, work work, : work; , . . , computers, computing, computer , : compute, . , , . , - , , , . , , .



A lo largo de los años, se han desarrollado varias herramientas que brindan funcionalidad de lematización. A pesar de los diferentes métodos de procesamiento utilizados, todos utilizan un léxico de palabras, un conjunto de reglas o una combinación de estos como recursos para el análisis morfológico. Las herramientas de lematización más famosas son:



  • WordNet ‒ WordNet . , , , , . , . WordNet . .
  • CLEAR ‒ . WordNet , . NLP, , .
  • GENIA POS , . POS, . : , , . WordNet, , , GENIA PennBioIE. , . , .
  • TreeTagger POS. , , TreeTagger , . GENIA TreeTagger , POS .
  • Norm LuiNorm , . , , . UMLS, , , -, . . , . POS .
  • MorphAdorner – , , , POS . , MorphAdorner , . , .
  • morpha – . 1400 , , , , . , WordNet, 5 000 6 000 . morpha , .


:



Properties props = new Properties();
props.setProperty("annotators", "tokenize, ssplit, pos, lemma");
StanfordCoreNLP pipeline = new StanfordCoreNLP(props);
String token = documentTokens.next().replaceAll("[^a-zA-Z]", "").toLowerCase();
         Annotation lemmaText = new Annotation(token);
         pipeline.annotate(lemmaText);
         List<CoreLabel> lemmaToken = lemmaText.get(TokensAnnotation.class);
         String word = "";
         for(CoreLabel t:lemmaToken) {
           word = t.get(LemmaAnnotation.class);  //   (  )
         }


4. –



:



Frecuencia de términos: la frecuencia de documentos inversa (TF-IDF) es el algoritmo más utilizado para calcular el peso de los términos (palabra clave en un documento) en los sistemas modernos de recuperación de información. Este peso es una medida estadística que se utiliza para evaluar la importancia de una palabra para un documento en una serie de documentos o en un corpus. El valor aumenta en proporción al número de veces que la palabra aparece en el documento, pero compensa la frecuencia de la palabra en el corpus.

...

(TF), , , , () . . (), , , . , . TF – . t D:



tf(t,D)=f_(t,D),



f_(t,D) – .

:

«»: tf(t,D) = 1, t D 0 ;

, :



tf(t,D)=f_(t,D)⁄(∑_(t^'∈D)▒f_(t^',D) )



:



log⁡〖(1+f_(t,D))〗



, , , :



tf(t,D)=0.5+0.5*f_(t,D)/(max⁡{f_(t^',D):t'∈D})



IDF, , , , . , , . , , , , :



idf(t,D)=log⁡N/|{d∈D:t∈d}|



TF IDF, TF-IDF, . , , . TF-IDF . TF-IDF : :



tfidf(t,D)=tf(t,D)*idf(t,D)





:

private final TObjectIntMap<T> counts;
public int count(T obj) {
    int count = counts.get(obj);
    count++;
    counts.put(obj, count);
    sum++;
    return count;
}


public synchronized int addColumn(SparseArray<? extends Number> column) {
     if (column.length() > numRows)
         numRows = column.length();
    
     int[] nonZero = column.getElementIndices();
     nonZeroValues += nonZero.length;
     try {
         matrixDos.writeInt(nonZero.length);
         for (int i : nonZero) {
             matrixDos.writeInt(i); // write the row index
             matrixDos.writeFloat(column.get(i).floatValue());
         }
     } catch (IOException ioe) {
         throw new IOError(ioe);
     }
     return ++curCol;
}


public interface SparseArray<T> {
    int cardinality();
    T get(int index);
    int[] getElementIndices();
    int length();
    void set(int index, T obj);
    <E> E[] toArray(E[] array);
}


public File transform(File inputFile, File outFile, GlobalTransform transform) {
     try {
         DataInputStream dis = new DataInputStream(
             new BufferedInputStream(new FileInputStream(inputFile)));
         int rows = dis.readInt();
         int cols = dis.readInt();
         DataOutputStream dos = new DataOutputStream(
             new BufferedOutputStream(new FileOutputStream(outFile)));
         dos.writeInt(rows);
         dos.writeInt(cols);
         for (int row = 0; row < rows; ++row) {
             for (int col = 0; col < cols; ++col) {
                 double val = dis.readFloat();
                 dos.writeFloat((float) transform.transform(row, col, val));
             }
         }
         dos.close();
         return outFile;
     } catch (IOException ioe) {
         throw new IOError(ioe);
     }
}

public double transform(int row, int column, double value) {
        double tf = value / docTermCount[column];
        double idf = Math.log(totalDocCount / (termDocCount[row] + 1));
        return tf * idf;
}


public void factorize(MatrixFile mFile, int dimensions) {
        try {
            String formatString = "";
            switch (mFile.getFormat()) {
            case SVDLIBC_DENSE_BINARY:
                formatString = " -r db ";
                break;
            case SVDLIBC_DENSE_TEXT:
                formatString = " -r dt ";
                break;
            case SVDLIBC_SPARSE_BINARY:
                formatString = " -r sb ";
                break;
            case SVDLIBC_SPARSE_TEXT:
                break;
            default:
                throw new UnsupportedOperationException(
                    "Format type is not accepted");
            }

            File outputMatrixFile = File.createTempFile("svdlibc", ".dat");
            outputMatrixFile.deleteOnExit();
            String outputMatrixPrefix = outputMatrixFile.getAbsolutePath();

            LOG.fine("creating SVDLIBC factor matrices at: " + 
                              outputMatrixPrefix);
            String commandLine = "svd -o " + outputMatrixPrefix + formatString +
                " -w dt " + 
                " -d " + dimensions + " " + mFile.getFile().getAbsolutePath();
            LOG.fine(commandLine);
            Process svdlibc = Runtime.getRuntime().exec(commandLine);
            BufferedReader stdout = new BufferedReader(
                new InputStreamReader(svdlibc.getInputStream()));
            BufferedReader stderr = new BufferedReader(
                new InputStreamReader(svdlibc.getErrorStream()));

            StringBuilder output = new StringBuilder("SVDLIBC output:\n");
            for (String line = null; (line = stderr.readLine()) != null; ) {
                output.append(line).append("\n");
            }
            LOG.fine(output.toString());
            
            int exitStatus = svdlibc.waitFor();
            LOG.fine("svdlibc exit status: " + exitStatus);

            if (exitStatus == 0) {
                File Ut = new File(outputMatrixPrefix + "-Ut");
                File S  = new File(outputMatrixPrefix + "-S");
                File Vt = new File(outputMatrixPrefix + "-Vt");
                U = MatrixIO.readMatrix(
                        Ut, Format.SVDLIBC_DENSE_TEXT, 
                        Type.DENSE_IN_MEMORY, true); //  U
                scaledDataClasses = false; 
                
                V = MatrixIO.readMatrix(
                        Vt, Format.SVDLIBC_DENSE_TEXT,
                        Type.DENSE_IN_MEMORY); //  V
                scaledClassFeatures = false;


                singularValues =  readSVDLIBCsingularVector(S, dimensions);
            } else {
                StringBuilder sb = new StringBuilder();
                for (String line = null; (line = stderr.readLine()) != null; )
                    sb.append(line).append("\n");
                // warning or error?
                LOG.warning("svdlibc exited with error status.  " + 
                               "stderr:\n" + sb.toString());
            }
        } catch (IOException ioe) {
            LOG.log(Level.SEVERE, "SVDLIBC", ioe);
        } catch (InterruptedException ie) {
            LOG.log(Level.SEVERE, "SVDLIBC", ie);
        }
    }

    public MatrixBuilder getBuilder() {
        return new SvdlibcSparseBinaryMatrixBuilder();
    }

    private static double[] readSVDLIBCsingularVector(File sigmaMatrixFile,
                                                      int dimensions)
            throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(sigmaMatrixFile));
        double[] m = new double[dimensions];

        int readDimensions = Integer.parseInt(br.readLine());
        if (readDimensions != dimensions)
            throw new RuntimeException(
                    "SVDLIBC generated the incorrect number of " +
                    "dimensions: " + readDimensions + " versus " + dimensions);

        int i = 0;
        for (String line = null; (line = br.readLine()) != null; )
            m[i++] = Double.parseDouble(line);
        return m;
    }


SVD Java ( S-space)



5. Aylien API



Aylien API Text Analysis ‒ API .

Aylien API , , , . ‒ .



, IPTC, -, ‒ IAB-QAG, .



La taxonomía contextual IAB-QAG fue desarrollada por la IAB (Interactive Advertising Bureau) junto con expertos en taxonomía de la academia para definir categorías de contenido en al menos dos niveles diferentes, haciendo que la clasificación de contenido sea mucho más consistente. El primer nivel es una categoría de nivel amplio y el segundo es una descripción más detallada de la estructura del tipo raíz (Figura 6).

Para utilizar esta API, debe obtener la clave y la identificación en el sitio web oficial. Luego, usando estos datos, puede usar el código Java para llamar a los métodos POST y GET.



private static TextAPIClient client = new TextAPIClient(" ", " ")


A continuación, puede utilizar la clasificación, pasando los datos a clasificar.



ClassifyByTaxonomyParams.Builder builder = ClassifyByTaxonomyParams.newBuilder();
URL url = new URL("http://techcrunch.com/2015/07/16/microsoft-will-never-give-up-on-mobile");
builder.setUrl(url);
builder.setTaxonomy(ClassifyByTaxonomyParams.StandardTaxonomy.IAB_QAG);
TaxonomyClassifications response = client.classifyByTaxonomy(builder.build());
for (TaxonomyCategory c: response.getCategories()) {
  System.out.println(c);
}


La respuesta del servicio se devuelve en formato json:



{
  "categories": [
    {
      "confident": true,
      "id": "IAB19-36",
      "label": "Windows",
      "links": [
        {
          "link": "https://api.aylien.com/api/v1/classify/taxonomy/iab-qag/IAB19-36",
          "rel": "self"
        },
        {
          "link": "https://api.aylien.com/api/v1/classify/taxonomy/iab-qag/IAB19",
          "rel": "parent"
        }
      ],
      "score": 0.5675236066291172
    },
    {
      "confident": true,
      "id": "IAB19",
      "label": "Technology & Computing",
      "links": [
        {
          "link": "https://api.aylien.com/api/v1/classify/taxonomy/iab-qag/IAB19",
          "rel": "self"
        }
      ],
      "score": 0.46704140928338533
    }
  ],
  "language": "en",
  "taxonomy": "iab-qag",
  "text": "When Microsoft announced its wrenching..."
}


Esta API se utiliza para clasificar los clústeres que se obtendrán mediante el método de agrupamiento en clústeres de aprendizaje no supervisado.



Epílogo



Al aplicar los algoritmos descritos anteriormente, existen alternativas y bibliotecas listas para usar. Solo tienes que mirar. Si le gustó el artículo, o tiene ideas o preguntas, deje sus comentarios. La tercera parte será abstracta, discutiendo principalmente la arquitectura del sistema. Descripción del algoritmo, qué se utilizó y en qué orden.



Adicionalmente, estarán los resultados de cada uno luego de la aplicación de cada algoritmo, así como el resultado final de este trabajo.




All Articles